Skip to main content

Executions

Executions represent attempted cross-chain transfers. Each deposit triggers an execution that tracks the transaction from start to finish, including status, amounts, fees, and any errors.

Get User Executions

Retrieve execution history for a specific user. Endpoint: GET /api/users/:userId/executions Authentication: Required (API Key)

Request

Headers:
x-api-key: your_api_key
Path Parameters:
ParameterTypeDescription
userIdstringUser identifier
Query Parameters:
ParameterTypeRequiredDefaultDescription
limitnumberNo20Number of executions to return (max: 100)
offsetnumberNo0Number of executions to skip (for pagination)
statusstringNo-Filter by status (e.g., “SUCCESS”, “FAILED”)

Response

Status: 200 OK
[
  {
    "id": "exec_abc123",
    "routeId": "route_xyz789",
    "userId": "user-123",
    "status": "SUCCESS",
    "fromChainId": 8453,
    "fromChainKey": "base",
    "fromTokenSymbol": "USDC",
    "fromTokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "toChainId": 137,
    "toChainKey": "polygon",
    "toTokenSymbol": "USDC",
    "toTokenAddress": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
    "amountIn": "10000000",
    "amountOutExpected": "9950000",
    "txHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
    "retryable": false,
    "retryCount": 0,
    "lastErrorCode": null,
    "lastErrorMessage": null,
    "createdAt": "2024-01-01T10:00:00.000Z",
    "updatedAt": "2024-01-01T10:01:30.000Z",
    "completedAt": "2024-01-01T10:01:30.000Z"
  }
]
Response Fields:
FieldTypeDescription
idstringUnique execution identifier
routeIdstringDeterministic route identifier (idempotency key)
userIdstringUser identifier
statusstringCurrent execution status (see Error Codes)
fromChainIdnumberSource chain ID
fromChainKeystringSource chain key (human-readable)
fromTokenSymbolstringSource token symbol
fromTokenAddressstringSource token contract address
toChainIdnumberDestination chain ID
toChainKeystringDestination chain key
toTokenSymbolstringDestination token symbol
toTokenAddressstringDestination token contract address
amountInstringDeposit amount in atomic units
amountOutExpectedstringExpected output in atomic units
txHashstringTransaction hash on source chain (if available)
retryablebooleanWhether execution can be retried automatically
retryCountnumberNumber of retry attempts
lastErrorCodestring|nullError code if failed
lastErrorMessagestring|nullHuman-readable error message
createdAtstringISO 8601 timestamp of creation
updatedAtstringISO 8601 timestamp of last update
completedAtstring|nullISO 8601 timestamp of completion (success or terminal failure)

Examples

Get all executions for a user:
curl https://api.bridgfy.com/api/users/user-abc-123/executions \
  -H "x-api-key: bfy_your_api_key"
Filter by status:
curl "https://api.bridgfy.com/api/users/user-abc-123/executions?status=FAILED" \
  -H "x-api-key: bfy_your_api_key"
Paginate results:
curl "https://api.bridgfy.com/api/users/user-abc-123/executions?limit=10&offset=20" \
  -H "x-api-key: bfy_your_api_key"

Error Responses

Unauthorized Status: 401 Unauthorized
{
  "statusCode": 401,
  "message": "Invalid API key",
  "error": "Unauthorized"
}
Invalid Parameters Status: 400 Bad Request
{
  "statusCode": 400,
  "message": "Invalid limit parameter: must be between 1 and 100",
  "error": "Bad Request"
}

Understanding Executions

Execution Lifecycle

Every execution moves through a lifecycle:
PENDING → RUNNING → SUCCESS

                FAILED → (automatic retry) → RUNNING

                DEAD_LETTER
Terminal Statuses:
  • SUCCESS - Transfer complete
  • DEAD_LETTER - Failed permanently
  • TOKEN_BLACKLISTED - Token blocked
  • FAILED_UNROUTABLE_MAIN - No route available
  • FAILED_INVALID_INPUT - Invalid parameters
  • FAILED_NO_FUNDS - Insufficient balance
  • FAILED_INSUFFICIENT_AFTER_FEES - Fees exceed deposit
  • QUOTA_EXCEEDED - Quota limit reached
