Skip to main content

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 (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 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:

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

  1. Initiate → POST /v1/withdrawals → Get _id
  2. Poll → GET /v1/withdrawals/{id}/claim → Wait for proof_ready: true
  3. Claim → Call bridge contract's claimAsset() → Receive funds on destination chain