astra-universal-router-sdk
Version:
sdk for integrating with the Astra Router contracts
984 lines (967 loc) • 42.9 kB
JavaScript
'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