中文 / English
该项目旨在聚合第三方跨链桥。跨链桥只需提供一个配置文件,该配置文件涵盖接口配置和数据转换。每个接口将返回相应的数据,以实现聚合。一旦配置完成,即可在“提交合并配置”中提交合并。
$ git clone https://github.com/DODOEX/dodo-bridge-aggregator.git
$ mkdir src/bridge/[bridgeName]
$ touch src/bridge/[bridgeName]/config.ts
复制下面的示例配置内容到 【src/bridge/[bridgeName]/config.ts】 文件中,然后修改配置中的数据。
在示例配置中主要有 4 个接口配置,分别是 route 、buildTransactionData、status、tokenList 等接口配置。所以需要修改示例配置中的这 4 个接口并且返回相应的数据。
其他可以参考 【src/bridge/swft/config.ts、src/bridge/squid/config.ts】测试配置数据
import { CrossChainBusinessException } from "./../../exception/index";
import {
CrossChainConfig,
CrossChainParamsData,
StatusInfo,
} from "../../types";
import BigNumber from "bignumber.js";
const serverHost = "http://127.0.0.1:8080";
const errorCodes = {
ERROR: { code: "ERROR", message: "unknown error" },
SERVICE_UNAVAILABLE: {
code: "SERVICE_UNAVAILABLE",
message: "service unavailable",
},
// other error code
};
const bridgeNameConfig: CrossChainConfig = {
name: "bridge_name", // 桥名称
// API 接口配置
apiInterface: {
// 路由接口(必填)
route: {
url: `${serverHost}/api/route`, // 接口请求地址
method: "get", // 接口类型 get/post/put
// 开始执行之前调用该函数
before: async (params: CrossChainParamsData) => {
const fromAmountUSD = new BigNumber(params.fromAmount)
.div(10 ** params.fromTokenDecimals)
.times(params.fromTokenPrice);
return { fromAmountUSD }; // 这里返回的数据在‘requestMapping’和‘responseMapping’ 中的 "format" 函数中可以使用
},
// 执行完成之后调用该函数
after: (err, res) => {
// 需要处理下error
if (err) throw new CrossChainBusinessException(errorCodes.ERROR);
return res;
},
// 请求接口之后调用该函数
requestAfter: (res) => {
// 检查是否返回正常结果数据
if (res.code !== 200)
throw new CrossChainBusinessException(errorCodes.ERROR);
return res.data;
},
// 接口请求参数数据放到“requestMapping”中,字段值会从“CrossChainParamsData”参数中做映射处理
requestMapping: {
fromChain: "fromChainId",
fromAmount: {
format: (_, { beforeResult }) => beforeResult.fromAmountUSD,
},
toChain: { field: "toChain", type: "string" },
// other params
},
// 接口返回数据放到“responseMapping”中,字段值会从“接口响应数据”做映射
responseMapping: {
depositContract: "data.contractAddress", // 存款合约地址
toAmount: "data.amountOutMin", // 目标链到账金额,单位(wei)
fee: {
swapFee: "0", // swap fee ,单位(USD)
destinationGasFee: {
//目标链 gas fee ,单位(USD)
format: (route, { crossChainParamsData }) => {
return new BigNumber(route.data.fee)
.div(10 ** crossChainParamsData.toTokenDecimals)
.times(crossChainParamsData.toTokenPrice)
.toString(10);
},
},
crossChainFee: "0", // 跨链 fee ,单位(USD)
otherFee: "0", // 其它 fee ,单位(USD)
},
// 如果在 route 接口中返回了 transactionData 数据,则可以不用在提供 buildTransactionData 接口
// transactionData: {
// data: 'data.transactionData',
// value: 'data.value',
// },
// 发起交易上链时额外从钱包扣除的费用(比如跨链平台将会使用“来源链平台币”提前预支一笔费用,该笔费用定义为“otherPayOut”, 单位USD)
otherPayOut: "0",
// 如果需要保存路由接口中一些数据以便后续几个接口使用可以放到 'interfaceParamData' 中
interfaceParamData: {
routeId: "data.routeId",
},
},
},
// 获取状态接口 (必填)
status: {
url: `${serverHost}/api/status`,
method: "get",
requestAfter: (res) => {
if (res.code !== 200)
throw new CrossChainBusinessException(errorCodes.ERROR);
return res.data;
},
requestMapping: {
hash: "fromHash",
// other params
},
responseMapping: {
toHash: "toHash",
statusInfo: {
format: (resResult: any): StatusInfo => {
const data: StatusInfo = {
status: "PENDING",
bridgeResponseResult: resResult,
};
if (resResult.status === "success") {
data.status = "DONE";
} else if (
resResult.status === "timeout" ||
resResult.sourceTxStatus === "error"
) {
data.status = "FAILED";
data.message = `failed: ${resResult.message}`;
}
return data;
},
},
},
},
// 获取支持跨链交易的token信息 (必填)
tokenList: {
url: `${serverHost}/api/tokens`,
method: "get",
requestAfter: (res) => {
if (res.code !== 200)
throw new CrossChainBusinessException(errorCodes.ERROR);
return res.data;
},
responseMapping: {
tokens: {
format: (tokenResult) => {
return tokenResult.tokens.map((item: any) => ({
chainId: Number(item.chainId),
address: item.address,
name: item.name,
symbol: item.symbol,
decimals: item.decimals,
logoImg: item.logoImg,
}));
},
},
},
},
buildTransactionData: {
// 生成上链事物数据接口, 如果在 route 接口中没有返回 transactionData,则需要提供该接口 (非必填)
url: `${serverHost}/api/buildTransactionData`,
method: "post",
headers: { "Content-Type": "application/json" },
requestAfter: (res) => {
if (res.code !== 200)
throw new CrossChainBusinessException(errorCodes.ERROR);
return res.data;
},
requestMapping: {
// routeId: 'routeId', // 这里的routeId 使用的是 ‘route’ 接口返回结果中 interfaceParamData 的数据
routeId: {
format: (_, { interfaceParamData }) => interfaceParamData.routeId,
},
// other params
},
responseMapping: {
data: "data.data",
value: "data.value",
},
},
createOrder: {
// 创建订单 (非必填)
url: `${serverHost}/api/createOrder`,
method: "post",
requestAfter: (res) => {
if (res.code !== 200)
throw new CrossChainBusinessException(errorCodes.ERROR);
return res.data;
},
requestMapping: {
routeId: "routeId",
// other params
},
responseMapping: {
interfaceParamData: {
orderId: "orderId",
},
},
},
// 健康检查接口 (非必填)
health: {
url: `${serverHost}/api/health`,
method: "get",
requestAfter: (res) => {
if (res.code !== 0)
throw new CrossChainBusinessException(errorCodes.SERVICE_UNAVAILABLE);
return { isAvailable: res.isAvailable, description: res.description };
},
responseMapping: {
isAvailable: "isAvailable",
description: "description",
},
},
},
errorCodes, // 错误状态信息
};
export default bridgeNameConfig;
name
桥名称apiInterface
API 接口配置route
路由接口(必填)url
接口地址(必填)method
接口类型 get/post/put (必填)headers
请求头(非必填)before
开始执行之前调用该函数(非必填)after
执行完成之后调用该函数(非必填)requestAfter
请求接口之后调用该函数(必填)requestMapping
接口请求参数数据responseMapping
接口响应结果数据
status
获取跨链订单状态接口(必填)
...tokenList
支持跨链代币列表接口 (必填)
...buildTransactionData
生成上链事物数据接口(非必填)
...createOrder
创建订单(非必填)
...health
健康检查接口 (非必填)
...
errorCodes
错误状态信息
用于查询路由,返回该路由的报价等信息
- 请求数据
我们会提供 【CrossChainParamsData】 跨链请求的基本参数数据给到接口配置中,只需要在 【requestMapping】 中配置请求参数数据做映射处理既可以完成
type CrossChainParamsData = {
fromChainId: number, // 来源链
fromAmount: string, // 来源金额
fromTokenAddress: string, // 来源 token 地址
fromTokenDecimals: number, // 来源 token decimals
fromTokenPrice: string, // 来源 token price
fromPlatformTokenPrice: string, // 来源 平台币 price
toChainId: number, // 目标链
toTokenAddress: string, // 目标token 地址
toTokenDecimals: number, // 目标token decimals
toTokenPrice: string, // 目标token price
fromAddress: string, // 来源用户地址
toAddress: string, // 目标用户地址
slippage: number, // 滑点
fromHash?: string, // 来源链交易hash
};
- 响应数据
这里需要将接口返回数据再次在【responseMapping】中做映射处理,返回 DODO 需要的字段数据信息
depositContract
存款合约地址toAmount
目标链到账金额,单位(wei)fee
包括 swap fee、目标链 gas fee、跨链 fee、其它 fee 四种费用,不包括来源链的 gas feeswapFee
swap fee (单位 USD)destinationGasFee
目标链 gas fee (单位 USD)crossChainFee
跨链 fee (单位 USD)otherFee
其它 fee (单位 USD)
otherPayOut
发起交易上链时额外从钱包扣除的费用(比如跨链平台将会使用“来源链平台币”提前预支一笔费用,该笔费用定义为“otherPayOut”, 单位 USD)interfaceParamData
如果需要保存路由接口中一些数据以便后续几个接口使用则可以放到 'interfaceParamData' 中
获取跨链订单交易的状态
-
请求数据
【CrossChainParamsData】数据 和 route 接口返回的 【interfaceParamData】的数据
-
响应数据
这里需要将接口返回数据再次在【responseMapping】中做映射处理,返回 DODO 需要的字段数据信息
toHash
目标链 hashstatusInfo
状态信息status
状态- PENDING 处理中状态 (交易处于处理中时的状态)
- DONE 完成状态 (交易成功)
- FAILED 失败状态 (交易失败)
- TRANSFER_REFUNDED 退款成功状态
- INVALID 无效数据状态 (数据不正确时的状态)
- NOT_FOUND NOT FOUND 状态 (交易 hash 不存在时的状态)
bridgeResponseResult
源第三方桥返回的相应数据
需要返回第三方跨链桥支持哪些 token 交易
-
请求数据
【CrossChainParamsData】数据
-
响应数据
这里需要将接口返回数据再次在【responseMapping】中做映射处理,返回 DODO 需要的字段数据信息
tokens
代币信息chainId
链 Idaddress
代币地址name
代币名称symbol
代币合约上的 symboldecimals
代币合约上的 decimalslogoImg
代币 logo 图标链接地址
在 route 接口后会调用该接口拿到发送事物的数据
-
请求数据
【CrossChainParamsData】数据 和 route 接口返回的 【interfaceParamData】的数据
-
响应数据
这里需要返回发送事物上链时的 data 和 value 数据
data
上链 data 数据value
上链 value 数据 (需要返回 16 进制)
在 buildTransactionData 接口后调用创建订单接口保存 hash 等数据
-
请求数据
【CrossChainParamsData】数据 和 route 接口返回的 【interfaceParamData】的数据
-
响应数据
interfaceParamData
如果需要保存一些数据可以放到这里
在调用 route 接口前会调用 health 检查服务或路由是否可用
-
请求数据
【CrossChainParamsData】数据
-
响应数据
如果【isAvailable】为 false 代表服务不可用
isAvailable
服务是否可用description
描述
调用 src/index.ts 中的方法测试配置
import {
buildTransactionData,
CrossChainParamsData,
getBridgeConfig,
getRoute,
} from "../src/index";
const crossChainParamsData: CrossChainParamsData = {
fromChainId: 56,
fromAmount: "20000000000000000000",
fromTokenAddress: "0x55d398326f99059ff775485246999027b3197955",
fromTokenDecimals: 18,
fromTokenPrice: "1",
fromPlatformTokenPrice: "300",
toChainId: 137,
toTokenAddress: "0xc2132d05d31c914a87c6611c10748aeb04b58e8f",
toTokenDecimals: 6,
toTokenPrice: "1",
fromAddress: "0xd8C446197cA9eE5b6cFC212460C9C5b621a5e1F2",
toAddress: "0xd8C446197cA9eE5b6cFC212460C9C5b621a5e1F2",
slippage: 0.01,
};
const bridgeName = "swft_test";
const swftConfig = await getBridgeConfig(bridgeName);
const routeResult = await getRoute(bridgeName, crossChainParamsData);
console.log("routeResult:", routeResult);
let transactionInfo;
if (swftConfig.apiInterface.buildTransactionData) {
transactionInfo = await buildTransactionData(
bridgeName,
crossChainParamsData,
routeResult?.interfaceParamData
);
} else if (routeResult && !routeResult.transactionData) {
transactionInfo = routeResult.transactionData;
}
console.log("transactionInfo:", transactionInfo);
// 拿到发送事物数据后进行上链交易得到 hash
// wallet.sendTransaction({data:transactionInfo.data,value:transactionInfo.value,...})
// ...
import { createOrder, getStatus, getTokenList, health } from "../src/index";
const createOrderResult = await createOrder(
bridgeName,
crossChainParamsData,
routeResult?.interfaceParamData
);
const statusResult = await getStatus(
bridgeName,
crossChainParamsData,
routeResult?.interfaceParamData
);
const tokenListResult = await getTokenList(bridgeName, crossChainParamsData);
const healthResult = await health(bridgeName, crossChainParamsData);
在完成配置测试后,您可以提交合并请求给我们。请确保请求包含正确的配置,并在 https://github.com/DODOEX/dodo-bridge-aggregator/pulls 页面中提交。一旦我们收到您的请求,我们会进行检查,确保配置正确无误。如果一切顺利,我们会进行合并,并在测试环境中进行测试。