See Error Codes for complete status descriptions.

Automatic Retries

When an execution fails with a retryable error:
  1. Status changes to FAILED
  2. retryable is set to true
  3. retryCount increments
  4. System automatically retries with exponential backoff
  5. On success, status changes to SUCCESS
  6. On repeated failure, eventually becomes DEAD_LETTER
You don’t need to do anything - retries happen automatically. Just poll the execution status to track progress.

Transaction Hashes

The txHash field contains the transaction hash on the source chain:
  • Available when status is RUNNING or SUCCESS
  • null for PENDING or early failures
  • Use this to link to block explorers
Example block explorer links:
  • Base: https://basescan.org/tx/{txHash}
  • Ethereum: https://etherscan.io/tx/{txHash}
  • Polygon: https://polygonscan.com/tx/{txHash}
  • Arbitrum: https://arbiscan.io/tx/{txHash}
  • Avalanche: https://snowtrace.io/tx/{txHash}

Route ID (Idempotency)

The routeId is a deterministic identifier based on:
  • User ID
  • Deposit details
  • Routing configuration
Purpose: Prevents duplicate execution of the same deposit. If a webhook fires twice for the same deposit, only one execution is created.

Polling for Updates

For active executions (PENDING, RUNNING, or FAILED with retryable: true), poll for status updates:

Example Polling Implementation

async function waitForExecution(userId, executionId, timeout = 300000) {
  const startTime = Date.now();
  const pollInterval = 5000; // 5 seconds
  
  while (Date.now() - startTime < timeout) {
    const executions = await getExecutions(userId);
    const execution = executions.find(e => e.id === executionId);
    
    if (!execution) {
      throw new Error('Execution not found');
    }
    
    // Check if terminal status
    const terminalStatuses = [
      'SUCCESS',
      'DEAD_LETTER',
      'TOKEN_BLACKLISTED',
      'FAILED_UNROUTABLE_MAIN',
      'FAILED_INVALID_INPUT',
      'FAILED_NO_FUNDS',
      'FAILED_INSUFFICIENT_AFTER_FEES',
      'QUOTA_EXCEEDED'
    ];
    
    if (terminalStatuses.includes(execution.status)) {
      return execution;
    }
    
    // Wait before next poll
    await new Promise(resolve => setTimeout(resolve, pollInterval));
  }
  
  throw new Error('Execution timeout');
}

Polling Best Practices

  1. Use appropriate intervals - 5-10 seconds for most cases
  2. Set timeouts - Don’t poll forever (5-10 minutes is reasonable)
  3. Handle all statuses - Check for both success and failure
  4. Respect rate limits - Don’t poll too aggressively
  5. Update UI - Show users the current status

Displaying Executions

Status Messages for Users

Map execution statuses to user-friendly messages:
function getStatusMessage(execution) {
  switch (execution.status) {
    case 'PENDING':
      return 'Processing your transfer...';
      
    case 'RUNNING':
      return 'Transfer in progress...';
      
    case 'SUCCESS':
      return 'Transfer complete!';
      
    case 'FAILED':
      if (execution.retryable) {
        return `Transfer failed. Automatically retrying... (Attempt ${execution.retryCount})`;
      } else {
        return `Transfer failed: ${execution.lastErrorMessage}`;
      }
      
    case 'DEAD_LETTER':
      return 'Transfer failed permanently. Please contact support.';
      
    case 'FAILED_UNROUTABLE_MAIN':
      return 'This token pair is not supported.';
      
    case 'FAILED_INSUFFICIENT_AFTER_FEES':
      return 'Deposit amount too small to cover fees.';
      
    case 'TOKEN_BLACKLISTED':
      return 'This token cannot be processed.';
      
    case 'QUOTA_EXCEEDED':
      return 'Monthly limit reached. Please upgrade your plan.';
      
    default:
      return 'Unknown status';
  }
}

Display Components

