跳至内容

构建网关连接器

概述

本指南将引导您完成为去中心化交易所(DEX)构建新网关连接器的过程。网关连接器使 Hummingbot 能够通过标准化的 REST API 接口与基于区块链的交易协议进行交互。

网关支持三种类型的 DEX 连接器:

  • 路由器:寻找最优兑换路径的 DEX 聚合器
  • AMM:传统的 V2 风格恒定乘积资金池
  • CLMM:具有自定义价格区间的集中流动性做市商

前提条件

在构建网关连接器之前,请确保您已具备以下条件:

开发环境

  • Node.js 18+ 和 pnpm
  • TypeScript 知识
  • 对目标区块链和 DEX 协议的理解

协议知识

  • 熟悉该 DEX 的智能合约
  • 了解该协议的 SDK 或 API
  • 了解流动性池的机制

网关设置

  • 已配置网关开发环境
  • 能够在本地运行和测试网关

连接器架构

网关连接器采用模块化架构:

src/connectors/{protocol}/
├── {protocol}.ts           # Main connector class
├── {protocol}.config.ts    # Configuration interface
├── {protocol}.constants.ts # Protocol-specific constants
├── {protocol}.utils.ts     # Helper functions
├── router-routes/          # Router endpoints (if applicable)
├── amm-routes/            # AMM endpoints (if applicable)
└── clmm-routes/           # CLMM endpoints (if applicable)

实现步骤

步骤 1:选择连接器类型

确定您的 DEX 支持哪些交易类型:

类型 使用场景 关键方法
路由 DEX 聚合器、仅支持兑换的协议 quotetradeestimateGas
AMM 带有流动性代币(LP tokens)的 V2 风格资金池 poolPriceaddLiquidityremoveLiquidity
CLMM 支持价格区间的集中流动性 openPositionaddLiquiditycollectFees

步骤 2:创建连接器类

创建主连接器类——如果你正在构建基于以太坊的去中心化交易所(DEX)连接器,请参考 Uniswap;如果你正在构建基于 Solana 的 DEX 连接器,请参考 Raydium

// src/connectors/mydex/mydex.ts
export class MyDex {
  private static instances: Record<string, MyDex> = {};
  public solana: Solana; // or Ethereum
  public sdk: MyDEXSDK;
  public config: MyDexConfig.RootConfig;

  private constructor() {
    this.config = MyDexConfig.config;
    this.txVersion = TxVersion.V0;
  }

  // Gets singleton instance
  public static getInstance(network: string): MyDex {
    if (!MyDex._instances) {
      MyDex._instances = {};
    }

    if (!MyDex._instances[network]) {
      const instance = new MyDex();
      await instance.init(network);
      MyDex._instances[network] = instance;
    }

    return MyDex._instances[network];
  }

  // Initializes instance
  private async init(network: string) {
    try {
      this.solana = await Solana.getInstance(network);
      this.sdk = await MyDEXSDK.load({
        connection: this.solana.connection,
        blockhashCommitment: 'confirmed',
      });

      logger.info('MyDEX initialized successfully');
    } catch (error) {
      logger.error('MyDEX initialization failed:', error);
      throw error;
    }
  }
}

步骤 3:实现交易方法

根据你的连接器类型,实现所需的方法:

路由方法

// Quote a swap
async quote(
  base: Token,
  quote: Token,
  amount: BigNumber,
  side: 'BUY' | 'SELL'
): Promise<SwapQuote> {
  // Implement quote logic
  return {
    route: optimalRoute,
    expectedOut: outputAmount,
    priceImpact: impact,
    gasEstimate: gasLimit
  };
}

// Execute a swap
async trade(
  wallet: Wallet,
  quote: SwapQuote,
  slippage: number
): Promise<Transaction> {
  // Build and execute transaction
  return transaction;
}

AMM 方法

