跳转到主要内容
Composer 使用标准的 LI.FI API 错误格式,但有一些特定于 Composer 的错误和考虑因素。

Composer 特定错误

错误代码描述常见原因解决方案
NO_POSSIBLE_ROUTE没有找到路径toToken 不是支持的金库代币验证 toToken 地址是否在支持的协议列表中
COMPOSER_EXECUTION_FAILEDComposer 执行失败协议特定问题(如金库已满)检查协议状态,稍后重试
INSUFFICIENT_LIQUIDITY流动性不足目标协议流动性不足尝试较小金额或稍后重试
TRANSACTION_FAILED模拟失败执行前模拟失败检查 gas 限制、协议状态
UNSUPPORTED_TOKEN不支持的代币toToken 不是支持的 Composer 代币使用支持的协议代币

常见失败模式

1. 协议级别失败

这些失败发生在目标协议层面:
  • 金库容量限制:金库已达到最大容量
  • 协议暂停:目标协议临时暂停
  • 代币黑名单:特定代币被协议阻止
解决方案:
try {
  const executedRoute = await executeRoute(route);
} catch (error) {
  if (error.code === 'COMPOSER_EXECUTION_FAILED') {
    console.error('协议级别失败,检查协议状态');
    // 提示用户检查协议应用
  }
}

2. 跨链失败

跨链 Composer 流程的特定失败:
  • 跨链桥失败:跨链桥临时不可用
  • 中间代币问题:跨链代币在目标链不可用
  • Gas 不足:目标链 gas 费用变化
解决方案:
const trackCrossChainStatus = async (txHash: string, fromChain: number, toChain: number) => {
  const status = await getStatus(txHash, fromChain, toChain);
  
  if (status.status === 'FAILED' && status.substatus?.includes('bridge')) {
    console.error('跨链失败,可能需要重试');
    // 提示用户重试或使用不同跨链桥
  }
};

3. 执行前模拟失败

模拟在链上执行前失败:
  • Gas 估算错误:交易需要比预期更多的 gas
  • 状态变化:模拟和执行之间状态发生变化
  • Slippage 过高:价格变化超出可接受范围
解决方案:
const quote = await getQuote({
  // ... 其他参数
  options: {
    slippage: 0.01, // 增加 slippage 容忍度
    gasLimit: 'auto', // 自动 gas 限制
  },
});

错误处理最佳实践

1. 预检查

在执行前验证条件:
const preCheck = async (toToken: string, amount: string) => {
  // 检查代币是否受支持
  const supportedProtocols = await getSupportedProtocols();
  const isSupported = supportedProtocols.some(protocol => 
    protocol.tokens.includes(toToken)
  );
  
  if (!isSupported) {
    throw new Error('代币不受 Composer 支持');
  }
  
  // 检查最小/最大金额
  const limits = await getProtocolLimits(toToken);
  if (BigInt(amount) < limits.minDeposit) {
    throw new Error('金额低于最小存款要求');
  }
};

2. 重试逻辑

为临时失败实现重试:
const executeWithRetry = async (route: Route, maxRetries = 3) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await executeRoute(route);
    } catch (error) {
      if (error.code === 'INSUFFICIENT_LIQUIDITY' && attempt < maxRetries) {
        console.log(`流动性不足,重试 ${attempt}/${maxRetries}`);
        await new Promise(resolve => setTimeout(resolve, 2000)); // 等待 2 秒
        continue;
      }
      throw error; // 重新抛出不可重试的错误
    }
  }
};

3. 用户友好的错误消息

将技术错误转换为用户友好的消息:
const getErrorMessage = (error: any) => {
  switch (error.code) {
    case 'NO_POSSIBLE_ROUTE':
      return '无法找到路径。请检查目标代币是否为支持的金库代币。';
    case 'COMPOSER_EXECUTION_FAILED':
      return '协议执行失败。请检查协议状态或稍后重试。';
    case 'INSUFFICIENT_LIQUIDITY':
      return '流动性不足。请尝试较小金额或稍后重试。';
    case 'TRANSACTION_FAILED':
      return '交易失败。请检查您的余额和网络状态。';
    default:
      return '发生未知错误。请稍后重试。';
  }
};

监控和日志

1. 错误跟踪

const executeWithErrorTracking = async (route: Route) => {
  try {
    return await executeRoute(route);
  } catch (error) {
    // 记录错误以供分析
    console.error('Composer 执行失败:', {
      error: error.message,
      code: error.code,
      route: route.id,
      timestamp: new Date().toISOString(),
    });
    
    // 发送到错误跟踪服务
    if (typeof window !== 'undefined') {
      window.analytics?.track('composer_error', {
        error_code: error.code,
        error_message: error.message,
        route_id: route.id,
      });
    }
    
    throw error;
  }
};

2. 性能监控

const executeWithMetrics = async (route: Route) => {
  const startTime = Date.now();
  
  try {
    const result = await executeRoute(route);
    
    // 记录成功指标
    const duration = Date.now() - startTime;
    console.log('Composer 执行成功:', {
      duration,
      route_id: route.id,
      steps: route.steps.length,
    });
    
    return result;
  } catch (error) {
    // 记录失败指标
    const duration = Date.now() - startTime;
    console.error('Composer 执行失败:', {
      duration,
      error: error.code,
      route_id: route.id,
    });
    
    throw error;
  }
};

测试错误场景

1. 单元测试

describe('Composer 错误处理', () => {
  it('应该处理不支持的代币', async () => {
    await expect(
      getQuote({
        toToken: 'UNSUPPORTED_TOKEN_ADDRESS',
        // ... 其他参数
      })
    ).rejects.toThrow('NO_POSSIBLE_ROUTE');
  });
  
  it('应该处理流动性不足', async () => {
    await expect(
      getQuote({
        fromAmount: '999999999999999999999', // 极大金额
        // ... 其他参数
      })
    ).rejects.toThrow('INSUFFICIENT_LIQUIDITY');
  });
});

2. 集成测试

describe('Composer 集成错误处理', () => {
  it('应该优雅地处理跨链失败', async () => {
    const route = await getCrossChainRoute();
    
    // 模拟跨链失败
    jest.spyOn(bridgeService, 'execute').mockRejectedValue(
      new Error('BRIDGE_UNAVAILABLE')
    );
    
    await expect(executeRoute(route)).rejects.toThrow('BRIDGE_UNAVAILABLE');
  });
});

下一步