Send Transactions
Learn how to send native tokens on different blockchains.
You can send native tokens, sign a transaction without broadcasting it, track transaction finality, and orchestrate multi-chain payments from WDK wallet accounts.
Get Testnet Funds: To test these transactions without spending real money, ensure you are on a testnet and have obtained funds. See Testnet Funds & Faucets for a list of available faucets.
BigInt Usage: Always use BigInt (the n suffix) for monetary values to avoid precision loss with large numbers.
Send Native Tokens
The sendTransaction method allows you to transfer value. It accepts a unified configuration object, though specific parameters (like value formatting) may vary slightly depending on the blockchain.
Ethereum Example
On EVM chains, values are typically expressed in Wei (1 ETH = 10^18 Wei).
The following example will:
- Retrieve the first Ethereum account (see Manage Accounts)
- Send 0.001 ETH (1000000000000000 wei) to an account using
sendTransaction.
const ethAccount = await wdk.getAccount('ethereum', 0)
const result = await ethAccount.sendTransaction({
to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F',
value: 1000000000000000n // 0.001 ETH (in Wei)
})
console.log('Transaction submitted:', result.hash)TON Example
On TON, values are expressed in Nanotons (1 TON = 10^9 Nanotons).
The following example will:
- Retrieve the first TON account
- Send 1 TON (1000000000 nton) to an account using
sendTransaction.
// Send TON transaction
const tonAccount = await wdk.getAccount('ton', 0)
const tonResult = await tonAccount.sendTransaction({
to: 'UQCz5ON7jjK32HnqPushubsHxgsXgeSZDZPvh8P__oqol90r',
value: 1000000000n // 1 TON (in nanotons)
})
console.log('Signed TON transfer body hash:', tonResult.hash)Sign Without Broadcasting
Use account.signTransaction() when your app needs a signed transaction payload but does not want WDK to broadcast it immediately. Wallet modules accept their own transaction shape and may return a module-specific signed payload.
const ethAccount = await wdk.getAccount('ethereum', 0)
const signedTransaction = await ethAccount.signTransaction({
to: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F',
value: 1000000000000000n
})Treat a signed transaction as a sensitive, potentially broadcastable authorization payload. Do not log it.
signTransaction() only signs. Use sendTransaction() when you want WDK to sign and submit the transaction. The returned identifier and settlement semantics depend on the concrete module.
Apply Local Transaction Policies
Use wdk.registerPolicy() to evaluate local ALLOW and DENY rules before account or protocol write methods run. Policies can target a full wallet identifier or selected account indices and derivation paths.
When a policy governs an account, wrapped write operations are default-denied unless a matching ALLOW permits them. For approval limits, start with an explicit ALLOW baseline and add narrower DENY rules for blocked cases.
See Transaction Policies for policy scope, evaluation order, simulation, and error-handling examples.
Handling Responses
The sendTransaction method returns a transaction result object. Its hash is a module-specific tracking identifier: it can be an on-chain transaction hash or Solana signature, an ERC-4337 UserOperation hash, a provider relay ID, a signed TON message-body hash, or a Spark transfer ID. Use the concrete module's getTransaction() or waitForTransaction() implementation; do not assume every value is immediately searchable as a native blockchain transaction ID.
Returning from send or transfer proves submission to that module's provider or relay, not inclusion or successful execution. Wait for the required finality and inspect success before releasing goods or committing dependent state.
Track Transaction Finality
Base Wallet v1.0.0-beta.17 defines getTransaction(hash) for one normalized lookup and waitForTransaction(hash, options) for polling until the requested finality. The common receipt reports pending, confirmed, final, or dropped; concrete wallet modules can add native chain fields.
Current first-party Bitcoin, standard EVM, ERC-4337, EIP-7702, Solana, Solana Gasless, Spark, TON, TON Gasless, TRON, TRON Gasfree, and Aptos releases implement these methods. Older and third-party implementations can still inherit the base getTransaction() method that throws NotImplementedError; confirm support in the concrete module before relying on it.
// `account` must implement the Base Wallet v1.0.0-beta.17 contract.
const receipt = await account.waitForTransaction(transactionHash, {
target: 'confirmed',
timeout: 120000,
interval: 4000,
maxPollErrors: 3
})
if (receipt.finality === 'dropped') {
console.error('Transaction dropped before confirmation')
} else if (receipt.success === false) {
console.error('Transaction confirmed but reverted')
} else {
console.log('Transaction confirmed in block:', receipt.block)
}waitForTransaction() defaults to the confirmed target. The base account uses a 60-second polling deadline and four-second interval, but chain modules can override both defaults. A transaction reported as dropped must remain dropped for two consecutive polls before the method returns it.
An unseen transaction raises NoSuchElementError from getTransaction(). The wait helper treats that as transient and keeps polling until its deadline. It also tolerates three consecutive ProviderError results by default and rethrows the next one. Other lookup errors are rethrown immediately; an observed expired deadline throws TimeoutError.
The timeout is checked only after awaited lookups and between polls. It does not cancel getTransaction() or shorten the final sleep to the remaining time, and a target receipt returned by a completed lookup is accepted before the deadline is checked again. A slow or hung provider call can therefore overrun the configured timeout indefinitely. Configure request timeouts or cancellation in the concrete provider separately.
A resolved wait does not guarantee successful execution. It also resolves for reverted transactions and stable dropped receipts, so always inspect finality and success before updating application state.
getTransactionReceipt() is deprecated in @tetherto/wdk-wallet v1.0.0-beta.17. Current first-party wallet releases implement getTransaction(); for an older or third-party module, migrate only after confirming that its concrete release implements the normalized finality shape.
Multi-Chain Transactions
You can orchestrate payments across different chains in a single function by acting on multiple account objects sequentially.
The following example retrieves ETH and TON accounts, submits each transfer in sequence, and waits for successful finality before starting the next payment.
async function sendCrossChainPayments(wdk) {
const ethAccount = await wdk.getAccount('ethereum', 0)
const tonAccount = await wdk.getAccount('ton', 0)
// 1. Send ETH
const ethResult = await ethAccount.sendTransaction({
to: '0x...',
value: 1000000000000000000n
})
const ethReceipt = await ethAccount.waitForTransaction(ethResult.hash, {
target: 'final'
})
if (ethReceipt.finality === 'dropped' || ethReceipt.success === false) {
throw new Error('ETH payment did not complete successfully')
}
// 2. Send TON
const tonResult = await tonAccount.sendTransaction({
to: 'EQ...',
value: 1000000000n
})
const tonReceipt = await tonAccount.waitForTransaction(tonResult.hash, {
target: 'final'
})
if (tonReceipt.finality === 'dropped' || tonReceipt.success === false) {
throw new Error('TON payment did not complete successfully')
}
}Next Steps
For more complex interactions like swapping tokens or bridging assets, learn how to integrate protocols. To guard writes before they execute, add local transaction policies.