Gateway 和 Uniswap
gateway通过用ethers(https://github.com/ethers-io/ethers.js)与ETH网络交互
使用@uniswap/sdk(https://github.com/Uniswap/uniswap-v2-sdk)来实现与uninswap的交互
EthereumService 组件(ethereumService)
ethereum.ts
封装了调用 ether 库针对 eth 网络的操作
getETHBalance
从钱包中检查 ETH 余额
getERC20Balance
从合约中检查 ERC20 的余额
getERC20Allowance
检查 spender 允许从钱包转移的数目
approveERC20
授权spender从钱包转移ERC20代币
deposit
给 ERC20 地址转币,暂时没人用这个 api
getERC20TokenAddress
获取 ERC20 代币地址
getERC20TokenByAddress
通过地址获取 ERC20 代币
getWallet
获取钱包信息
getTransactionReceipt
通过 txHash 获得交易信息
Uniswap 组件(uni)
uniswap.js
封装了针对 uniswap 的操作,比如下单,获取交易对等操作
fetch_route
: 获取交易路径,主要在获取兑换价格priceSwapIn
时候使用,因为有的币可能需要经过>1次兑换才能换到目标币种,这种情况下就需要获取交易路径。
extend_update_pairs
: 在获取兑换价格priceSwapIn
的时候会使用,用来更新交换币对的扩展的属性。
update_pairs
: 定时执行更新维护币对列表,主要只维护币对,扩展信息交由获取价格时候调用 extend_update_pairs
实现。
priceSwapIn
:获取换入币种的价格,卖出币种时候使用。
priceSwapOut
:获取换出币种的价格,买入币种的时候使用。
两个函数结构相似,区分的是 base 和 quote 币种要互换位置。买入quote数目->预计付出base数目->成交->实际付出和预计数量不一定一样,卖出quote数目->预计获得base数目->成交->实际获得和预计数量不一定一样
swapExactIn
:在这个函数里面会实际调用 eth 合约实现交易,如果要卖出币种调用的是这个函数
swapExactOut
:在这个函数里面会实际调用 eth 合约实现交易,如果要买入币种调用的是这个函数
其实两个函数并没有什么本质区别站在不同的角度上 swapIn 也是一种 swapOut,代码也是一样的只是变量交换了位置。
授权
授权币种
[email protected]
const wallet = ethereumService.getWallet(req.body.privateKey);
// Getting token info
const tokenContractInfo = ethereumService.getERC20TokenAddress(
req.body.token
);
const tokenAddress = tokenContractInfo.address;
const gasPrice = req.body.gasPrice || ethereumGasService.getGasPrice();
let amount = ethers.constants.MaxUint256;
if (req.body.amount) {
amount = ethers.utils.parseUnits(
req.body.amount,
tokenContractInfo.decimals
);
}
// call approve function
let approval;
try {
approval = await ethereumService.approveERC20(
wallet,
spender,
tokenAddress,
amount,
gasPrice
);
} catch (err) {
approval = err;
}
......
[email protected]
const contract = new Contract(tokenAddress, abi.ERC20Abi, wallet);
contract.approve(spender, amount, {
gasPrice: gasPrice * 1e9,
// fixate gas limit to prevent overwriting
gasLimit: this.config.approvalGasLimit,
});
获取钱包授权的数量
//[email protected]
const wallet = ethereumService.getWallet(req.body.privateKey);
approvals[symbol] = await ethereumService.getERC20Allowance(
wallet,
spender,
address,
decimals
);
const contract = new Contract(tokenAddress, abi.ERC20Abi, this.provider);
const allowance = await contract.allowance(wallet.address, spender);
获取 swap 价格
//[email protected]
//还有一个priceSwapIn 类似
async priceSwapOut(tokenIn, tokenOut, tokenOutAmount) {
await this.extend_update_pairs([tokenIn, tokenOut]);
const tOut = this.tokenList[tokenOut];
const tIn = this.tokenList[tokenIn];
const tokenAmountOut = new uni.TokenAmount(
tOut,
ethers.utils.parseUnits(tokenOutAmount, tOut.decimals)
);
//先查询本地缓存
if (this.pairs.length === 0) {
const route = await this.fetch_route(tIn, tOut);
const trade = uni.Trade.exactOut(route, tokenAmountOut);
if (trade !== undefined) {
const expectedAmount = trade.maximumAmountIn(this.allowedSlippage);
this.cachedRoutes[tIn.symbol + tOut.Symbol] = trade;
return { trade, expectedAmount };
}
return;
}
// 最大可以换出的数目
let trade = uni.Trade.bestTradeExactOut(
this.pairs,
this.tokenList[tokenIn],
tokenAmountOut,
{ maxHops: 5 }
)[0];
if (trade === undefined) {
trade = this.cachedRoutes[tIn.symbol + tOut.Symbol];
} else {
this.cachedRoutes[tIn.symbol + tOut.Symbol] = trade;
}
const expectedAmount = trade.maximumAmountIn(this.allowedSlippage);
return { trade, expectedAmount };
}
SWAP
// [email protected]
//还有一个swapExactIn 类似
async swapExactOut(wallet, trade, tokenAddress, gasPrice) {
const result = uni.Router.swapCallParameters(trade, {
ttl: TTL,
recipient: wallet.address,
allowedSlippage: this.allowedSlippage,
});
const contract = new ethers.Contract(
this.router,
proxyArtifact.abi,
wallet
);
//真正调用合约的地方
const tx = await contract[result.methodName](...result.args, {
gasPrice: gasPrice * 1e9,
gasLimit: GAS_LIMIT,
value: result.value,
});
debug(`Tx Hash: ${tx.hash}`);
return tx;
}
交易记录
//202@ethereum.
//json-rpc-provider
async perform(method: string, params: any): Promise {
const args = this.prepareRequest(method, params);
if (args == null) {
logger.throwError(method + " not implemented", Logger.errors.NOT_IMPLEMENTED, { operation: method });
}
try {
return await this.send(args[0], args[1])
} catch (error) {
return checkError(method, error, params);
}
}
...
send(method: string, params: Array): Promise {
this.emit("debug", {
action: "request",
request: deepCopy(request),
provider: this
});
}
emit(eventName: EventType, ...args: Array): boolean {
let result = false;
let stopped: Array = [ ];
let eventTag = getEventTag(eventName);
this._events = this._events.filter((event) => {
if (event.tag !== eventTag) { return true; }
setTimeout(() => {
event.listener.apply(this, args);
}, 0);
result = true;
if (event.once) {
stopped.push(event);
return false;
}
return true;
});
stopped.forEach((event) => { this._stopEvent(event); });
return result;
}
eth 钱包余额
//[email protected]
const wallet: ethers.Wallet = ethereumService.getWallet(
req.body.privateKey || ''
);
for (const symbol of JSON.parse(req.body.tokenList)) {
const tokenContractInfo = ethereumService.getERC20TokenAddress(symbol);
if (!tokenContractInfo) {
continue;
}
tokenContractList[symbol] = tokenContractInfo;
}
// Getting user balancers
const balances: Record = {};
balances.ETH = await ethereumService.getETHBalance(wallet);
await Promise.all(
Object.keys(tokenContractList).map(async (symbol) => {
if (tokenContractList[symbol] !== undefined) {
const address = tokenContractList[symbol].address;
const decimals = tokenContractList[symbol].decimals;
balances[symbol] = await ethereumService.getERC20Balance(
wallet,
address,
decimals
);
} else {
logger.error(`Token contract info for ${symbol} not found`);
}
})
);