// Get pool information
async poolInfo(
  base: Token,
  quote: Token
): Promise<PoolInfo> {
  // Fetch pool data
  return {
    reserves: [baseReserve, quoteReserve],
    fee: poolFee,
    liquidity: totalLiquidity
  };
}

// Add liquidity
async addLiquidity(
  wallet: Wallet,
  base: Token,
  quote: Token,
  baseAmount: BigNumber,
  quoteAmount: BigNumber,
  slippage: number
): Promise<Transaction> {
  // Add liquidity logic
  return transaction;
}

CLMM 方法

// Open a concentrated liquidity position
async openPosition(
  wallet: Wallet,
  pool: Pool,
  lowerPrice: number,
  upperPrice: number,
  baseAmount: BigNumber,
  quoteAmount: BigNumber
): Promise<Position> {
  // Create position NFT
  return position;
}

// Collect earned fees
async collectFees(
  wallet: Wallet,
  positionId: string
): Promise<Transaction> {
  // Collect fees logic
  return transaction;
}

步骤 4:创建路由处理器

为你支持的操作创建路由处理器文件:

// src/connectors/mydex/router-routes/router.routes.ts
import { Router, Request, Response } from 'express';
import { MyDex } from '../mydex';
import { 
  QuoteSwapRequest,
  QuoteSwapResponse,
  ExecuteSwapRequest,
  ExecuteSwapResponse 
} from '../../../schemas/router-schema';

export const routerRoutes = Router();

