use crate::{Error, Result, BASE_FEE_ATTO, DEFAULT_GAS_LIMIT};
use alloy_primitives::{Address, Bytes, B256, U256, keccak256};
use alloy_rlp::Encodable;
const FEE_PAYER_SIGNATURE_MAGIC_BYTE: u8 = 0x7B;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TxUsdMultiToken {
pub chain_id: u64,
pub nonce: u64,
pub max_priority_fee_per_gas: u128,
pub max_fee_per_gas: u128,
pub gas_limit: u64,
pub to: Option<Address>,
pub value: U256,
pub input: Bytes,
pub fee_token: Address,
pub fee_payer: Address,
pub max_fee_per_gas_usd_attodollars: u128,
pub fee_payer_signature: Bytes,
}
impl Default for TxUsdMultiToken {
fn default() -> Self {
Self {
chain_id: 0,
nonce: 0,
max_priority_fee_per_gas: 0,
max_fee_per_gas: 0,
gas_limit: DEFAULT_GAS_LIMIT,
to: None,
value: U256::ZERO,
input: Bytes::default(),
fee_token: Address::ZERO,
fee_payer: Address::ZERO,
max_fee_per_gas_usd_attodollars: (BASE_FEE_ATTO as u128) * 2,
fee_payer_signature: Bytes::default(),
}
}
}
pub fn fee_payer_signature_hash(tx: &TxUsdMultiToken, sender: Address) -> B256 {
let mut buf = Vec::new();
buf.push(FEE_PAYER_SIGNATURE_MAGIC_BYTE);
tx.chain_id.encode(&mut buf);
tx.nonce.encode(&mut buf);
tx.gas_limit.encode(&mut buf);
tx.fee_token.encode(&mut buf);
tx.fee_payer.encode(&mut buf);
tx.max_fee_per_gas_usd_attodollars.encode(&mut buf);
sender.encode(&mut buf);
keccak256(&buf)
}
impl TxUsdMultiToken {
pub fn signature_hash(&self) -> B256 {
let mut buf = Vec::new();
buf.push(0x7a);
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);
match &self.to {
None => buf.push(0x80), Some(addr) => addr.encode(&mut buf),
}
self.value.encode(&mut buf);
self.input.encode(&mut buf);
buf.push(0xc0);
self.fee_token.encode(&mut buf);
self.fee_payer.encode(&mut buf);
self.max_fee_per_gas_usd_attodollars.encode(&mut buf);
self.fee_payer_signature.encode(&mut buf);
keccak256(&buf)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Signature {
pub v: u64,
pub r: B256,
pub s: B256,
}
impl Signature {
pub fn new(v: u64, r: B256, s: B256) -> Self {
Self { v, r, s }
}
pub fn to_bytes(&self) -> Bytes {
let mut buf = Vec::with_capacity(65);
buf.push(self.v as u8);
buf.extend_from_slice(self.r.as_slice());
buf.extend_from_slice(self.s.as_slice());
Bytes::from(buf)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SignedTxUsdMultiToken {
pub tx: TxUsdMultiToken,
pub signature: Signature,
}
impl SignedTxUsdMultiToken {
pub fn encode_2718(&self) -> Bytes {
let mut encoded = vec![0x7a];
let mut tx_bytes = Vec::new();
self.tx.encode(&mut tx_bytes);
encoded.extend_from_slice(&tx_bytes);
Bytes::from(encoded)
}
}
#[derive(Debug, Clone)]
pub struct TxBuilder {
chain_id: Option<u64>,
nonce: Option<u64>,
max_priority_fee_per_gas: u128,
max_fee_per_gas: u128,
gas_limit: u64,
to: Option<Address>,
value: U256,
input: Bytes,
fee_token: Address,
fee_payer: Address,
max_fee_per_gas_usd_attodollars: u128,
fee_payer_signature: Bytes,
}
impl Default for TxBuilder {
fn default() -> Self {
Self {
chain_id: None,
nonce: None,
max_priority_fee_per_gas: 0,
max_fee_per_gas: 0,
gas_limit: DEFAULT_GAS_LIMIT,
to: None,
value: U256::ZERO,
input: Bytes::default(),
fee_token: Address::ZERO,
fee_payer: Address::ZERO,
max_fee_per_gas_usd_attodollars: (BASE_FEE_ATTO as u128) * 2,
fee_payer_signature: Bytes::default(),
}
}
}
impl TxBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn chain_id(mut self, chain_id: u64) -> Self {
self.chain_id = Some(chain_id);
self
}
pub fn nonce(mut self, nonce: u64) -> Self {
self.nonce = Some(nonce);
self
}
pub fn gas_limit(mut self, gas_limit: u64) -> Self {
self.gas_limit = gas_limit;
self
}
pub fn to(mut self, to: Address) -> Self {
self.to = Some(to);
self
}
pub fn value(mut self, value: U256) -> Self {
self.value = value;
self
}
pub fn input(mut self, input: Bytes) -> Self {
self.input = input;
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: Address) -> Self {
self.fee_payer = fee_payer;
self
}
pub fn max_fee_per_gas_usd(mut self, max_fee: u128) -> Self {
self.max_fee_per_gas_usd_attodollars = max_fee;
self
}
pub fn fee_payer_signature(mut self, signature: Bytes) -> Self {
self.fee_payer_signature = signature;
self
}
pub fn fee_payer_with_signature(mut self, fee_payer: Address, signature: Bytes) -> Self {
self.fee_payer = fee_payer;
self.fee_payer_signature = signature;
self
}
pub fn erc20_transfer(mut self, token: Address, recipient: Address, amount: U256) -> Self {
let mut data = vec![0xa9, 0x05, 0x9c, 0xbb];
let mut recipient_bytes = [0u8; 32];
recipient_bytes[12..32].copy_from_slice(recipient.as_slice());
data.extend_from_slice(&recipient_bytes);
let amount_le: [u8; 32] = amount.to_be_bytes();
data.extend_from_slice(&amount_le);
self.to = Some(token);
self.input = Bytes::from(data);
self
}
pub fn build(self) -> TxUsdMultiToken {
TxUsdMultiToken {
chain_id: self.chain_id.expect("chain_id is required"),
nonce: self.nonce.expect("nonce is required"),
max_priority_fee_per_gas: self.max_priority_fee_per_gas,
max_fee_per_gas: self.max_fee_per_gas,
gas_limit: self.gas_limit,
to: self.to,
value: self.value,
input: self.input,
fee_token: self.fee_token,
fee_payer: self.fee_payer,
max_fee_per_gas_usd_attodollars: self.max_fee_per_gas_usd_attodollars,
fee_payer_signature: self.fee_payer_signature,
}
}
pub fn try_build(self) -> Result<TxUsdMultiToken> {
let chain_id = self.chain_id.ok_or_else(|| Error::MissingField("chain_id".into()))?;
let nonce = self.nonce.ok_or_else(|| Error::MissingField("nonce".into()))?;
Ok(TxUsdMultiToken {
chain_id,
nonce,
max_priority_fee_per_gas: self.max_priority_fee_per_gas,
max_fee_per_gas: self.max_fee_per_gas,
gas_limit: self.gas_limit,
to: self.to,
value: self.value,
input: self.input,
fee_token: self.fee_token,
fee_payer: self.fee_payer,
max_fee_per_gas_usd_attodollars: self.max_fee_per_gas_usd_attodollars,
fee_payer_signature: self.fee_payer_signature,
})
}
}
impl Encodable for TxUsdMultiToken {
fn encode(&self, w: &mut dyn alloy_rlp::BufMut) {
w.put_slice(&[0x7a]);
self.chain_id.encode(w);
self.nonce.encode(w);
self.max_priority_fee_per_gas.encode(w);
self.max_fee_per_gas.encode(w);
self.gas_limit.encode(w);
match &self.to {
None => w.put_slice(&[0x80]), Some(addr) => addr.encode(w),
}
self.value.encode(w);
self.input.encode(w);
w.put_slice(&[0xc0]);
self.fee_token.encode(w);
self.fee_payer.encode(w);
self.max_fee_per_gas_usd_attodollars.encode(w);
self.fee_payer_signature.encode(w);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_erc20_transfer() {
let token: Address = "0xa1700000000000000000000000000000000000002".parse().unwrap();
let recipient: Address = "0x1234567890123456789012345678901234567890".parse().unwrap();
let amount = U256::from(100_000_000u64);
let tx = TxBuilder::new()
.chain_id(1337)
.nonce(0)
.erc20_transfer(token, recipient, amount)
.fee_token(token)
.max_fee_per_gas_usd(BASE_FEE_ATTO * 2)
.build();
assert_eq!(tx.chain_id, 1337);
assert_eq!(tx.nonce, 0);
}
}