UNPKG

astra-universal-router-sdk

Version:

sdk for integrating with the Astra Router contracts

984 lines (967 loc) 42.9 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var invariant = _interopDefault(require('tiny-invariant')); var UniversalRouter_json = require('@airdao/astra-universal-router/artifacts/contracts/UniversalRouter.sol/UniversalRouter.json'); var abi = require('@ethersproject/abi'); var ethers = require('ethers'); var utils = require('ethers/lib/utils'); var astraClassicSdk = require('astra-classic-sdk'); var astraClSdkDev = require('astra-cl-sdk-dev'); var astraRouterSdk = require('astra-router-sdk'); var astraSdkCore = require('astra-sdk-core'); require('jsbi'); require('bignumber.js'); function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } function _createForOfIteratorHelperLoose(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (t) return (t = t.call(r)).next.bind(t); if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var o = 0; return function () { return o >= r.length ? { done: !0 } : { done: !1, value: r[o++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } var _COMMAND_DEFINITION; (function (CommandType) { CommandType[CommandType["CL_SWAP_EXACT_IN"] = 0] = "CL_SWAP_EXACT_IN"; CommandType[CommandType["CL_SWAP_EXACT_OUT"] = 1] = "CL_SWAP_EXACT_OUT"; CommandType[CommandType["PERMIT2_TRANSFER_FROM"] = 2] = "PERMIT2_TRANSFER_FROM"; CommandType[CommandType["PERMIT2_PERMIT_BATCH"] = 3] = "PERMIT2_PERMIT_BATCH"; CommandType[CommandType["SWEEP"] = 4] = "SWEEP"; CommandType[CommandType["TRANSFER"] = 5] = "TRANSFER"; CommandType[CommandType["PAY_PORTION"] = 6] = "PAY_PORTION"; CommandType[CommandType["CLASSIC_SWAP_EXACT_IN"] = 8] = "CLASSIC_SWAP_EXACT_IN"; CommandType[CommandType["CLASSIC_SWAP_EXACT_OUT"] = 9] = "CLASSIC_SWAP_EXACT_OUT"; CommandType[CommandType["PERMIT2_PERMIT"] = 10] = "PERMIT2_PERMIT"; CommandType[CommandType["WRAP_AMB"] = 11] = "WRAP_AMB"; CommandType[CommandType["UNWRAP_SAMB"] = 12] = "UNWRAP_SAMB"; CommandType[CommandType["PERMIT2_TRANSFER_FROM_BATCH"] = 13] = "PERMIT2_TRANSFER_FROM_BATCH"; CommandType[CommandType["BALANCE_CHECK_ERC20"] = 14] = "BALANCE_CHECK_ERC20"; CommandType[CommandType["CL_POSITION_MANAGER_PERMIT"] = 17] = "CL_POSITION_MANAGER_PERMIT"; CommandType[CommandType["CL_POSITION_MANAGER_CALL"] = 18] = "CL_POSITION_MANAGER_CALL"; CommandType[CommandType["EXECUTE_SUB_PLAN"] = 33] = "EXECUTE_SUB_PLAN"; })(exports.CommandType || (exports.CommandType = {})); (function (Subparser) { Subparser[Subparser["CLPathExactIn"] = 0] = "CLPathExactIn"; Subparser[Subparser["CLPathExactOut"] = 1] = "CLPathExactOut"; })(exports.Subparser || (exports.Subparser = {})); (function (Parser) { Parser[Parser["Abi"] = 0] = "Abi"; Parser[Parser["CLActions"] = 1] = "CLActions"; })(exports.Parser || (exports.Parser = {})); var ALLOW_REVERT_FLAG = 0x80; var REVERTIBLE_COMMANDS = /*#__PURE__*/new Set([exports.CommandType.EXECUTE_SUB_PLAN]); var PERMIT_STRUCT = '((address token,uint160 amount,uint48 expiration,uint48 nonce) details,address spender,uint256 sigDeadline)'; var PERMIT_BATCH_STRUCT = '((address token,uint160 amount,uint48 expiration,uint48 nonce)[] details,address spender,uint256 sigDeadline)'; var PERMIT2_TRANSFER_FROM_STRUCT = '(address from,address to,uint160 amount,address token)'; var PERMIT2_TRANSFER_FROM_BATCH_STRUCT = PERMIT2_TRANSFER_FROM_STRUCT + '[]'; var COMMAND_DEFINITION = (_COMMAND_DEFINITION = {}, _COMMAND_DEFINITION[exports.CommandType.EXECUTE_SUB_PLAN] = { parser: exports.Parser.Abi, params: [{ name: 'commands', type: 'bytes' }, { name: 'inputs', type: 'bytes[]' }] }, _COMMAND_DEFINITION[exports.CommandType.PERMIT2_PERMIT] = { parser: exports.Parser.Abi, params: [{ name: 'permit', type: PERMIT_STRUCT }, { name: 'signature', type: 'bytes' }] }, _COMMAND_DEFINITION[exports.CommandType.PERMIT2_PERMIT_BATCH] = { parser: exports.Parser.Abi, params: [{ name: 'permit', type: PERMIT_BATCH_STRUCT }, { name: 'signature', type: 'bytes' }] }, _COMMAND_DEFINITION[exports.CommandType.PERMIT2_TRANSFER_FROM] = { parser: exports.Parser.Abi, params: [{ name: 'token', type: 'address' }, { name: 'recipient', type: 'address' }, { name: 'amount', type: 'uint160' }] }, _COMMAND_DEFINITION[exports.CommandType.PERMIT2_TRANSFER_FROM_BATCH] = { parser: exports.Parser.Abi, params: [{ name: 'transferFrom', type: PERMIT2_TRANSFER_FROM_BATCH_STRUCT }] }, _COMMAND_DEFINITION[exports.CommandType.CL_SWAP_EXACT_IN] = { parser: exports.Parser.Abi, params: [{ name: 'recipient', type: 'address' }, { name: 'amountIn', type: 'uint256' }, { name: 'amountOutMin', type: 'uint256' }, { name: 'path', subparser: exports.Subparser.CLPathExactIn, type: 'bytes' }, { name: 'payerIsUser', type: 'bool' }] }, _COMMAND_DEFINITION[exports.CommandType.CL_SWAP_EXACT_OUT] = { parser: exports.Parser.Abi, params: [{ name: 'recipient', type: 'address' }, { name: 'amountOut', type: 'uint256' }, { name: 'amountInMax', type: 'uint256' }, { name: 'path', subparser: exports.Subparser.CLPathExactOut, type: 'bytes' }, { name: 'payerIsUser', type: 'bool' }] }, _COMMAND_DEFINITION[exports.CommandType.CLASSIC_SWAP_EXACT_IN] = { parser: exports.Parser.Abi, params: [{ name: 'recipient', type: 'address' }, { name: 'amountIn', type: 'uint256' }, { name: 'amountOutMin', type: 'uint256' }, { name: 'path', type: 'address[]' }, { name: 'payerIsUser', type: 'bool' }] }, _COMMAND_DEFINITION[exports.CommandType.CLASSIC_SWAP_EXACT_OUT] = { parser: exports.Parser.Abi, params: [{ name: 'recipient', type: 'address' }, { name: 'amountOut', type: 'uint256' }, { name: 'amountInMax', type: 'uint256' }, { name: 'path', type: 'address[]' }, { name: 'payerIsUser', type: 'bool' }] }, _COMMAND_DEFINITION[exports.CommandType.WRAP_AMB] = { parser: exports.Parser.Abi, params: [{ name: 'recipient', type: 'address' }, { name: 'amount', type: 'uint256' }] }, _COMMAND_DEFINITION[exports.CommandType.UNWRAP_SAMB] = { parser: exports.Parser.Abi, params: [{ name: 'recipient', type: 'address' }, { name: 'amountMin', type: 'uint256' }] }, _COMMAND_DEFINITION[exports.CommandType.SWEEP] = { parser: exports.Parser.Abi, params: [{ name: 'token', type: 'address' }, { name: 'recipient', type: 'address' }, { name: 'amountMin', type: 'uint256' }] }, _COMMAND_DEFINITION[exports.CommandType.TRANSFER] = { parser: exports.Parser.Abi, params: [{ name: 'token', type: 'address' }, { name: 'recipient', type: 'address' }, { name: 'value', type: 'uint256' }] }, _COMMAND_DEFINITION[exports.CommandType.PAY_PORTION] = { parser: exports.Parser.Abi, params: [{ name: 'token', type: 'address' }, { name: 'recipient', type: 'address' }, { name: 'bips', type: 'uint256' }] }, _COMMAND_DEFINITION[exports.CommandType.BALANCE_CHECK_ERC20] = { parser: exports.Parser.Abi, params: [{ name: 'owner', type: 'address' }, { name: 'token', type: 'address' }, { name: 'minBalance', type: 'uint256' }] }, _COMMAND_DEFINITION[exports.CommandType.CL_POSITION_MANAGER_PERMIT] = { parser: exports.Parser.CLActions }, _COMMAND_DEFINITION[exports.CommandType.CL_POSITION_MANAGER_CALL] = { parser: exports.Parser.CLActions }, _COMMAND_DEFINITION); var RoutePlanner = /*#__PURE__*/function () { function RoutePlanner() { this.commands = '0x'; this.inputs = []; } var _proto = RoutePlanner.prototype; _proto.addSubPlan = function addSubPlan(subplan) { this.addCommand(exports.CommandType.EXECUTE_SUB_PLAN, [subplan.commands, subplan.inputs], true); return this; }; _proto.addCommand = function addCommand(type, parameters, allowRevert) { if (allowRevert === void 0) { allowRevert = false; } var command = createCommand(type, parameters); this.inputs.push(command.encodedInput); if (allowRevert) { if (!REVERTIBLE_COMMANDS.has(command.type)) { throw new Error("command type: " + command.type + " cannot be allowed to revert"); } command.type = command.type | ALLOW_REVERT_FLAG; } this.commands = this.commands.concat(command.type.toString(16).padStart(2, '0')); return this; }; return RoutePlanner; }(); function createCommand(type, parameters) { var commandDef = COMMAND_DEFINITION[type]; switch (commandDef.parser) { case exports.Parser.Abi: var encodedInput = utils.defaultAbiCoder.encode(commandDef.params.map(function (abi) { return abi.type; }), parameters); return { type: type, encodedInput: encodedInput }; case exports.Parser.CLActions: // v4 swap data comes pre-encoded at index 0 return { type: type, encodedInput: parameters[0] }; } } function getPathCurrency(currency, pool) { // return currency if the currency matches a currency of the pool if (pool.involvesToken(currency)) { return currency; // return if currency.wrapped if pool involves wrapped currency } else if (pool.involvesToken(currency.wrapped)) { return currency.wrapped; } else { throw new Error("Expected currency " + currency.symbol + " to be either " + pool.token0.symbol + " or " + pool.token1.symbol); } } (function (RouterActionType) { RouterActionType["AstraTrade"] = "AstraTrade"; RouterActionType["UnwrapSAMB"] = "UnwrapSAMB"; })(exports.RouterActionType || (exports.RouterActionType = {})); var _routerConfigs, _routerConfigs2, _routerConfigs3, _CHAIN_CONFIGS; (function (UniversalRouterVersion) { UniversalRouterVersion["V1_2"] = "1.2"; UniversalRouterVersion["V2_0"] = "2.0"; })(exports.UniversalRouterVersion || (exports.UniversalRouterVersion = {})); var SAMB_NOT_SUPPORTED_ON_CHAIN = '0x0000000000000000000000000000000000000000'; var CHAIN_CONFIGS = (_CHAIN_CONFIGS = {}, _CHAIN_CONFIGS[16718] = { samb: '0x2b2d892C3fe2b4113dd7aC0D2c1882AF202FB28F', //TODO: update addresses routerConfigs: (_routerConfigs = {}, _routerConfigs[exports.UniversalRouterVersion.V1_2] = { address: '0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD', creationBlock: 17143817 }, _routerConfigs[exports.UniversalRouterVersion.V2_0] = { address: '0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD', creationBlock: 17143817 }, _routerConfigs) }, _CHAIN_CONFIGS[30746] = { //TODO: update addresses samb: '0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', routerConfigs: (_routerConfigs2 = {}, _routerConfigs2[exports.UniversalRouterVersion.V1_2] = { address: '0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD', creationBlock: 8940568 }, _routerConfigs2[exports.UniversalRouterVersion.V2_0] = { address: '0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD', creationBlock: 8940568 }, _routerConfigs2) }, _CHAIN_CONFIGS[22040] = { samb: '0x2Cf845b49e1c4E5D657fbBF36E97B7B5B7B7b74b', routerConfigs: (_routerConfigs3 = {}, _routerConfigs3[exports.UniversalRouterVersion.V1_2] = { address: '0xb0d6892CbC3f2dC5830cd8758FF3c16Fef5e847A', creationBlock: 3543575 }, _routerConfigs3[exports.UniversalRouterVersion.V2_0] = { address: '0xb0d6892CbC3f2dC5830cd8758FF3c16Fef5e847A', creationBlock: 6898582 }, _routerConfigs3) }, _CHAIN_CONFIGS); var UNIVERSAL_ROUTER_ADDRESS = function UNIVERSAL_ROUTER_ADDRESS(version, chainId) { if (!(chainId in CHAIN_CONFIGS)) throw new Error("Universal Router not deployed on chain " + chainId); return CHAIN_CONFIGS[chainId].routerConfigs[version].address; }; var UNIVERSAL_ROUTER_CREATION_BLOCK = function UNIVERSAL_ROUTER_CREATION_BLOCK(version, chainId) { if (!(chainId in CHAIN_CONFIGS)) throw new Error("Universal Router not deployed on chain " + chainId); return CHAIN_CONFIGS[chainId].routerConfigs[version].creationBlock; }; var SAMB_ADDRESS = function SAMB_ADDRESS(chainId) { if (!(chainId in CHAIN_CONFIGS)) throw new Error("Universal Router not deployed on chain " + chainId); if (CHAIN_CONFIGS[chainId].samb == SAMB_NOT_SUPPORTED_ON_CHAIN) throw new Error("Chain " + chainId + " does not have SAMB"); return CHAIN_CONFIGS[chainId].samb; }; var CONTRACT_BALANCE = /*#__PURE__*/ethers.BigNumber.from(2).pow(255); var AMB_ADDRESS = '0x0000000000000000000000000000000000000000'; var E_AMB_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; var SENDER_AS_RECIPIENT = '0x0000000000000000000000000000000000000001'; var ROUTER_AS_RECIPIENT = '0x0000000000000000000000000000000000000002'; function encodeFeeBips(fee) { return astraClSdkDev.toHex(fee.multiply(10000).quotient); } var REFUND_AMB_PRICE_IMPACT_THRESHOLD = /*#__PURE__*/new astraSdkCore.Percent(50, 100); // Wrapper for astra router-sdk trade entity to encode swaps for Universal Router // also translates trade objects from previous (Classic, CL) SDKs var AstraTrade = /*#__PURE__*/function () { function AstraTrade(trade, options) { this.trade = trade; this.options = options; this.tradeType = exports.RouterActionType.AstraTrade; if (!!options.fee && !!options.flatFee) throw new Error('Only one fee option permitted'); if (this.inputRequiresWrap) this.payerIsUser = false;else this.payerIsUser = !this.options.useRouterBalance; } var _proto = AstraTrade.prototype; _proto.encode = function encode(planner, _config) { var _this$options$recipie; // If the input currency is the native currency, we need to wrap it with the router as the recipient if (this.inputRequiresWrap) { // TODO: optimize if only one Classic pool we can directly send this to the pool planner.addCommand(exports.CommandType.WRAP_AMB, [ROUTER_AS_RECIPIENT, this.trade.maximumAmountIn(this.options.slippageTolerance).quotient.toString()]); } // The overall recipient at the end of the trade, SENDER_AS_RECIPIENT uses the msg.sender this.options.recipient = (_this$options$recipie = this.options.recipient) != null ? _this$options$recipie : SENDER_AS_RECIPIENT; // flag for whether we want to perform slippage check on aggregate output of multiple routes // 1. when there are >2 exact input trades. this is only a heuristic, // as it's still more gas-expensive even in this case, but has benefits // in that the reversion probability is lower var performAggregatedSlippageCheck = this.trade.tradeType === astraSdkCore.TradeType.EXACT_INPUT && this.trade.routes.length > 2; var routerMustCustody = performAggregatedSlippageCheck || this.outputRequiresUnwrap || hasFeeOption(this.options); for (var _iterator = _createForOfIteratorHelperLoose(this.trade.swaps), _step; !(_step = _iterator()).done;) { var swap = _step.value; switch (swap.route.protocol) { case astraRouterSdk.Protocol.Classic: addClassicSwap(planner, swap, this.trade.tradeType, this.options, this.payerIsUser, routerMustCustody); break; case astraRouterSdk.Protocol.CL: addCLSwap(planner, swap, this.trade.tradeType, this.options, this.payerIsUser, routerMustCustody); break; case astraRouterSdk.Protocol.MIXED: addMixedSwap(planner, swap, this.trade.tradeType, this.options, this.payerIsUser, routerMustCustody); break; default: throw new Error('UNSUPPORTED_TRADE_PROTOCOL'); } } var minimumAmountOut = ethers.BigNumber.from(this.trade.minimumAmountOut(this.options.slippageTolerance).quotient.toString()); // The router custodies for 3 reasons: to unwrap, to take a fee, and/or to do a slippage check if (routerMustCustody) { // If there is a fee, that percentage is sent to the fee recipient // In the case where AMB is the output currency, the fee is taken in SAMB (for gas reasons) if (!!this.options.fee) { var feeBips = encodeFeeBips(this.options.fee.fee); planner.addCommand(exports.CommandType.PAY_PORTION, [this.trade.outputAmount.currency.wrapped.address, this.options.fee.recipient, feeBips]); // If the trade is exact output, and a fee was taken, we must adjust the amount out to be the amount after the fee // Otherwise we continue as expected with the trade's normal expected output if (this.trade.tradeType === astraSdkCore.TradeType.EXACT_OUTPUT) { minimumAmountOut = minimumAmountOut.sub(minimumAmountOut.mul(feeBips).div(10000)); } } // If there is a flat fee, that absolute amount is sent to the fee recipient // In the case where AMB is the output currency, the fee is taken in SAMB (for gas reasons) if (!!this.options.flatFee) { var feeAmount = this.options.flatFee.amount; if (minimumAmountOut.lt(feeAmount)) throw new Error('Flat fee amount greater than minimumAmountOut'); planner.addCommand(exports.CommandType.TRANSFER, [this.trade.outputAmount.currency.wrapped.address, this.options.flatFee.recipient, feeAmount]); // If the trade is exact output, and a fee was taken, we must adjust the amount out to be the amount after the fee // Otherwise we continue as expected with the trade's normal expected output if (this.trade.tradeType === astraSdkCore.TradeType.EXACT_OUTPUT) { minimumAmountOut = minimumAmountOut.sub(feeAmount); } } // The remaining tokens that need to be sent to the user after the fee is taken will be caught // by this if-else clause. if (this.outputRequiresUnwrap) { planner.addCommand(exports.CommandType.UNWRAP_SAMB, [this.options.recipient, minimumAmountOut]); } else { planner.addCommand(exports.CommandType.SWEEP, [this.trade.outputAmount.currency.wrapped.address, this.options.recipient, minimumAmountOut]); } } if (this.inputRequiresWrap && (this.trade.tradeType === astraSdkCore.TradeType.EXACT_OUTPUT || riskOfPartialFill(this.trade))) { // for exactOutput swaps that take native currency as input // we need to send back the change to the user planner.addCommand(exports.CommandType.UNWRAP_SAMB, [this.options.recipient, 0]); } if (this.options.safeMode) planner.addCommand(exports.CommandType.SWEEP, [AMB_ADDRESS, this.options.recipient, 0]); }; return _createClass(AstraTrade, [{ key: "inputRequiresWrap", get: function get() { // We only support wrapping all AMB or no AMB currently. We cannot support splitting where half needs to be wrapped // If the input currency is AMB and the input of the first path is not AMB it must be SAMB that needs wrapping return this.trade.inputAmount.currency.isNative && !this.trade.swaps[0].route.input.isNative; } }, { key: "outputRequiresUnwrap", get: function get() { // If the output currency is AMB and the output of the swap is not AMB it must be SAMB that needs unwrapping return this.trade.outputAmount.currency.isNative && !this.trade.swaps[0].route.output.isNative; } }]); }(); // encode a astra Classic swap function addClassicSwap(planner, _ref, tradeType, options, payerIsUser, routerMustCustody) { var route = _ref.route, inputAmount = _ref.inputAmount, outputAmount = _ref.outputAmount; var trade = new astraClassicSdk.Trade(route, tradeType == astraSdkCore.TradeType.EXACT_INPUT ? inputAmount : outputAmount, tradeType); if (tradeType == astraSdkCore.TradeType.EXACT_INPUT) { planner.addCommand(exports.CommandType.CLASSIC_SWAP_EXACT_IN, [ // if native, we have to unwrap so keep in the router for now routerMustCustody ? ROUTER_AS_RECIPIENT : options.recipient, trade.maximumAmountIn(options.slippageTolerance).quotient.toString(), // if router will custody funds, we do aggregated slippage check from router routerMustCustody ? 0 : trade.minimumAmountOut(options.slippageTolerance).quotient.toString(), route.path.map(function (token) { return token.wrapped.address; }), payerIsUser]); } else if (tradeType == astraSdkCore.TradeType.EXACT_OUTPUT) { planner.addCommand(exports.CommandType.CLASSIC_SWAP_EXACT_OUT, [routerMustCustody ? ROUTER_AS_RECIPIENT : options.recipient, trade.minimumAmountOut(options.slippageTolerance).quotient.toString(), trade.maximumAmountIn(options.slippageTolerance).quotient.toString(), route.path.map(function (token) { return token.wrapped.address; }), payerIsUser]); } } // encode a astra CL swap function addCLSwap(planner, _ref2, tradeType, options, payerIsUser, routerMustCustody) { var route = _ref2.route, inputAmount = _ref2.inputAmount, outputAmount = _ref2.outputAmount; var trade = astraClSdkDev.Trade.createUncheckedTrade({ route: route, inputAmount: inputAmount, outputAmount: outputAmount, tradeType: tradeType }); var path = astraClSdkDev.encodeRouteToPath(route, trade.tradeType === astraSdkCore.TradeType.EXACT_OUTPUT); if (tradeType == astraSdkCore.TradeType.EXACT_INPUT) { planner.addCommand(exports.CommandType.CL_SWAP_EXACT_IN, [routerMustCustody ? ROUTER_AS_RECIPIENT : options.recipient, trade.maximumAmountIn(options.slippageTolerance).quotient.toString(), routerMustCustody ? 0 : trade.minimumAmountOut(options.slippageTolerance).quotient.toString(), path, payerIsUser]); } else if (tradeType == astraSdkCore.TradeType.EXACT_OUTPUT) { planner.addCommand(exports.CommandType.CL_SWAP_EXACT_OUT, [routerMustCustody ? ROUTER_AS_RECIPIENT : options.recipient, trade.minimumAmountOut(options.slippageTolerance).quotient.toString(), trade.maximumAmountIn(options.slippageTolerance).quotient.toString(), path, payerIsUser]); } } // encode a mixed route swap, i.e. including both Classic and CL pools function addMixedSwap(planner, swap, tradeType, options, payerIsUser, routerMustCustody) { var _options$recipient; var route = swap.route; var inputAmount = swap.inputAmount; var outputAmount = swap.outputAmount; var tradeRecipient = routerMustCustody ? ROUTER_AS_RECIPIENT : (_options$recipient = options.recipient) != null ? _options$recipient : SENDER_AS_RECIPIENT; // single hop, so it can be reduced to plain swap logic for one protocol version if (route.pools.length === 1) { if (route.pools[0] instanceof astraClSdkDev.Pool) { return addCLSwap(planner, swap, tradeType, options, payerIsUser, routerMustCustody); } else if (route.pools[0] instanceof astraClassicSdk.Pair) { return addClassicSwap(planner, swap, tradeType, options, payerIsUser, routerMustCustody); } else { throw new Error('Invalid route type'); } } var trade = astraRouterSdk.MixedRouteTrade.createUncheckedTrade({ route: route, inputAmount: inputAmount, outputAmount: outputAmount, tradeType: tradeType }); var amountIn = trade.maximumAmountIn(options.slippageTolerance, inputAmount).quotient.toString(); var amountOut = routerMustCustody ? 0 : trade.minimumAmountOut(options.slippageTolerance, outputAmount).quotient.toString(); var sections = astraRouterSdk.partitionMixedRouteByProtocol(route); var isLastSectionInRoute = function isLastSectionInRoute(i) { return i === sections.length - 1; }; var inputToken = route.pathInput; for (var i = 0; i < sections.length; i++) { var section = sections[i]; var routePool = section[0]; var outputToken = astraRouterSdk.getOutputOfPools(section, inputToken); var subRoute = new astraRouterSdk.MixedRoute(new astraRouterSdk.MixedRouteSDK([].concat(section), inputToken, outputToken)); var nextInputToken = void 0; var swapRecipient = void 0; if (isLastSectionInRoute(i)) { nextInputToken = outputToken; swapRecipient = tradeRecipient; } else { var nextPool = sections[i + 1][0]; nextInputToken = getPathCurrency(outputToken, nextPool); var classicPoolIsSwapRecipient = nextPool instanceof astraClassicSdk.Pair && outputToken.equals(nextInputToken); swapRecipient = classicPoolIsSwapRecipient ? nextPool.liquidityToken.address : ROUTER_AS_RECIPIENT; } if (routePool instanceof astraClSdkDev.Pool) { planner.addCommand(exports.CommandType.CL_SWAP_EXACT_IN, [swapRecipient, i == 0 ? amountIn : CONTRACT_BALANCE, !isLastSectionInRoute(i) ? 0 : amountOut, astraRouterSdk.encodeMixedRouteToPath(subRoute), payerIsUser && i === 0]); } else { planner.addCommand(exports.CommandType.CLASSIC_SWAP_EXACT_IN, [swapRecipient, i === 0 ? amountIn : CONTRACT_BALANCE, !isLastSectionInRoute(i) ? 0 : amountOut, subRoute.path.map(function (token) { return token.wrapped.address; }), payerIsUser && i === 0]); } // perform a token transition (wrap/unwrap if necessary) if (!isLastSectionInRoute(i)) { if (outputToken.isNative && !nextInputToken.isNative) { planner.addCommand(exports.CommandType.WRAP_AMB, [ROUTER_AS_RECIPIENT, CONTRACT_BALANCE]); } else if (!outputToken.isNative && nextInputToken.isNative) { planner.addCommand(exports.CommandType.UNWRAP_SAMB, [ROUTER_AS_RECIPIENT, 0]); } } inputToken = nextInputToken; } } // if price impact is very high, there's a chance of hitting max/min prices resulting in a partial fill of the swap function riskOfPartialFill(trade) { return trade.priceImpact.greaterThan(REFUND_AMB_PRICE_IMPACT_THRESHOLD); } function hasFeeOption(swapOptions) { return !!swapOptions.fee || !!swapOptions.flatFee; } var SIGNATURE_LENGTH = 65; var EIP_2098_SIGNATURE_LENGTH = 64; function encodePermit(planner, permit2) { var signature = permit2.signature; var length = ethers.ethers.utils.arrayify(permit2.signature).length; // signature data provided for EIP-1271 may have length different from ECDSA signature if (length === SIGNATURE_LENGTH || length === EIP_2098_SIGNATURE_LENGTH) { // sanitizes signature to cover edge cases of malformed EIP-2098 sigs and v used as recovery id signature = ethers.ethers.utils.joinSignature(ethers.ethers.utils.splitSignature(permit2.signature)); } planner.addCommand(exports.CommandType.PERMIT2_PERMIT, [permit2, signature]); } // Handles the encoding of commands needed to gather input tokens for a trade // Approval: The router approving another address to take tokens. // note: Only seaport and sudoswap support this action. Approvals are left open. // Permit: A Permit2 signature-based Permit to allow the router to access a user's tokens // Transfer: A Permit2 TransferFrom of tokens from a user to either the router or another address function encodeInputTokenOptions(planner, options) { // first ensure that all tokens provided for encoding are the same if (!!options.permit2TransferFrom && !!options.permit2Permit) !(options.permit2TransferFrom.token === options.permit2Permit.details.token) ? invariant(false, "inconsistent token") : void 0; // if this order has a options.permit2Permit, encode it if (!!options.permit2Permit) { encodePermit(planner, options.permit2Permit); } if (!!options.permit2TransferFrom) { planner.addCommand(exports.CommandType.PERMIT2_TRANSFER_FROM, [options.permit2TransferFrom.token, options.permit2TransferFrom.recipient ? options.permit2TransferFrom.recipient : ROUTER_AS_RECIPIENT, options.permit2TransferFrom.amount]); } } var SwapRouter = /*#__PURE__*/function () { function SwapRouter() {} SwapRouter.swapCallParameters = function swapCallParameters(trades, options) { // TODO: use permit if signature included in swapOptions var planner = new RoutePlanner(); var trade = new AstraTrade(trades, options); var inputCurrency = trade.trade.inputAmount.currency; !!(inputCurrency.isNative && !!options.inputTokenPermit) ? invariant(false, 'NATIVE_INPUT_PERMIT') : void 0; if (options.inputTokenPermit) { encodePermit(planner, options.inputTokenPermit); } var nativeCurrencyValue = inputCurrency.isNative ? ethers.BigNumber.from(trade.trade.maximumAmountIn(options.slippageTolerance).quotient.toString()) : ethers.BigNumber.from(0); trade.encode(planner, { allowRevert: false }); return SwapRouter.encodePlan(planner, nativeCurrencyValue, { deadline: options.deadlineOrPreviousBlockhash ? ethers.BigNumber.from(options.deadlineOrPreviousBlockhash) : undefined }); } /** * Encodes a planned route into a method name and parameters for the Router contract. * @param planner the planned route * @param nativeCurrencyValue the native currency value of the planned route * @param config the router config */; SwapRouter.encodePlan = function encodePlan(planner, nativeCurrencyValue, config) { if (config === void 0) { config = {}; } var commands = planner.commands, inputs = planner.inputs; var functionSignature = !!config.deadline ? 'execute(bytes,bytes[],uint256)' : 'execute(bytes,bytes[])'; var parameters = !!config.deadline ? [commands, inputs, config.deadline] : [commands, inputs]; var calldata = SwapRouter.INTERFACE.encodeFunctionData(functionSignature, parameters); return { calldata: calldata, value: nativeCurrencyValue.toHexString() }; }; return SwapRouter; }(); SwapRouter.INTERFACE = /*#__PURE__*/new abi.Interface(UniversalRouter_json.abi); var UnwrapSAMB = /*#__PURE__*/function () { function UnwrapSAMB(amount, chainId, permit2) { this.tradeType = exports.RouterActionType.UnwrapSAMB; this.sambAddress = SAMB_ADDRESS(chainId); this.amount = amount; if (!!permit2) { !(permit2.details.token.toLowerCase() === this.sambAddress.toLowerCase()) ? invariant(false, "must be permitting SAMB address: " + this.sambAddress) : void 0; !(permit2.details.amount >= amount) ? invariant(false, "Did not permit enough SAMB for unwrapSAMB transaction") : void 0; this.permit2Data = permit2; } } var _proto = UnwrapSAMB.prototype; _proto.encode = function encode(planner, _) { encodeInputTokenOptions(planner, { permit2Permit: this.permit2Data, permit2TransferFrom: { token: this.sambAddress, amount: this.amount.toString() } }); planner.addCommand(exports.CommandType.UNWRAP_SAMB, [ROUTER_AS_RECIPIENT, this.amount]); }; return UnwrapSAMB; }(); (function (PoolType) { PoolType["ClassicPool"] = "classic-pool"; PoolType["CLPool"] = "cl-pool"; })(exports.PoolType || (exports.PoolType = {})); var isNativeCurrency = function isNativeCurrency(address) { return address.toLowerCase() === AMB_ADDRESS.toLowerCase() || address.toLowerCase() === E_AMB_ADDRESS.toLowerCase(); }; // Helper class to convert routing-specific quote entities to RouterTrade entities // the returned RouterTrade can then be used to build the AstraTrade entity in this package var RouterTradeAdapter = /*#__PURE__*/function () { function RouterTradeAdapter() {} // Generate a RouterTrade using fields from a classic quote response RouterTradeAdapter.fromClassicQuote = function fromClassicQuote(quote) { var route = quote.route, tokenIn = quote.tokenIn, tokenOut = quote.tokenOut; if (!route) throw new Error('Expected route to be present'); if (!route.length) throw new Error('Expected there to be at least one route'); if (route.some(function (r) { return !r.length; })) throw new Error('Expected all routes to have at least one pool'); var firstRoute = route[0]; var tokenInData = firstRoute[0].tokenIn; var tokenOutData = firstRoute[firstRoute.length - 1].tokenOut; if (!tokenInData || !tokenOutData) throw new Error('Expected both tokenIn and tokenOut to be present'); if (tokenInData.chainId !== tokenOutData.chainId) throw new Error('Expected tokenIn and tokenOut to be have same chainId'); var parsedCurrencyIn = RouterTradeAdapter.toCurrency(isNativeCurrency(tokenIn), tokenInData); var parsedCurrencyOut = RouterTradeAdapter.toCurrency(isNativeCurrency(tokenOut), tokenOutData); var typedRoutes = route.map(function (subRoute) { var rawAmountIn = subRoute[0].amountIn; var rawAmountOut = subRoute[subRoute.length - 1].amountOut; if (!rawAmountIn || !rawAmountOut) { throw new Error('Expected both raw amountIn and raw amountOut to be present'); } var inputAmount = astraSdkCore.CurrencyAmount.fromRawAmount(parsedCurrencyIn, rawAmountIn); var outputAmount = astraSdkCore.CurrencyAmount.fromRawAmount(parsedCurrencyOut, rawAmountOut); var isOnlyClassic = RouterTradeAdapter.isVersionedRoute(exports.PoolType.ClassicPool, subRoute); var isOnlyCL = RouterTradeAdapter.isVersionedRoute(exports.PoolType.CLPool, subRoute); return { routecl: isOnlyCL ? new astraClSdkDev.Route(subRoute.map(RouterTradeAdapter.toPool), parsedCurrencyIn, parsedCurrencyOut) : null, routeclassic: isOnlyClassic ? new astraClassicSdk.Route(subRoute.map(RouterTradeAdapter.toPair), parsedCurrencyIn, parsedCurrencyOut) : null, mixedRoute: !isOnlyCL && !isOnlyClassic ? new astraRouterSdk.MixedRouteSDK(subRoute.map(RouterTradeAdapter.toPoolOrPair), parsedCurrencyIn, parsedCurrencyOut) : null, inputAmount: inputAmount, outputAmount: outputAmount }; }); return new astraRouterSdk.Trade({ classicRoutes: typedRoutes.filter(function (route) { return route.routeclassic; }).map(function (route) { return { routeclassic: route.routeclassic, inputAmount: route.inputAmount, outputAmount: route.outputAmount }; }), clRoutes: typedRoutes.filter(function (route) { return route.routecl; }).map(function (route) { return { routecl: route.routecl, inputAmount: route.inputAmount, outputAmount: route.outputAmount }; }), mixedRoutes: typedRoutes.filter(function (route) { return route.mixedRoute; }).map(function (route) { return { mixedRoute: route.mixedRoute, inputAmount: route.inputAmount, outputAmount: route.outputAmount }; }), tradeType: quote.tradeType }); }; RouterTradeAdapter.toCurrency = function toCurrency(isNative, token) { if (isNative) { return astraSdkCore.Amber.onChain(token.chainId); } return this.toToken(token); }; RouterTradeAdapter.toToken = function toToken(token) { var chainId = token.chainId, address = token.address, decimals = token.decimals, symbol = token.symbol, buyFeeBps = token.buyFeeBps, sellFeeBps = token.sellFeeBps; return new astraSdkCore.Token(chainId, address, parseInt(decimals.toString()), symbol, /* name */undefined, false, buyFeeBps ? ethers.BigNumber.from(buyFeeBps) : undefined, sellFeeBps ? ethers.BigNumber.from(sellFeeBps) : undefined); }; RouterTradeAdapter.toPool = function toPool(_ref) { var fee = _ref.fee, sqrtRatioX96 = _ref.sqrtRatioX96, liquidity = _ref.liquidity, tickCurrent = _ref.tickCurrent, tokenIn = _ref.tokenIn, tokenOut = _ref.tokenOut; return new astraClSdkDev.Pool(RouterTradeAdapter.toToken(tokenIn), RouterTradeAdapter.toToken(tokenOut), parseInt(fee), sqrtRatioX96, liquidity, parseInt(tickCurrent)); }; RouterTradeAdapter.isVersionedRoute = function isVersionedRoute(type, route) { return route.every(function (pool) { return pool.type === type; }); }; return RouterTradeAdapter; }(); RouterTradeAdapter.toPoolOrPair = function (pool) { return pool.type === exports.PoolType.CLPool ? RouterTradeAdapter.toPool(pool) : RouterTradeAdapter.toPair(pool); }; RouterTradeAdapter.toPair = function (_ref2) { var reserve0 = _ref2.reserve0, reserve1 = _ref2.reserve1; return new astraClassicSdk.Pair(astraSdkCore.CurrencyAmount.fromRawAmount(RouterTradeAdapter.toToken(reserve0.token), reserve0.quotient), astraSdkCore.CurrencyAmount.fromRawAmount(RouterTradeAdapter.toToken(reserve1.token), reserve1.quotient)); }; // Parses UniversalRouter Classic commands var CommandParser = /*#__PURE__*/function () { function CommandParser() {} CommandParser.parseCalldata = function parseCalldata(calldata) { var genericParser = new GenericCommandParser(COMMAND_DEFINITION); var txDescription = CommandParser.INTERFACE.parseTransaction({ data: calldata }); var _txDescription$args = txDescription.args, commands = _txDescription$args.commands, inputs = _txDescription$args.inputs; return genericParser.parse(commands, inputs); }; return CommandParser; }(); CommandParser.INTERFACE = /*#__PURE__*/new abi.Interface(UniversalRouter_json.abi); // Parses commands based on given command definition var GenericCommandParser = /*#__PURE__*/function () { function GenericCommandParser(commandDefinition) { this.commandDefinition = commandDefinition; } var _proto = GenericCommandParser.prototype; _proto.parse = function parse(commands, inputs) { var _this = this; var commandTypes = GenericCommandParser.getCommands(commands); return { commands: commandTypes.map(function (commandType, i) { var commandDef = _this.commandDefinition[commandType]; if (commandDef.parser === exports.Parser.Abi) { var abiDef = commandDef.params; var rawParams = ethers.ethers.utils.defaultAbiCoder.decode(abiDef.map(function (command) { return command.type; }), inputs[i]); var params = rawParams.map(function (param, j) { switch (abiDef[j].subparser) { case exports.Subparser.CLPathExactIn: return { name: abiDef[j].name, value: parseCLPathExactIn(param) }; case exports.Subparser.CLPathExactOut: return { name: abiDef[j].name, value: parseCLPathExactOut(param) }; default: return { name: abiDef[j].name, value: param }; } }); return { commandName: exports.CommandType[commandType], commandType: commandType, params: params }; } else if (commandDef.parser === exports.Parser.CLActions) { // TODO: implement better parsing here return { commandName: exports.CommandType[commandType], commandType: commandType, params: inputs.map(function (input) { return { name: 'command', value: input }; }) }; } else { throw new Error("Unsupported parser: " + commandDef); } }) }; } // parse command types from bytes string ; GenericCommandParser.getCommands = function getCommands(commands) { var commandTypes = []; for (var i = 2; i < commands.length; i += 2) { var _byte = commands.substring(i, i + 2); commandTypes.push(parseInt(_byte, 16)); } return commandTypes; }; return GenericCommandParser; }(); function parseCLPathExactIn(path) { var strippedPath = path.replace('0x', ''); var tokenIn = ethers.ethers.utils.getAddress(strippedPath.substring(0, 40)); var loc = 40; var res = []; while (loc < strippedPath.length) { var feeAndTokenOut = strippedPath.substring(loc, loc + 46); var fee = parseInt(feeAndTokenOut.substring(0, 6), 16); var tokenOut = ethers.ethers.utils.getAddress(feeAndTokenOut.substring(6, 46)); res.push({ tokenIn: tokenIn, tokenOut: tokenOut, fee: fee }); tokenIn = tokenOut; loc += 46; } return res; } function parseCLPathExactOut(path) { var strippedPath = path.replace('0x', ''); var tokenIn = ethers.ethers.utils.getAddress(strippedPath.substring(strippedPath.length - 40)); var loc = strippedPath.length - 86; // 86 = (20 addr + 3 fee + 20 addr) * 2 (for hex characters) var res = []; while (loc >= 0) { var feeAndTokenOut = strippedPath.substring(loc, loc + 46); var tokenOut = ethers.ethers.utils.getAddress(feeAndTokenOut.substring(0, 40)); var fee = parseInt(feeAndTokenOut.substring(40, 46), 16); res.push({ tokenIn: tokenIn, tokenOut: tokenOut, fee: fee }); tokenIn = tokenOut; loc -= 46; } return res; } exports.AstraTrade = AstraTrade; exports.COMMAND_DEFINITION = COMMAND_DEFINITION; exports.CommandParser = CommandParser; exports.GenericCommandParser = GenericCommandParser; exports.ROUTER_AS_RECIPIENT = ROUTER_AS_RECIPIENT; exports.RoutePlanner = RoutePlanner; exports.RouterTradeAdapter = RouterTradeAdapter; exports.SAMB_ADDRESS = SAMB_ADDRESS; exports.SwapRouter = SwapRouter; exports.UNIVERSAL_ROUTER_ADDRESS = UNIVERSAL_ROUTER_ADDRESS; exports.UNIVERSAL_ROUTER_CREATION_BLOCK = UNIVERSAL_ROUTER_CREATION_BLOCK; exports.UnwrapSAMB = UnwrapSAMB; exports.isNativeCurrency = isNativeCurrency; //# sourceMappingURL=astra-universal-router-sdk.cjs.development.js.map