Skip to main content
The Execute Service handles bridge transaction execution via the LI.FI API, including token approvals, transaction signing, status polling, and step-by-step progress tracking.

Exports

import {
  execute,
  validateQuote,
  QuoteExpiredError,
  InvalidQuoteError,
  isQuoteExpiredError,
  isInvalidQuoteError,
} from '@mina-bridge/sdk/services/execute';

Functions

execute(config)

Execute a bridge transaction from a quote.
async function execute(config: ExecuteConfig): Promise<ExecutionResult>

ExecuteConfig

ParameterTypeRequiredDefaultDescription
quoteQuoteYes-Quote to execute
signerTransactionSignerYes-Wallet signer (viem WalletClient compatible)
onStepChangeOnStepChangeNo-Callback for step status updates
onStatusChangeOnStatusChangeNo-Callback for overall status updates
onApprovalRequest() => voidNo-Callback before approval transaction
onTransactionRequest() => voidNo-Callback before main transaction
infiniteApprovalbooleanNofalseAllow infinite token approval
emitterSDKEventEmitterNo-Event emitter for SDK events
Returns: ExecutionResult
PropertyTypeDescription
executionIdstringUnique execution ID for tracking
status'pending' | 'executing' | 'completed' | 'failed'Overall status
stepsStepStatus[]Array of step statuses
txHashstring | undefinedFinal transaction hash
fromAmountstring | undefinedInput amount
toAmountstring | undefinedExpected output amount
receivedAmountstring | undefinedActual received amount
depositTxHashstring | nullDeposit transaction hash (if auto-deposit)
errorError | undefinedError details (if failed)
Throws:
  • QuoteExpiredError if quote has expired (5 minute max age)
  • InvalidQuoteError if quote is malformed
  • TransactionFailedError if transaction fails on-chain
  • UserRejectedError if user rejects transaction
  • NetworkError if network request fails

Execution Flow

The execute function processes a quote through these stages:
1. VALIDATION
   - Validate quote structure
   - Check quote expiration (5 minute max)

2. APPROVAL (if needed)
   - Check current token allowance
   - Request approval transaction
   - Wait for approval confirmation

3. EXECUTION
   - Submit bridge/swap transaction
   - Update step status to 'executing'

4. BRIDGING
   - Poll LI.FI status API (5 second interval)
   - Update progress based on substatus
   - Wait up to 10 minutes for completion

5. COMPLETION
   - Mark all steps as completed
   - Return final execution result

Example: Basic Execution

import { execute } from '@mina-bridge/sdk';

const result = await execute({
  quote,
  signer: walletClient,
  infiniteApproval: true,
});

if (result.status === 'completed') {
  console.log('Bridge complete!');
  console.log('TxHash:', result.txHash);
  console.log('Received:', result.receivedAmount);
} else if (result.status === 'failed') {
  console.error('Bridge failed:', result.error?.message);
}

Example: With Progress Callbacks

const result = await execute({
  quote,
  signer: walletClient,
  onStepChange: (step) => {
    console.log(`Step ${step.stepId}: ${step.status}`);

    if (step.txHash) {
      console.log(`  Transaction: ${step.txHash}`);
    }

    if (step.error) {
      console.error(`  Error: ${step.error.message}`);
    }
  },
  onStatusChange: (status) => {
    console.log(`\n=== Status Update ===`);
    console.log(`Status: ${status.status}`);
    console.log(`Substatus: ${status.substatus}`);
    console.log(`Progress: ${status.progress}%`);
    console.log(`Step: ${status.currentStep}/${status.totalSteps}`);

    if (status.txHash) {
      console.log(`TxHash: ${status.txHash}`);
    }

    if (status.receivingTxHash) {
      console.log(`Receiving TxHash: ${status.receivingTxHash}`);
    }
  },
  onApprovalRequest: () => {
    console.log('Approval required - please confirm in wallet');
  },
  onTransactionRequest: () => {
    console.log('Transaction ready - please confirm in wallet');
  },
});