routerRoutes.post('/quote-swap', async (req: Request, res: Response) => {
  const request = req.body as QuoteSwapRequest;
  const connector = MyDex.getInstance(request.chain, request.network);

  try {
    const quote = await connector.quote(
      request.base,
      request.quote,
      request.amount,
      request.side
    );

    const response: QuoteSwapResponse = {
      network: request.network,
      timestamp: Date.now(),
      latency: 0,
      base: request.base,
      quote: request.quote,
      amount: request.amount,
      expectedOut: quote.expectedOut,
      price: quote.price,
      gasEstimate: quote.gasEstimate,
      route: quote.route
    };

    res.status(200).json(response);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

routerRoutes.post('/execute-swap', async (req: Request, res: Response) => {
  // Implementation for swap execution
});

步骤 5:添加配置

为你的连接器创建配置文件:

模式定义

// src/templates/namespace/mydex-schema.json
{
  "type": "object",
  "properties": {
    "slippagePct": {
      "type": "number",
      "description": "Maximum slippage percentage",
      "default": 1.0
    },
    "customParam": {
      "type": "number",
      "description": "Custom parameter",
    },
  },
  "required": ["slippagePct"]
}

默认配置

# src/templates/connectors/mydex.yml
allowedSlippage: 1.0
customParam: 300000

步骤 6:注册连接器

在主连接器路由中注册你的连接器:

// src/connectors/connector.routes.ts
import { Router } from 'express';
import { routerRoutes as mydexRouterRoutes } from './mydex/router-routes/router.routes';
import { ammRoutes as mydexAmmRoutes } from './mydex/amm-routes/amm.routes';

export const connectorRoutes = Router();

// Add your connector routes
connectorRoutes.use('/mydex/router', mydexRouterRoutes);
connectorRoutes.use('/mydex/amm', mydexAmmRoutes);

步骤 7:编写测试

为你的连接器创建全面的测试:

// test/connectors/mydex/mydex.test.ts
import { MyDex } from '../../../src/connectors/mydex/mydex';

describe('MyDex Connector', () => {
  let connector: MyDex;

  beforeEach(() => {
    connector = MyDex.getInstance('ethereum', 'mainnet');
  });

  describe('quote', () => {
    it('should return valid quote for token swap', async () => {
      const quote = await connector.quote(
        mockTokenA,
        mockTokenB,
        BigNumber.from('1000000'),
        'SELL'
      );

      expect(quote).toBeDefined();
      expect(quote.expectedOut).toBeGreaterThan(0);
      expect(quote.priceImpact).toBeLessThan(0.1);
    });

    it('should handle insufficient liquidity', async () => {
      await expect(
        connector.quote(
          mockTokenA,
          mockTokenB,
          BigNumber.from('999999999999'),
          'SELL'
        )
      ).rejects.toThrow('Insufficient liquidity');
    });
  });

  // Add tests for all methods
});

添加链支持

链支持状态

Gateway 当前不接受新的区块链实现的拉取请求。该框架目前支持: - EVM 链:以太坊及 EVM 兼容链(Arbitrum、Optimism、Base、Polygon、BSC、Avalanche 等) - SVM 链:Solana 及 SVM 兼容链

如果你的连接器需要基于 EVM 或 SVM 架构的链,你可以继续执行以下实现。对于全新的区块链架构,请查看 GitHub 仓库,了解何时将接受新的链支持。

如果你的连接器需要一个新的区块链:

步骤 1:创建链实现

// src/chains/mychain/mychain.ts
import { ChainBase } from '../../services/chain-base';

export class MyChain extends ChainBase {
  private static instances: Record<string, MyChain> = {};

  public static getInstance(network: string): MyChain {
    if (!MyChain.instances[network]) {
      MyChain.instances[network] = new MyChain(network);
    }
    return MyChain.instances[network];
  }

  // Implement required methods
  async getWallet(address: string): Promise<Wallet> {
    // Wallet implementation
  }

  async getBalance(address: string): Promise<Balance> {
    // Balance checking logic
  }

  async getTokens(symbols: string[]): Promise<Token[]> {
    // Token resolution logic
  }
}

步骤 2:创建链路由

// src/chains/mychain/routes/mychain.routes.ts
import { Router } from 'express';
import { MyChain } from '../mychain';

export const chainRoutes = Router();

chainRoutes.get('/balance', async (req, res) => {
  const { address } = req.query;
  const chain = MyChain.getInstance(req.query.network);
  const balance = await chain.getBalance(address);
  res.json(balance);
});

步骤 3:添加链配置

# src/templates/chains/mychain.yml
networks:
  mainnet:
    rpcUrl: 'https://rpc.mychain.io'
    chainId: 1234
    nativeCurrency: 'MYCOIN'
  testnet:
    rpcUrl: 'https://testnet-rpc.mychain.io'
    chainId: 5678
    nativeCurrency: 'TESTCOIN'

测试要求

所有 Gateway 连接器必须满足以下测试标准:

  1. 代码覆盖率:最低 75% 覆盖率
  2. 单元测试:测试所有公共方法
  3. 集成测试:测试 API 端点
  4. 错误处理:测试失败场景
  5. 模拟数据:使用真实的测试数据

运行测试

# Run all tests
pnpm test

# Run tests for specific connector
pnpm test -- mydex

# Check coverage
pnpm test:coverage

代码质量标准

代码检查与格式化

网关使用 ESLint 和 Prettier 来保证代码质量:

# Run linting
pnpm lint

# Auto-fix linting issues
pnpm lint:fix

# Format code
pnpm format

TypeScript 最佳实践

  1. 强类型:使用明确的类型,避免使用 any
  2. 错误处理:实现正确的错误类
  3. 异步/等待:使用现代的异步模式
  4. 文档:为公共方法添加 JSDoc 注释

提交清单

在提交连接器之前:

  • 所有测试通过且覆盖率超过 75%
  • 代码通过代码检查和格式化检查
  • 为所有支持的网络添加了配置文件
  • API 端点符合模式规范
  • 文档包含使用示例
  • 错误处理覆盖边界情况
  • 已使用真实负载进行性能测试
  • 安全审查已完成

下一步

  1. 本地测试:使用你的连接器运行网关并测试所有操作
  2. 创建示例:添加展示连接器用法的示例脚本
  3. 提交 PR:向网关仓库创建拉取请求
  4. 治理提案:如需,提交新连接器提案

资源