Transfer Token - Integration Guide

This guide explains how to transfer ERC20 tokens from a Safe wallet to another address using the TRANSFER_TOKEN operation. This is a generic token transfer operation that works with any ERC20 token on supported blockchains.

📊 View Flow Diagram

1. Prepare Token Transfer

Before executing a token transfer, you need to prepare the operation. The API will validate the token address, check if it exists and is active on the target chain, and prepare the transaction.

Endpoint : POST /v1.2/safes/operations

Headers :

Body (JSON) :

TRANSFER_TOKEN Operation :

Request Example :

POST /v1.2/safes/operations
Content-Type: application/json
Authorization: Bearer <access_token>

{
  "safeAddress": "0xd676c6188195372EC269E9C2cAf815C56436A679",
  "chainId": 421614,
  "operations": [
    {
      "type": "TRANSFER_TOKEN",
      "tokenAddress": "0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430",
      "to": "0xDEST000000000000000000000000000000000000",
      "amount": "1.23"
    }
  ]
}

Response (200 OK) :

{
  "credentialRequestOptions": {
    "challenge": "<base64url_encoded_userOpHash>",
    "rpId": "foo.domain",
    "userVerification": "required",
    "allowCredentials": [
      {
        "id": "<base64url_encoded_credential_id>",
        "type": "public-key"
      }
    ],
    "timeout": 60000
  }
}
💾 To Store :
💡 Note : The API automatically validates the token address via BCReader to ensure: If the token is not found or inactive, the request will be rejected with a 4xx error.

Token Validation

The API validates tokens by querying BCReader:

Balance Verification

The API automatically checks the Safe's balance before preparing the operation. If the balance is insufficient, the request will be rejected with a 400 error.

2. Sign and Finalize Transfer

After receiving credentialRequestOptions, you must sign the operation using WebAuthn and submit the credential to finalize the transfer.

⚠️ Important : After receiving credentialRequestOptions, you must:
  1. Use navigator.credentials.get() with these options to obtain a credential
  2. Send a PUT /v1.2/safes/operations request with the credential to finalize the transfer

Sign Operation (Client-side)

Use WebAuthn to sign the operation:

const credential = await navigator.credentials.get({
  publicKey: credentialRequestOptions
});

Finalize Execution (PUT)

Endpoint : PUT /v1.2/safes/operations

Body (JSON) :

Request Example :

PUT /v1.2/safes/operations
Content-Type: application/json
Authorization: Bearer <access_token>

{
  "credential": {
    "id": "AVZs0qRCBSmfThZWu37g...",
    "rawId": "AVZs0qRCBSmfThZWu37g...",
    "type": "public-key",
    "response": {
      "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MFAAAAAQ...",
      "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoi...",
      "signature": "MEUCIQC..."
    }
  }
}

Response (200 OK) :

{
  "userOpHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}
💾 To Store :

3. Track Operation Status

Once the operation is submitted, you can track its status. Possible statuses are:

Endpoint to check status : GET /v1.2/safes/operations/{userOpHash}

Request Example :

GET /v1.2/safes/operations/0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
Authorization: Bearer <access_token>

Response (200 OK) :

{
  "userOpHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
  "safeAddress": "0xd676c6188195372EC269E9C2cAf815C56436A679",
  "chainId": 421614,
  "status": "CONFIRMED",
  "transactionHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
  "createdAt": "2025-01-15T10:30:00.000Z",
  "updatedAt": "2025-01-15T10:35:00.000Z",
  "operations": [
    { "index": 0, "type": "TRANSFER_TOKEN" }
  ]
}

4. Error Handling

Token Not Found

If the token address is not found on the specified chain, you will receive a 404 error:

{
  "error": "Token contract not found on chain 421614: from=0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430, to=0xDEST000000000000000000000000000000000000"
}

Token Inactive

If the token exists but is inactive, you will receive a 400 error:

{
  "error": "Token contract inactive on chain 421614: from=0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430, to=0xDEST000000000000000000000000000000000000 (active=false)"
}

Insufficient Balance

If the Safe does not have sufficient tokens, you will receive a 400 error:

{
  "error": "Insufficient balance: wallet 0xd676c6188195372EC269E9C2cAf815C56436A679 has 0.5 tokens, required 1.23"
}

Invalid Address

If the recipient address is invalid, you will receive a 400 error:

{
  "error": "Invalid recipient address: 0xINVALID"
}

5. Complete Flow Summary

  1. Prepare the transfer : POST /v1.2/safes/operations with TRANSFER_TOKEN operation
  2. Store credentialRequestOptions : Store the WebAuthn options received in the response
  3. Sign with passkey : Use navigator.credentials.get() with the credentialRequestOptions
  4. Finalize execution : PUT /v1.2/safes/operations with the credential
  5. Track status : Check status via GET /v1.2/safes/operations/{userOpHash}
  6. Verify result : Once status = "CONFIRMED", the transfer is complete
⚠️ Important Points :

Technical Deep Dive - For AI Integration

This section provides detailed technical information for AI systems integrating the IBEX token transfer flow, including architecture patterns, data models, and implementation details.

TRANSFER_TOKEN Operation - Technical Details

POST /v1.2/safes/operations

Token Validation Flow:

Balance Verification:

PUT /v1.2/safes/operations

Operation Status Tracking

Status Flow:

Status Polling:

Error Handling

Key Implementation Files

Complete Flow - Technical Sequence

  1. Prepare transfer : POST /v1.2/safes/operations with { type: "TRANSFER_TOKEN", tokenAddress, to, amount }
  2. Validate token : Server queries BCReader to verify token exists and is active
  3. Check balance : Server verifies Safe has sufficient balance (best-effort)
  4. Convert amount : Human-readable amount converted to wei using token decimals
  5. Build transaction : Server creates ERC20 transfer call
  6. Get credential options : Server returns credentialRequestOptions with userOpHash
  7. Sign with passkey : Client uses WebAuthn to sign challenge
  8. Finalize execution : PUT /v1.2/safes/operations with credential
  9. Operation signed : Server updates status to SIGNED
  10. Bundler execution : Bundler picks up signed operation, submits to blockchain
  11. Status tracking : Poll GET /v1.2/safes/operations/{userOpHash} until CONFIRMED
  12. Transfer complete : Once CONFIRMED, tokens have been transferred on-chain
← Back to Main Guide