构建网关连接器¶
概述¶
本指南将引导您完成为去中心化交易所(DEX)构建新网关连接器的过程。网关连接器使 Hummingbot 能够通过标准化的 REST API 接口与基于区块链的交易协议进行交互。
网关支持三种类型的 DEX 连接器:
- Router:查找最佳兑换路线的 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 支持哪些交易类型:
类型 | 用例 | 关键方法 |
---|---|---|
Router | DEX 聚合器,仅兑换协议 | quote , trade , estimateGas |
AMM | 具有 LP 代币的 V2 风格池 | poolPrice , addLiquidity , removeLiquidity |
CLMM | 具有范围的集中流动性 | openPosition , addLiquidity , collectFees |
步骤 2:创建连接器类¶
使用单例模式创建主连接器类:
// src/connectors/mydex/mydex.ts
import { ConnectorBase } from '../../services/connector-base';
export class MyDex extends ConnectorBase {
private static instances: Record<string, MyDex> = {};
private chain: string;
private network: string;
private constructor(chain: string, network: string) {
super(chain, network);
this.chain = chain;
this.network = network;
}
public static getInstance(chain: string, network: string): MyDex {
const key = `${chain}:${network}`;
if (!MyDex.instances[key]) {
MyDex.instances[key] = new MyDex(chain, network);
}
return MyDex.instances[key];
}
// Core initialization
public async init(): Promise<void> {
// Initialize SDK, load contracts, etc.
}
// Required: Get connector name
public get name(): string {
return 'mydex';
}
// Required: Get router/factory address
public get routerAddress(): string {
return this.config.routerAddress;
}
}
步骤 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": {
"allowedSlippage": {
"type": "number",
"description": "Maximum slippage percentage",
"default": 1.0
},
"gasLimitEstimate": {
"type": "number",
"description": "Estimated gas limit",
"default": 300000
},
"ttl": {
"type": "number",
"description": "Quote time-to-live in seconds",
"default": 30
},
"contractAddresses": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"type": "object",
"properties": {
"routerAddress": { "type": "string" },
"factoryAddress": { "type": "string" }
}
}
}
}
},
"required": ["allowedSlippage", "gasLimitEstimate", "ttl"]
}
默认配置¶
# src/templates/connectors/mydex.yml
allowedSlippage: 1.0
gasLimitEstimate: 300000
ttl: 30
contractAddresses:
mainnet:
routerAddress: '0x...'
factoryAddress: '0x...'
testnet:
routerAddress: '0x...'
factoryAddress: '0x...'
步骤 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 连接器必须满足这些测试标准:
- 代码覆盖率:最低 75% 覆盖率
- 单元测试:测试所有公共方法
- 集成测试:测试 API 端点
- 错误处理:测试失败场景
- 模拟数据:使用真实测试数据
运行测试¶
# Run all tests
pnpm test
# Run tests for specific connector
pnpm test -- mydex
# Check coverage
pnpm test:coverage
代码质量标准¶
代码检查和格式化¶
Gateway 使用 ESLint 和 Prettier 进行代码质量控制:
TypeScript 最佳实践¶
- 强类型:使用显式类型,避免
any
- 错误处理:实现正确的错误类
- Async/Await:使用现代异步模式
- 文档:为公共方法添加 JSDoc 注释
提交检查清单¶
提交您的连接器之前:
- 所有测试通过,覆盖率 >75%
- 代码通过代码检查和格式化检查
- 为所有支持的网络添加了配置文件
- API 端点遵循架构规范
- 文档包含使用示例
- 错误处理涵盖边缘情况
- 使用真实负载进行性能测试
- 安全审查已完成
下一步¶
- 本地测试:使用您的连接器运行 Gateway 并测试所有操作
- 创建示例:添加显示连接器用法的示例脚本
- 提交 PR:创建拉取请求到 Gateway 仓库
- 治理提案:如需要,提交 新连接器提案