Skip to main content

Signing transactions

Advanced
Ethereum
Tutorial

Overview

Before a transaction can be sent to the Ethereum network, it must be signed and formatted into a raw ETH transaction. Transactions are signed using threshold ECDSA chain-key signatures.

A code example using the ethers-core Rust library to sign and format an ETH EIP-1559 transaction can be found below:

#[update(guard = "caller_is_not_anonymous")]
async fn sign_transaction(req: SignRequest) -> String {
use ethers_core::types::transaction::eip1559::Eip1559TransactionRequest;
use ethers_core::types::Signature;

const EIP1559_TX_ID: u8 = 2;

let caller = ic_cdk::caller();

let data = req.data.as_ref().map(|s| decode_hex(s));

let tx = Eip1559TransactionRequest {
chain_id: Some(nat_to_u64(&req.chain_id)),
from: None,
to: Some(
Address::from_str(&req.to)
.expect("failed to parse the destination address")
.into(),
),
gas: Some(nat_to_u256(&req.gas)),
value: Some(nat_to_u256(&req.value)),
nonce: Some(nat_to_u256(&req.nonce)),
data,
access_list: Default::default(),
max_priority_fee_per_gas: Some(nat_to_u256(&req.max_priority_fee_per_gas)),
max_fee_per_gas: Some(nat_to_u256(&req.max_fee_per_gas)),
};

let mut unsigned_tx_bytes = tx.rlp().to_vec();
unsigned_tx_bytes.insert(0, EIP1559_TX_ID);

let txhash = keccak256(&unsigned_tx_bytes);

let (pubkey, signature) = pubkey_and_signature(&caller, txhash.to_vec()).await;

let signature = Signature {
v: y_parity(&txhash, &signature, &pubkey),
r: U256::from_big_endian(&signature[0..32]),
s: U256::from_big_endian(&signature[32..64]),
};

let mut signed_tx_bytes = tx.rlp_signed(&signature).to_vec();
signed_tx_bytes.insert(0, EIP1559_TX_ID);

format!("0x{}", hex::encode(&signed_tx_bytes))
}

Additional examples of signing transactions can be found in the threshold ECDSA documentation.

Next steps

Now that your transaction is signed, it can be submitted to Ethereum to be executed.

Learn how to submit Ethereum transactions.