diff --git a/package.json b/package.json index 2d306b41d..1efc6781d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.37.5", + "version": "2.17.0", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", diff --git a/scripts/dex-integration.ts b/scripts/dex-integration.ts index e3487e690..ad04f3b19 100644 --- a/scripts/dex-integration.ts +++ b/scripts/dex-integration.ts @@ -116,7 +116,7 @@ function testIntegration(argv: IOptions) { process.env.NODE_ENV = 'test'; } - require('../node_modules/jest-cli/build/cli').run( + require('../node_modules/jest-cli/build').run( `src\/dex\/${dexNameParam}\/.+\.test\.ts`, ); } diff --git a/src/abi/dexalot/DexalotMainnetRFQ.json b/src/abi/dexalot/DexalotMainnetRFQ.json new file mode 100644 index 000000000..b6139cae1 --- /dev/null +++ b/src/abi/dexalot/DexalotMainnetRFQ.json @@ -0,0 +1,952 @@ +[ + { + "name": "AddressSet", + "type": "event", + "inputs": [ + { + "name": "name", + "type": "string", + "indexed": true, + "internalType": "string" + }, + { + "name": "actionName", + "type": "string", + "indexed": false, + "internalType": "string" + }, + { + "name": "newAddress", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "name": "ExpiryUpdated", + "type": "event", + "inputs": [ + { + "name": "nonceAndMeta", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newExpiry", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "name": "Initialized", + "type": "event", + "inputs": [ + { + "name": "version", + "type": "uint8", + "indexed": false, + "internalType": "uint8" + } + ], + "anonymous": false + }, + { + "name": "Paused", + "type": "event", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "name": "RebalancerWithdraw", + "type": "event", + "inputs": [ + { + "name": "asset", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "name": "RoleAdminChanged", + "type": "event", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "previousAdminRole", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "newAdminRole", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "name": "RoleGranted", + "type": "event", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "name": "RoleRevoked", + "type": "event", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "name": "RoleUpdated", + "type": "event", + "inputs": [ + { + "name": "name", + "type": "string", + "indexed": true, + "internalType": "string" + }, + { + "name": "actionName", + "type": "string", + "indexed": false, + "internalType": "string" + }, + { + "name": "updatedRole", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "updatedAddress", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "name": "SlippageApplied", + "type": "event", + "inputs": [ + { + "name": "nonceAndMeta", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newMakerAmount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "name": "SlippageToleranceUpdated", + "type": "event", + "inputs": [ + { + "name": "newSlippageTolerance", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "name": "SwapExecuted", + "type": "event", + "inputs": [ + { + "name": "nonceAndMeta", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "maker", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "taker", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "makerAsset", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "takerAsset", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "makerAmountReceived", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "takerAmountReceived", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "name": "SwapSignerUpdated", + "type": "event", + "inputs": [ + { + "name": "newSwapSigner", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "name": "Unpaused", + "type": "event", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "name": "DEFAULT_ADMIN_ROLE", + "type": "function", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "name": "REBALANCER_ADMIN_ROLE", + "type": "function", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "name": "VERSION", + "type": "function", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "name": "addAdmin", + "type": "function", + "inputs": [ + { + "name": "_address", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "addRebalancer", + "type": "function", + "inputs": [ + { + "name": "_address", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "batchClaimBalance", + "type": "function", + "inputs": [ + { + "name": "_assets", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "_amounts", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "claimBalance", + "type": "function", + "inputs": [ + { + "name": "_asset", + "type": "address", + "internalType": "address" + }, + { + "name": "_amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "getRoleAdmin", + "type": "function", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "name": "getRoleMember", + "type": "function", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "index", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "name": "getRoleMemberCount", + "type": "function", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "name": "grantRole", + "type": "function", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "hasRole", + "type": "function", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "name": "initialize", + "type": "function", + "inputs": [ + { + "name": "_swapSigner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "isAdmin", + "type": "function", + "inputs": [ + { + "name": "_address", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "name": "isRebalancer", + "type": "function", + "inputs": [ + { + "name": "_address", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "name": "isValidSignature", + "type": "function", + "inputs": [ + { + "name": "_hash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "_signature", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "stateMutability": "view" + }, + { + "name": "orderExpiryUpdated", + "type": "function", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "name": "orderMakerAmountUpdated", + "type": "function", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "name": "partialSwap", + "type": "function", + "inputs": [ + { + "name": "_order", + "type": "tuple", + "components": [ + { + "name": "nonceAndMeta", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "expiry", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "makerAsset", + "type": "address", + "internalType": "address" + }, + { + "name": "takerAsset", + "type": "address", + "internalType": "address" + }, + { + "name": "maker", + "type": "address", + "internalType": "address" + }, + { + "name": "taker", + "type": "address", + "internalType": "address" + }, + { + "name": "makerAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "takerAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "internalType": "struct MainnetRFQ.Order" + }, + { + "name": "_signature", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "_takerAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "name": "pause", + "type": "function", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "paused", + "type": "function", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "name": "removeAdmin", + "type": "function", + "inputs": [ + { + "name": "_address", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "removeRebalancer", + "type": "function", + "inputs": [ + { + "name": "_address", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "renounceRole", + "type": "function", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "revokeRole", + "type": "function", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "setSlippageTolerance", + "type": "function", + "inputs": [ + { + "name": "_newSlippageTolerance", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "setSwapSigner", + "type": "function", + "inputs": [ + { + "name": "_swapSigner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "simpleSwap", + "type": "function", + "inputs": [ + { + "name": "_order", + "type": "tuple", + "components": [ + { + "name": "nonceAndMeta", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "expiry", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "makerAsset", + "type": "address", + "internalType": "address" + }, + { + "name": "takerAsset", + "type": "address", + "internalType": "address" + }, + { + "name": "maker", + "type": "address", + "internalType": "address" + }, + { + "name": "taker", + "type": "address", + "internalType": "address" + }, + { + "name": "makerAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "takerAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "internalType": "struct MainnetRFQ.Order" + }, + { + "name": "_signature", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "name": "slippageTolerance", + "type": "function", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "name": "supportsInterface", + "type": "function", + "inputs": [ + { + "name": "interfaceId", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "name": "swapSigner", + "type": "function", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "name": "unpause", + "type": "function", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "updateOrderExpiry", + "type": "function", + "inputs": [ + { + "name": "_nonceAndMeta", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_newExpiry", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "name": "updateOrderMakerAmount", + "type": "function", + "inputs": [ + { + "name": "_nonceAndMeta", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_newMakerAmount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_oldMakerAmount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "receive", + "stateMutability": "payable" + } +] diff --git a/src/abi/polygon-migration/PolygonMigration.abi.json b/src/abi/polygon-migration/PolygonMigration.abi.json new file mode 100644 index 000000000..56bace0fb --- /dev/null +++ b/src/abi/polygon-migration/PolygonMigration.abi.json @@ -0,0 +1,364 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "matic_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "InvalidAddress", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidAddressOrAlreadySet", + "type": "error" + }, + { + "inputs": [], + "name": "UnmigrationLocked", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Migrated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Unmigrated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "lock", + "type": "bool" + } + ], + "name": "UnmigrationLockUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "matic", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "migrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "polygon", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "polygon_", + "type": "address" + } + ], + "name": "setPolygonToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "unmigrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "unmigrateTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "unmigrateWithPermit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unmigrationLocked", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "unmigrationLocked_", + "type": "bool" + } + ], + "name": "updateUnmigrationLock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + } +] diff --git a/src/config.ts b/src/config.ts index 1d9ddc1e5..b457ea258 100644 --- a/src/config.ts +++ b/src/config.ts @@ -30,7 +30,7 @@ type BaseConfig = { hashFlowAuthToken?: string; hashFlowDisabledMMs: string[]; swaapV2AuthToken?: string; - smardexSubgraphAuthToken?: string; + dexalotAuthToken?: string; forceRpcFallbackDexs: string[]; }; @@ -52,14 +52,13 @@ const baseConfigs: { [network: number]: BaseConfig } = { Adapter01: '0x9bE264469eF954c139Da4A45Cf76CbCC5e3A6A73', Adapter02: '0xFC2Ba6E830a04C25e207B8214b26d8C713F6881F', Adapter03: '0xfb2a3de6c7B8c77b520E3da16021f3D8A4E93168', - Adapter04: '0x30F6B9b6485ff0B67E881f5ac80D3F1c70A4B23d', - BuyAdapter: '0x613876f3dE2Ec633f8054fE7a561324c1a01d9cB', + Adapter04: '0x1A23Aa6f7ff4215Fd2d220EA6f4a0DF45A77fc6C', + BuyAdapter: '0x1310dE2C69e9753bee19B5522bad39c5f788efd9', }, uniswapV2ExchangeRouterAddress: '0xF9234CB08edb93c0d4a4d4c70cC3FfD070e78e07', rpcPollingMaxAllowedStateDelayInBlocks: 0, rpcPollingBlocksBackToTriggerUpdate: 0, - smardexSubgraphAuthToken: process.env.API_KEY_SMARDEX_SUBGRAPH || '', swaapV2AuthToken: process.env.API_KEY_SWAAP_V2_AUTH_TOKEN || '', hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', hashFlowDisabledMMs: @@ -146,7 +145,6 @@ const baseConfigs: { [network: number]: BaseConfig } = { rfqConfigs: {}, rpcPollingMaxAllowedStateDelayInBlocks: 5, rpcPollingBlocksBackToTriggerUpdate: 3, - forceRpcFallbackDexs: [], }, [Network.BSC]: { network: Network.BSC, @@ -160,7 +158,6 @@ const baseConfigs: { [network: number]: BaseConfig } = { tokenTransferProxyAddress: '0x216b4b4ba9f3e719726886d34a177484278bfcae', multicallV2Address: '0xC50F4c1E81c873B2204D7eFf7069Ffec6Fbe136D', privateHttpProvider: process.env.HTTP_PROVIDER_56, - smardexSubgraphAuthToken: process.env.API_KEY_SMARDEX_SUBGRAPH || '', hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', hashFlowDisabledMMs: process.env[`HASHFLOW_DISABLED_MMS_56`]?.split(',') || [], @@ -189,7 +186,6 @@ const baseConfigs: { [network: number]: BaseConfig } = { tokenTransferProxyAddress: '0x216b4b4ba9f3e719726886d34a177484278bfcae', multicallV2Address: '0x275617327c958bD06b5D6b871E7f491D76113dd8', privateHttpProvider: process.env.HTTP_PROVIDER_137, - smardexSubgraphAuthToken: process.env.API_KEY_SMARDEX_SUBGRAPH || '', hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', hashFlowDisabledMMs: process.env[`HASHFLOW_DISABLED_MMS_137`]?.split(',') || [], @@ -223,10 +219,11 @@ const baseConfigs: { [network: number]: BaseConfig } = { hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', hashFlowDisabledMMs: process.env[`HASHFLOW_DISABLED_MMS_43114`]?.split(',') || [], + dexalotAuthToken: process.env.API_KEY_DEXALOT_AUTH_TOKEN || '', adapterAddresses: { AvalancheAdapter01: '0x745Ec73855CeC7249E5fF4c9DD81cc65b4D297a9', - AvalancheAdapter02: '0xFb8773AA4Fd02e54bbd352061D8Be1911FAa210a', - AvalancheBuyAdapter: '0x434C1Cca4842629230067674Dd54E21a14D9FD5D', + AvalancheAdapter02: '0x2cdB0cDc2a9321ac2ED5b741828a5216C265Be80', + AvalancheBuyAdapter: '0x9Aa41A24A10af2a965A6D406b913a7Cd9C6886ea', }, uniswapV2ExchangeRouterAddress: '0x53e693c6C7FFC4446c53B205Cf513105Bf140D7b', @@ -276,7 +273,6 @@ const baseConfigs: { [network: number]: BaseConfig } = { tokenTransferProxyAddress: '0x216b4b4ba9f3e719726886d34a177484278bfcae', multicallV2Address: '0x7eCfBaa8742fDf5756DAC92fbc8b90a19b8815bF', privateHttpProvider: process.env.HTTP_PROVIDER_42161, - smardexSubgraphAuthToken: process.env.API_KEY_SMARDEX_SUBGRAPH || '', hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', hashFlowDisabledMMs: process.env[`HASHFLOW_DISABLED_MMS_42161`]?.split(',') || [], @@ -363,7 +359,6 @@ const baseConfigs: { [network: number]: BaseConfig } = { augustusRFQAddress: '0xa003dFBA51C9e1e56C67ae445b852bdEd7aC5EEd', tokenTransferProxyAddress: '0x93aAAe79a53759cD164340E4C8766E4Db5331cD7', multicallV2Address: '0xeDF6D2a16e8081F777eB623EeB4411466556aF3d', - smardexSubgraphAuthToken: process.env.API_KEY_SMARDEX_SUBGRAPH || '', privateHttpProvider: process.env.HTTP_PROVIDER_8453, hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', hashFlowDisabledMMs: [], @@ -420,8 +415,8 @@ export function generateConfig(network: number): Config { baseConfig.rpcPollingBlocksBackToTriggerUpdate, hashFlowAuthToken: baseConfig.hashFlowAuthToken, swaapV2AuthToken: baseConfig.swaapV2AuthToken, + dexalotAuthToken: baseConfig.dexalotAuthToken, hashFlowDisabledMMs: baseConfig.hashFlowDisabledMMs, - smardexSubgraphAuthToken: baseConfig.smardexSubgraphAuthToken, forceRpcFallbackDexs: baseConfig.forceRpcFallbackDexs, }; } diff --git a/src/dex/algebra/algebra-e2e.test.ts b/src/dex/algebra/algebra-e2e.test.ts index d945141d1..318378069 100644 --- a/src/dex/algebra/algebra-e2e.test.ts +++ b/src/dex/algebra/algebra-e2e.test.ts @@ -198,7 +198,7 @@ describe('Algebra', () => { tokenAAmount, tokenBAmount, nativeTokenAmount, - ) + ); }); }); diff --git a/src/dex/dexalot/config.ts b/src/dex/dexalot/config.ts new file mode 100644 index 000000000..658792a20 --- /dev/null +++ b/src/dex/dexalot/config.ts @@ -0,0 +1,18 @@ +import { DexParams } from './types'; +import { DexConfigMap, AdapterMappings } from '../../types'; +import { Network, SwapSide } from '../../constants'; + +export const DexalotConfig: DexConfigMap = { + Dexalot: { + [Network.AVALANCHE]: { + mainnetRFQAddress: '0xEed3c159F3A96aB8d41c8B9cA49EE1e5071A7cdD', + }, + }, +}; + +export const Adapters: Record = { + [Network.AVALANCHE]: { + [SwapSide.SELL]: [{ name: 'AvalancheAdapter02', index: 6 }], + [SwapSide.BUY]: [{ name: 'AvalancheBuyAdapter', index: 8 }], + }, +}; diff --git a/src/dex/dexalot/constants.ts b/src/dex/dexalot/constants.ts new file mode 100644 index 000000000..458ff9010 --- /dev/null +++ b/src/dex/dexalot/constants.ts @@ -0,0 +1,32 @@ +import BigNumber from 'bignumber.js'; + +export const DEXALOT_PRICES_CACHES_TTL_S = 3; + +export const DEXALOT_PAIRS_CACHES_TTL_S = 21 * 60; // 21 mins + +export const DEXALOT_TOKENS_CACHES_TTL_S = 21 * 60; // 21 mins + +export const DEXALOT_BLACKLIST_CACHES_TTL_S = 180 * 60; // 3 hours + +export const DEXALOT_API_PRICES_POLLING_INTERVAL_MS = 1000; + +export const DEXALOT_API_PAIRS_POLLING_INTERVAL_MS = 1000 * 60 * 10; // 10 mins + +export const DEXALOT_API_BLACKLIST_POLLING_INTERVAL_MS = 1000 * 60 * 60; // 1 hour + +export const DEXALOT_API_URL = 'https://api.dexalot.com'; + +export const DEXALOT_GAS_COST = 120_000; + +export const DEXALOT_RATE_LIMITED_TTL_S = 60 * 60; // 1 hour + +export const DEXALOT_RATELIMIT_CACHE_VALUE = 'limited'; + +export const DEXALOT_RESTRICT_TTL_S = 60 * 30; // 30 minutes + +export const DEXALOT_RESTRICTED_CACHE_KEY = 'restricted'; + +export const DEXALOT_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION = + new BigNumber('0.001'); + +export const DEXALOT_FIRM_QUOTE_TIMEOUT_MS = 2000; diff --git a/src/dex/dexalot/dexalot-e2e.test.ts b/src/dex/dexalot/dexalot-e2e.test.ts new file mode 100644 index 000000000..2fc23f588 --- /dev/null +++ b/src/dex/dexalot/dexalot-e2e.test.ts @@ -0,0 +1,214 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { testE2E } from '../../../tests/utils-e2e'; +import { + Tokens, + NativeTokenSymbols, + Holders, +} from '../../../tests/constants-e2e'; +import { Network, ContractMethod, SwapSide } from '../../constants'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { generateConfig } from '../../config'; + +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, + nativeTokenAmount: string, + excludeNativeTokenTests: boolean = false, +) { + const config = generateConfig(network); + const provider = new StaticJsonRpcProvider( + config.privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + const sleepMs = 10000; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.megaSwap, + ContractMethod.multiSwap, + ], + ], + [SwapSide.BUY, [ContractMethod.simpleBuy, ContractMethod.buy]], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + if (excludeNativeTokenTests) { + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, + ); + }); + it(`${tokenBSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[tokenBSymbol], + tokens[tokenASymbol], + holders[tokenBSymbol], + side === SwapSide.SELL ? tokenBAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, + ); + }); + } else { + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, + ); + }); + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, + ); + }); + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, + ); + }); + it(`${tokenBSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[tokenBSymbol], + tokens[tokenASymbol], + holders[tokenBSymbol], + side === SwapSide.SELL ? tokenBAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, + ); + }); + } + }); + }); + }), + ); + }); +} + +describe('Dexalot E2E', () => { + const dexKey = 'Dexalot'; + + describe('Avalanche', () => { + const network = Network.AVALANCHE; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDT'; + + const tokenAAmount: string = '1000000'; + const tokenBAmount: string = '1000000'; + const nativeTokenAmount = '1000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + + describe('BTC.b -> USDC', () => { + const network = Network.AVALANCHE; + + const tokenASymbol: string = 'BTCb'; + const tokenBSymbol: string = 'USDC'; + + const tokenAAmount: string = '100'; + const tokenBAmount: string = '9000000000'; + const nativeTokenAmount = '1000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + true, + ); + }); +}); diff --git a/src/dex/dexalot/dexalot-integration.test.ts b/src/dex/dexalot/dexalot-integration.test.ts new file mode 100644 index 000000000..5c767a34f --- /dev/null +++ b/src/dex/dexalot/dexalot-integration.test.ts @@ -0,0 +1,188 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { DummyDexHelper } from '../../dex-helper/index'; +import { Network, SwapSide } from '../../constants'; +import { BI_POWS } from '../../bigint-constants'; +import { Dexalot } from './dexalot'; +import { + checkPoolPrices, + checkPoolsLiquidity, + checkConstantPoolPrices, + sleep, +} from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; + +async function testPricingOnNetwork( + dexalot: Dexalot, + network: Network, + dexKey: string, + blockNumber: number, + srcTokenSymbol: string, + destTokenSymbol: string, + side: SwapSide, + amounts: bigint[], +) { + const networkTokens = Tokens[network]; + + const pools = await dexalot.getPoolIdentifiers( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + side, + blockNumber, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await dexalot.getPricesVolume( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + amounts, + side, + blockNumber, + pools, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + if (dexalot.hasConstantPriceLargeAmounts) { + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + } else { + checkPoolPrices(poolPrices!, amounts, side, dexKey); + } +} + +describe('Dexalot', function () { + const dexKey = 'Dexalot'; + let blockNumber: number; + let dexalot: Dexalot; + + describe('Avalanche', () => { + const network = Network.AVALANCHE; + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + + const tokenASymbol = 'AVAX'; + const tokenBSymbol = 'USDC'; + + const amountsForTokenA = [ + 0n, + 1n * BI_POWS[tokens[tokenASymbol].decimals], + 2n * BI_POWS[tokens[tokenASymbol].decimals], + 3n * BI_POWS[tokens[tokenASymbol].decimals], + 4n * BI_POWS[tokens[tokenASymbol].decimals], + 5n * BI_POWS[tokens[tokenASymbol].decimals], + 6n * BI_POWS[tokens[tokenASymbol].decimals], + 7n * BI_POWS[tokens[tokenASymbol].decimals], + 8n * BI_POWS[tokens[tokenASymbol].decimals], + 9n * BI_POWS[tokens[tokenASymbol].decimals], + 10n * BI_POWS[tokens[tokenASymbol].decimals], + ]; + + const amountsForTokenB = [ + 0n, + 1n * BI_POWS[tokens[tokenBSymbol].decimals], + 2n * BI_POWS[tokens[tokenBSymbol].decimals], + 3n * BI_POWS[tokens[tokenBSymbol].decimals], + 4n * BI_POWS[tokens[tokenBSymbol].decimals], + 5n * BI_POWS[tokens[tokenBSymbol].decimals], + 6n * BI_POWS[tokens[tokenBSymbol].decimals], + 7n * BI_POWS[tokens[tokenBSymbol].decimals], + 8n * BI_POWS[tokens[tokenBSymbol].decimals], + 9n * BI_POWS[tokens[tokenBSymbol].decimals], + 10n * BI_POWS[tokens[tokenBSymbol].decimals], + ]; + + beforeEach(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + dexalot = new Dexalot(network, dexKey, dexHelper); + await dexalot.initializePricing(blockNumber); + await sleep(5000); + }); + + afterEach(async () => { + dexalot.releaseResources(); + await sleep(5000); + }); + + it(`getPoolIdentifiers and getPricesVolume SELL ${tokenASymbol} ${tokenBSymbol}`, async function () { + await testPricingOnNetwork( + dexalot, + network, + dexKey, + blockNumber, + tokenASymbol, + tokenBSymbol, + SwapSide.SELL, + amountsForTokenA, + ); + }); + + it(`getPoolIdentifiers and getPricesVolume BUY ${tokenASymbol} ${tokenBSymbol}`, async function () { + await testPricingOnNetwork( + dexalot, + network, + dexKey, + blockNumber, + tokenASymbol, + tokenBSymbol, + SwapSide.BUY, + amountsForTokenB, + ); + }); + + it(`getPoolIdentifiers and getPricesVolume SELL ${tokenBSymbol} ${tokenASymbol}`, async function () { + await testPricingOnNetwork( + dexalot, + network, + dexKey, + blockNumber, + tokenBSymbol, + tokenASymbol, + SwapSide.SELL, + amountsForTokenB, + ); + }); + + it(`getPoolIdentifiers and getPricesVolume BUY ${tokenBSymbol} ${tokenASymbol}`, async function () { + await testPricingOnNetwork( + dexalot, + network, + dexKey, + blockNumber, + tokenBSymbol, + tokenASymbol, + SwapSide.BUY, + amountsForTokenA, + ); + }); + + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const dexalot = new Dexalot(network, dexKey, dexHelper); + const poolLiquidity = await dexalot.getTopPoolsForToken( + tokens[tokenASymbol].address, + 10, + ); + console.log(`${tokenASymbol} Top Pools:`, poolLiquidity); + + if (!dexalot.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][tokenASymbol].address, + dexKey, + ); + } + }); + }); +}); diff --git a/src/dex/dexalot/dexalot.ts b/src/dex/dexalot/dexalot.ts new file mode 100644 index 000000000..633007272 --- /dev/null +++ b/src/dex/dexalot/dexalot.ts @@ -0,0 +1,1026 @@ +import { + Token, + Address, + ExchangePrices, + PoolPrices, + AdapterExchangeParam, + SimpleExchangeParam, + PoolLiquidity, + Logger, + ExchangeTxInfo, + OptimalSwapExchange, + PreprocessTransactionOptions, + TransferFeeParams, +} from '../../types'; +import { + SwapSide, + Network, + ETHER_ADDRESS, + NULL_ADDRESS, +} from '../../constants'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { getDexKeysWithNetwork, isAxiosError } from '../../utils'; +import { IDex } from '../idex'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { + ClobSide, + DexalotData, + PairData, + PairDataMap, + PriceDataMap, + DexalotRfqError, + DexalotAPIParameters, + RFQResponse, + RFQResponseError, + TokenAddrDataMap, + TokenDataMap, +} from './types'; +import { + SlippageCheckError, + TooStrictSlippageCheckError, +} from '../generic-rfq/types'; +import { SimpleExchange } from '../simple-exchange'; +import { Adapters, DexalotConfig } from './config'; +import { RateFetcher } from './rate-fetcher'; +import mainnetRFQAbi from '../../abi/dexalot/DexalotMainnetRFQ.json'; +import { Interface } from 'ethers/lib/utils'; +import { assert } from 'ts-essentials'; +import { + DEXALOT_API_URL, + DEXALOT_API_PRICES_POLLING_INTERVAL_MS, + DEXALOT_PRICES_CACHES_TTL_S, + DEXALOT_GAS_COST, + DEXALOT_PAIRS_CACHES_TTL_S, + DEXALOT_API_PAIRS_POLLING_INTERVAL_MS, + DEXALOT_TOKENS_CACHES_TTL_S, + DEXALOT_API_BLACKLIST_POLLING_INTERVAL_MS, + DEXALOT_RATE_LIMITED_TTL_S, + DEXALOT_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION, + DEXALOT_RESTRICTED_CACHE_KEY, + DEXALOT_RESTRICT_TTL_S, + DEXALOT_RATELIMIT_CACHE_VALUE, + DEXALOT_BLACKLIST_CACHES_TTL_S, + DEXALOT_FIRM_QUOTE_TIMEOUT_MS, +} from './constants'; +import { BI_MAX_UINT256 } from '../../bigint-constants'; +import { ethers } from 'ethers'; +import BigNumber from 'bignumber.js'; +import { Method } from '../../dex-helper/irequest-wrapper'; + +export class Dexalot extends SimpleExchange implements IDex { + readonly isStatePollingDex = true; + readonly hasConstantPriceLargeAmounts = false; + readonly needWrapNative = false; + readonly isFeeOnTransferSupported = false; + private rateFetcher: RateFetcher; + + private dexalotAuthToken: string; + + private pricesCacheKey: string; + private pairsCacheKey: string; + private tokensAddrCacheKey: string; + private tokensCacheKey: string; + private blacklistCacheKey: string; + private tokensMap: TokenDataMap = {}; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(DexalotConfig); + + logger: Logger; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + protected adapters = Adapters[network] || {}, + readonly mainnetRFQAddress: string = DexalotConfig['Dexalot'][network] + .mainnetRFQAddress, + protected rfqInterface = new Interface(mainnetRFQAbi), + ) { + super(dexHelper, dexKey); + this.logger = dexHelper.getLogger(dexKey); + + const authToken = dexHelper.config.data.dexalotAuthToken; + assert( + authToken !== undefined, + 'Dexalot auth token is not specified with env variable', + ); + this.dexalotAuthToken = authToken; + + this.pricesCacheKey = 'prices'; + this.pairsCacheKey = 'pairs'; + this.tokensAddrCacheKey = 'tokens_addr'; + this.tokensCacheKey = 'tokens'; + this.blacklistCacheKey = 'blacklist'; + + this.rateFetcher = new RateFetcher( + this.dexHelper, + this.dexKey, + this.network, + this.logger, + { + rateConfig: { + pairsIntervalMs: DEXALOT_API_PAIRS_POLLING_INTERVAL_MS, + pricesIntervalMs: DEXALOT_API_PRICES_POLLING_INTERVAL_MS, + blacklistIntervalMs: DEXALOT_API_BLACKLIST_POLLING_INTERVAL_MS, + pairsReqParams: this.getAPIReqParams('api/rfq/pairs', 'GET'), + pricesReqParams: this.getAPIReqParams('api/rfq/prices', 'GET'), + blacklistReqParams: this.getAPIReqParams('api/rfq/blacklist', 'GET'), + pairsCacheKey: this.pairsCacheKey, + pairsCacheTTLSecs: DEXALOT_PAIRS_CACHES_TTL_S, + pricesCacheKey: this.pricesCacheKey, + pricesCacheTTLSecs: DEXALOT_PRICES_CACHES_TTL_S, + tokensAddrCacheKey: this.tokensAddrCacheKey, + tokensCacheKey: this.tokensCacheKey, + tokensCacheTTLSecs: DEXALOT_TOKENS_CACHES_TTL_S, + blacklistCacheKey: this.blacklistCacheKey, + blacklistCacheTTLSecs: DEXALOT_BLACKLIST_CACHES_TTL_S, + }, + }, + ); + } + + async initializePricing(blockNumber: number): Promise { + if (!this.dexHelper.config.isSlave) { + this.rateFetcher.start(); + } + + return; + } + + getAdapters(side: SwapSide): { name: string; index: number }[] | null { + return this.adapters[side] ? this.adapters[side] : null; + } + + getPairString(baseToken: Token, quoteToken: Token): string { + return `${baseToken.symbol}/${quoteToken.symbol}`.toLowerCase(); + } + + async getPairData( + srcToken: Token, + destToken: Token, + ): Promise { + const normalizedSrcToken = this.normalizeToken(srcToken); + const normalizedDestToken = this.normalizeToken(destToken); + if (normalizedSrcToken.address === normalizedDestToken.address) { + return null; + } + + const cachedTokens = (await this.getCachedTokens()) || {}; + if ( + !(normalizedSrcToken.address in cachedTokens) || + !(normalizedDestToken.address in cachedTokens) + ) { + return null; + } + normalizedSrcToken.symbol = cachedTokens[normalizedSrcToken.address].symbol; + normalizedDestToken.symbol = + cachedTokens[normalizedDestToken.address].symbol; + + const cachedPairs = (await this.getCachedPairs()) || {}; + + const potentialPairs = [ + { + identifier: this.getPairString(normalizedSrcToken, normalizedDestToken), + isSrcBase: true, + }, + { + identifier: this.getPairString(normalizedDestToken, normalizedSrcToken), + isSrcBase: false, + }, + ]; + + for (const pair of potentialPairs) { + if (pair.identifier in cachedPairs) { + const pairData = cachedPairs[pair.identifier]; + pairData.isSrcBase = pair.isSrcBase; + return pairData; + } + } + return null; + } + + getIdentifier(srcAddress: Address, destAddress: Address) { + const sortedAddresses = + srcAddress < destAddress + ? [srcAddress, destAddress] + : [destAddress, srcAddress]; + + return `${ + this.dexKey + }_${sortedAddresses[0].toLowerCase()}_${sortedAddresses[1].toLowerCase()}`; + } + + async getPoolIdentifiers( + srcToken: Token, + destToken: Token, + side: SwapSide, + blockNumber: number, + ): Promise { + if (!srcToken || !destToken) { + return []; + } + const pairData = await this.getPairData(srcToken, destToken); + if (!pairData) { + return []; + } + + const tokensAddr = (await this.getCachedTokensAddr()) || {}; + + return [ + this.getIdentifier( + tokensAddr[pairData.base.toLowerCase()], + tokensAddr[pairData.quote.toLowerCase()], + ), + ]; + } + + async getCachedPairs(): Promise { + const cachedPairs = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.pairsCacheKey, + ); + + if (cachedPairs) { + return JSON.parse(cachedPairs) as PairDataMap; + } + + return null; + } + + async getCachedPrices(): Promise { + const cachedPrices = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.pricesCacheKey, + ); + + if (cachedPrices) { + return JSON.parse(cachedPrices) as PriceDataMap; + } + + return null; + } + + async getCachedTokensAddr(): Promise { + const cachedTokensAddr = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.tokensAddrCacheKey, + ); + + if (cachedTokensAddr) { + return JSON.parse(cachedTokensAddr) as TokenAddrDataMap; + } + + return null; + } + + async getCachedTokens(): Promise { + const cachedTokens = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.tokensCacheKey, + ); + + if (cachedTokens) { + return JSON.parse(cachedTokens) as TokenDataMap; + } + + return null; + } + + normalizeAddress(address: string): string { + return address.toLowerCase() === ETHER_ADDRESS + ? NULL_ADDRESS + : address.toLowerCase(); + } + + denormalizeAddress(address: string): string { + return address.toLowerCase() === NULL_ADDRESS + ? ETHER_ADDRESS + : address.toLowerCase(); + } + + // Dexalot protocol for native token expects 0x00000... instead of 0xeeeee... + normalizeToken(token: Token): Token { + return { + address: this.normalizeAddress(token.address), + decimals: token.decimals, + }; + } + + denormalizeToken(token: Token): Token { + return { + address: this.denormalizeAddress(token.address), + decimals: token.decimals, + }; + } + + calculateOrderPrice( + amounts: bigint[], + orderbook: string[][], + baseToken: Token, + quoteToken: Token, + side: ClobSide, + ) { + let result = []; + + for (let i = 0; i < amounts.length; i++) { + let amt = amounts[i]; + if (amt === 0n) { + result.push(amt); + continue; + } + + let left = 0, + right = orderbook.length; + let qty = BigInt(0); + + while (left < right) { + let mid = Math.floor((left + right) / 2); + qty = BigInt( + ethers.utils + .parseUnits(orderbook[mid][1], quoteToken.decimals) + .toString(), + ); + if (side === ClobSide.ASK) { + const price = BigInt( + ethers.utils + .parseUnits(orderbook[mid][0], baseToken.decimals) + .toString(), + ); + qty = + (qty * BigInt(10 ** (baseToken.decimals * 2))) / + (price * BigInt(10 ** quoteToken.decimals)); + } + if (qty <= amt) { + left = mid + 1; + } else { + right = mid; + } + } + + let price = BigInt(0), + amount = BigInt(0); + if (amounts[i] === qty) { + price = BigInt( + ethers.utils + .parseUnits(orderbook[left][0], baseToken.decimals) + .toString(), + ); + amount = amounts[i]; + } else if (left < orderbook.length) { + const lPrice = BigInt( + ethers.utils + .parseUnits(orderbook[left - 1][0], baseToken.decimals) + .toString(), + ); + const rPrice = BigInt( + ethers.utils + .parseUnits(orderbook[left][0], baseToken.decimals) + .toString(), + ); + let lQty = BigInt( + ethers.utils + .parseUnits(orderbook[left - 1][1], quoteToken.decimals) + .toString(), + ); + let rQty = BigInt( + ethers.utils + .parseUnits(orderbook[left][1], quoteToken.decimals) + .toString(), + ); + if (side === ClobSide.ASK) { + lQty = + (lQty * BigInt(10 ** (baseToken.decimals * 2))) / + (lPrice * BigInt(10 ** quoteToken.decimals)); + rQty = + (rQty * BigInt(10 ** (baseToken.decimals * 2))) / + (rPrice * BigInt(10 ** quoteToken.decimals)); + } + price = lPrice + ((rPrice - lPrice) * (amt - lQty)) / (rQty - lQty); + amount = amounts[i]; + } + + if (side === ClobSide.BID) { + result.push( + price !== 0n // To avoid division by zero error + ? (amount * BigInt(10 ** (baseToken.decimals * 2))) / + (price * BigInt(10 ** quoteToken.decimals)) + : 0n, + ); + } else { + result.push( + (price * amount * BigInt(10 ** quoteToken.decimals)) / + BigInt(10 ** (baseToken.decimals * 2)), + ); + } + } + return result; + } + + async getPricesVolume( + srcToken: Token, + destToken: Token, + amounts: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[], + transferFees?: TransferFeeParams, + ): Promise> { + try { + if (await this.isRestricted()) { + return null; + } + + const normalizedSrcToken = this.normalizeToken(srcToken); + const normalizedDestToken = this.normalizeToken(destToken); + + this.tokensMap = (await this.getCachedTokens()) || {}; + if (normalizedSrcToken.address === normalizedDestToken.address) { + return null; + } + + const pools = limitPools + ? limitPools.filter( + p => + p === + this.getIdentifier( + normalizedSrcToken.address, + normalizedDestToken.address, + ), + ) + : await this.getPoolIdentifiers(srcToken, destToken, side, blockNumber); + + if (pools.length === 0) { + return null; + } + + const pairData = await this.getPairData( + normalizedSrcToken, + normalizedDestToken, + ); + if (!pairData) { + return null; + } + + const priceMap = await this.getCachedPrices(); + if (!priceMap) { + return null; + } + + const tokensAddr = (await this.getCachedTokensAddr()) || {}; + const pairKey = `${pairData.base}/${pairData.quote}`.toLowerCase(); + if ( + !(pairKey in priceMap) || + !pools.includes( + this.getIdentifier( + tokensAddr[pairData.base.toLowerCase()], + tokensAddr[pairData.quote.toLowerCase()], + ), + ) + ) { + return null; + } + + const priceData = priceMap[pairKey]; + const baseToken = pairData.isSrcBase + ? normalizedSrcToken + : normalizedDestToken; + const quoteToken = pairData.isSrcBase + ? normalizedDestToken + : normalizedSrcToken; + + // convert from swap to clob side + let orderbook = priceData.asks; + let clobSide = ClobSide.BID; + if ( + (side === SwapSide.SELL && pairData.isSrcBase) || + (side === SwapSide.BUY && !pairData.isSrcBase) + ) { + orderbook = priceData.bids; + clobSide = ClobSide.ASK; + } + if (orderbook.length === 0) { + throw new Error(`Empty orderbook for ${pairKey}`); + } + + const prices = this.calculateOrderPrice( + amounts, + orderbook, + baseToken, + quoteToken, + clobSide, + ); + const outDecimals = + clobSide === ClobSide.BID ? baseToken.decimals : quoteToken.decimals; + const poolIdentifier = this.getIdentifier(pairData.base, pairData.quote); + + return [ + { + prices, + unit: BigInt(outDecimals), + data: {}, + poolIdentifier: poolIdentifier, + exchange: this.dexKey, + gasCost: DEXALOT_GAS_COST, + poolAddresses: [this.mainnetRFQAddress], + }, + ]; + } catch (e: unknown) { + this.logger.error( + `Error_getPricesVolume ${srcToken.address || srcToken.symbol}, ${ + destToken.address || destToken.symbol + }, ${side}:`, + e, + ); + return null; + } + } + + generateRFQError(errorStr: string, swapIdentifier: string) { + const message = `${this.dexKey}-${this.network}: Failed to fetch RFQ for ${swapIdentifier}. ${errorStr}`; + this.logger.warn(message); + throw new DexalotRfqError(message); + } + + async preProcessTransaction( + optimalSwapExchange: OptimalSwapExchange, + srcToken: Token, + destToken: Token, + side: SwapSide, + options: PreprocessTransactionOptions, + ): Promise<[OptimalSwapExchange, ExchangeTxInfo]> { + if (await this.isBlacklisted(options.txOrigin)) { + this.logger.warn( + `${this.dexKey}-${this.network}: blacklisted TX Origin address '${options.txOrigin}' trying to build a transaction. Bailing...`, + ); + throw new Error( + `${this.dexKey}-${ + this.network + }: user=${options.txOrigin.toLowerCase()} is blacklisted`, + ); + } + + if (BigInt(optimalSwapExchange.srcAmount) === 0n) { + throw new Error('getFirmRate failed with srcAmount === 0'); + } + + const normalizedSrcToken = this.normalizeToken(srcToken); + const normalizedDestToken = this.normalizeToken(destToken); + const swapIdentifier = `${this.getIdentifier( + normalizedSrcToken.address, + normalizedDestToken.address, + )}_${side}`; + + try { + const makerToken = normalizedDestToken; + const takerToken = normalizedSrcToken; + + const rfqParams = { + makerAsset: ethers.utils.getAddress(makerToken.address), + takerAsset: ethers.utils.getAddress(takerToken.address), + makerAmount: + side === SwapSide.BUY ? optimalSwapExchange.destAmount : undefined, + takerAmount: + side === SwapSide.SELL ? optimalSwapExchange.srcAmount : undefined, + userAddress: options.txOrigin, + chainid: this.network, + }; + + const rfq: RFQResponse = await this.dexHelper.httpRequest.post( + `${DEXALOT_API_URL}/api/rfq/firm`, + rfqParams, + DEXALOT_FIRM_QUOTE_TIMEOUT_MS, + { 'x-apikey': this.dexalotAuthToken }, + ); + if (!rfq) { + this.generateRFQError( + 'Missing quote data', + `RFQ ${swapIdentifier} ${JSON.stringify(rfq)}`, + ); + } else if (!rfq.signature) { + this.generateRFQError('Missing signature', swapIdentifier); + } + rfq.order.signature = rfq.signature; + + const { order } = rfq; + + assert( + order.makerAsset.toLowerCase() === makerToken.address, + `QuoteData makerAsset=${order.makerAsset} is different from Paraswap makerAsset=${makerToken.address}`, + ); + assert( + order.takerAsset.toLowerCase() === takerToken.address, + `QuoteData takerAsset=${order.takerAsset} is different from Paraswap takerAsset=${takerToken.address}`, + ); + if (side === SwapSide.SELL) { + assert( + order.takerAmount === optimalSwapExchange.srcAmount, + `QuoteData takerAmount=${order.takerAmount} is different from Paraswap srcAmount=${optimalSwapExchange.srcAmount}`, + ); + } else { + assert( + order.makerAmount === optimalSwapExchange.destAmount, + `QuoteData makerAmount=${order.makerAmount} is different from Paraswap destAmount=${optimalSwapExchange.destAmount}`, + ); + } + + const expiryAsBigInt = BigInt(order.expiry); + const minDeadline = expiryAsBigInt > 0 ? expiryAsBigInt : BI_MAX_UINT256; + + const slippageFactor = options.slippageFactor; + let isFailOnSlippage = false; + let slippageErrorMessage = ''; + + if (side === SwapSide.SELL) { + if ( + BigInt(order.makerAmount) < + BigInt( + new BigNumber(optimalSwapExchange.destAmount.toString()) + .times(slippageFactor) + .toFixed(0), + ) + ) { + isFailOnSlippage = true; + const message = `${this.dexKey}-${this.network}: too much slippage on quote ${side} quoteTokenAmount ${order.makerAmount} / destAmount ${optimalSwapExchange.destAmount} < ${slippageFactor}`; + slippageErrorMessage = message; + this.logger.warn(message); + } + } else { + if ( + BigInt(order.takerAmount) > + BigInt( + slippageFactor + .times(optimalSwapExchange.srcAmount.toString()) + .toFixed(0), + ) + ) { + isFailOnSlippage = true; + const message = `${this.dexKey}-${ + this.network + }: too much slippage on quote ${side} baseTokenAmount ${ + order.takerAmount + } / srcAmount ${ + optimalSwapExchange.srcAmount + } > ${slippageFactor.toFixed()}`; + slippageErrorMessage = message; + this.logger.warn(message); + } + } + + let isTooStrictSlippage = false; + if ( + isFailOnSlippage && + side === SwapSide.SELL && + new BigNumber(1) + .minus(slippageFactor) + .lt(DEXALOT_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION) + ) { + isTooStrictSlippage = true; + } else if ( + isFailOnSlippage && + side === SwapSide.BUY && + slippageFactor + .minus(1) + .lt(DEXALOT_MIN_SLIPPAGE_FACTOR_THRESHOLD_FOR_RESTRICTION) + ) { + isTooStrictSlippage = true; + } + + if (isFailOnSlippage && isTooStrictSlippage) { + throw new TooStrictSlippageCheckError(slippageErrorMessage); + } else if (isFailOnSlippage && !isTooStrictSlippage) { + throw new SlippageCheckError(slippageErrorMessage); + } + + return [ + { + ...optimalSwapExchange, + data: { + quoteData: order, + }, + }, + { deadline: minDeadline }, + ]; + } catch (e) { + if (isAxiosError(e) && e.response && e.response.data) { + const errorData: RFQResponseError = e.response.data; + if (errorData.ReasonCode === 'FQ-009') { + this.logger.warn( + `${this.dexKey}-${this.network}: Encountered rate limited user=${options.txOrigin}. Adding to local rate limit cache`, + ); + await this.setRateLimited(options.txOrigin, errorData.RetryAfter); + } else { + await this.setBlacklist(options.txOrigin); + this.logger.error( + `${this.dexKey}-${this.network}: Failed to fetch RFQ for ${swapIdentifier}: ${errorData.Reason}`, + ); + } + } else { + if (e instanceof TooStrictSlippageCheckError) { + this.logger.warn( + `${this.dexKey}-${this.network}: failed to build transaction on side ${side} with too strict slippage. Skipping restriction`, + ); + } else { + this.logger.warn( + `${this.dexKey}-${this.network}: protocol is restricted`, + ); + await this.restrict(); + } + } + + throw e; + } + } + + getCalldataGasCost(poolPrices: PoolPrices): number | number[] { + return ( + CALLDATA_GAS_COST.DEX_OVERHEAD + + // addresses: makerAsset, takerAsset, maker, taker + CALLDATA_GAS_COST.ADDRESS * 4 + + // uint256: expiry + CALLDATA_GAS_COST.wordNonZeroBytes(16) + + // uint256: nonceAndMeta, makerAmount, takerAmount + CALLDATA_GAS_COST.AMOUNT * 3 + + // bytes: _signature (65 bytes) + CALLDATA_GAS_COST.FULL_WORD * 2 + + CALLDATA_GAS_COST.OFFSET_SMALL + ); + } + + getTokenFromAddress(address: Address): Token { + return this.tokensMap[this.normalizeAddress(address)]; + } + + getAdapterParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: DexalotData, + side: SwapSide, + ): AdapterExchangeParam { + const { quoteData } = data; + + assert( + quoteData !== undefined, + `${this.dexKey}-${this.network}: quoteData undefined`, + ); + + const params = [ + { + nonceAndMeta: quoteData.nonceAndMeta, + expiry: quoteData.expiry, + makerAsset: quoteData.makerAsset, + takerAsset: quoteData.takerAsset, + maker: quoteData.maker, + taker: quoteData.taker, + makerAmount: quoteData.makerAmount, + takerAmount: quoteData.takerAmount, + }, + quoteData.signature, + ]; + + const payload = this.abiCoder.encodeParameter( + { + ParentStruct: { + order: { + nonceAndMeta: 'uint256', + expiry: 'uint128', + makerAsset: 'address', + takerAsset: 'address', + maker: 'address', + taker: 'address', + makerAmount: 'uint256', + takerAmount: 'uint256', + }, + signature: 'bytes', + }, + }, + { + order: params[0], + signature: params[1], + }, + ); + + return { + targetExchange: this.mainnetRFQAddress, + payload, + networkFee: '0', + }; + } + + async restrict(ttl: number = DEXALOT_RESTRICT_TTL_S): Promise { + await this.dexHelper.cache.setex( + this.dexKey, + this.network, + DEXALOT_RESTRICTED_CACHE_KEY, + ttl, + 'true', + ); + return true; + } + + async isRestricted(): Promise { + const result = await this.dexHelper.cache.get( + this.dexKey, + this.network, + DEXALOT_RESTRICTED_CACHE_KEY, + ); + + return result === 'true'; + } + + async setBlacklist( + txOrigin: Address, + ttl: number = DEXALOT_BLACKLIST_CACHES_TTL_S, + ): Promise { + const cachedBlacklist = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.blacklistCacheKey, + ); + + let blacklist: string[] = []; + if (cachedBlacklist) { + blacklist = JSON.parse(cachedBlacklist); + } + + blacklist.push(txOrigin.toLowerCase()); + + this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.blacklistCacheKey, + ttl, + JSON.stringify(blacklist), + ); + + return true; + } + + async isBlacklisted(txOrigin: Address): Promise { + const cachedBlacklist = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.blacklistCacheKey, + ); + + if (cachedBlacklist) { + const blacklist = JSON.parse(cachedBlacklist) as string[]; + return blacklist.includes(txOrigin.toLowerCase()); + } + + // To not show pricing for rate limited users + if (await this.isRateLimited(txOrigin)) { + return true; + } + + return false; + } + + getRateLimitedKey(address: Address) { + return `rate_limited_${address}`.toLowerCase(); + } + + async isRateLimited(txOrigin: Address): Promise { + const result = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.getRateLimitedKey(txOrigin), + ); + return result === DEXALOT_RATELIMIT_CACHE_VALUE; + } + + async setRateLimited(txOrigin: Address, ttl = DEXALOT_RATE_LIMITED_TTL_S) { + await this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.getRateLimitedKey(txOrigin), + ttl, + DEXALOT_RATELIMIT_CACHE_VALUE, + ); + return true; + } + + async getSimpleParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: DexalotData, + side: SwapSide, + ): Promise { + const { quoteData } = data; + + assert( + quoteData !== undefined, + `${this.dexKey}-${this.network}: quoteData undefined`, + ); + + const swapFunction = 'simpleSwap'; + const swapFunctionParams = [ + [ + quoteData.nonceAndMeta, + quoteData.expiry, + quoteData.makerAsset, + quoteData.takerAsset, + quoteData.maker, + quoteData.taker, + quoteData.makerAmount, + quoteData.takerAmount, + ], + quoteData.signature, + ]; + + const swapData = this.rfqInterface.encodeFunctionData( + swapFunction, + swapFunctionParams, + ); + + return this.buildSimpleParamWithoutWETHConversion( + srcToken, + srcAmount, + destToken, + destAmount, + swapData, + this.mainnetRFQAddress, + ); + } + + async getTopPoolsForToken( + tokenAddress: Address, + limit: number, + ): Promise { + const normalizedTokenAddress = this.normalizeAddress(tokenAddress); + const pairs = (await this.getCachedPairs()) || {}; + this.tokensMap = (await this.getCachedTokens()) || {}; + const tokensAddr = (await this.getCachedTokensAddr()) || {}; + const token = this.getTokenFromAddress(normalizedTokenAddress); + if (!token) { + return []; + } + + const tokenSymbol = token.symbol?.toLowerCase() || ''; + + let pairsByLiquidity = []; + for (const pairName of Object.keys(pairs)) { + if (!pairName.includes(tokenSymbol)) { + continue; + } + + const tokensInPair = pairName.split('/'); + if (tokensInPair.length !== 2) { + continue; + } + + const [baseToken, quoteToken] = tokensInPair; + const addr = tokensAddr[baseToken.toLowerCase()]; + let outputToken = this.getTokenFromAddress(addr); + if (baseToken === tokenSymbol) { + const addr = tokensAddr[quoteToken.toLowerCase()]; + outputToken = this.getTokenFromAddress(addr); + } + + const denormalizedToken = this.denormalizeToken(outputToken); + + pairsByLiquidity.push({ + exchange: this.dexKey, + address: this.mainnetRFQAddress, + connectorTokens: [ + { + address: denormalizedToken.address, + decimals: denormalizedToken.decimals, + }, + ], + liquidityUSD: pairs[pairName].liquidityUSD, + }); + } + + pairsByLiquidity.sort( + (a: PoolLiquidity, b: PoolLiquidity) => b.liquidityUSD - a.liquidityUSD, + ); + + return pairsByLiquidity.slice(0, limit); + } + + getAPIReqParams(endpoint: string, method: Method): DexalotAPIParameters { + return { + url: `${DEXALOT_API_URL}/${endpoint}`, + headers: { 'x-apikey': this.dexalotAuthToken }, + params: { + chainid: this.network, + }, + method: method, + }; + } + + releaseResources(): void { + if (this.rateFetcher) { + this.rateFetcher.stop(); + } + } +} diff --git a/src/dex/dexalot/rate-fetcher.ts b/src/dex/dexalot/rate-fetcher.ts new file mode 100644 index 000000000..558ee598b --- /dev/null +++ b/src/dex/dexalot/rate-fetcher.ts @@ -0,0 +1,197 @@ +import { IDexHelper } from '../../dex-helper'; +import { Fetcher } from '../../lib/fetcher/fetcher'; +import { validateAndCast } from '../../lib/validators'; +import { Logger, Token } from '../../types'; +import { + DexalotRateFetcherConfig, + DexalotPairsResponse, + PairDataMap, + DexalotPricesResponse, + PriceDataMap, + DexalotBlacklistResponse, +} from './types'; +import { + pricesResponseValidator, + pairsResponseValidator, + blacklistResponseValidator, +} from './validators'; +import { Network } from '../../constants'; + +export class RateFetcher { + private pairsFetcher: Fetcher; + private pairsCacheKey: string; + private pairsCacheTTL: number; + + private rateFetcher: Fetcher; + private pricesCacheKey: string; + private pricesCacheTTL: number; + + private tokensAddrCacheKey: string; + private tokensCacheKey: string; + private tokensCacheTTL: number; + + private blacklistFetcher: Fetcher; + private blacklistCacheKey: string; + private blacklistCacheTTL: number; + + constructor( + private dexHelper: IDexHelper, + private dexKey: string, + private network: Network, + private logger: Logger, + config: DexalotRateFetcherConfig, + ) { + this.pairsCacheKey = config.rateConfig.pairsCacheKey; + this.pairsCacheTTL = config.rateConfig.pairsCacheTTLSecs; + this.pricesCacheKey = config.rateConfig.pricesCacheKey; + this.pricesCacheTTL = config.rateConfig.pricesCacheTTLSecs; + this.tokensAddrCacheKey = config.rateConfig.tokensAddrCacheKey; + this.tokensCacheKey = config.rateConfig.tokensCacheKey; + this.tokensCacheTTL = config.rateConfig.tokensCacheTTLSecs; + this.blacklistCacheKey = config.rateConfig.blacklistCacheKey; + this.blacklistCacheTTL = config.rateConfig.blacklistCacheTTLSecs; + + this.pairsFetcher = new Fetcher( + dexHelper.httpRequest, + { + info: { + requestOptions: config.rateConfig.pairsReqParams, + caster: (data: unknown) => { + return validateAndCast( + data, + pairsResponseValidator, + ); + }, + }, + handler: this.handlePairsResponse.bind(this), + }, + config.rateConfig.pairsIntervalMs, + logger, + ); + + this.rateFetcher = new Fetcher( + dexHelper.httpRequest, + { + info: { + requestOptions: config.rateConfig.pricesReqParams, + caster: (data: unknown) => { + return validateAndCast( + data, + pricesResponseValidator, + ); + }, + }, + handler: this.handleRatesResponse.bind(this), + }, + config.rateConfig.pricesIntervalMs, + logger, + ); + + this.blacklistFetcher = new Fetcher( + dexHelper.httpRequest, + { + info: { + requestOptions: config.rateConfig.blacklistReqParams, + caster: (data: unknown) => { + return validateAndCast( + data, + blacklistResponseValidator, + ); + }, + }, + handler: this.handleBlacklistResponse.bind(this), + }, + config.rateConfig.blacklistIntervalMs, + logger, + ); + } + + start() { + this.pairsFetcher.startPolling(); + this.rateFetcher.startPolling(); + this.blacklistFetcher.startPolling(); + } + + stop() { + this.pairsFetcher.stopPolling(); + this.rateFetcher.stopPolling(); + this.blacklistFetcher.stopPolling(); + } + + private handlePairsResponse(resp: DexalotPairsResponse): void { + const pairs = resp; + const dexPairs: PairDataMap = {}; + const tokenMap: { [address: string]: Token } = {}; + const tokenAddrMap: { [symbol: string]: string } = {}; + Object.keys(pairs).forEach(pair => { + dexPairs[pair.toLowerCase()] = pairs[pair]; + tokenAddrMap[pairs[pair].base.toLowerCase()] = + pairs[pair].baseAddress.toLowerCase(); + tokenAddrMap[pairs[pair].quote.toLowerCase()] = + pairs[pair].quoteAddress.toLowerCase(); + tokenMap[pairs[pair].baseAddress.toLowerCase()] = { + address: pairs[pair].baseAddress.toLowerCase(), + symbol: pairs[pair].base, + decimals: pairs[pair].baseDecimals, + }; + tokenMap[pairs[pair].quoteAddress.toLowerCase()] = { + address: pairs[pair].quoteAddress.toLowerCase(), + symbol: pairs[pair].quote, + decimals: pairs[pair].quoteDecimals, + }; + }); + + this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.pairsCacheKey, + this.pairsCacheTTL, + JSON.stringify(dexPairs), + ); + + this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.tokensCacheKey, + this.tokensCacheTTL, + JSON.stringify(tokenMap), + ); + + this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.tokensAddrCacheKey, + this.tokensCacheTTL, + JSON.stringify(tokenAddrMap), + ); + } + + private handleRatesResponse(resp: DexalotPricesResponse): void { + const { prices } = resp; + const dexPrices: PriceDataMap = {}; + Object.keys(prices).forEach(pair => { + dexPrices[pair.toLowerCase()] = prices[pair]; + }); + + this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.pricesCacheKey, + this.pricesCacheTTL, + JSON.stringify(dexPrices), + ); + } + + private async handleBlacklistResponse( + resp: DexalotBlacklistResponse, + ): Promise { + const { blacklist } = resp; + this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.blacklistCacheKey, + this.blacklistCacheTTL, + JSON.stringify(blacklist.map(item => item.toLowerCase())), + ); + } +} diff --git a/src/dex/dexalot/types.ts b/src/dex/dexalot/types.ts new file mode 100644 index 000000000..41bc53257 --- /dev/null +++ b/src/dex/dexalot/types.ts @@ -0,0 +1,131 @@ +import { RequestHeaders } from '../../dex-helper'; +import { Token } from '../../types'; +import { Method } from '../../dex-helper/irequest-wrapper'; + +type RFQOrder = { + nonceAndMeta: string; + expiry: number; + makerAsset: string; + takerAsset: string; + maker: string; + taker: string; + makerAmount: string; + takerAmount: string; + signature?: string; +}; + +export type RFQResponse = { + order: RFQOrder; + signature: string; +}; + +export type RFQResponseError = { + Reason: string; + ReasonCode: string; + Success: boolean; + RetryAfter?: number; +}; + +export type DexalotData = { + quoteData?: RFQOrder; +}; + +export type DexParams = { + mainnetRFQAddress: string; +}; + +export enum ClobSide { + BID = 'BID', + ASK = 'ASK', +} + +export class DexalotRfqError extends Error {} + +export type PairData = { + base: string; + quote: string; + liquidityUSD: number; + isSrcBase?: boolean; +}; + +export type PairDataMap = { + [pair: string]: PairData; +}; + +export type PairDataResp = { + base: string; + quote: string; + liquidityUSD: number; + baseAddress: string; + quoteAddress: string; + baseDecimals: number; + quoteDecimals: number; +}; + +export type DexalotPairsResponse = { + [pair: string]: PairDataResp; +}; + +type PriceData = { + bids: string[][]; + asks: string[][]; +}; + +export type PriceDataMap = { + [pair: string]: PriceData; +}; + +export type DexalotPricesResponse = { + prices: PriceDataMap; +}; + +export type TokenAddrDataMap = { + [symbol: string]: string; +}; + +export type TokenDataMap = { + [address: string]: Token; +}; + +export type DexalotBlacklistResponse = { + blacklist: string[]; +}; + +export type DexalotRateFetcherConfig = { + rateConfig: { + pairsReqParams: { + url: string; + headers?: RequestHeaders; + params?: any; + }; + pricesReqParams: { + url: string; + headers?: RequestHeaders; + params?: any; + }; + blacklistReqParams: { + url: string; + headers?: RequestHeaders; + params?: any; + }; + pairsIntervalMs: number; + pricesIntervalMs: number; + blacklistIntervalMs: number; + pairsCacheKey: string; + pricesCacheKey: string; + tokensAddrCacheKey: string; + tokensCacheKey: string; + blacklistCacheKey: string; + blacklistCacheTTLSecs: number; + pairsCacheTTLSecs: number; + pricesCacheTTLSecs: number; + tokensCacheTTLSecs: number; + }; +}; + +export type DexalotAPIParameters = { + url: string; + headers?: RequestHeaders; + params?: any; + method?: Method; +}; diff --git a/src/dex/dexalot/validators.ts b/src/dex/dexalot/validators.ts new file mode 100644 index 000000000..89a2e11e1 --- /dev/null +++ b/src/dex/dexalot/validators.ts @@ -0,0 +1,46 @@ +import joi from 'joi'; + +const pairValidator = joi.object({ + base: joi.string().min(1).required(), + quote: joi.string().min(1).required(), + liquidityUSD: joi.number().min(0).required(), + baseAddress: joi.string().min(1).required(), + quoteAddress: joi.string().min(1).required(), + baseDecimals: joi.number().min(0).required(), + quoteDecimals: joi.number().min(0).required(), +}); + +export const pairsResponseValidator = joi + .object() + .pattern(joi.string(), pairValidator); + +const orderbookRecordValidator = joi + .array() + .items(joi.string().min(1)) + .length(2); + +const orderbookValidator = joi.object({ + bids: joi.array().items(orderbookRecordValidator).required(), + asks: joi.array().items(orderbookRecordValidator).required(), +}); + +export const pricesResponseValidator = joi.object({ + prices: joi.object().pattern(joi.string(), orderbookValidator), +}); + +const tokenValidator = joi.object({ + symbol: joi.string().min(1).required(), + name: joi.string().min(1).required(), + description: joi.string().min(1).required(), + address: joi.string().min(1).required(), + decimals: joi.number().min(0).required(), + type: joi.string().min(1).required(), +}); + +export const tokensResponseValidator = joi.object({ + tokens: joi.object().pattern(joi.string(), tokenValidator), +}); + +export const blacklistResponseValidator = joi.object({ + blacklist: joi.array().items(joi.string().min(1)).required(), +}); diff --git a/src/dex/gmx/config.ts b/src/dex/gmx/config.ts index 5ae6f1a9d..3822ee464 100644 --- a/src/dex/gmx/config.ts +++ b/src/dex/gmx/config.ts @@ -15,7 +15,7 @@ export const GMXConfig: DexConfigMap = { [Network.ARBITRUM]: { vault: '0x489ee077994B6658eAfA855C308275EAd8097C4A', reader: '0x22199a49A999c351eF7927602CFB187ec3cae489', - priceFeed: '0xfe661cbf27da0656b7a1151a761ff194849c387a', + priceFeed: '0x2d68011bca022ed0e474264145f46cc4de96a002', fastPriceFeed: '0x8960d1b45a2d15d063b84b34dfb2fb2ca7535527', fastPriceEvents: '0x4530b7de1958270a2376be192a24175d795e1b07', usdg: '0x45096e7aA921f27590f8F19e457794EB09678141', diff --git a/src/dex/idex.ts b/src/dex/idex.ts index 3dd20f53d..ada44c557 100644 --- a/src/dex/idex.ts +++ b/src/dex/idex.ts @@ -131,9 +131,11 @@ export interface IDexPricing { blockNumber: number, // list of pool identifiers to use for pricing, if undefined use all pools limitPools?: string[], + // I don't like putting this as new params, but in order to not change interface // across all integrations, done it like this transferFees?: TransferFeeParams, + isFirstSwap?: boolean, ): Promise | null>; // Returns estimated gas cost for calldata for DEX when used in multiSwap. diff --git a/src/dex/index.ts b/src/dex/index.ts index 368456cd1..a4f58e878 100644 --- a/src/dex/index.ts +++ b/src/dex/index.ts @@ -5,6 +5,7 @@ import { Jarvis } from './jarvis'; import { JarvisV6 } from './jarvis-v6/jarvis-v6'; import { StablePool } from './stable-pool'; import { Weth } from './weth/weth'; +import { PolygonMigrator } from './polygon-migrator/polygon-migrator'; import { ZeroX } from './zerox'; import { UniswapV3 } from './uniswap-v3/uniswap-v3'; import { BalancerV2 } from './balancer-v2/balancer-v2'; @@ -52,7 +53,9 @@ import { SpiritSwapV2 } from './solidly/forks-override/spiritSwapV2'; import { Synthetix } from './synthetix/synthetix'; import { Cone } from './solidly/forks-override/cone'; import { SoliSnek } from './solidly/forks-override/solisnek'; +import { Usdfi } from './solidly/forks-override/usdfi'; import { Equalizer } from './solidly/forks-override/equalizer'; +import { Velocimeter } from './solidly/forks-override/velocimeter'; import { BalancerV1 } from './balancer-v1/balancer-v1'; import { balancerV1Merge } from './balancer-v1/optimizer'; import { CurveV1 } from './curve-v1/curve-v1'; @@ -76,7 +79,7 @@ import { PancakeswapV3 } from './pancakeswap-v3/pancakeswap-v3'; import { Algebra } from './algebra/algebra'; import { QuickPerps } from './quick-perps/quick-perps'; import { NomiswapV2 } from './uniswap-v2/nomiswap-v2'; -import { Smardex } from './smardex/smardex'; +import { Dexalot } from './dexalot/dexalot'; const LegacyDexes = [ CurveV2, @@ -103,6 +106,7 @@ const LegacyDexes = [ ]; const Dexes = [ + Dexalot, CurveV1, CurveFork, Swerve, @@ -121,6 +125,7 @@ const Dexes = [ AaveV3, KyberDmm, Weth, + PolygonMigrator, MakerPsm, Nerve, Platypus, @@ -140,6 +145,8 @@ const Dexes = [ Cone, SoliSnek, Equalizer, + Velocimeter, + Usdfi, Synthetix, CurveV1Factory, SwaapV1, @@ -150,7 +157,6 @@ const Dexes = [ SwaapV2, QuickPerps, NomiswapV2, - Smardex, ]; export type LegacyDexConstructor = new (dexHelper: IDexHelper) => IDexTxBuilder< diff --git a/src/dex/polygon-migrator/config.ts b/src/dex/polygon-migrator/config.ts new file mode 100644 index 000000000..8eef9dc20 --- /dev/null +++ b/src/dex/polygon-migrator/config.ts @@ -0,0 +1,25 @@ +import { DexParams } from './types'; +import { DexConfigMap } from '../../types'; +import { Network } from '../../constants'; +import { SwapSide } from '@paraswap/core'; + +export const PolygonMigratorConfig: DexConfigMap = { + PolygonMigrator: { + [Network.MAINNET]: { + migratorAddress: '0x29e7df7b6a1b2b07b731457f499e1696c60e2c4e', + polTokenAddress: '0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6', + maticTokenAddress: '0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0' + }, + }, +}; + +export const Adapters: { + [chainId: number]: { + [side: string]: { name: string; index: number }[]; + }; +} = { + [Network.MAINNET]: { + [SwapSide.SELL]: [{ name: 'Adapter04', index: 4 }], + [SwapSide.BUY]: [{ name: 'BuyAdapter', index: 11 }], + }, +}; diff --git a/src/dex/polygon-migrator/constants.ts b/src/dex/polygon-migrator/constants.ts new file mode 100644 index 000000000..f873a0fce --- /dev/null +++ b/src/dex/polygon-migrator/constants.ts @@ -0,0 +1,2 @@ + +export const POLYGON_MIGRATION_GAS_COST= 90_000; diff --git a/src/dex/polygon-migrator/polygon-migrator-e2e.test.ts b/src/dex/polygon-migrator/polygon-migrator-e2e.test.ts new file mode 100644 index 000000000..8696a078d --- /dev/null +++ b/src/dex/polygon-migrator/polygon-migrator-e2e.test.ts @@ -0,0 +1,102 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { ContractMethod, Network, SwapSide } from '../../constants'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { generateConfig } from '../../config'; +import { Holders, Tokens } from '../../../tests/constants-e2e'; +import { testE2E } from '../../../tests/utils-e2e'; + +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + [ + SwapSide.BUY, + [ + ContractMethod.simpleBuy, + ContractMethod.buy, + ], + ], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${tokenBSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[tokenBSymbol], + tokens[tokenASymbol], + holders[tokenBSymbol], + side === SwapSide.SELL ? tokenBAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); + }), + ); + }); +} + +describe('PolygonMigrator E2E', () => { + const dexKey = 'PolygonMigrator'; + + describe('Mainnet', () => { + const network = Network.MAINNET; + + const tokenASymbol: string = 'POL'; + const tokenBSymbol: string = 'MATIC'; + + const tokenAAmount: string = '10000000000000000000'; + const tokenBAmount: string = '20000000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); +}); diff --git a/src/dex/polygon-migrator/polygon-migrator-integration.test.ts b/src/dex/polygon-migrator/polygon-migrator-integration.test.ts new file mode 100644 index 000000000..9ad346f5f --- /dev/null +++ b/src/dex/polygon-migrator/polygon-migrator-integration.test.ts @@ -0,0 +1,140 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { DummyDexHelper } from '../../dex-helper/index'; +import { Network, SwapSide } from '../../constants'; +import { checkConstantPoolPrices } from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; +import { BI_POWS } from '../../bigint-constants'; +import { PolygonMigrator } from './polygon-migrator'; + +const network = Network.MAINNET; + +const MaticSymbol = 'MATIC'; +const MaticToken = Tokens[network][MaticSymbol]; + +const PolSymbol = 'POL'; +const PolToken = Tokens[network][PolSymbol]; + +const amounts = [0n, BI_POWS[18], 2000000000000000000n]; + +const dexKey = 'PolygonMigrator'; + +describe('PolygonMigrator', function () { + it('getPoolIdentifiers and getPricesVolume MATIC -> POL SELL', async function () { + const dexHelper = new DummyDexHelper(network); + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const polygonMigrator = new PolygonMigrator(network, dexKey, dexHelper); + + const pools = await polygonMigrator.getPoolIdentifiers( + MaticToken, + PolToken, + SwapSide.SELL, + blocknumber, + ); + console.log(`${MaticSymbol} <> ${PolSymbol} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await polygonMigrator.getPricesVolume( + MaticToken, + PolToken, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log(`${MaticSymbol} <> ${PolSymbol} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + }); + + it('getPoolIdentifiers and getPricesVolume POL -> MATIC SELL', async function () { + const dexHelper = new DummyDexHelper(network); + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const polygonMigrator = new PolygonMigrator(network, dexKey, dexHelper); + + const pools = await polygonMigrator.getPoolIdentifiers( + PolToken, + MaticToken, + SwapSide.SELL, + blocknumber, + ); + console.log(`${PolSymbol} <> ${MaticSymbol} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await polygonMigrator.getPricesVolume( + PolToken, + MaticToken, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log(`${PolSymbol} <> ${MaticSymbol} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + }); + + it('getPoolIdentifiers and getPricesVolume MATIC -> POL BUY', async function () { + const dexHelper = new DummyDexHelper(network); + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const polygonMigrator = new PolygonMigrator(network, dexKey, dexHelper); + + const pools = await polygonMigrator.getPoolIdentifiers( + MaticToken, + PolToken, + SwapSide.BUY, + blocknumber, + ); + console.log(`${MaticSymbol} <> ${PolSymbol} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await polygonMigrator.getPricesVolume( + MaticToken, + PolToken, + amounts, + SwapSide.BUY, + blocknumber, + pools, + ); + console.log(`${MaticSymbol} <> ${PolSymbol} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + }); + + it('getPoolIdentifiers and getPricesVolume POL -> MATIC BUY', async function () { + const dexHelper = new DummyDexHelper(network); + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const polygonMigrator = new PolygonMigrator(network, dexKey, dexHelper); + + const pools = await polygonMigrator.getPoolIdentifiers( + PolToken, + MaticToken, + SwapSide.BUY, + blocknumber, + ); + console.log(`${PolSymbol} <> ${MaticSymbol} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await polygonMigrator.getPricesVolume( + PolToken, + MaticToken, + amounts, + SwapSide.BUY, + blocknumber, + pools, + ); + console.log(`${PolSymbol} <> ${MaticSymbol} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + }); + +}); diff --git a/src/dex/polygon-migrator/polygon-migrator.ts b/src/dex/polygon-migrator/polygon-migrator.ts new file mode 100644 index 000000000..1631248da --- /dev/null +++ b/src/dex/polygon-migrator/polygon-migrator.ts @@ -0,0 +1,151 @@ +import { SimpleExchange } from '../simple-exchange'; +import { IDex } from '../idex'; +import { DexParams, PolygonMigrationData, PolygonMigratorFunctions } from './types'; +import { Network, SwapSide } from '../../constants'; +import { getDexKeysWithNetwork } from '../../utils'; +import { Adapters, PolygonMigratorConfig } from './config'; +import { + AdapterExchangeParam, + Address, + ExchangePrices, + Logger, + PoolLiquidity, + PoolPrices, + SimpleExchangeParam, + Token +} from '../../types'; +import { IDexHelper } from '../../dex-helper'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { BI_POWS } from '../../bigint-constants'; +import { POLYGON_MIGRATION_GAS_COST } from './constants'; +import PolygonMigrationAbi from '../../abi/polygon-migration/PolygonMigration.abi.json'; +import { Interface } from 'ethers/lib/utils'; + +export class PolygonMigrator extends SimpleExchange implements IDex { + readonly hasConstantPriceLargeAmounts = true; + readonly isFeeOnTransferSupported = false; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(PolygonMigratorConfig); + + logger: Logger; + + constructor( + protected network: Network, + dexKey: string, + protected dexHelper: IDexHelper, + readonly migratorAddress: string = PolygonMigratorConfig[dexKey][network].migratorAddress, + readonly polTokenAddress: string = PolygonMigratorConfig[dexKey][network].polTokenAddress, + readonly maticTokenAddress: string = PolygonMigratorConfig[dexKey][network].maticTokenAddress, + protected unitPrice = BI_POWS[18], + protected adapters = Adapters[network] || {}, + protected migratorInterface = new Interface(PolygonMigrationAbi), + ) { + super(dexHelper, dexKey); + this.logger = dexHelper.getLogger(dexKey); + } + + getAdapters(side: SwapSide): { name: string; index: number }[] | null { + return this.adapters[side] || null; + } + + isMatic(tokenAddress: Address) { + return this.maticTokenAddress.toLowerCase() === tokenAddress.toLowerCase(); + } + + isPol(tokenAddress: Address) { + return this.polTokenAddress.toLowerCase() === tokenAddress.toLowerCase(); + } + + isAppropriatePair(srcToken: Token, destToken: Token) { + return (this.isMatic(srcToken.address) && this.isPol(destToken.address)) + || (this.isMatic(destToken.address) && this.isPol(srcToken.address)); + } + + async getPoolIdentifiers( + srcToken: Token, + destToken: Token, + side: SwapSide, + blockNumber: number, + ): Promise { + if(this.isAppropriatePair(srcToken, destToken)) { + return [`${this.dexKey}_${srcToken.address}_${destToken.address}`]; + } + + return []; + } + + async getPricesVolume( + srcToken: Token, + destToken: Token, + amounts: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[], + ): Promise> { + if(!this.isAppropriatePair(srcToken, destToken)) { + return null; + } + + return [ + { + prices: amounts, + unit: this.unitPrice, + gasCost: POLYGON_MIGRATION_GAS_COST, + exchange: this.dexKey, + poolAddresses: [this.migratorAddress], + data: null, + }, + ]; + } + + // Returns estimated gas cost of calldata for this DEX in multiSwap + getCalldataGasCost(poolPrices: PoolPrices): number | number[] { + return CALLDATA_GAS_COST.DEX_NO_PAYLOAD; + } + + getAdapterParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: PolygonMigrationData, + side: SwapSide, + ): AdapterExchangeParam { + return { + targetExchange: this.migratorAddress, + payload: '0x', + networkFee: '0', + }; + } + + async getSimpleParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: PolygonMigrationData, + side: SwapSide, + ): Promise { + const swapData = this.migratorInterface.encodeFunctionData( + this.isMatic(srcToken) ? PolygonMigratorFunctions.migrate : PolygonMigratorFunctions.unmigrate, + [ srcAmount ], + ); + + return this.buildSimpleParamWithoutWETHConversion( + srcToken, + srcAmount, + destToken, + destAmount, + swapData, + this.migratorAddress, + ); + } + + async getTopPoolsForToken( + tokenAddress: Address, + limit: number, + ): Promise { + return []; + } +} diff --git a/src/dex/polygon-migrator/types.ts b/src/dex/polygon-migrator/types.ts new file mode 100644 index 000000000..d4d23377d --- /dev/null +++ b/src/dex/polygon-migrator/types.ts @@ -0,0 +1,14 @@ +import { Address } from '../../types'; + +export type PolygonMigrationData = null; + +export type DexParams = { + migratorAddress: Address; + polTokenAddress: Address; + maticTokenAddress: Address; +}; + +export enum PolygonMigratorFunctions { + migrate = 'migrate', + unmigrate = 'unmigrate', +} diff --git a/src/dex/solidly/config.ts b/src/dex/solidly/config.ts index baedb5183..0a9bb9f12 100644 --- a/src/dex/solidly/config.ts +++ b/src/dex/solidly/config.ts @@ -178,6 +178,38 @@ export const SolidlyConfig: DexConfigMap = { poolGasCost: 180 * 1000, }, }, + Velocimeter: { + [Network.FANTOM]: { + factoryAddress: '0x472f3C3c9608fe0aE8d702f3f8A2d12c410C881A', + router: '0x93d2611EB8b85bE4FDEa9D94Ce9913D90072eC0f', + initCode: + '0xac4013aa7118234c1dd1f9cc4cdd3933d5a426224bc691c1bde3d8930a7e6151', // PairFactory.pairCodeHash + feeCode: 0, // dynamic fees + poolGasCost: 180 * 1000, // just same as other forks + // no subgraph + }, + [Network.BASE]: { + factoryAddress: '0xe21Aac7F113Bd5DC2389e4d8a8db854a87fD6951', + router: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', + initCode: + '0xac4013aa7118234c1dd1f9cc4cdd3933d5a426224bc691c1bde3d8930a7e6151', // PairFactory.pairCodeHash + feeCode: 0, // dynamic fees + poolGasCost: 180 * 1000, // just same as other forks + // no subgraph + }, + }, + Usdfi: { + [Network.BSC]: { + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/tbotteam/usdfi-dexv2', + factoryAddress: '0xB3863573d9f25e6a84895d4685a408db7a488416', + router: '0xc2b5a8082D2E1867A9CBBF41b625E3ae9dF81f8b', + initCode: + '0x1d770cc32abcf060a45b0de3f0afbd8594effe9f6d836f93d19c05d76b4b4dfa', + poolGasCost: 180 * 1000, + feeCode: 0, // dynamic fees + }, + }, }; export const Adapters: Record = { @@ -185,13 +217,13 @@ export const Adapters: Record = { [SwapSide.SELL]: [{ name: 'PolygonAdapter02', index: 3 }], // dystopia }, [Network.FANTOM]: { - [SwapSide.SELL]: [{ name: 'FantomAdapter01', index: 10 }], // solidly, spiritSwapV2, equalizer + [SwapSide.SELL]: [{ name: 'FantomAdapter01', index: 10 }], // solidly, spiritSwapV2, equalizer, velocimeter }, [Network.OPTIMISM]: { [SwapSide.SELL]: [{ name: 'OptimismAdapter01', index: 8 }], // velodrome }, [Network.BSC]: { - [SwapSide.SELL]: [{ name: 'BscAdapter02', index: 1 }], // thena + cone + [SwapSide.SELL]: [{ name: 'BscAdapter02', index: 1 }], // thena + cone, usdFi }, [Network.MAINNET]: { [SwapSide.SELL]: [{ name: 'Adapter04', index: 1 }], // solidly @@ -203,6 +235,6 @@ export const Adapters: Record = { [SwapSide.SELL]: [{ name: 'ArbitrumAdapter02', index: 1 }], // chronos, ramses }, [Network.BASE]: { - [SwapSide.SELL]: [{ name: 'BaseAdapter01', index: 3 }], // aerodrome, equalizer + [SwapSide.SELL]: [{ name: 'BaseAdapter01', index: 3 }], // aerodrome, equalizer, velocimeter }, }; diff --git a/src/dex/solidly/forks-override/chronos.ts b/src/dex/solidly/forks-override/chronos.ts index 845b46e01..34d3d8fe2 100644 --- a/src/dex/solidly/forks-override/chronos.ts +++ b/src/dex/solidly/forks-override/chronos.ts @@ -27,7 +27,7 @@ type ChronosSubgraphPool = { token0: { id: string; decimals: string }; reserve0: string; reserve1: string; - token1: { id: string; decimals: string; }; + token1: { id: string; decimals: string }; }; export class Chronos extends Solidly { @@ -114,35 +114,43 @@ export class Chronos extends Solidly { if (!(data && data.pools0 && data.pools1)) throw new Error("Couldn't fetch the pools from the subgraph"); - const pools0 = await this.prepareSubgraphPools(data.pools0, (pool,{ - address1, decimals1, liquidityUSDToken0, liquidityUSDToken1, - }) => ({ - exchange: this.dexKey, - stable: pool.isStable, - address: pool.id.toLowerCase(), - connectorTokens: [ - { - address: address1, - decimals: decimals1, - }, - ], - liquidityUSD: liquidityUSDToken0 + liquidityUSDToken1, - })); - - const pools1 = await this.prepareSubgraphPools(data.pools1, (pool,{ - address0, decimals0, liquidityUSDToken0, liquidityUSDToken1, - }) => ({ - exchange: this.dexKey, - stable: pool.isStable, - address: pool.id.toLowerCase(), - connectorTokens: [ - { - address: address0, - decimals: decimals0, - }, - ], - liquidityUSD: liquidityUSDToken0 + liquidityUSDToken1, - })); + const pools0 = await this.prepareSubgraphPools( + data.pools0, + ( + pool, + { address1, decimals1, liquidityUSDToken0, liquidityUSDToken1 }, + ) => ({ + exchange: this.dexKey, + stable: pool.isStable, + address: pool.id.toLowerCase(), + connectorTokens: [ + { + address: address1, + decimals: decimals1, + }, + ], + liquidityUSD: liquidityUSDToken0 + liquidityUSDToken1, + }), + ); + + const pools1 = await this.prepareSubgraphPools( + data.pools1, + ( + pool, + { address0, decimals0, liquidityUSDToken0, liquidityUSDToken1 }, + ) => ({ + exchange: this.dexKey, + stable: pool.isStable, + address: pool.id.toLowerCase(), + connectorTokens: [ + { + address: address0, + decimals: decimals0, + }, + ], + liquidityUSD: liquidityUSDToken0 + liquidityUSDToken1, + }), + ); return _.slice( _.sortBy(_.concat(pools0, pools1), [pool => -1 * pool.liquidityUSD]), @@ -154,7 +162,8 @@ export class Chronos extends Solidly { private async prepareSubgraphPools( pools: ChronosSubgraphPool[], iterator: ( - pool: ChronosSubgraphPool, { + pool: ChronosSubgraphPool, + { address0, address1, decimals0, @@ -164,43 +173,59 @@ export class Chronos extends Solidly { liquidityUSDToken0, liquidityUSDToken1, }: { - address0: string, - address1: string, - decimals0: number, - decimals1: number, - reserve0: bigint, - reserve1: bigint, - liquidityUSDToken0: number, - liquidityUSDToken1: number, - }) => PoolLiquidity + address0: string; + address1: string; + decimals0: number; + decimals1: number; + reserve0: bigint; + reserve1: bigint; + liquidityUSDToken0: number; + liquidityUSDToken1: number; + }, + ) => PoolLiquidity, ): Promise { - return Promise.all(pools.map(async ( - pool: ChronosSubgraphPool, - ) => { - const address0 = pool.token0.id.toLowerCase(); - const address1 = pool.token1.id.toLowerCase(); - - const decimals0 = parseInt(pool.token0.decimals); - const decimals1 = parseInt(pool.token1.decimals); - - const reserve0 = BigInt(new BigNumber(pool.reserve0).multipliedBy(10 ** decimals0).toFixed()); - const reserve1 = BigInt(new BigNumber(pool.reserve1).multipliedBy(10 ** decimals1).toFixed()); - - const liquidityUSDToken0 = await this.dexHelper.getTokenUSDPrice({ - address: address0, - decimals: decimals0, - }, reserve0); - - const liquidityUSDToken1 = await this.dexHelper.getTokenUSDPrice({ - address: address1, - decimals: decimals1, - }, reserve1); - - return iterator( - pool, { - address0, address1, decimals0, decimals1, reserve0, reserve1, liquidityUSDToken0, liquidityUSDToken1, - }, - ); - })); + return Promise.all( + pools.map(async (pool: ChronosSubgraphPool) => { + const address0 = pool.token0.id.toLowerCase(); + const address1 = pool.token1.id.toLowerCase(); + + const decimals0 = parseInt(pool.token0.decimals); + const decimals1 = parseInt(pool.token1.decimals); + + const reserve0 = BigInt( + new BigNumber(pool.reserve0).multipliedBy(10 ** decimals0).toFixed(), + ); + const reserve1 = BigInt( + new BigNumber(pool.reserve1).multipliedBy(10 ** decimals1).toFixed(), + ); + + const liquidityUSDToken0 = await this.dexHelper.getTokenUSDPrice( + { + address: address0, + decimals: decimals0, + }, + reserve0, + ); + + const liquidityUSDToken1 = await this.dexHelper.getTokenUSDPrice( + { + address: address1, + decimals: decimals1, + }, + reserve1, + ); + + return iterator(pool, { + address0, + address1, + decimals0, + decimals1, + reserve0, + reserve1, + liquidityUSDToken0, + liquidityUSDToken1, + }); + }), + ); } } diff --git a/src/dex/solidly/forks-override/usdfi.ts b/src/dex/solidly/forks-override/usdfi.ts new file mode 100644 index 000000000..6ebad08ae --- /dev/null +++ b/src/dex/solidly/forks-override/usdfi.ts @@ -0,0 +1,10 @@ +import { Network } from '../../../constants'; +import { getDexKeysWithNetwork } from '../../../utils'; +import { SolidlyConfig } from '../config'; +import _ from 'lodash'; +import { SpiritSwapV2 } from './spiritSwapV2'; + +export class Usdfi extends SpiritSwapV2 { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(_.pick(SolidlyConfig, ['Usdfi'])); +} diff --git a/src/dex/solidly/forks-override/velocimeter.ts b/src/dex/solidly/forks-override/velocimeter.ts new file mode 100644 index 000000000..878a1f2d7 --- /dev/null +++ b/src/dex/solidly/forks-override/velocimeter.ts @@ -0,0 +1,58 @@ +import { Solidly } from '../solidly'; +import { SolidlyPair } from '../types'; +import { Network } from '../../../constants'; +import { IDexHelper } from '../../../dex-helper'; +import { Interface } from '@ethersproject/abi'; +import { getDexKeysWithNetwork } from '../../../utils'; +import { SolidlyConfig } from '../config'; +import _ from 'lodash'; + +const velocimeterFactoryABI = [ + { + inputs: [{ internalType: '_pair', name: '_stable', type: 'address' }], + name: 'getFee', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, +]; + +const velocimeterFactoryIface = new Interface(velocimeterFactoryABI); + +export class Velocimeter extends Solidly { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(_.pick(SolidlyConfig, ['Velocimeter'])); + + constructor( + protected network: Network, + dexKey: string, + protected dexHelper: IDexHelper, + ) { + super( + network, + dexKey, + dexHelper, + true, // dynamic fees + ); + } + + protected getFeesMultiCallData(pair: SolidlyPair) { + const callEntry = { + target: this.factoryAddress, + callData: velocimeterFactoryIface.encodeFunctionData('getFee', [ + pair.exchange, + ]), + }; + const callDecoder = (values: any[]) => + parseInt( + velocimeterFactoryIface + .decodeFunctionResult('getFee', values)[0] + .toString(), + ); + + return { + callEntry, + callDecoder, + }; + } +} diff --git a/src/dex/solidly/solidly-e2e.test.ts b/src/dex/solidly/solidly-e2e.test.ts index 79bfc833a..d6abdc5b5 100644 --- a/src/dex/solidly/solidly-e2e.test.ts +++ b/src/dex/solidly/solidly-e2e.test.ts @@ -415,6 +415,28 @@ describe('Solidly E2E', () => { nativeTokenAmount, ); }); + + describe('Velocimeter', () => { + const dexKey = 'Velocimeter'; + const network = Network.FANTOM; + + const tokenASymbol: string = 'lzUSDC'; + const tokenBSymbol: string = 'axlUSDC'; + + const tokenAAmount: string = '1111100'; + const tokenBAmount: string = '1000000'; + const nativeTokenAmount = '11000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); describe('Mainnet', () => { @@ -1104,6 +1126,26 @@ describe('Solidly E2E', () => { }), ); }); + + describe('Usdfi', () => { + const dexKey = 'Usdfi'; + const tokenASymbol: string = 'FRAX'; + const tokenBSymbol: string = 'frxETH'; + + const tokenAAmount: string = '100000000000000000'; + const tokenBAmount: string = '1111100000'; + const nativeTokenAmount = '100000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); describe('Avalanche', () => { @@ -1491,5 +1533,26 @@ describe('Solidly E2E', () => { }), ); }); + + describe('Velocimeter', () => { + const dexKey = 'Velocimeter'; + + const tokenASymbol: string = 'USDbC'; + const tokenBSymbol: string = 'DAI'; + + const tokenAAmount: string = '1111100000'; + const tokenBAmount: string = '1111100000'; + const nativeTokenAmount = '100000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); }); diff --git a/src/dex/solidly/solidly-integration.test.ts b/src/dex/solidly/solidly-integration.test.ts index 73fce1d7d..efe1a1c1b 100644 --- a/src/dex/solidly/solidly-integration.test.ts +++ b/src/dex/solidly/solidly-integration.test.ts @@ -17,6 +17,8 @@ import { Ramses } from './forks-override/ramses'; import * as util from 'util'; import { VelodromeV2 } from './forks-override/velodromeV2'; import { Equalizer } from './forks-override/equalizer'; +import { Velocimeter } from './forks-override/velocimeter'; +import { Usdfi } from './forks-override/usdfi'; const amounts18 = [0n, BI_POWS[18], 2000000000000000000n]; const amounts6 = [0n, BI_POWS[6], 2000000n]; @@ -525,6 +527,176 @@ describe('Solidly integration tests', () => { }); }); }); + + describe('Velocimeter', () => { + const dexKey = 'Velocimeter'; + const velocimeter = new Velocimeter(network, dexKey, dexHelper); + + describe('UniswapV2 like pool', function () { + const TokenASymbol = 'WFTM'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'lzUSDC'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts18; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await velocimeter.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await velocimeter.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + velocimeter, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + + describe('Curve like stable pool', function () { + const TokenASymbol = 'lzUSDC'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'axlUSDC'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts6; // amounts6; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await velocimeter.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await velocimeter.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + velocimeter, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + + describe('FTM -> FVM', () => { + const TokenASymbol = 'WFTM'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'FVM'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = [0n, 10000000n]; + + console.log('AMOUNTS: ', amounts); + it('getPoolIdentifiers and getPricesVolume', async function () { + // const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const blocknumber = 67666611; + const pools = await velocimeter.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await velocimeter.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + velocimeter, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + }); }); describe('Polygon', () => { @@ -805,6 +977,194 @@ describe('Solidly integration tests', () => { }); }); }); + + describe('Usdfi', function () { + const dexKey = 'Usdfi'; + const usdfi = new Usdfi(network, dexKey, dexHelper); + + describe('UniswapV2 like pool', function () { + const TokenASymbol = 'USDFI'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'USDT'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts18; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await usdfi.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await usdfi.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + usdfi, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + + it('getTopPoolsForToken', async function () { + const poolLiquidity = await usdfi.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + }); + + describe('Curve like stable pool', function () { + const TokenASymbol = 'USDT'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'BUSD'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts6; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await usdfi.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await usdfi.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + usdfi, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + + it('getTopPoolsForToken', async function () { + const poolLiquidity = await usdfi.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + }); + + describe('FRAX -> frxETH', () => { + const TokenASymbol = 'FRAX'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'frxETH'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts18; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await usdfi.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await usdfi.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + usdfi, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + }); }); describe('Arbitrum', () => { @@ -1267,5 +1627,119 @@ describe('Solidly integration tests', () => { }); }); }); + + describe('Velocimeter', () => { + const dexKey = 'Velocimeter'; + const velocimeter = new Velocimeter(network, dexKey, dexHelper); + + describe('UniswapV2 like pool', function () { + const TokenASymbol = 'WETH'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'USDbC'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts18; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await velocimeter.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await velocimeter.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + velocimeter, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + + describe('Curve like stable pool', function () { + const TokenASymbol = 'USDbC'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'USDC'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts6; // amounts6; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await velocimeter.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await velocimeter.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + velocimeter, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + }); }); }); diff --git a/src/dex/solidly/solidly.ts b/src/dex/solidly/solidly.ts index 13b5a0c75..fa380c425 100644 --- a/src/dex/solidly/solidly.ts +++ b/src/dex/solidly/solidly.ts @@ -75,7 +75,8 @@ export class Solidly extends UniswapV2 { 'Chronos', 'Ramses', 'Equalizer', - 'Aerodrome', + 'Velocimeter', + 'Usdfi', ]), ); diff --git a/src/dex/swaap-v2/swaap-v2.ts b/src/dex/swaap-v2/swaap-v2.ts index 1b32a5103..c672da964 100644 --- a/src/dex/swaap-v2/swaap-v2.ts +++ b/src/dex/swaap-v2/swaap-v2.ts @@ -55,7 +55,10 @@ import { } from './constants'; import { getPoolIdentifier, normalizeTokenAddress, getPairName } from './utils'; import { Method } from '../../dex-helper/irequest-wrapper'; -import { SlippageCheckError, TooStrictSlippageCheckError } from '../generic-rfq/types'; +import { + SlippageCheckError, + TooStrictSlippageCheckError, +} from '../generic-rfq/types'; import { BI_MAX_UINT256 } from '../../bigint-constants'; const BLACKLISTED = 'blacklisted'; @@ -567,7 +570,7 @@ export class SwaapV2 extends SimpleExchange implements IDex { `${this.dexKey}-${this.network}: Encountered restricted user=${options.txOrigin}. Adding to local blacklist cache`, ); } else { - if(e instanceof TooStrictSlippageCheckError) { + if (e instanceof TooStrictSlippageCheckError) { this.logger.warn( `${this.dexKey}-${this.network}: failed to build transaction on side ${side} with too strict slippage. Skipping restriction`, ); diff --git a/src/dex/uniswap-v2/config.ts b/src/dex/uniswap-v2/config.ts index 34fb93bc6..9845fc2ec 100644 --- a/src/dex/uniswap-v2/config.ts +++ b/src/dex/uniswap-v2/config.ts @@ -117,6 +117,20 @@ export const Adapters: { }, ], }, + [Network.BASE]: { + [SwapSide.SELL]: [ + { + name: 'BaseAdapter01', + index: 6, + }, + ], + [SwapSide.BUY]: [ + { + name: 'BaseBuyAdapter', + index: 4, + }, + ], + }, }; export const UniswapV2Config: DexConfigMap = { @@ -696,4 +710,13 @@ export const UniswapV2Config: DexConfigMap = { feeCode: 30, }, }, + BaseSwap: { + [Network.BASE]: { + factoryAddress: '0xFDa619b6d20975be80A10332cD39b9a4b0FAa8BB', + initCode: + '0xb618a2730fae167f5f8ac7bd659dd8436d571872655bcb6fd11f2158c8a64a3b', + poolGasCost: 90 * 1000, + feeCode: 25, + }, + }, }; diff --git a/src/dex/uniswap-v3/uniswap-v3.ts b/src/dex/uniswap-v3/uniswap-v3.ts index 57c32f5c6..2ab48c822 100644 --- a/src/dex/uniswap-v3/uniswap-v3.ts +++ b/src/dex/uniswap-v3/uniswap-v3.ts @@ -542,7 +542,7 @@ export class UniswapV3 tokenIn: from.address, tokenOut: to.address, fee: pool.feeCodeAsString, - currentFee: states[index].fee.toString(), + currentFee: states[index]?.fee.toString(), }, ], exchange: pool.poolAddress, diff --git a/src/router/buy.ts b/src/router/buy.ts index 4bb1759f3..1ffe062b2 100644 --- a/src/router/buy.ts +++ b/src/router/buy.ts @@ -42,7 +42,7 @@ export class Buy extends PayloadEncoder implements IRouter { referrerAddress: Address | undefined, partnerAddress: Address, partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, beneficiary: Address, permit: string, deadline: string, @@ -62,22 +62,6 @@ export class Buy extends PayloadEncoder implements IRouter { minMaxAmount, priceRoute.srcAmount, ); - - const [partner, feePercent] = referrerAddress - ? [referrerAddress, encodeFeePercentForReferrer(SwapSide.BUY)] - : [ - encodePartnerAddressForFeeLogic({ - partnerAddress, - partnerFeePercent, - positiveSlippageToUser, - }), - encodeFeePercent( - partnerFeePercent, - positiveSlippageToUser, - SwapSide.BUY, - ), - ]; - const buyData: ContractBuyData = { adapter, fromToken: priceRoute.srcToken, diff --git a/src/router/directswap.ts b/src/router/directswap.ts index 2af9245bd..a0a44edc2 100644 --- a/src/router/directswap.ts +++ b/src/router/directswap.ts @@ -29,7 +29,7 @@ export class DirectSwap implements IRouter { referrerAddress: Address | undefined, partnerAddress: Address, partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, beneficiary: Address, permit: string, deadline: string, @@ -76,13 +76,9 @@ export class DirectSwap implements IRouter { encodePartnerAddressForFeeLogic({ partnerAddress, partnerFeePercent, - positiveSlippageToUser, + takeSurplus, }), - encodeFeePercent( - partnerFeePercent, - positiveSlippageToUser, - priceRoute.side, - ), + encodeFeePercent(partnerFeePercent, takeSurplus, priceRoute.side), ]; return dex.getDirectParam!( diff --git a/src/router/irouter.ts b/src/router/irouter.ts index 01e1ff8f0..b17283e14 100644 --- a/src/router/irouter.ts +++ b/src/router/irouter.ts @@ -9,7 +9,7 @@ export interface IRouter { referrerAddress: Address | undefined, partner: Address, partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, beneficiary: Address, permit: string, deadline: string, diff --git a/src/router/megaswap.ts b/src/router/megaswap.ts index 58433b270..ce8cbee10 100644 --- a/src/router/megaswap.ts +++ b/src/router/megaswap.ts @@ -42,7 +42,7 @@ export class MegaSwap extends PayloadEncoder implements IRouter { referrerAddress: Address | undefined, partnerAddress: Address, partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, beneficiary: Address, permit: string, deadline: string, @@ -58,13 +58,9 @@ export class MegaSwap extends PayloadEncoder implements IRouter { encodePartnerAddressForFeeLogic({ partnerAddress, partnerFeePercent, - positiveSlippageToUser, + takeSurplus, }), - encodeFeePercent( - partnerFeePercent, - positiveSlippageToUser, - SwapSide.SELL, - ), + encodeFeePercent(partnerFeePercent, takeSurplus, SwapSide.SELL), ]; const sellData: ContractMegaSwapSellData = { diff --git a/src/router/multiswap.ts b/src/router/multiswap.ts index d90695d0c..429e8b3c0 100644 --- a/src/router/multiswap.ts +++ b/src/router/multiswap.ts @@ -45,7 +45,7 @@ export class MultiSwap referrerAddress: Address | undefined, partnerAddress: Address, partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, beneficiary: Address, permit: string, deadline: string, @@ -66,13 +66,9 @@ export class MultiSwap encodePartnerAddressForFeeLogic({ partnerAddress, partnerFeePercent, - positiveSlippageToUser, + takeSurplus, }), - encodeFeePercent( - partnerFeePercent, - positiveSlippageToUser, - SwapSide.SELL, - ), + encodeFeePercent(partnerFeePercent, takeSurplus, SwapSide.SELL), ]; const sellData: ContractSellData = { diff --git a/src/router/payload-encoder.ts b/src/router/payload-encoder.ts index 3a9a9bc93..cf057846c 100644 --- a/src/router/payload-encoder.ts +++ b/src/router/payload-encoder.ts @@ -29,14 +29,14 @@ const HALF_SPLIT = '5000'; export function encodePartnerAddressForFeeLogic({ partnerAddress, partnerFeePercent, - positiveSlippageToUser, + takeSurplus, }: { partnerAddress: string; partnerFeePercent: string; - positiveSlippageToUser: boolean; + takeSurplus: boolean; }): string { const isPartnerTakeNoFeeNoPos = - +partnerFeePercent === 0 && positiveSlippageToUser == true; + +partnerFeePercent === 0 && takeSurplus == false; // nullify partner address to fallback default circuit contract without partner/referrer (no harm as no fee taken at all) const partner = isPartnerTakeNoFeeNoPos ? NULL_ADDRESS : partnerAddress; @@ -51,11 +51,11 @@ export function encodePartnerAddressForFeeLogic({ export function encodeFeePercent( partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, side: SwapSide, ) { const isNoFeeAndPositiveSlippageToPartner = - positiveSlippageToUser === false && BigInt(partnerFeePercent) === 0n; + takeSurplus === true && BigInt(partnerFeePercent) === 0n; let fee = isNoFeeAndPositiveSlippageToPartner ? BigInt(HALF_SPLIT) @@ -70,9 +70,9 @@ export function encodeFeePercent( fee |= OneShift17; } - // Set 14th bit if positiveSlippageToUser is true + // Set 14th bit if takeSurplus is false // Upd: not used onchain anymore but better to keep to prevent collisions and ensure continuity of analytics - if (positiveSlippageToUser) fee |= OneShift14; + if (!takeSurplus) fee |= OneShift14; // Set 15th bit to take fee from srcToken if (side === SwapSide.BUY && !isNoFeeAndPositiveSlippageToPartner) diff --git a/src/router/simpleswap.ts b/src/router/simpleswap.ts index 1b8e9e108..6047de047 100644 --- a/src/router/simpleswap.ts +++ b/src/router/simpleswap.ts @@ -223,7 +223,7 @@ export abstract class SimpleRouterBase referrerAddress: Address | undefined, partnerAddress: Address, partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, beneficiary: Address, permit: string, deadline: string, @@ -272,7 +272,7 @@ export abstract class SimpleRouter extends SimpleRouterBase { referrerAddress: Address | undefined, partnerAddress: Address, partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, beneficiary: Address, permit: string, deadline: string, @@ -292,13 +292,9 @@ export abstract class SimpleRouter extends SimpleRouterBase { encodePartnerAddressForFeeLogic({ partnerAddress, partnerFeePercent, - positiveSlippageToUser, + takeSurplus, }), - encodeFeePercent( - partnerFeePercent, - positiveSlippageToUser, - this.side, - ), + encodeFeePercent(partnerFeePercent, takeSurplus, this.side), ]; const sellData: ConstractSimpleData = { diff --git a/src/router/simpleswapnft.ts b/src/router/simpleswapnft.ts index 1fce10a08..fa33bab0f 100644 --- a/src/router/simpleswapnft.ts +++ b/src/router/simpleswapnft.ts @@ -60,7 +60,7 @@ export class SimpleBuyNFT extends SimpleRouterBase { referrerAddress: Address | undefined, partnerAddress: Address, partnerFeePercent: string, - positiveSlippageToUser: boolean, + takeSurplus: boolean, beneficiary: Address, permit: string, deadline: string, @@ -102,13 +102,9 @@ export class SimpleBuyNFT extends SimpleRouterBase { encodePartnerAddressForFeeLogic({ partnerAddress, partnerFeePercent, - positiveSlippageToUser, + takeSurplus, }), - encodeFeePercent( - partnerFeePercent, - positiveSlippageToUser, - SwapSide.BUY, - ), + encodeFeePercent(partnerFeePercent, takeSurplus, SwapSide.BUY), ]; const buyData: ContractSimpleBuyNFTData = { diff --git a/src/transaction-builder.ts b/src/transaction-builder.ts index 41da59942..db64867ea 100644 --- a/src/transaction-builder.ts +++ b/src/transaction-builder.ts @@ -17,7 +17,7 @@ export class TransactionBuilder { referrerAddress, partnerAddress, partnerFeePercent, - positiveSlippageToUser, + takeSurplus, gasPrice, maxFeePerGas, maxPriorityFeePerGas, @@ -33,7 +33,7 @@ export class TransactionBuilder { referrerAddress?: Address; partnerAddress: Address; partnerFeePercent: string; - positiveSlippageToUser?: boolean; + takeSurplus?: boolean; gasPrice?: string; // // @TODO: improve types? so that either gasPrice or ALL of max.*FeePerGas MUST be returned? maxFeePerGas?: string; maxPriorityFeePerGas?: string; @@ -53,7 +53,7 @@ export class TransactionBuilder { referrerAddress, partnerAddress, partnerFeePercent, - positiveSlippageToUser ?? true, + takeSurplus ?? false, _beneficiary, permit || '0x', deadline, diff --git a/src/types.ts b/src/types.ts index 612ad2933..4e9133013 100644 --- a/src/types.ts +++ b/src/types.ts @@ -276,7 +276,7 @@ export type Config = { hashFlowDisabledMMs: string[]; uniswapV3EventLoggingSampleRate?: number; swaapV2AuthToken?: string; - smardexSubgraphAuthToken?: string, + dexalotAuthToken?: string; forceRpcFallbackDexs: string[]; }; diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index 34b3517eb..b2acc4809 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -315,6 +315,14 @@ export const Tokens: { address: '0x8751d4196027d4e6da63716fa7786b5174f04c15', decimals: 18, }, + MATIC: { + address: '0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0', + decimals: 18, + }, + POL: { + address: '0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6', + decimals: 19, + } }, [Network.ROPSTEN]: { DAI: { @@ -445,10 +453,6 @@ export const Tokens: { address: '0xa3fa99a148fa48d14ed51d610c367c61876997f1', decimals: 18, }, - SDEX: { - address: '0x6899fAcE15c14348E1759371049ab64A3a06bFA6', - decimals: 18, - }, }, [Network.FANTOM]: { FTM: { address: ETHER_ADDRESS, decimals: 18 }, @@ -520,6 +524,18 @@ export const Tokens: { address: '0xe578C856933D8e1082740bf7661e379Aa2A30b26', decimals: 6, }, + axlUSDC: { + address: '0x1B6382DBDEa11d97f24495C9A90b7c88469134a4', + decimals: 6, + }, + lzUSDC: { + address: '0x28a92dde19D9989F39A49905d7C9C2FAc7799bDf', + decimals: 6, + }, + FVM: { + address: '0x07BB65fAaC502d4996532F834A1B7ba5dC32Ff96', + decimals: 18, + }, }, [Network.BSC]: { POPS: { @@ -579,8 +595,16 @@ export const Tokens: { address: '0x4268B8F0B87b6Eae5d897996E6b845ddbD99Adf3', decimals: 6, }, - SDEX: { - address: '0xFdc66A08B0d0Dc44c17bbd471B88f49F50CdD20F', + FRAX: { + address: '0x90C97F71E18723b0Cf0dfa30ee176Ab653E89F40', + decimals: 18, + }, + frxETH: { + address: '0x64048A7eEcF3a2F1BA9e144aAc3D7dB6e58F555e', + decimals: 18, + }, + USDFI: { + address: '0x11A38e06699b238D6D9A0C7A01f3AC63a07ad318', decimals: 18, }, }, @@ -813,18 +837,6 @@ export const Tokens: { address: '0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f', decimals: 8, }, - LEX: { - address: '0x6bB7A17AcC227fd1F6781D1EEDEAE01B42047eE0', - decimals: 18, - }, - GRAIL: { - address: '0x3d9907f9a368ad0a51be60f7da3b97cf940982d8', - decimals: 18, - }, - SDEX: { - address: '0xabD587f2607542723b17f14d00d99b987C29b074', - decimals: 18, - }, }, [Network.OPTIMISM]: { DAI: { @@ -932,10 +944,6 @@ export const Tokens: { address: '0xbeFD5C25A59ef2C1316c5A4944931171F30Cd3E4', decimals: 18, }, - SDEX: { - address: '0xFd4330b0312fdEEC6d4225075b82E00493FF2e3f', - decimals: 18, - }, ETH: { address: ETHER_ADDRESS, decimals: 18 }, }, }; @@ -1004,7 +1012,8 @@ export const Holders: { crvUSD: '0xA920De414eA4Ab66b97dA1bFE9e6EcA7d4219635', GHO: '0x844Dc85EdD8492A56228D293cfEbb823EF3E10EC', wibBTC: '0xFbdCA68601f835b27790D98bbb8eC7f05FDEaA9B', - SDEX: '0xB0470cF15B22a6A32c49a7C20E3821B944A76058', + MATIC: '0x7073783eee7e9b3e6e4ddac4d7f49dc46044dd9a', + POL: '0x57B6Ad484ccdd902C4419424bA648ba6Ed45dc68', }, [Network.ROPSTEN]: { ETH: '0x43262A12d8610AA70C15DbaeAC321d51613c9071', @@ -1040,7 +1049,6 @@ export const Holders: { amUSDT: '0x832b11846a27b3ba25d68ae80c39fab155d18c49', amUSDC: '0x6e7f19cd23049c7118e14470e2bf85d2e26ee0ae', MAI: '0x9a8cf02f3e56c664ce75e395d0e4f3dc3dafe138', - SDEX: '0xB0470cF15B22a6A32c49a7C20E3821B944A76058', }, [Network.FANTOM]: { DAI: '0x370f4b2dcf75c94d8d4450b493661a9c6170d0b5', @@ -1063,6 +1071,9 @@ export const Holders: { GUSDC: '0x894d774a293f8aa3d23d67815d4cadb5319c1094', GDAI: '0x0e2ed73f9c1409e2b36fe6c46e60d4557b7c2ac0', EQUAL: '0x8b187ea19c93091a4d6b426b71871648182b5fac', + FVM: '0x07BB65fAaC502d4996532F834A1B7ba5dC32Ff96', + lzUSDC: '0x1e38e2e0e7df3be6592867d0ac2713a4dbda8350', + axlUSDC: '0xccf932cd565c21d2e516c8ff3a4f244eea27e09a', }, [Network.BSC]: { DAI: '0xf68a4b64162906eff0ff6ae34e2bb1cd42fef62d', @@ -1078,7 +1089,9 @@ export const Holders: { anyBTC: '0x4ffef8e8a75c20ab0ddf96c50d2457277d27923c', nUSD: '0x28ec0b36f0819ecb5005cab836f4ed5a2eca4d13', axlUSD: '0xc03fbeda9069b22a120ae6a09349a0b5eea5570a', - SDEX: '0xB0470cF15B22a6A32c49a7C20E3821B944A76058', + FRAX: '0xEB4576fE753DAB07635c0Bb6c8f0A355e1Db5d31', + frxETH: '0xf324adC872005197A6f7DAE214d3b63aa0C3625F', + USDFI: '0x2E00D722e091836B39Db3e4dcE6eE51c90c5B221', }, [Network.AVALANCHE]: { AVAX: '0xD6216fC19DB775Df9774a6E33526131dA7D19a2c', @@ -1088,7 +1101,7 @@ export const Holders: { BETS: '0x8cc2284c90d05578633418f9cde104f402375a65', HATCHY: '0x14ec295ec8def851ec6e2959df872dd24e422631', USDCe: '0x3a2434c698f8d79af1f5a9e43013157ca8b11a66', - USDC: '0xbf14db80d9275fb721383a77c00ae180fc40ae98', + USDC: '0x0d0707963952f2fba59dd06f2b425ace40b492fe', USDTe: '0x84d34f4f83a87596cd3fb6887cff8f17bf5a7b83', WETHe: '0xD291B51f7a1a1F4917D085F2a7731A447E4aF82D', POPS: '0x5268c2331658cb0b2858cfa9db27d8f22f5434bc', @@ -1104,13 +1117,14 @@ export const Holders: { TSD: '0x691A89db352B72dDb249bFe16503494eC0D920A4', THO: '0xc40d16c47394a506d451475c8a7c46c1175c1da1', aAvaUSDT: '0x50B1Ba98Cf117c9682048D56628B294ebbAA4ec2', - USDT: '0x4aeFa39caEAdD662aE31ab0CE7c8C2c9c0a013E8', + USDT: '0x764fe7fb23f0f995c45faef5e32a4526f2028814', aAvaWAVAX: '0x1B18Df70863636AEe4BfBAb6F7C70ceBCA9bA404', oldFRAX: '0x4e3376018add04ebe4c46bf6f924ddec8c67aa7b', newFRAX: '0x4e3376018add04ebe4c46bf6f924ddec8c67aa7b', nETH: '0xcf2ef00e75558512ae735679ea5df62ad2056786', avWETH: '0x92d78e32b990d10aeca0875dc5585f1a6f958179', YUSD: '0x6c1a5ef2acde1fd2fc68def440d2c1eb35bae24a', + BTCb: '0x84c06d3c27821d0136f66306f5028d43ceac268d', }, [Network.ARBITRUM]: { ARB: '0xb65edba80a3d81903ecd499c8eb9cf0e19096bd0', @@ -1135,7 +1149,6 @@ export const Holders: { ZYB: '0x3ec0eddcd1e25025077327886a78133589082fb2', WBTC: '0xd9d611c6943585bc0e18e51034af8fa28778f7da', RDNT: '0x62383739d68dd0f844103db8dfb05a7eded5bbe6', - SDEX: '0xb0470cf15b22a6a32c49a7c20e3821b944a76058', }, [Network.OPTIMISM]: { ETH: '0x9ef21bE1C270AA1c3c3d750F458442397fBFFCB6', @@ -1171,7 +1184,6 @@ export const Holders: { DAI: '0x20f03e26968b179025f65c1f4afadfd3959c8d03', BAL: '0x854b004700885a61107b458f11ecc169a019b764', GOLD: '0x1374c25b3710758c326ee0c70ec48b595d5ccf8c', - SDEX: '0xa5d378c05192e3f1f365d6298921879c4d51c5a3', }, };