Skip to content

Commit 9c2fbcb

Browse files
authored
feat(cli): support erc20 transfers (#211)
**Motivation** We want to be able to use the interface of `ERC20` transfers via cli. **Description** * The `transfer` function in `sdk/src/sdk.rs` is refactored to accept an optional `calldata` argument and to use the provided `Overrides` value directly, enabling both native and ERC20 token transfers. * The CLI transfer commands now construct ERC20 transfer transactions by encoding the `transfer(address,uint256)` calldata when a token address is provided, and set the transfer value to zero.
1 parent 314447c commit 9c2fbcb

File tree

7 files changed

+84
-36
lines changed

7 files changed

+84
-36
lines changed

cli/src/cli.rs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ use crate::{
88
};
99
use clap::{ArgAction, Parser, Subcommand};
1010
use ethrex_common::types::{AuthorizationTupleEntry, TxType};
11-
use ethrex_common::{Address, Bytes, H256, H520};
11+
use ethrex_common::{Address, Bytes, H256, H520, U256};
1212
use ethrex_l2_common::calldata::Value;
1313
use ethrex_l2_common::utils::get_address_from_secret_key;
1414
use ethrex_l2_rpc::signer::{LocalSigner, Signer};
1515
use ethrex_rlp::encode::RLPEncode;
1616
use ethrex_rpc::EthClient;
1717
use ethrex_rpc::clients::Overrides;
1818
use ethrex_rpc::types::block_identifier::{BlockIdentifier, BlockTag};
19-
use ethrex_sdk::calldata::decode_calldata;
19+
use ethrex_sdk::calldata::{decode_calldata, encode_calldata};
2020
use ethrex_sdk::{build_generic_tx, create2_deploy_from_bytecode, send_generic_transaction};
2121
use ethrex_sdk::{compile_contract, git_clone};
2222
use keccak_hash::keccak;
@@ -450,27 +450,45 @@ impl Command {
450450
println!("{signer:x?}");
451451
}
452452
Command::Transfer { args, rpc_url } => {
453-
if args.token_address.is_some() {
454-
todo!("Handle ERC20 transfers")
455-
}
456-
457453
if args.explorer_url {
458454
todo!("Display transaction URL in the explorer")
459455
}
460456

457+
let client = EthClient::new(rpc_url)?;
461458
let from = get_address_from_secret_key(&args.private_key.secret_bytes())
462459
.map_err(|e| eyre::eyre!(e))?;
463-
464-
let client = EthClient::new(rpc_url)?;
460+
let (to, calldata, overrides) = if let Some(token_address) = args.token_address {
461+
let signature = "transfer(address,uint256)";
462+
let values = vec![Value::Address(args.to), Value::Uint(args.amount)];
463+
let calldata = encode_calldata(signature, &values)?;
464+
465+
(
466+
token_address,
467+
Some(calldata.into()),
468+
Overrides {
469+
value: Some(U256::zero()),
470+
..Default::default()
471+
},
472+
)
473+
} else {
474+
(
475+
args.to,
476+
None,
477+
Overrides {
478+
value: Some(args.amount),
479+
..Default::default()
480+
},
481+
)
482+
};
465483

466484
let tx_hash = transfer(
467-
args.amount,
468485
from,
469-
args.to,
486+
to,
470487
TxType::EIP1559,
471488
&args.private_key,
472489
&client,
473-
Overrides::default(),
490+
overrides,
491+
calldata,
474492
)
475493
.await?;
476494

cli/src/commands/l2.rs

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ use crate::{
66
use clap::Subcommand;
77
use ethrex_common::{Address, H256, U256};
88
use ethrex_common::{Bytes, types::TxType};
9-
use ethrex_l2_common::utils::get_address_from_secret_key;
9+
use ethrex_l2_common::{calldata::Value, utils::get_address_from_secret_key};
1010
use ethrex_l2_rpc::clients::{get_batch_by_number, get_batch_number};
1111
use ethrex_rpc::EthClient;
1212
use ethrex_rpc::clients::Overrides;
13-
use ethrex_sdk::wait_for_l1_message_proof;
13+
use ethrex_sdk::{calldata::encode_calldata, wait_for_l1_message_proof};
1414
use rex_sdk::transfer;
1515
use rex_sdk::{
1616
l2::authorize::send_authorized_transaction,
@@ -572,10 +572,6 @@ impl Command {
572572
fee_token,
573573
rpc_url,
574574
} => {
575-
if args.token_address.is_some() {
576-
todo!("Handle ERC20 transfers")
577-
}
578-
579575
if args.explorer_url {
580576
todo!("Display transaction URL in the explorer")
581577
}
@@ -590,17 +586,40 @@ impl Command {
590586
TxType::EIP1559
591587
};
592588

589+
let (to, calldata, overrides) = if let Some(token_address) = args.token_address {
590+
let signature = "transfer(address,uint256)";
591+
let values = vec![Value::Address(args.to), Value::Uint(args.amount)];
592+
let calldata = encode_calldata(signature, &values)?;
593+
594+
(
595+
token_address,
596+
Some(calldata.into()),
597+
Overrides {
598+
fee_token,
599+
value: Some(U256::zero()),
600+
..Default::default()
601+
},
602+
)
603+
} else {
604+
(
605+
args.to,
606+
None,
607+
Overrides {
608+
fee_token,
609+
value: Some(args.amount),
610+
..Default::default()
611+
},
612+
)
613+
};
614+
593615
let tx_hash = transfer(
594-
args.amount,
595616
from,
596-
args.to,
617+
to,
597618
tx_type,
598619
&args.private_key,
599620
&client,
600-
Overrides {
601-
fee_token,
602-
..Default::default()
603-
},
621+
overrides,
622+
calldata,
604623
)
605624
.await?;
606625

@@ -638,7 +657,6 @@ impl Command {
638657
.l1_fee_vault_address
639658
.map(|addr| format!("{addr:#x}"))
640659
.unwrap_or_else(String::new);
641-
642660
println!("L2 fee info for block {}:", fee_info.block_number);
643661
println!(" Base fee vault: {base_fee_vault_address}");
644662
println!(" Operator fee vault: {operator_fee_vault_address}");

sdk/examples/keystore/main.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
6767
let rich_wallet_address = get_address_from_secret_key(&rich_wallet_pk.secret_bytes())?;
6868
let amount = U256::from_dec_str("1000000000000000000").expect("Failed to parse amount");
6969
let transfer_tx_hash = transfer(
70-
amount,
7170
rich_wallet_address,
7271
keystore_address,
7372
TxType::EIP1559,
7473
&rich_wallet_pk,
7574
&eth_client,
76-
Overrides::default(),
75+
Overrides {
76+
value: Some(amount),
77+
..Default::default()
78+
},
79+
None,
7780
)
7881
.await?;
7982

sdk/examples/simple_usage.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,16 @@ async fn main() {
6161
let to = Address::from_str("0x4852f44fd706e34cb906b399b729798665f64a83").unwrap();
6262

6363
let tx_hash = transfer(
64-
amount,
6564
from,
6665
to,
6766
TxType::EIP1559,
6867
&args.private_key,
6968
&eth_client,
70-
Overrides::default(),
69+
Overrides {
70+
value: Some(amount),
71+
..Default::default()
72+
},
73+
None,
7174
)
7275
.await
7376
.unwrap();

sdk/src/l2/deposit.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,16 @@ pub async fn deposit_through_transfer(
2121
eth_client: &EthClient,
2222
) -> Result<H256, EthClientError> {
2323
transfer(
24-
amount,
2524
from,
2625
bridge_address,
2726
TxType::EIP1559,
2827
from_pk,
2928
eth_client,
30-
Overrides::default(),
29+
Overrides {
30+
value: Some(amount),
31+
..Default::default()
32+
},
33+
None,
3134
)
3235
.await
3336
}

sdk/src/sdk.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,16 @@ pub enum SdkError {
3636
}
3737

3838
pub async fn transfer(
39-
amount: U256,
4039
from: Address,
4140
to: Address,
4241
tx_type: TxType,
4342
private_key: &SecretKey,
4443
client: &EthClient,
45-
mut overrides: Overrides,
44+
overrides: Overrides,
45+
calldata: Option<Bytes>,
4646
) -> Result<H256, EthClientError> {
47-
overrides.value = Some(amount);
48-
let tx = build_generic_tx(client, tx_type, to, from, Bytes::new(), overrides).await?;
47+
let calldata = calldata.unwrap_or_default();
48+
let tx = build_generic_tx(client, tx_type, to, from, calldata, overrides).await?;
4949

5050
let signer = LocalSigner::new(*private_key).into();
5151
send_generic_transaction(client, tx, &signer).await

sdk/tests/tests.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -721,13 +721,16 @@ async fn perform_transfer(
721721
.await?;
722722

723723
let transfer_tx = transfer(
724-
transfer_value,
725724
transferer_address,
726725
transfer_recipient_address,
727726
TxType::EIP1559,
728727
transferer_private_key,
729728
proposer_client,
730-
Overrides::default(),
729+
Overrides {
730+
value: Some(transfer_value),
731+
..Default::default()
732+
},
733+
None,
731734
)
732735
.await?;
733736

0 commit comments

Comments
 (0)