Skip to main content
If you’re a remote blockchain partner, you can submit to withdraw from xReserve after a user burns tokens on your chain. This tutorial walks through the process using Canton TestNet as the example remote blockchain and Ethereum Sepolia as both the example source and destination blockchain.
Note: This tutorial is intended for remote blockchain partners. If you’re a developer or user, visit the remote blockchain website or contact them for instructions on how to withdraw funds from xReserve.

Step 1. Monitor burn events

Listen for burn events on your blockchain. Each event should include at least:
  • The recipient wallet address on the destination chain.
  • The remote domain ID.

Step 2. Prepare the withdrawal

Send the burn intent data to the /prepare-withdrawal endpoint. The following example request contains data to withdraw 2500 USDC on Ethereum:
json
{
  "batches": [
    {
      "token": "USDC",
      "valueExcludingFees": "2500.00",
      "remoteDomain": 10001,
      "remoteDepositor": "0x000000000000000000000000c0ffee254729296a45a3885639ac7e10f9d54979",
      "finalDestinationDomain": 0,
      "finalDestinationRecipient": "0x000000000000000000000000aabbee11223344556677889900aabbccddeeff00",
      "finalDestinationCaller": "0x00000000000000000000000000112233445566778899aabbccddeeff00112233",
      "useCircleForwarding": true,
      "salt": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
    }
  ]
}
Response The API returns a fully encoded burn intent and the canonical message hash for you to sign, for example:
json
{
  "batches": [
    {
      "burnIntents": [
        {
          "maxBlockHeight": "18976543",
          "maxFee": "750000",
          "spec": {
            "version": 1,
            "sourceDomain": 0,
            "destinationDomain": 0,
            "sourceContract": "0x0000000000000000000000000077777d7eba4688bdef3e311b846f25870a19b9",
            "destinationContract": "0x0000000000000000000000000022222abe238cc2c7bb1f21003f0a260052475b",
            "sourceToken": "0x74ed63088c070c8fd5d8ad71f2a1cef868c63d00e0ac6dc2a6722d171691a422",
            "destinationToken": "0x0000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238",
            "sourceDepositor": "0x000000000000000000000000c0ffee254729296a45a3885639ac7e10f9d54979",
            "destinationRecipient": "0x000000000000000000000000aabbee11223344556677889900aabbccddeeff00",
            "sourceSigner": "0x0000000000000000000000005555555555555555555555555555555555555555",
            "destinationCaller": "0x00000000000000000000000000112233445566778899aabbccddeeff00112233",
            "value": "2500000000",
            "salt": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
            "hookData": {
              "remoteDomain": 10001,
              "remoteDepositor": "0xc0ffee254729296a45a3885639ac7e10f9d54979",
              "remoteToken": "0x74ed63088c070c8fd5d8ad71f2a1cef868c63d00e0ac6dc2a6722d171691a422",
              "forwardingContractAddress": "0x0000000000000000000000000000000000000000",
              "forwardingCalldata": "0x"
            }
          }
        }
      ],
      "encoded": "0x8ddf3a...f9a2",
      "messageHashToSign": "0x4f5c6d7e8f90123456789abcdeffedcba9876543210fedcba9876543210abcd"
    }
  ]
}

Step 3. Sign the message hash

Use your attester keys to sign the messageHashToSign returned in the previous step. This produces the burn intent signatures.

Step 4. Submit the withdrawal

Send the encoded burn intent payload, the array of burn signatures, and the burn transaction hash to the /withdraw endpoint. Set useCircleForwarding to true if you want Circle to submit the withdrawal on behalf of your user.
Note: If you don’t use Circle forwarding, you’ll need to perform an additional crosschain transfer to withdraw funds from xReserve.
The following is an example request:
json
{
  "batches": [
    {
      "burnIntents": {
        "maxBlockHeight": "18976543",
        "maxFee": "750000",
        "spec": {
          "version": 1,
          "sourceDomain": 0,
          "destinationDomain": 0,
          "sourceContract": "0x0000000000000000000000000077777d7eba4688bdef3e311b846f25870a19b9",
          "destinationContract": "0x0000000000000000000000000022222abe238cc2c7bb1f21003f0a260052475b",
          "sourceToken": "0x74ed63088c070c8fd5d8ad71f2a1cef868c63d00e0ac6dc2a6722d171691a422",
          "destinationToken": "0x0000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c7238",
          "sourceDepositor": "0x000000000000000000000000c0ffee254729296a45a3885639ac7e10f9d54979",
          "destinationRecipient": "0x000000000000000000000000aabbee11223344556677889900aabbccddeeff00",
          "sourceSigner": "0x0000000000000000000000005555555555555555555555555555555555555555",
          "destinationCaller": "0x00000000000000000000000000112233445566778899aabbccddeeff00112233",
          "value": "2500000000",
          "salt": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
          "hookData": {
            "remoteDomain": 10001,
            "remoteDepositor": "0xc0ffee254729296a45a3885639ac7e10f9d54979",
            "remoteToken": "0x74ed63088c070c8fd5d8ad71f2a1cef868c63d00e0ac6dc2a6722d171691a422",
            "forwardingContractAddress": "0x0000000000000000000000000000000000000000",
            "forwardingCalldata": "0x"
          }
        }
      },
      "burnSignatures": [
        "0x9c1d2e3f4a5b6c7d8e9f00112233445566778899aabbccddeeff0011223344556677",
        "0x7f6e5d4c3b2a19080706050403020100ffeeddccbbaa99887766554433221100"
      ],
      "burnTxId": "0x5a7b3c1d4e6f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70819",
      "useCircleForwarding": true
    }
  ]
}
Response The API returns withdrawalId, a unique identifier for each withdrawal batch. For example:
json
[
  {
    "withdrawalId": "6149dc3d-71bf-4d57-8cc1-5e2d4c0a8e70",
    "transferSpecHashes": [
      "0x23f8c66d5a8bb5b712f2f4bc1c0a5a51d7c2b4f93d587f1449bcf3da6d8e1f20"
    ],
    "verificationStatus": "unverified",
    "attestation": null,
    "requireCircleRelay": true
  }
]

Step 5. Monitor withdrawal status

Check the status of your withdrawal by calling GET /withdrawals/{withdrawalId}. xReserve releases funds once the status is finalized. If the status remains created, verified, or unverified, keep polling. If the status changes to failed, resolve the issue and resubmit the withdrawal.