Example: With Event Emitter

import { SDKEventEmitter, SDK_EVENTS } from '@mina-bridge/sdk';

const emitter = new SDKEventEmitter();

// Subscribe to events
emitter.on(SDK_EVENTS.EXECUTION_STARTED, ({ executionId, quoteId }) => {
  console.log(`Execution ${executionId} started for quote ${quoteId}`);
});

emitter.on(SDK_EVENTS.STEP_CHANGED, (step) => {
  console.log(`Step ${step.stepId}: ${step.status}`);
});

emitter.on(SDK_EVENTS.TRANSACTION_SENT, ({ txHash, chainId, stepType }) => {
  console.log(`Transaction sent: ${txHash} on chain ${chainId} (${stepType})`);
});

emitter.on(SDK_EVENTS.EXECUTION_COMPLETED, ({ executionId, txHash }) => {
  console.log(`Execution ${executionId} completed: ${txHash}`);
});

emitter.on(SDK_EVENTS.EXECUTION_FAILED, ({ executionId, error, step }) => {
  console.error(`Execution ${executionId} failed at step ${step}:`, error);
});

// Execute with emitter
const result = await execute({
  quote,
  signer,
  emitter,
});

validateQuote(quote)

Validate a quote before execution.
function validateQuote(quote: Quote): void
Throws:
  • QuoteExpiredError if quote has expired
  • InvalidQuoteError if quote is malformed
Validation Checks:
  1. Quote is not null/undefined
  2. Quote has a valid ID
  3. Quote has at least one execution step
  4. Quote has fromAmount and toAmount
  5. Quote has not expired (expiresAt timestamp)
  6. Quote age does not exceed 5 minutes
Example:
import { validateQuote } from '@mina-bridge/sdk';

try {
  validateQuote(quote);
  console.log('Quote is valid');
} catch (error) {
  if (isQuoteExpiredError(error)) {
    console.error('Quote expired at:', new Date(error.expiredAt));
    // Fetch a new quote
  } else if (isInvalidQuoteError(error)) {
    console.error('Invalid quote:', error.reason);
  }
}

Error Types

QuoteExpiredError

Thrown when a quote has exceeded its validity period.
class QuoteExpiredError extends MinaError {
  readonly code: 'QUOTE_EXPIRED';
  readonly recoverable: true;
  readonly recoveryAction: 'fetch_new_quote';
  readonly quoteId: string;
  readonly expiredAt: number;
}
PropertyTypeDescription
quoteIdstringID of the expired quote
expiredAtnumberTimestamp when quote expired
Example:
try {
  const result = await execute({ quote, signer });
} catch (error) {
  if (isQuoteExpiredError(error)) {
    console.error('Quote expired:', error.quoteId);
    console.log('Expired at:', new Date(error.expiredAt));

    // Fetch new quote and retry
    const newQuote = await mina.getQuote(originalParams);
    const result = await execute({ quote: newQuote, signer });
  }
}

InvalidQuoteError

Thrown when a quote is malformed or missing required data.
class InvalidQuoteError extends MinaError {
  readonly code: 'INVALID_QUOTE';
  readonly recoverable: false;
  readonly reason: string;
}
PropertyTypeDescription
reasonstringWhy the quote is invalid
Reasons:
  • null_quote - Quote is null or undefined
  • missing_id - Quote has no ID
  • no_steps - Quote has no execution steps
  • missing_amounts - Quote is missing amount information
Example:
try {
  const result = await execute({ quote, signer });
} catch (error) {
  if (isInvalidQuoteError(error)) {
    console.error('Quote is invalid:', error.reason);
    // Fetch a new quote
  }
}

Types

TransactionSigner

