use alloy_primitives::{Address, Bytes, B256, U256, keccak256};
use alloy_rlp::Encodable;
pub const TX_TYPE_USD_MULTI_TOKEN: u8 = 0x7a;
pub const FEE_PAYER_SIGNATURE_MAGIC_BYTE: u8 = 0x7b;
fn encode_opt_address(addr: &Option<Address>, out: &mut Vec<u8>) {
match addr {
Some(a) => a.encode(out),
None => {
out.push(0x80);
}
}
}
fn len_opt_address(addr: &Option<Address>) -> usize {
match addr {
Some(a) => a.length(),
None => 1, }
}
#[derive(Debug, Clone)]
pub struct TxBuilder {
pub chain_id: u64,
pub nonce: u64,
pub gas_limit: u64,
pub to: Option<Address>,
pub value: U256,
pub data: Bytes,
pub max_priority_fee_per_gas: u128,
pub max_fee_per_gas: u128,
pub fee_token: Address,
pub fee_payer: Option<Address>,
pub max_fee_per_gas_usd: Option<u128>,
pub fee_payer_signature: Option<Bytes>,
}
impl Default for TxBuilder {
fn default() -> Self {
Self::new()
}
}
impl TxBuilder {
pub fn new() -> Self {
Self {
chain_id: 0,
nonce: 0,
gas_limit: 21000,
to: None,
value: U256::ZERO,
data: Bytes::new(),
max_priority_fee_per_gas: 0,
max_fee_per_gas: 0,
fee_token: Address::ZERO,
fee_payer: None,
max_fee_per_gas_usd: Some(40_000_000_000), fee_payer_signature: None,
}
}
pub fn chain_id(mut self, chain_id: u64) -> Self {
self.chain_id = chain_id;
self
}
pub fn nonce(mut self, nonce: u64) -> Self {
self.nonce = nonce;
self
}
pub fn gas_limit(mut self, gas_limit: u64) -> Self {
self.gas_limit = gas_limit;
self
}
pub fn to(mut self, to: Option<Address>) -> Self {
self.to = to;
self
}
pub fn to_address(mut self, address: Address) -> Self {
self.to = Some(address);
self
}
pub fn value(mut self, value: U256) -> Self {
self.value = value;
self
}
pub fn data(mut self, data: Bytes) -> Self {
self.data = data;
self
}
pub fn max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: u128) -> Self {
self.max_priority_fee_per_gas = max_priority_fee_per_gas;
self
}
pub fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
self.max_fee_per_gas = max_fee_per_gas;
self
}
pub fn fee_token(mut self, fee_token: Address) -> Self {
self.fee_token = fee_token;
self
}
pub fn fee_payer(mut self, fee_payer: Option<Address>) -> Self {
self.fee_payer = fee_payer;
self
}
pub fn max_fee_per_gas_usd(mut self, max_fee_per_gas_usd: u128) -> Self {
self.max_fee_per_gas_usd = Some(max_fee_per_gas_usd);
self
}
pub fn fee_payer_signature(mut self, signature: Bytes) -> Self {
self.fee_payer_signature = Some(signature);
self
}
pub fn erc20_transfer(mut self, token: Address, to: Address, amount: U256) -> Self {
let mut data = vec![0xa9, 0x05, 0x9c, 0xbb];
let mut recipient = [0u8; 32];
recipient[12..].copy_from_slice(to.as_slice());
data.extend_from_slice(&recipient);
let mut amount_padded = [0u8; 32];
let amount_bytes: [u8; 32] = amount.to_be_bytes();
amount_padded.copy_from_slice(&amount_bytes);
data.extend_from_slice(&amount_padded);
self.to = Some(token);
self.data = Bytes::from(data);
self
}
pub fn build(&self) -> TxFields {
TxFields {
chain_id: self.chain_id,
nonce: self.nonce,
gas_limit: self.gas_limit,
to: self.to,
value: self.value,
data: self.data.clone(),
max_priority_fee_per_gas: self.max_priority_fee_per_gas,
max_fee_per_gas: self.max_fee_per_gas,
fee_token: self.fee_token,
fee_payer: self.fee_payer.unwrap_or(Address::ZERO),
max_fee_per_gas_usd: self.max_fee_per_gas_usd.unwrap_or(40_000_000_000),
fee_payer_signature: self.fee_payer_signature.clone(),
}
}
pub fn signature_hash(&self) -> B256 {
let fields = self.build();
let mut buf = Vec::new();
buf.push(TX_TYPE_USD_MULTI_TOKEN);
fields.encode_for_signing(&mut buf);
keccak256(&buf)
}
pub fn sign(&self, signer: &impl Signer) -> Result<SignedTx, Box<dyn std::error::Error>> {
let hash = self.signature_hash();
let signature = signer.sign_hash(&hash)?;
let fields = self.build();
let y_parity = signature.y_parity();
let raw_tx = fields.encode_signed(y_parity, signature.r(), signature.s());
let tx_hash = keccak256(&raw_tx);
Ok(SignedTx {
raw_transaction: format!("0x{}", hex::encode(&raw_tx)),
transaction_hash: tx_hash,
chain_id: fields.chain_id,
nonce: fields.nonce,
gas_limit: fields.gas_limit,
to: fields.to,
value: fields.value,
data: fields.data,
max_priority_fee_per_gas: fields.max_priority_fee_per_gas,
max_fee_per_gas: fields.max_fee_per_gas,
fee_token: fields.fee_token,
fee_payer: fields.fee_payer,
max_fee_per_gas_usd_attodollars: fields.max_fee_per_gas_usd,
fee_payer_signature: fields.fee_payer_signature,
v: y_parity,
r: signature.r(),
s: signature.s(),
})
}
}
pub trait Signer {
fn sign_hash(&self, hash: &B256) -> Result<Signature, Box<dyn std::error::Error>>;
fn address(&self) -> Address;
}
#[derive(Debug, Clone)]
pub struct Signature {
pub r: B256,
pub s: B256,
pub v: u8,
}
impl Signature {
pub fn new(r: B256, s: B256, v: u8) -> Self {
Self { r, s, v }
}
pub fn y_parity(&self) -> u8 {
self.v
}
pub fn r(&self) -> B256 {
self.r
}
pub fn s(&self) -> B256 {
self.s
}
pub fn to_bytes(&self) -> Bytes {
let mut buf = vec![self.v];
buf.extend_from_slice(self.r.as_slice());
buf.extend_from_slice(self.s.as_slice());
Bytes::from(buf)
}
}
#[derive(Debug, Clone)]
pub struct SignedTx {
pub raw_transaction: String,
pub transaction_hash: B256,
pub chain_id: u64,
pub nonce: u64,
pub gas_limit: u64,
pub to: Option<Address>,
pub value: U256,
pub data: Bytes,
pub max_priority_fee_per_gas: u128,
pub max_fee_per_gas: u128,
pub fee_token: Address,
pub fee_payer: Address,
pub max_fee_per_gas_usd_attodollars: u128,
pub fee_payer_signature: Option<Bytes>,
pub v: u8,
pub r: B256,
pub s: B256,
}
#[derive(Debug, Clone)]
pub struct TxFields {
pub chain_id: u64,
pub nonce: u64,
pub gas_limit: u64,
pub to: Option<Address>,
pub value: U256,
pub data: Bytes,
pub max_priority_fee_per_gas: u128,
pub max_fee_per_gas: u128,
pub fee_token: Address,
pub fee_payer: Address,
pub max_fee_per_gas_usd: u128,
pub fee_payer_signature: Option<Bytes>,
}
impl TxFields {
pub fn encode_for_signing(&self, out: &mut Vec<u8>) {
let payload_len = self.payload_len_for_signing();
alloy_rlp::Header { list: true, payload_length: payload_len }.encode(out);
self.chain_id.encode(out);
self.nonce.encode(out);
self.max_priority_fee_per_gas.encode(out);
self.max_fee_per_gas.encode(out);
self.gas_limit.encode(out);
encode_opt_address(&self.to, out);
self.value.encode(out);
self.data.encode(out);
alloy_rlp::Header { list: true, payload_length: 0 }.encode(out);
self.fee_token.encode(out);
self.fee_payer.encode(out);
self.max_fee_per_gas_usd.encode(out);
}
fn payload_len_for_signing(&self) -> usize {
self.chain_id.length() +
self.nonce.length() +
self.max_priority_fee_per_gas.length() +
self.max_fee_per_gas.length() +
self.gas_limit.length() +
len_opt_address(&self.to) +
self.value.length() +
self.data.length() +
1 + self.fee_token.length() +
self.fee_payer.length() +
self.max_fee_per_gas_usd.length()
}
pub fn encode_signed(&self, y_parity: u8, r: B256, s: B256) -> Vec<u8> {
let mut buf = Vec::new();
buf.push(TX_TYPE_USD_MULTI_TOKEN);
let payload_len = self.payload_len_for_signed(&r, &s);
alloy_rlp::Header { list: true, payload_length: payload_len }.encode(&mut buf);
self.chain_id.encode(&mut buf);
self.nonce.encode(&mut buf);
self.max_priority_fee_per_gas.encode(&mut buf);
self.max_fee_per_gas.encode(&mut buf);
self.gas_limit.encode(&mut buf);
encode_opt_address(&self.to, &mut buf);
self.value.encode(&mut buf);
self.data.encode(&mut buf);
alloy_rlp::Header { list: true, payload_length: 0 }.encode(&mut buf);
self.fee_token.encode(&mut buf);
self.fee_payer.encode(&mut buf);
self.max_fee_per_gas_usd.encode(&mut buf);
if let Some(ref sig) = self.fee_payer_signature {
sig.encode(&mut buf);
} else {
Bytes::new().encode(&mut buf);
}
y_parity.encode(&mut buf);
r.encode(&mut buf);
s.encode(&mut buf);
buf
}
fn payload_len_for_signed(&self, r: &B256, s: &B256) -> usize {
let fee_payer_sig_len = self.fee_payer_signature.as_ref()
.map(|sig| sig.length())
.unwrap_or_else(|| Bytes::new().length());
self.payload_len_for_signing() +
fee_payer_sig_len +
1 + r.length() +
s.length()
}
}
pub fn fee_payer_signature_hash(
chain_id: u64,
nonce: u64,
gas_limit: u64,
fee_token: Address,
fee_payer: Address,
max_fee_per_gas_usd: u128,
sender: Address,
) -> B256 {
let mut buf = Vec::new();
chain_id.encode(&mut buf);
nonce.encode(&mut buf);
gas_limit.encode(&mut buf);
fee_token.encode(&mut buf);
fee_payer.encode(&mut buf);
max_fee_per_gas_usd.encode(&mut buf);
sender.encode(&mut buf);
let mut data = vec![FEE_PAYER_SIGNATURE_MAGIC_BYTE];
data.extend_from_slice(&buf);
keccak256(&data)
}
pub fn create_transaction() -> TxBuilder {
TxBuilder::new()
}