EVM Transactions
Build, sign, broadcast, and monitor EVM transactions with Zafeguard components.
EVM Transactions
Components for the full EVM transaction lifecycle: calldata encoding → transaction building → signing → broadcasting → confirmation.
Public RPC URLs enforce rate limits per IP address and can fail under concurrent load. Use a private endpoint (Alchemy, Infura, QuickNode, or self-hosted). See Connecting to any chain.
BUILD_EVM_CALLDATA
BUILD_EVM_CALLDATA Workflow Component
ABI-encode a contract function call into hex calldata.
Config
| Field | Type | Required | Description |
|---|---|---|---|
functionName | string | Yes | Name of the contract function to encode (e.g. "transfer") |
Inputs
| Field | Type | Description |
|---|---|---|
abi | AbiItem[] | Contract ABI (array of function/event definitions) |
args | string[] | Function arguments in order, as strings |
Outputs
| Field | Type | Description |
|---|---|---|
calldata | string | ABI-encoded hex calldata (0x...) |
SDK example
import { WorkspaceClient, ComponentModule } from '@zafeguard/caller-sdk';
const workspace = new WorkspaceClient({ apiKey: process.env.ZAFEGUARD_API_KEY! });
const result = await workspace.call(ComponentModule.BUILD_EVM_CALLDATA, {
abi: [
{
type: 'function',
name: 'transfer',
inputs: [
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [{ name: '', type: 'bool' }],
stateMutability: 'nonpayable',
},
],
args: ['0xRecipientAddress', '1000000'],
}).promise();
console.log(result.calldata); // "0xa9059cbb..."BUILD_EVM_TRANSFER_CALLDATA
BUILD_EVM_TRANSFER_CALLDATA Workflow Component
Convenience wrapper around BUILD_EVM_CALLDATA for the common case of sending native ETH or an ERC-20 transfer. Picks the right encoding automatically:
tokenAddressisnull→ native ETH transfer. Returnscalldata: "0x",to: recipient,value: amount.tokenAddressset → ERC-20transfer(recipient, amount). Returns the encoded calldata,to: tokenAddress,value: "0x0".
The output plugs directly into BUILD_EVM_TRANSACTION.
Inputs
| Field | Type | Description |
|---|---|---|
recipient | string (address) | Recipient address |
amount | string (bigint) | Amount in the token's smallest unit (wei for ETH) |
tokenAddress | string | null | ERC-20 contract — omit/null for native ETH |
Outputs
| Field | Type | Description |
|---|---|---|
calldata | string | ABI-encoded transfer calldata, or 0x for native ETH |
to | string | Token contract address (ERC-20) or recipient (ETH) |
value | string | ETH value to send (0x0 for ERC-20, amount for native) |
BUILD_EVM_ERC20_APPROVE_CALLDATA
BUILD_EVM_ERC20_APPROVE_CALLDATA Workflow Component
ABI-encode an ERC-20 approve(spender, amount) call. The standard upstream step before any operation that pulls tokens via transferFrom — bridges, DEX swaps, lending deposits, vault entries.
Pass amount = 2^256 - 1 (max uint256) for an infinite approval.
Inputs
| Field | Type | Description |
|---|---|---|
tokenAddress | string (address) | ERC-20 contract to approve against |
spender | string (address) | Address allowed to pull tokens (e.g. a bridge router, a Uniswap router, a vault) |
amount | string (bigint) | Allowance in the token's smallest unit |
Outputs
| Field | Type | Description |
|---|---|---|
calldata | string | ABI-encoded approve(spender, amount) calldata |
to | string | The ERC-20 contract address (forwarded from input) |
value | string | Always "0x0" — approve is a non-payable call |
The output plugs directly into BUILD_EVM_TRANSACTION.
SDK example
import { WorkspaceClient, ComponentModule } from '@zafeguard/caller-sdk';
const workspace = new WorkspaceClient({ apiKey: process.env.ZAFEGUARD_API_KEY! });
const approve = await workspace.call(ComponentModule.BUILD_EVM_ERC20_APPROVE_CALLDATA, {
tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum
spender: '0xc026395860Db2d07ee33e05fE50ed7bD583189C7', // bridge router
amount: '1000000', // 1 USDC (6 decimals)
}).promise();
// Feed into BUILD_EVM_TRANSACTION → SIGN → BROADCAST → WAITBUILD_EVM_TRANSACTION
BUILD_EVM_TRANSACTION Workflow Component
Construct a complete unsigned EVM transaction object. Estimates gas automatically.
Config
| Field | Type | Default | Description |
|---|---|---|---|
type | 'LEGACY' | 'EIP1559' | 'EIP1559' | Transaction type. Use LEGACY for chains that don't support EIP-1559 |
deductFee | boolean | false | Subtract estimated gas fee from value. Useful when sending the full native balance |
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
jsonRpcUrl | string | Yes | JSON-RPC endpoint |
from | string | Yes | Sender address |
to | string | Yes | Recipient or contract address |
value | string | No | Native token amount to send in wei (bigint string, default "0") |
calldata | string | No | ABI-encoded calldata (0x...). Omit for plain transfers |
gasLimit | string | No | Manual gas limit override (bigint string). Auto-estimated if omitted |
Outputs
| Field | Type | Description |
|---|---|---|
unsignedTransaction | string | RLP-encoded unsigned transaction hex |
serializedHash | string | Transaction hash pre-image — pass directly to SIGN_WITH_KEY_SHARE |
estimatedFee | string (bigint) | Estimated maximum gas fee in wei. For LEGACY: gasLimit × gasPrice. For EIP1559: gasLimit × maxFeePerGas. gasLimit includes a 20% safety buffer |
SIGN_EVM_TRANSACTION
SIGN_EVM_TRANSACTION Workflow Component
Combine an unsigned transaction with an MPC signature to produce a signed transaction ready for broadcast.
Config
None.
Inputs
| Field | Type | Description |
|---|---|---|
unsignedTransaction | string | Output of BUILD_EVM_TRANSACTION |
signature | string | ECDSA signature from SIGN_WITH_KEY_SHARE |
Outputs
| Field | Type | Description |
|---|---|---|
signedTransaction | string | RLP-encoded signed transaction hex |
BROADCAST_EVM_TRANSACTION
BROADCAST_EVM_TRANSACTION Workflow Component
Submit a signed transaction to the network and return the transaction hash.
Config
None.
Inputs
| Field | Type | Description |
|---|---|---|
jsonRpcUrl | string | JSON-RPC endpoint |
signedTransaction | string | Output of SIGN_EVM_TRANSACTION |
Outputs
| Field | Type | Description |
|---|---|---|
transactionHash | string | Transaction hash (0x...) |
WAIT_FOR_EVM_TRANSACTION
WAIT_FOR_EVM_TRANSACTION Workflow Component
Block execution until a transaction is confirmed on-chain. Useful for sequencing dependent transactions.
Config
| Field | Type | Default | Description |
|---|---|---|---|
confirmations | number | 1 | Number of block confirmations to wait for before resolving (1–25) |
Inputs
| Field | Type | Description |
|---|---|---|
jsonRpcUrl | string | JSON-RPC endpoint |
transactionHash | string | Transaction hash to monitor (0x..., 32 bytes) |
Outputs
| Field | Type | Description |
|---|---|---|
blockNo | number | Block number in which the transaction was included |
gasUsed | number | Actual gas consumed by the transaction |
gasPrice | string | Effective gas price in wei (bigint string) |
gasFee | string | Total gas fee paid in wei — gasUsed × gasPrice (bigint string) |
WAIT_FOR_EVM_TRANSACTION is billed PER_EXECUTION_TIME — confirmations on congested chains can take minutes. Consider your credit budget.
Full transaction workflow
import { WorkspaceClient, ComponentModule } from '@zafeguard/caller-sdk';
const workspace = new WorkspaceClient({ apiKey: process.env.ZAFEGUARD_API_KEY! });
const path = await workspace.call(ComponentModule.GET_EVM_DERIVATION_PATH, { addressIndex: 0 }).promise();
const pubKey = await workspace.call(ComponentModule.COMPUTE_PUBLIC_KEY, {
keyId: 'my-key-id',
derivationPath: path.derivationPath,
}).promise();
const address = await workspace.call(ComponentModule.COMPUTE_EVM_ADDRESS, {
publicKey: pubKey.publicKey,
}).promise();
const calldata = await workspace.call(ComponentModule.BUILD_EVM_CALLDATA, {
abi: ERC20_ABI,
args: [recipientAddress, '1000000'],
}).promise();
const unsignedTx = await workspace.call(ComponentModule.BUILD_EVM_TRANSACTION, {
jsonRpcUrl: RPC_URL,
from: address.address,
to: TOKEN_CONTRACT,
value: '0',
calldata: calldata.calldata,
}).promise();
// ... sign and broadcast