Send on EVM
Every EVM send follows the same shape: you describe the transaction, the SDK builds a canonical intent, signs it with your API key, and submits it. XKOVA binds that signature to exactly what it broadcasts — an operator cannot change the recipient or amount after you approve.
Chains and IDs
Section titled “Chains and IDs”| Chain | chain | Sandbox chainId | Production chainId |
|---|---|---|---|
| Avalanche C-Chain | avalanche_c | 43113 (Fuji) | 43114 |
| Base | base | 84532 (Sepolia) | 8453 |
You pass the numeric chainId on each send; it is part of the signed intent, so
a transaction can never be replayed onto a different chain.
Native value
Section titled “Native value”const tx = await xkova.sendNative({ wallet, chainId: 43113, to: "0xRecipient…", amount: "5000000000000000", // wei, decimal string idempotencyKey: "payout-2026-09-05-001",});Amounts are base units as decimal strings (wei for native, the token’s
smallest unit for ERC-20). Strings avoid the precision loss of JavaScript
numbers; bigint is also accepted.
Tokens
Section titled “Tokens”// ERC-20 transfer(to, amount)await xkova.sendErc20({ wallet, chainId, token: "0xToken…", to, amount: "1000000", idempotencyKey });
// ERC-721 safeTransferFrom(wallet → to, tokenId)await xkova.sendErc721({ wallet, chainId, token: "0xNft…", to, tokenId: "42", idempotencyKey });
// ERC-1155 safeTransferFrom(wallet → to, tokenId, amount)await xkova.sendErc1155({ wallet, chainId, token: "0xNft…", to, tokenId: "7", amount: "3", idempotencyKey });The from is always the wallet you name — you can only move assets the vault
controls.
Any contract call
Section titled “Any contract call”For an arbitrary call, encode the calldata yourself (e.g. with viem or
ethers) and send the exact { to, value, data }:
import { encodeFunctionData } from "viem";
const data = encodeFunctionData({ abi, functionName: "approve", args: ["0xSpender…", 1000000n],});
await xkova.sendTransaction({ wallet, chainId: 43113, to: "0xToken…", data, // 0x-hex value: 0n, idempotencyKey: "approve-usdc-001",});What you sign is exactly { to, value, data } — nothing about the call is
inferred or rewritten on our side.
The operator fills the mechanics
Section titled “The operator fills the mechanics”You authorize intent — chain, recipient, amount, asset. Nonce, gas, and fee
are mechanical and are filled by the operator at broadcast. To bound the fee, set
max_fee (base units) on the send; a transaction that would exceed it is
rejected rather than broadcast.
Idempotency
Section titled “Idempotency”Every send carries an idempotencyKey you choose. Retrying with the same key
returns the same transaction instead of sending twice — so a timeout or a retry
never double-spends. Reusing a key with different contents is a 409
conflict. Use a key derived from your own operation ID, not a random value you
forget on retry.
Tracking
Section titled “Tracking”sendNative and friends return a Transaction in an early state. Follow it to a
terminal state by polling getTransaction(id) or, better, with
webhooks. The state machine and terminal outcomes are in
Core concepts.