Withdrawals
Follow this guide to complete your withdrawal process.
Overview
Withdrawal is a three-step process that requires authentication and on-chain verification.
Step 1: Initiate Withdrawal
Provide details about which asset you want to withdraw.
Endpoint: POST /v1/withdrawals
Required Headers:
x-api-key: Your API keyx-api-signature: HMAC signature (see Quick Start Guide)x-api-timestamp: Current timestamp in millisecondsContent-Type: application/json
Request Body:
{
"asset": "USDC",
"amount": "100",
"chain_id": 1,
"signature": "0x...",
"timestamp": 1767225600000
}
Field Descriptions:
asset(string): Asset symbol (e.g., "USDC", "BTC")amount(string): Amount in formatted decimalschain_id(number): Destination chain ID (1 = Ethereum mainnet, etc.)signature(string): Wallet signature of the withdrawal payloadtimestamp(number): Current time as Unix timestamp in milliseconds
Message Signing (v1.4.0+):
Sign a canonicalized JSON payload with sorted keys:
const payload = {
action: 'WITHDRAW',
asset: 'USDC',
amount: '100',
chain_id: 1,
timestamp: Date.now()
};
// Sort keys and create canonical JSON
const sortedKeys = Object.keys(payload).sort();
const canonicalPayload = {};
sortedKeys.forEach(key => {
canonicalPayload[key] = payload[key];
});
const message = JSON.stringify(canonicalPayload);
const signature = await wallet.signMessage(message);
Important:
- Timestamp must be within 5 minutes of server time
- All keys must be sorted alphabetically
- Include explicit
action: 'WITHDRAW'in signed payload
Success Response (200):
{
"_id": "unique-withdrawal-request-id"
}
Step 2: Get Proof
Using the withdrawal ID from Step 1, fetch the withdrawal Merkle Proof. Proofs are submitted on-chain for verification.
Important: Proofs usually take a couple of seconds to generate. Poll this endpoint until proof_ready returns true. We recommend polling every 2-3 seconds.
Endpoint: GET /v1/withdrawals/{id}/claim
Required Headers:
x-api-key: Your API keyx-api-signature: HMAC signaturex-api-timestamp: Current timestamp in milliseconds
Path Parameters:
id(string): The withdrawal request ID from Step 1
Response:
{
"proof_ready": true,
"amount": "100000000",
"amount_formatted": "100.0",
"local_exit_proof": "",
"global_exit_proof": "",
"global_claim_index": "42",
"token_address": "",
"chain_id": 1,
"destination_network_id": 2,
"destination_address": "0x...",
"bridge_contract_address": "0x..."
}
Field Descriptions:
proof_ready(boolean): Whether the proof is ready for claimingamount(string): Amount in base units (wei)amount_formatted(string): Human-readable amount with decimalslocal_exit_proof(string): Local Merkle proofglobal_exit_proof(string): Global Merkle proofglobal_claim_index(string): Index in the global Merkle treetoken_address(string): Smart contract address of the tokenchain_id(number): Blockchain chain IDdestination_network_id(number): Destination network ID on Kalqix treedestination_address(string): Your wallet address on destination chainbridge_contract_address(string): Bridge contract address on destination chain
Step 3: Claim Withdrawal
Once your proof is ready, submit it to the on-chain smart contract to complete the withdrawal.
Call the destination network's bridge contract with the proof payload using web3 or ethers.js:
Example using ethers.js:
// Submit the claim transaction
const tx = await contract['claimAsset'](
local_exit_proof,
global_exit_proof,
global_claim_index,
token_address,
destination_network_id,
destination_address,
amount
);
// Wait for confirmation
const receipt = await tx.wait();
console.log('Withdrawal claimed! Transaction hash:', receipt.hash);
Complete Workflow Summary
- Initiate → POST
/v1/withdrawals→ Get_id - Poll → GET
/v1/withdrawals/{id}/claim→ Wait forproof_ready: true - Claim → Call bridge contract's
claimAsset()→ Receive funds on destination chain