Compatible with viem WalletClient and ethers Signer.
interface TransactionSigner {
  /** Sign and send a transaction, returns tx hash */
  sendTransaction: (request: TransactionRequest) => Promise<string>;
  /** Get the signer's address */
  getAddress: () => Promise<string>;
  /** Get the current chain ID */
  getChainId: () => Promise<number>;
}

TransactionRequest

interface TransactionRequest {
  to: string;
  data: string;
  value: string;
  gasLimit?: string;
  gasPrice?: string;
  chainId: number;
}

ExecutionStatus

type ExecutionStatus =
  | 'idle'
  | 'approving'
  | 'approved'
  | 'executing'
  | 'bridging'
  | 'completed'
  | 'failed';

StepStatus

interface StepStatus {
  stepId: string;
  stepType?: StepType;
  status: 'pending' | 'executing' | 'completed' | 'failed';
  txHash?: string;
  error?: string;
  updatedAt: number;
}

StepStatusPayload

Payload passed to onStepChange callback.
interface StepStatusPayload {
  stepId: string;
  step: StepType;
  status: 'pending' | 'active' | 'completed' | 'failed';
  txHash: string | null;
  error: Error | null;
  timestamp: number;
}

TransactionStatusPayload

Payload passed to onStatusChange callback.
interface TransactionStatusPayload {
  status: 'pending' | 'in_progress' | 'completed' | 'failed';
  substatus: string;
  currentStep: number;
  totalSteps: number;
  fromAmount: string;
  toAmount: string | null;
  txHash: string;
  receivingTxHash: string | null;
  progress: number;
  estimatedTime: number;
}

Constants

Timing Constants

ConstantValueDescription
MAX_QUOTE_AGE_MS3000005 minutes - maximum quote validity
STATUS_POLL_INTERVAL_MS50005 seconds - polling interval
MAX_EXECUTION_WAIT_MS60000010 minutes - maximum wait time
APPROVAL_CONFIRMATION_WAIT_MS30003 seconds - approval wait

Common Patterns

Retry on Expiration

async function executeWithRetry(
  params: QuoteParams,
  signer: TransactionSigner,
  maxRetries = 3
) {
  let retries = 0;

  while (retries < maxRetries) {
    try {
      const quote = await mina.getQuote(params);
      return await execute({ quote, signer });
    } catch (error) {
      if (isQuoteExpiredError(error) && retries < maxRetries - 1) {
        console.log('Quote expired, fetching new quote...');
        retries++;
        continue;
      }
      throw error;
    }
  }
}

Progress UI State Machine

type UIState = 'idle' | 'approving' | 'sending' | 'bridging' | 'complete' | 'error';

function executeWithUI(quote: Quote, signer: TransactionSigner) {
  let uiState: UIState = 'idle';

  return execute({
    quote,
    signer,
    onApprovalRequest: () => {
      uiState = 'approving';
      updateUI(uiState);
    },
    onTransactionRequest: () => {
      uiState = 'sending';
      updateUI(uiState);
    },
    onStatusChange: (status) => {
      if (status.status === 'in_progress') {
        uiState = 'bridging';
      } else if (status.status === 'completed') {
        uiState = 'complete';
      } else if (status.status === 'failed') {
        uiState = 'error';
      }
      updateUI(uiState, status);
    },
  });
}

Cancel Support

// The execute function doesn't support cancellation directly,
// but you can track the execution and handle UI accordingly

let currentExecutionId: string | null = null;
let cancelled = false;

async function startExecution(quote: Quote, signer: TransactionSigner) {
  cancelled = false;

  const result = await execute({
    quote,
    signer,
    onStatusChange: (status) => {
      if (cancelled) {
        // Can't cancel on-chain, but can stop UI updates
        return;
      }
      // Update UI
    },
  });

  currentExecutionId = result.executionId;
  return result;
}

function cancelUI() {
  cancelled = true;
  // Note: The transaction will still complete on-chain
  console.warn('UI cancelled, but transaction may still complete');
}