@catalabs/catalyst-sdk
Version:
Catalyst AMM SDK
558 lines • 24 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RouterArguments = exports.gasCostTable = void 0;
const ethers_1 = require("ethers");
const evm_1 = require("../evm");
const route_enum_1 = require("./enums/route.enum");
const abi = ethers_1.AbiCoder.defaultAbiCoder();
const routerCommands = {
LOCALSWAP: (0x00).toString(16).padStart(2, '0'),
SENDASSET: (0x01).toString(16).padStart(2, '0'),
PERMIT2_TRANSFER_FROM: (0x02).toString(16).padStart(2, '0'),
PERMIT2_PERMIT_BATCH: (0x03).toString(16).padStart(2, '0'),
PERMIT2_TRANSFER_FROM_BATCH: (0x1e).toString(16).padStart(2, '0'),
SWEEP: (0x04).toString(16).padStart(2, '0'),
TRANSFER: (0x05).toString(16).padStart(2, '0'),
PAY_PORTION: (0x06).toString(16).padStart(2, '0'),
PERMIT2_PERMIT: (0x07).toString(16).padStart(2, '0'),
WRAP_GAS: (0x08).toString(16).padStart(2, '0'),
UNWRAP_GAS: (0x09).toString(16).padStart(2, '0'),
WITHDRAW_EQUAL: (0x0a).toString(16).padStart(2, '0'),
WITHDRAW_MIXED: (0x0b).toString(16).padStart(2, '0'),
DEPOSIT_MIXED: (0x0c).toString(16).padStart(2, '0'),
SENDLIQUIDITY: (0x0d).toString(16).padStart(2, '0'),
ALLOW_CANCEL: (0x0e).toString(16).padStart(2, '0'),
BALANCE_CHECK_ERC20: (0x0f).toString(16).padStart(2, '0'),
TRANSFER_FROM: (0x1f).toString(16).padStart(2, '0'),
};
const RouteDescription = 'tuple(bytes32, bytes, bytes, tuple(uint48, uint48, address, uint96, uint96, uint64), uint64)';
const PermitSingle = 'tuple(tuple(address, uint160, uint48, uint48), address, uint256)';
const PermitBatch = 'tuple(tuple(address, uint160, uint48, uint48)[], address, uint256)';
const AllowanceTransferDetailsArray = 'tuple(address, address, uint160, address)[]';
const encodeTable = {
LOCALSWAP: ['address', 'address', 'address', 'uint256', 'uint256'],
SENDASSET: [
'address',
RouteDescription,
'address',
'uint8',
'uint256',
'uint256',
'address',
'uint256',
'uint16',
],
PERMIT2_TRANSFER_FROM: ['address', 'address', 'uint160'],
PERMIT2_PERMIT_BATCH: [PermitBatch, 'bytes'],
PERMIT2_TRANSFER_FROM_BATCH: [AllowanceTransferDetailsArray],
SWEEP: ['address', 'address', 'uint256'],
TRANSFER: ['address', 'address', 'uint256'],
PAY_PORTION: ['address', 'address', 'uint256'],
PERMIT2_PERMIT: [PermitSingle, 'bytes'],
WRAP_GAS: ['address', 'uint256'],
UNWRAP_GAS: ['address', 'uint256'],
WITHDRAW_EQUAL: ['address', 'uint256', 'uint256[]'],
WITHDRAW_MIXED: ['address', 'uint256', 'uint256[]', 'uint256[]'],
DEPOSIT_MIXED: ['address', 'address[]', 'uint256[]', 'uint256'],
SENDLIQUIDITY: [
'address',
RouteDescription,
'uint256',
'uint256[2]',
'address',
'uint256',
],
ALLOW_CANCEL: ['address', 'bytes32'],
TRANSFER_FROM: ['address', 'address', 'uint160'],
};
const TX_TRANSFER_COST = 65000n - 21000n;
exports.gasCostTable = {
LOCALSWAP: BigInt('123549') - BigInt('24929') - BigInt('27450'),
SENDASSET: BigInt('216540') - BigInt(TX_TRANSFER_COST),
PERMIT2_TRANSFER_FROM: TX_TRANSFER_COST,
PERMIT2_PERMIT_BATCH: TX_TRANSFER_COST * 3n,
PERMIT2_TRANSFER_FROM_BATCH: TX_TRANSFER_COST * 3n,
SWEEP: TX_TRANSFER_COST,
TRANSFER: TX_TRANSFER_COST,
PAY_PORTION: TX_TRANSFER_COST,
PERMIT2_PERMIT: BigInt('10000'),
WRAP_GAS: BigInt('52807'),
UNWRAP_GAS: BigInt('35392') - BigInt('20805'),
WITHDRAW_EQUAL: BigInt('140270'),
WITHDRAW_MIXED: BigInt('141467'),
DEPOSIT_MIXED: BigInt('119056'),
SENDLIQUIDITY: BigInt('181294'),
ALLOW_CANCEL: BigInt('2000'),
BALANCE_CHECK_ERC20: BigInt('2000'),
TRANSFER_FROM: BigInt('65000') - BigInt('10000'),
ROUTE: BigInt('21000') + BigInt('4786'),
RECEIVE: BigInt('21000') + BigInt('113957') + BigInt('210000'),
ACK: BigInt('21000') + BigInt('89876') + BigInt('210000'),
};
const UNITS = '0xUNITS';
const GAS = 'GAS';
const EXEC_ABI = [
'function execute(bytes calldata commands, bytes[] calldata inputs)',
];
const execInterface = new ethers_1.ethers.Interface(EXEC_ABI);
class RouterArguments {
static MSG_SENDER = '0x0000000000000000000000000000000000000001';
static ADDRESS_THIS = '0x0000000000000000000000000000000000000002';
static BALANCE_THIS = BigInt('57896044618658097711785492504343953926634992332820282019728792003956564819968');
static ROUTER = evm_1.EVM_ROUTER_ADDRESS;
WGAS;
constructor(options = {}) {
const { WGAS = 'WGAS' } = options;
this.WGAS = WGAS;
}
params = [[], []];
gasCost = exports.gasCostTable['ROUTE'];
routerContains = {};
static getIncentiveCost(routingIncentive) {
return (routingIncentive.maxGasAck * routingIncentive.priceOfAckGas +
routingIncentive.maxGasDelivery * routingIncentive.priceOfDeliveryGas +
routingIncentive.messageVerifyGasCost);
}
ensureRouterEmpty() {
for (const key in this.routerContains) {
if (this.routerContains[key] > 0n) {
throw new Error(`Router contains ${this.routerContains[key].toString()} of ${key}. It should be empty`);
}
}
}
modifyRouterContains(asset, amount) {
if (this.routerContains[asset] === undefined) {
this.routerContains[asset] = 0n;
}
if (amount === RouterArguments.BALANCE_THIS) {
this.routerContains[asset] = BigInt(2) ** 256n - 1n;
return;
}
if (amount === RouterArguments.BALANCE_THIS * -1n) {
this.routerContains[asset] = 0n;
return;
}
this.routerContains[asset] = this.routerContains[asset] + amount;
}
getParams(checkRouterIsEmpty = true) {
if (checkRouterIsEmpty) {
this.ensureRouterEmpty();
}
return ['0x' + this.params[0].join(''), this.params[1]];
}
getCalldata() {
return (RouterArguments.ROUTER.replace('0x', '') +
execInterface.encodeFunctionData('execute', this.getParams()).slice(10));
}
getGasCostEstimate(options = {}) {
const { additionalMargin = 0.01, remote = false } = options;
const resolution = 1000000n;
const additionalGas = remote ? exports.gasCostTable['RECEIVE'] : 0n;
const totalGasCost = this.gasCost + additionalGas;
const marginWithScalar = (totalGasCost * BigInt(Number(resolution) * additionalMargin)) /
resolution;
return totalGasCost + marginWithScalar;
}
static splitRoute(fromAsset, route) {
const [vaults, tokens] = route;
const chainSwitch = tokens.indexOf(UNITS) + 1;
if (chainSwitch === 0) {
const fromRoute = {
fromAsset: fromAsset,
routeDetails: route,
};
const crossRoute = {
fromAsset: '',
routeDetails: [[], []],
};
const toRoute = {
fromAsset: fromAsset,
routeDetails: [[], []],
};
return [fromRoute, crossRoute, toRoute];
}
const fromRoute = {
fromAsset: fromAsset,
routeDetails: [
vaults.slice(0, chainSwitch - 1),
tokens.slice(0, chainSwitch - 1),
],
};
const crossRoute = {
fromAsset: [fromAsset, ...tokens][chainSwitch - 1],
routeDetails: [
vaults.slice(chainSwitch - 1, chainSwitch + 1),
tokens.slice(chainSwitch - 1, chainSwitch + 1),
],
};
const toRoute = {
fromAsset: tokens[chainSwitch],
routeDetails: [
vaults.slice(chainSwitch + 1),
tokens.slice(chainSwitch + 1),
],
};
return [fromRoute, crossRoute, toRoute];
}
wrapGas(to = RouterArguments.ADDRESS_THIS, amount = RouterArguments.BALANCE_THIS) {
const commands = [routerCommands['WRAP_GAS']];
const encodedParams = [abi.encode(encodeTable['WRAP_GAS'], [to, amount])];
const routeParams = [commands, encodedParams];
this.modifyRouterContains(GAS, amount * -1n);
if (to === RouterArguments.ADDRESS_THIS) {
this.modifyRouterContains(this.WGAS, amount);
}
this.gasCost += exports.gasCostTable['WRAP_GAS'];
this.params = this.combineRouteParams(this.params, routeParams);
}
unwrapGas(to, minOut = 0n) {
const commands = [routerCommands['UNWRAP_GAS']];
const encodedParams = [abi.encode(encodeTable['UNWRAP_GAS'], [to, minOut])];
const routeParams = [commands, encodedParams];
this.modifyRouterContains(this.WGAS, RouterArguments.BALANCE_THIS * -1n);
if (to === RouterArguments.ADDRESS_THIS) {
this.modifyRouterContains(GAS, RouterArguments.BALANCE_THIS);
}
this.gasCost += exports.gasCostTable['UNWRAP_GAS'];
this.params = this.combineRouteParams(this.params, routeParams);
}
localroute(fromAsset, route, minOut) {
const commands = route[0].map((_) => routerCommands['LOCALSWAP']);
const parameters = [];
const [vaults, assets] = route;
let pastAsset = fromAsset;
for (let i = 0; i < vaults.length; i++) {
const toVault = vaults[i];
const toAsset = assets[i];
parameters.push([
toVault,
pastAsset,
toAsset,
RouterArguments.BALANCE_THIS,
i === vaults.length - 1 ? minOut : 0n,
]);
this.modifyRouterContains(pastAsset, RouterArguments.BALANCE_THIS * -1n);
this.modifyRouterContains(toAsset, RouterArguments.BALANCE_THIS);
pastAsset = toAsset;
}
const encodedParams = parameters.map((param) => abi.encode(encodeTable['LOCALSWAP'], param));
const routeParams = [commands, encodedParams];
this.gasCost += exports.gasCostTable['LOCALSWAP'];
this.params = this.combineRouteParams(this.params, routeParams);
}
sendAsset(fromAsset, toAssetIndex, fromVault, toVault, channelId, minOut, transferTo, gasTokensToSend, routingIncentive, deadline = 0n, underwritingIncentive = 0n, target_data = '', revert_to = RouterArguments.MSG_SENDER) {
const toVaultEncoded = RouterArguments.addressEncodeHelper(toVault);
const transferToEncoded = RouterArguments.addressEncodeHelper(transferTo);
const commands = [routerCommands['SENDASSET']];
const encodedParams = [
abi.encode(encodeTable['SENDASSET'], [
fromVault,
[
channelId,
toVaultEncoded,
transferToEncoded,
[
routingIncentive.maxGasDelivery,
routingIncentive.maxGasAck,
routingIncentive.refundGasTo,
routingIncentive.priceOfDeliveryGas,
routingIncentive.priceOfAckGas,
routingIncentive.targetDelta,
],
deadline,
],
fromAsset,
toAssetIndex,
RouterArguments.BALANCE_THIS,
minOut,
revert_to,
gasTokensToSend,
underwritingIncentive,
]) + target_data,
];
const routeParams = [commands, encodedParams];
this.modifyRouterContains(GAS, gasTokensToSend * -1n);
this.modifyRouterContains(fromAsset, RouterArguments.BALANCE_THIS * -1n);
this.gasCost += exports.gasCostTable['SENDASSET'];
this.params = this.combineRouteParams(this.params, routeParams);
}
deposit(vault, tokens, tokenAmounts, minOut) {
const commands = [routerCommands['DEPOSIT_MIXED']];
const encodedParams = [
abi.encode(encodeTable['DEPOSIT_MIXED'], [
vault,
tokens,
tokenAmounts,
minOut,
]),
];
const routeParams = [commands, encodedParams];
for (const token of tokens) {
this.modifyRouterContains(token, RouterArguments.BALANCE_THIS * -1n);
}
this.modifyRouterContains(vault, RouterArguments.BALANCE_THIS);
this.gasCost += exports.gasCostTable['DEPOSIT_MIXED'];
this.params = this.combineRouteParams(this.params, routeParams);
}
withdrawEqual(vault, amountVaultTokens, minOuts) {
const commands = [routerCommands['WITHDRAW_EQUAL']];
const encodedParams = [
abi.encode(encodeTable['WITHDRAW_EQUAL'], [
vault,
amountVaultTokens,
minOuts,
]),
];
const routeParams = [commands, encodedParams];
this.modifyRouterContains(vault, amountVaultTokens * -1n);
this.gasCost += exports.gasCostTable['WITHDRAW_EQUAL'];
this.params = this.combineRouteParams(this.params, routeParams);
}
withdrawMixed(vault, amountVaultTokens, withdrawRatios, minOuts) {
const commands = [routerCommands['WITHDRAW_MIXED']];
const encodedParams = [
abi.encode(encodeTable['WITHDRAW_MIXED'], [
vault,
amountVaultTokens,
withdrawRatios,
minOuts,
]),
];
const routeParams = [commands, encodedParams];
this.modifyRouterContains(vault, amountVaultTokens * -1n);
this.gasCost += exports.gasCostTable['WITHDRAW_MIXED'];
this.params = this.combineRouteParams(this.params, routeParams);
}
sweep(token, target, minOut = 0n) {
const commands = [routerCommands['SWEEP']];
const encodedParams = [
abi.encode(encodeTable['SWEEP'], [token, target, minOut]),
];
const routeParams = [commands, encodedParams];
if (target !== RouterArguments.ADDRESS_THIS) {
this.modifyRouterContains(token, RouterArguments.BALANCE_THIS * -1n);
}
this.gasCost += exports.gasCostTable['SWEEP'];
this.params = this.combineRouteParams(this.params, routeParams);
}
transferFrom(token, amount) {
const commands = [routerCommands['TRANSFER_FROM']];
const encodedParams = [
abi.encode(encodeTable['TRANSFER_FROM'], [
token,
RouterArguments.ADDRESS_THIS,
amount,
]),
];
const routeParams = [commands, encodedParams];
this.modifyRouterContains(token, amount);
this.gasCost += exports.gasCostTable['TRANSFER_FROM'];
this.params = this.combineRouteParams(this.params, routeParams);
}
liquiditySwap(fromVault, toVault, channelId, minOuts, transferTo, gasTokensToSend, routingIncentive, amount = RouterArguments.BALANCE_THIS, deadline = 0n, target_data = '', revert_to = RouterArguments.MSG_SENDER) {
const commands = [routerCommands['SENDLIQUIDITY']];
const toVaultEncoded = RouterArguments.addressEncodeHelper(toVault);
const transferToEncoded = RouterArguments.addressEncodeHelper(transferTo);
const encodedParams = [
abi.encode(encodeTable['SENDLIQUIDITY'], [
fromVault,
[
channelId,
toVaultEncoded,
transferToEncoded,
[
routingIncentive.maxGasDelivery,
routingIncentive.maxGasAck,
routingIncentive.refundGasTo,
routingIncentive.priceOfDeliveryGas,
routingIncentive.priceOfAckGas,
routingIncentive.targetDelta,
],
deadline,
],
amount,
minOuts,
revert_to,
gasTokensToSend,
]) + target_data,
];
const routeParams = [commands, encodedParams];
this.modifyRouterContains(GAS, gasTokensToSend * -1n);
this.modifyRouterContains(fromVault, amount * -1n);
this.gasCost += exports.gasCostTable['SENDLIQUIDITY'];
this.params = this.combineRouteParams(this.params, routeParams);
}
permit2Permit(permitSingle, signature, order = route_enum_1.CombineRouteParamsOrder.Last) {
const commands = [routerCommands['PERMIT2_PERMIT']];
const encodedParams = [
abi.encode(encodeTable['PERMIT2_PERMIT'], [
[
[
permitSingle.details.token,
permitSingle.details.amount,
permitSingle.details.expiration,
permitSingle.details.nonce,
],
permitSingle.spender,
permitSingle.sigDeadline,
],
signature,
]),
];
const routeParams = [commands, encodedParams];
this.gasCost += exports.gasCostTable['PERMIT2_PERMIT'];
this.params = this.combineRouteParams(this.params, routeParams, order);
}
permit2PermitBatch(permitBatch, signature, order = route_enum_1.CombineRouteParamsOrder.Last) {
const commands = [routerCommands['PERMIT2_PERMIT_BATCH']];
const encodedParams = [
abi.encode(encodeTable['PERMIT2_PERMIT_BATCH'], [
[
permitBatch.details.map((permit) => [
permit.token,
permit.amount,
permit.expiration,
permit.nonce,
]),
permitBatch.spender,
permitBatch.sigDeadline,
],
signature,
]),
];
const routeParams = [commands, encodedParams];
this.gasCost += exports.gasCostTable['PERMIT2_PERMIT_BATCH'];
this.params = this.combineRouteParams(this.params, routeParams, order);
}
permit2TransferFrom(token, amount, order = route_enum_1.CombineRouteParamsOrder.Last) {
const commands = [routerCommands['PERMIT2_TRANSFER_FROM']];
const encodedParams = [
abi.encode(encodeTable['PERMIT2_TRANSFER_FROM'], [
token,
RouterArguments.ADDRESS_THIS,
amount,
]),
];
const routeParams = [commands, encodedParams];
if (order !== route_enum_1.CombineRouteParamsOrder.First) {
this.modifyRouterContains(token, amount);
}
this.gasCost += exports.gasCostTable['PERMIT2_TRANSFER_FROM'];
this.params = this.combineRouteParams(this.params, routeParams, order);
}
permit2TransferFromBatch(assets, owner, order = route_enum_1.CombineRouteParamsOrder.Last) {
const assetsWithFromAndTo = assets.map(({ token, amount }) => ({
token,
amount,
from: owner,
to: RouterArguments.ROUTER,
}));
const commands = [routerCommands['PERMIT2_TRANSFER_FROM_BATCH']];
const encodedParams = [
abi.encode(encodeTable['PERMIT2_TRANSFER_FROM_BATCH'], [
assetsWithFromAndTo.map((asset) => [
asset.from,
asset.to,
asset.amount,
asset.token,
]),
]),
];
const routeParams = [commands, encodedParams];
for (const asset of assets) {
const { token, amount } = asset;
if (order !== route_enum_1.CombineRouteParamsOrder.First) {
this.modifyRouterContains(token, amount);
}
}
this.gasCost += exports.gasCostTable['PERMIT2_TRANSFER_FROM_BATCH'];
this.params = this.combineRouteParams(this.params, routeParams, order);
}
transferWithOptionalPermit(asset, permitData, order = route_enum_1.CombineRouteParamsOrder.Last) {
if (order === route_enum_1.CombineRouteParamsOrder.First) {
this.permit2TransferFrom(asset.token, asset.amount, order);
if (permitData) {
this.permit2Permit(permitData.permit, permitData.signature, order);
}
return;
}
if (permitData) {
this.permit2Permit(permitData.permit, permitData.signature, order);
}
this.permit2TransferFrom(asset.token, asset.amount, order);
}
transferWithOptionalPermitBatch(assets, owner, permitBatchData, order = route_enum_1.CombineRouteParamsOrder.Last) {
if (assets.length === 0) {
return;
}
if (order === route_enum_1.CombineRouteParamsOrder.First) {
this.permit2TransferFromBatch(assets, owner, order);
if (permitBatchData) {
this.permit2PermitBatch(permitBatchData.permit, permitBatchData.signature, order);
}
return;
}
if (permitBatchData) {
this.permit2PermitBatch(permitBatchData.permit, permitBatchData.signature, order);
}
this.permit2TransferFromBatch(assets, owner, order);
}
transferWithPermitForDepositWithLiquiditySwaps(assets, owner, gasUsage, permitBatchData) {
this.transferWithOptionalPermitBatch(assets, owner, permitBatchData, route_enum_1.CombineRouteParamsOrder.First);
const estimatedGasUsedOnLocal = this.getGasCostEstimate();
const toCallParameters = this.getParams();
const estimatedRefund = (gasUsage.estimatedGasUsedOnRemote * 2n) / 12n;
return {
executionInstructions: {
commands: toCallParameters[0],
inputs: toCallParameters[1],
gas: {
...gasUsage,
estimatedGasUsedOnLocal: estimatedGasUsedOnLocal,
estimatedRefundOnAck: estimatedRefund,
},
},
};
}
transferWithPermitForWithdrawWithLiquiditySwap(asset, gasUsage, permitData) {
this.transferWithOptionalPermit(asset, permitData, route_enum_1.CombineRouteParamsOrder.First);
const estimatedGasUsedOnLocal = this.getGasCostEstimate();
const toCallParameters = this.getParams();
const estimatedRefund = (gasUsage.estimatedGasUsedOnRemote * 2n) / 12n;
return {
executionInstructions: {
commands: toCallParameters[0],
inputs: toCallParameters[1],
gas: {
...gasUsage,
estimatedGasUsedOnLocal: estimatedGasUsedOnLocal,
estimatedRefundOnAck: estimatedRefund,
},
},
};
}
static addressEncodeHelper(address) {
if (address.length === 20 * 2 + 1 * 2) {
return ('0x140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' +
address.replace('0x', ''));
}
else if (address.length === 65) {
return address;
}
throw new Error(`The address, ${address}, is incorrectly provided`);
}
combineRouteParams(firstParams, lastParams, order = route_enum_1.CombineRouteParamsOrder.Last) {
if (order === route_enum_1.CombineRouteParamsOrder.First) {
return [
[...lastParams[0], ...firstParams[0]],
[...lastParams[1], ...firstParams[1]],
];
}
return [
[...firstParams[0], ...lastParams[0]],
[...firstParams[1], ...lastParams[1]],
];
}
}
exports.RouterArguments = RouterArguments;
//# sourceMappingURL=router.utils.js.map