Execution Card:
function ExecutionCard({ execution }) {
  // Convert atomic amounts to human-readable
  const decimals = execution.fromTokenSymbol === 'USDC' ? 6 : 18;
  const amountIn = Number(execution.amountIn) / Math.pow(10, decimals);
  const amountOut = Number(execution.amountOutExpected) / Math.pow(10, decimals);
  
  return (
    <div className="execution-card">
      <div className="status">{getStatusBadge(execution.status)}</div>
      <div className="route">
        {amountIn} {execution.fromTokenSymbol} on {execution.fromChainKey}

        {amountOut} {execution.toTokenSymbol} on {execution.toChainKey}
      </div>
      <div className="time">{formatDate(execution.createdAt)}</div>
      {execution.txHash && (
        <a href={getExplorerLink(execution)} target="_blank">
          View transaction
        </a>
      )}
    </div>
  );
}

Amount Conversion

Always convert atomic units to human-readable format for display:
function atomicToHuman(atomic, decimals) {
  return (Number(atomic) / Math.pow(10, decimals)).toFixed(decimals);
}

// Usage
const amountUSDC = atomicToHuman(execution.amountIn, 6);  // "10.000000"
const amountETH = atomicToHuman(execution.amountIn, 18);  // "0.500000000000000000"

Filtering and Searching

Filter by Status

Get only successful executions:
curl "https://api.bridgfy.com/api/users/user-123/executions?status=SUCCESS" \
  -H "x-api-key: bfy_your_api_key"
Get failed executions:
curl "https://api.bridgfy.com/api/users/user-123/executions?status=FAILED" \
  -H "x-api-key: bfy_your_api_key"

Pagination

For users with many executions, use pagination:
async function getAllExecutions(userId) {
  const allExecutions = [];
  let offset = 0;
  const limit = 100;
  
  while (true) {
    const executions = await fetch(
      `https://api.bridgfy.com/api/users/${userId}/executions?limit=${limit}&offset=${offset}`,
      { headers: { 'x-api-key': apiKey } }
    ).then(r => r.json());
    
    allExecutions.push(...executions);
    
    if (executions.length < limit) {
      break; // No more results
    }
    
    offset += limit;
  }
  
  return allExecutions;
}

Client-Side Filtering

Get executions from API and filter locally:
const executions = await getExecutions(userId);

// Get executions from last 24 hours
const recent = executions.filter(e => 
  new Date(e.createdAt) > new Date(Date.now() - 86400000)
);

// Get executions by chain
const baseExecutions = executions.filter(e => 
  e.fromChainKey === 'base'
);

// Get pending/running executions
const active = executions.filter(e => 
  ['PENDING', 'RUNNING'].includes(e.status) ||
  (e.status === 'FAILED' && e.retryable)
);

Best Practices

For Integrators

  1. Poll active executions - Check status every 5-10 seconds for PENDING/RUNNING
  2. Show transaction hashes - Link to block explorers when available
  3. Handle all statuses - Build UI for every possible execution status
  4. Display amounts clearly - Convert atomic units to human-readable format
  5. Provide context - Show source/destination chains and tokens
  6. Support pagination - Handle users with many executions
  7. Cache results - Don’t fetch the same executions repeatedly

Error Handling

async function getExecutions(userId, options = {}) {
  try {
    const params = new URLSearchParams(options);
    const response = await fetch(
      `https://api.bridgfy.com/api/users/${userId}/executions?${params}`,
      { headers: { 'x-api-key': apiKey } }
    );
    
    if (!response.ok) {
      if (response.status === 401) {
        throw new Error('Invalid API key');
      } else if (response.status === 400) {
        const error = await response.json();
        throw new Error(error.message);
      } else {
        throw new Error('Failed to fetch executions');
      }
    }
    
    return await response.json();
    
  } catch (error) {
    console.error('Error fetching executions:', error);
    throw error;
  }
}

Performance Tips

  1. Limit results - Only fetch what you need to display
  2. Use status filters - Don’t fetch all executions if you only need one status
  3. Cache terminal executions - Success/failed executions won’t change
  4. Poll only active - Don’t poll executions with terminal statuses

Next Steps

  • Review Error Codes for handling execution failures
  • Learn about Deposit Intents to understand execution triggers
  • See Routing to simulate executions before they happen