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 key -
x-api-signature: HMAC signature (see Quick Start Guide) -
x-api-timestamp: Current timestamp in milliseconds -
Content-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 decimals -
chain_id(number): Destination chain ID (1 = Ethereum mainnet, etc.) -
signature(string): Wallet signature of the withdrawal payload -
timestamp(number): Current time as Unix timestamp in milliseconds
Message Signing:
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 key -
x-api-signature: HMAC signature -
x-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 claiming -
amount(string): Amount in base units (wei) -
amount_formatted(string): Human-readable amount with decimals -
local_exit_proof(string): Local Merkle proof -
global_exit_proof(string): Global Merkle proof -
global_claim_index(string): Index in the global Merkle tree -
token_address(string): Smart contract address of the token -
chain_id(number): Blockchain chain ID -
destination_network_id(number): Destination network ID on Kalqix tree -
destination_address(string): Your wallet address on destination chain -
bridge_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:
// 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