Skip to main content

evmSignTransaction

Ethereum: Sign transaction

  • Use evmSignTransaction to sign EVM transactions
  • Supports EIP-1559 and Legacy transactions; make sure your RPC node supports the transaction type you use

Asks the device to sign a transaction using the private key derived by the given BIP32 path. Before signing, the user should confirm the transaction target, amount, fees, and other key details on Ukey Wallet.

const result = await HardwareSDK.evmSignTransaction(
connectId,
deviceId,
params,
);

Params

Optional common params

  • path - required string | Array<number> minimum length is 3. read more
  • transaction - required EthereumTransactionEIP1559 | EthereumSignTransaction transaction object to sign; the 0x prefix is optional for each field.
  • chainId - optional number identifies the target Ethereum network and helps distinguish different chain configurations. Reference.

Examples

EIP-1559 (after The London Upgrade)

If both maxFeePerGas and maxPriorityFeePerGas are provided, the transaction will be signed as the new type introduced by EIP-1559.

HardwareSDK.evmSignTransaction(connectId, deviceId, {
path: "m/44'/60'/0'/0/0",
transaction: {
to: "0x7314e0f1c0e28474bdb6be3e2c3e0453255188f8",
value: "0x38d7ea4c68000",
data: "0x",
chainId: 1,
nonce: "0x1",
maxFeePerGas: "0x77359400",
maxPriorityFeePerGas: "0x59682f00",
gasLimit: "0x5208",
},
chainId: 1,
});

Legacy

HardwareSDK.evmSignTransaction(connectId, deviceId, {
path: "m/44'/60'/0'/0/0",
transaction: {
to: "0x7314e0f1c0e28474bdb6be3e2c3e0453255188f8",
value: "0x38d7ea4c68000",
data: "0x",
chainId: 1,
nonce: "0x1",
gasLimit: "0x5208",
gasPrice: "0x77359400",
},
chainId: 1,
});

Result

{
success: true,
payload: {
v: string, // hexadecimal string with `0x` prefix
r: string, // hexadecimal string with `0x` prefix
s: string, // hexadecimal string with `0x` prefix
}
}

Error

{
success: false,
payload: {
error: string, // error message
code: number // error code
}
}

Transaction Signing and Broadcasting example

import type { UnsignedTransaction } from '@ethersproject/transactions';
import { TransactionTypes, serialize } from '@ethersproject/transactions';
import { keccak256 } from '@ethersproject/keccak256';
function buildSignedTxFromSignatureEvm({
tx,
signature,
}: {
tx: UnsignedTransaction;
signature: {
v: string | number; // examples: '0x11', '17', or 17
r: string; // with `0x` prefix
s: string; // with `0x` prefix
};
}) {
const { r, s, v } = signature;
/**
* SDK Legacy returns {v,r,s}; EIP-1559 returns {recoveryParam,r,s}
* splitSignature automatically converts v to recoveryParam
*/
const sig = splitSignature({
v: Number(v),
r,
s,
});
const rawTx = serialize(tx, sig);
const txid = keccak256(rawTx);
return {
rawTx,
txid,
};
}

// 1. Create transaction parameters
const txParams = {
to: '0x7314e0f1c0e28474bdb6be3e2c3e0453255188f8',
value: '0x38d7ea4c68000', // 0.001 ETH
data: '0x', // Empty data
chainId: 1,
nonce: '0x1',
// For EIP-1559 transaction
maxFeePerGas: '0x77359400', // 2 Gwei
maxPriorityFeePerGas: '0x59682f00', // 1.5 Gwei
gasLimit: '0x5208', // 21000
// For Legacy transaction
// gasPrice: '0x77359400', // 2 Gwei
};

// 2. Sign the transaction
const result = await HardwareSDK.evmSignTransaction(connectId, deviceId, {
path: "m/44'/60'/0'/0/0",
transaction: txParams,
...deviceCommonParams
});

// 3. Build signed transaction
const baseTx = {
to: txParams.to,
nonce: parseInt(txParams.nonce, 16),
gasLimit: txParams.gasLimit,
data: txParams.data,
value: txParams.value,
chainId: txParams.chainId,
};

// Add EIP-1559 specific fields
const isEIP1559 = txParams?.maxFeePerGas || txParams?.maxPriorityFeePerGas;
if (isEIP1559) {
Object.assign(baseTx, {
type: TransactionTypes.eip1559,
maxFeePerGas: txParams?.maxFeePerGas,
maxPriorityFeePerGas: txParams?.maxPriorityFeePerGas
});
} else {
Object.assign(baseTx, {
gasPrice: txParams.gasPrice,
});
}

// 4. Build final transaction with signature
const { rawTx, txid } = buildSignedTxFromSignatureEvm({
tx: baseTx,
signature: result.payload
});

// 5. Broadcast the transaction
// const provider = new ethers.providers.JsonRpcProvider();
// const broadcastedTx = await provider.sendTransaction(rawTx);

// 6. Wait for confirmation
// const receipt = await broadcastedTx.wait();
// console.log('Transaction confirmed:', receipt);