diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e6c22514..7bb55ecb 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -43,7 +43,6 @@ jobs: - run: npm run test:builders - run: npm run test:quotes - run: npm run test:base - - run: npm run test:ic21 - run: npm run test:icusd - run: npm run test:hyeth # - run: npm run test:btc2x diff --git a/docs/icusd/README.md b/docs/icusd/README.md new file mode 100644 index 00000000..44c0a760 --- /dev/null +++ b/docs/icusd/README.md @@ -0,0 +1,7 @@ +# icUSD + +## Composition Updates + +1. Update wrap data according to the new composition watching integration names [here](src/utils/wrap-data.ts) +2. Update wrap data tests accordingly +3. Make sure nothing changes about how to generate the (issuance) amounts for the different components in the [component swap data](src/utils/component-swap-data.ts) diff --git a/package-lock.json b/package-lock.json index 45ade96c..8c5f34dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@ethersproject/contracts": "^5.6.2", "@ethersproject/providers": "^5.6.8", "@ethersproject/units": "^5.6.1", - "@indexcoop/tokenlists": "^1.58.0", + "@indexcoop/tokenlists": "^3.1.5", "@lifi/sdk": "3.0.0-beta.1", "@uniswap/sdk-core": "^5.3.1", "@uniswap/v3-sdk": "^3.13.1", @@ -48,9 +48,9 @@ } }, "node_modules/@adraffy/ens-normalize": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", - "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==" + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz", + "integrity": "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==" }, "node_modules/@ampproject/remapping": { "version": "2.2.0", @@ -3079,15 +3079,15 @@ "dev": true }, "node_modules/@indexcoop/tokenlists": { - "version": "1.58.0", - "resolved": "https://registry.npmjs.org/@indexcoop/tokenlists/-/tokenlists-1.58.0.tgz", - "integrity": "sha512-lmQJ8yPpevg8HWJogDcnx7IjmbnfqbEIE6kbTmzQ9Nc00+O+/TwsMWiLE8rGjM5OIBF2HTf3d7oV8ruINaRaQQ==", - "engines": { - "node": ">=16" + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@indexcoop/tokenlists/-/tokenlists-3.1.5.tgz", + "integrity": "sha512-371RQO5Dm7w8wXA7EWt0ebGFVewiOxnQqf3P7+qA7whSaZNrdpDwPPy/4Nlzv1Wz+C7MacNQMRPZF361lzeQzw==", + "dependencies": { + "@uniswap/token-lists": "^1.0.0-beta.34", + "viem": "^2.21.14" }, "peerDependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0" + "typescript": "^5.0.0" } }, "node_modules/@istanbuljs/load-nyc-config": { @@ -3510,22 +3510,25 @@ } }, "node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", + "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", "dependencies": { - "@noble/hashes": "1.3.2" + "@noble/hashes": "1.5.0" + }, + "engines": { + "node": "^14.21.3 || >=16" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@noble/curves/node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", "engines": { - "node": ">= 16" + "node": "^14.21.3 || >=16" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -4135,9 +4138,9 @@ } }, "node_modules/@scure/base": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz", - "integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", "funding": { "url": "https://paulmillr.com/funding/" } @@ -4850,17 +4853,6 @@ "superstruct": "^1.0.4" } }, - "node_modules/@solana/web3.js/node_modules/@noble/curves": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", - "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", - "dependencies": { - "@noble/hashes": "1.4.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@solana/web3.js/node_modules/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", @@ -5384,6 +5376,14 @@ "node": ">=12" } }, + "node_modules/@uniswap/token-lists": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@uniswap/token-lists/-/token-lists-1.0.0-beta.34.tgz", + "integrity": "sha512-Hc3TfrFaupg0M84e/Zv7BoF+fmMWDV15mZ5s8ZQt2qZxUcNw2GQW+L6L/2k74who31G+p1m3GRYbJpAo7d1pqA==", + "engines": { + "node": ">=10" + } + }, "node_modules/@uniswap/v2-core": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.1.tgz", @@ -5480,9 +5480,9 @@ } }, "node_modules/abitype": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.0.tgz", - "integrity": "sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.6.tgz", + "integrity": "sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A==", "funding": { "url": "https://github.com/sponsors/wevm" }, @@ -9919,13 +9919,13 @@ } }, "node_modules/isows": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.4.tgz", - "integrity": "sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", + "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", "funding": [ { "type": "github", - "url": "https://github.com/sponsors/wagmi-dev" + "url": "https://github.com/sponsors/wevm" } ], "peerDependencies": { @@ -10639,7 +10639,8 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "node_modules/js-yaml": { "version": "4.1.0", @@ -10974,18 +10975,6 @@ "node": ">=0.10.0" } }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/lru_map": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", @@ -14915,31 +14904,6 @@ "node": ">=0.10.0" } }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -15507,15 +15471,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - } - }, "node_modules/scrypt-js": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", @@ -17020,7 +16975,6 @@ "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -17248,9 +17202,9 @@ } }, "node_modules/viem": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.15.1.tgz", - "integrity": "sha512-Vrveen3vDOJyPf8Q8TDyWePG2pTdK6IpSi4P6qlvAP+rXkAeqRvwYBy9AmGm+BeYpCETAyTT0SrCP6458XSt+w==", + "version": "2.21.35", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.21.35.tgz", + "integrity": "sha512-f3EFc5JILeA9veuNymUN8HG/nKP9ykC0NCgwFrZWuxcCc822GaP0IEnkRBsHGqmjwbz//FxJFmvtx7TBcdVs0A==", "funding": [ { "type": "github", @@ -17258,14 +17212,15 @@ } ], "dependencies": { - "@adraffy/ens-normalize": "1.10.0", - "@noble/curves": "1.2.0", - "@noble/hashes": "1.3.2", - "@scure/bip32": "1.3.2", - "@scure/bip39": "1.2.1", - "abitype": "1.0.0", - "isows": "1.0.4", - "ws": "8.17.1" + "@adraffy/ens-normalize": "1.11.0", + "@noble/curves": "1.6.0", + "@noble/hashes": "1.5.0", + "@scure/bip32": "1.5.0", + "@scure/bip39": "1.4.0", + "abitype": "1.0.6", + "isows": "1.0.6", + "webauthn-p256": "0.0.10", + "ws": "8.18.0" }, "peerDependencies": { "typescript": ">=5.0.4" @@ -17277,45 +17232,45 @@ } }, "node_modules/viem/node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", "engines": { - "node": ">= 16" + "node": "^14.21.3 || >=16" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/viem/node_modules/@scure/bip32": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.2.tgz", - "integrity": "sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.5.0.tgz", + "integrity": "sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw==", "dependencies": { - "@noble/curves": "~1.2.0", - "@noble/hashes": "~1.3.2", - "@scure/base": "~1.1.2" + "@noble/curves": "~1.6.0", + "@noble/hashes": "~1.5.0", + "@scure/base": "~1.1.7" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/viem/node_modules/@scure/bip39": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", - "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.4.0.tgz", + "integrity": "sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw==", "dependencies": { - "@noble/hashes": "~1.3.0", - "@scure/base": "~1.1.0" + "@noble/hashes": "~1.5.0", + "@scure/base": "~1.1.8" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/viem/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "engines": { "node": ">=10.0.0" }, @@ -17350,6 +17305,32 @@ "defaults": "^1.0.3" } }, + "node_modules/webauthn-p256": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/webauthn-p256/-/webauthn-p256-0.0.10.tgz", + "integrity": "sha512-EeYD+gmIT80YkSIDb2iWq0lq2zbHo1CxHlQTeJ+KkCILWpVy3zASH3ByD4bopzfk0uCwXxLqKGLqp2W4O28VFA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "dependencies": { + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0" + } + }, + "node_modules/webauthn-p256/node_modules/@noble/hashes": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -17633,9 +17614,9 @@ }, "dependencies": { "@adraffy/ens-normalize": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", - "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==" + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz", + "integrity": "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==" }, "@ampproject/remapping": { "version": "2.2.0", @@ -19598,10 +19579,13 @@ "dev": true }, "@indexcoop/tokenlists": { - "version": "1.58.0", - "resolved": "https://registry.npmjs.org/@indexcoop/tokenlists/-/tokenlists-1.58.0.tgz", - "integrity": "sha512-lmQJ8yPpevg8HWJogDcnx7IjmbnfqbEIE6kbTmzQ9Nc00+O+/TwsMWiLE8rGjM5OIBF2HTf3d7oV8ruINaRaQQ==", - "requires": {} + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@indexcoop/tokenlists/-/tokenlists-3.1.5.tgz", + "integrity": "sha512-371RQO5Dm7w8wXA7EWt0ebGFVewiOxnQqf3P7+qA7whSaZNrdpDwPPy/4Nlzv1Wz+C7MacNQMRPZF361lzeQzw==", + "requires": { + "@uniswap/token-lists": "^1.0.0-beta.34", + "viem": "^2.21.14" + } }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -19938,17 +19922,17 @@ } }, "@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", + "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", "requires": { - "@noble/hashes": "1.3.2" + "@noble/hashes": "1.5.0" }, "dependencies": { "@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==" } } }, @@ -20335,9 +20319,9 @@ } }, "@scure/base": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz", - "integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==" + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==" }, "@scure/bip32": { "version": "1.1.0", @@ -20825,14 +20809,6 @@ "superstruct": "^1.0.4" }, "dependencies": { - "@noble/curves": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", - "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", - "requires": { - "@noble/hashes": "1.4.0" - } - }, "@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", @@ -21237,6 +21213,11 @@ } } }, + "@uniswap/token-lists": { + "version": "1.0.0-beta.34", + "resolved": "https://registry.npmjs.org/@uniswap/token-lists/-/token-lists-1.0.0-beta.34.tgz", + "integrity": "sha512-Hc3TfrFaupg0M84e/Zv7BoF+fmMWDV15mZ5s8ZQt2qZxUcNw2GQW+L6L/2k74who31G+p1m3GRYbJpAo7d1pqA==" + }, "@uniswap/v2-core": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.1.tgz", @@ -21310,9 +21291,9 @@ } }, "abitype": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.0.tgz", - "integrity": "sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.6.tgz", + "integrity": "sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A==", "requires": {} }, "acorn": { @@ -24548,9 +24529,9 @@ "requires": {} }, "isows": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.4.tgz", - "integrity": "sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", + "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", "requires": {} }, "issue-parser": { @@ -25102,7 +25083,8 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "js-yaml": { "version": "4.1.0", @@ -25368,15 +25350,6 @@ "integrity": "sha512-Ajzxb8CM6WAnFjgiloPsI3bF+WCxcvhdIG3KNA2KN962+tdBsHcuQ4k4qX/EcS/2CRkcc0iAkR956Nib6aXU/Q==", "dev": true }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, "lru_map": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", @@ -28053,25 +28026,6 @@ } } }, - "react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "peer": true, - "requires": { - "loose-envify": "^1.1.0" - } - }, - "react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "peer": true, - "requires": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - } - }, "react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -28480,15 +28434,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "peer": true, - "requires": { - "loose-envify": "^1.1.0" - } - }, "scrypt-js": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", @@ -29577,8 +29522,7 @@ "typescript": { "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "devOptional": true + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==" }, "uglify-js": { "version": "3.18.0", @@ -29741,48 +29685,49 @@ } }, "viem": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.15.1.tgz", - "integrity": "sha512-Vrveen3vDOJyPf8Q8TDyWePG2pTdK6IpSi4P6qlvAP+rXkAeqRvwYBy9AmGm+BeYpCETAyTT0SrCP6458XSt+w==", + "version": "2.21.35", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.21.35.tgz", + "integrity": "sha512-f3EFc5JILeA9veuNymUN8HG/nKP9ykC0NCgwFrZWuxcCc822GaP0IEnkRBsHGqmjwbz//FxJFmvtx7TBcdVs0A==", "requires": { - "@adraffy/ens-normalize": "1.10.0", - "@noble/curves": "1.2.0", - "@noble/hashes": "1.3.2", - "@scure/bip32": "1.3.2", - "@scure/bip39": "1.2.1", - "abitype": "1.0.0", - "isows": "1.0.4", - "ws": "8.17.1" + "@adraffy/ens-normalize": "1.11.0", + "@noble/curves": "1.6.0", + "@noble/hashes": "1.5.0", + "@scure/bip32": "1.5.0", + "@scure/bip39": "1.4.0", + "abitype": "1.0.6", + "isows": "1.0.6", + "webauthn-p256": "0.0.10", + "ws": "8.18.0" }, "dependencies": { "@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==" }, "@scure/bip32": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.2.tgz", - "integrity": "sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.5.0.tgz", + "integrity": "sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw==", "requires": { - "@noble/curves": "~1.2.0", - "@noble/hashes": "~1.3.2", - "@scure/base": "~1.1.2" + "@noble/curves": "~1.6.0", + "@noble/hashes": "~1.5.0", + "@scure/base": "~1.1.7" } }, "@scure/bip39": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", - "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.4.0.tgz", + "integrity": "sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw==", "requires": { - "@noble/hashes": "~1.3.0", - "@scure/base": "~1.1.0" + "@noble/hashes": "~1.5.0", + "@scure/base": "~1.1.8" } }, "ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "requires": {} } } @@ -29805,6 +29750,22 @@ "defaults": "^1.0.3" } }, + "webauthn-p256": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/webauthn-p256/-/webauthn-p256-0.0.10.tgz", + "integrity": "sha512-EeYD+gmIT80YkSIDb2iWq0lq2zbHo1CxHlQTeJ+KkCILWpVy3zASH3ByD4bopzfk0uCwXxLqKGLqp2W4O28VFA==", + "requires": { + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.4.0" + }, + "dependencies": { + "@noble/hashes": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==" + } + } + }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index dd8e11fd..40beae14 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "@ethersproject/contracts": "^5.6.2", "@ethersproject/providers": "^5.6.8", "@ethersproject/units": "^5.6.1", - "@indexcoop/tokenlists": "^1.58.0", + "@indexcoop/tokenlists": "^3.1.5", "@lifi/sdk": "3.0.0-beta.1", "@uniswap/sdk-core": "^5.3.1", "@uniswap/v3-sdk": "^3.13.1", diff --git a/src/constants/abis/FlashMintNav.json b/src/constants/abis/FlashMintNav.json new file mode 100644 index 00000000..2614de9e --- /dev/null +++ b/src/constants/abis/FlashMintNav.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"contract IController","name":"_setController","type":"address"},{"internalType":"contract INAVIssuanceModule","name":"_navIssuanceModule","type":"address"},{"components":[{"internalType":"address","name":"quickRouter","type":"address"},{"internalType":"address","name":"sushiRouter","type":"address"},{"internalType":"address","name":"uniV3Router","type":"address"},{"internalType":"address","name":"uniV3Quoter","type":"address"},{"internalType":"address","name":"curveAddressProvider","type":"address"},{"internalType":"address","name":"curveCalculator","type":"address"},{"internalType":"address","name":"balV2Vault","type":"address"},{"internalType":"address","name":"weth","type":"address"}],"internalType":"struct DEXAdapterV3.Addresses","name":"_dexAddresses","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_recipient","type":"address"},{"indexed":true,"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"indexed":true,"internalType":"contract IERC20","name":"_inputToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"_amountSetIssued","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_amountInputToken","type":"uint256"}],"name":"FlashMint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_recipient","type":"address"},{"indexed":true,"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"indexed":true,"internalType":"contract IERC20","name":"_outputToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"_amountSetRedeemed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_amountOutputToken","type":"uint256"}],"name":"FlashRedeem","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"},{"inputs":[],"name":"ETH_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"}],"name":"approveSetToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"dexAdapter","outputs":[{"internalType":"address","name":"quickRouter","type":"address"},{"internalType":"address","name":"sushiRouter","type":"address"},{"internalType":"address","name":"uniV3Router","type":"address"},{"internalType":"address","name":"uniV3Quoter","type":"address"},{"internalType":"address","name":"curveAddressProvider","type":"address"},{"internalType":"address","name":"curveCalculator","type":"address"},{"internalType":"address","name":"balV2Vault","type":"address"},{"internalType":"address","name":"weth","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"address","name":"_inputToken","type":"address"},{"internalType":"uint256","name":"_inputTokenAmount","type":"uint256"},{"components":[{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"uint24[]","name":"fees","type":"uint24[]"},{"internalType":"address","name":"pool","type":"address"},{"internalType":"bytes32[]","name":"poolIds","type":"bytes32[]"},{"internalType":"enum DEXAdapterV3.Exchange","name":"exchange","type":"uint8"}],"internalType":"struct DEXAdapterV3.SwapData","name":"_reserveAssetSwapData","type":"tuple"}],"name":"getIssueAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_setTokenAmount","type":"uint256"},{"internalType":"address","name":"_outputToken","type":"address"},{"components":[{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"uint24[]","name":"fees","type":"uint24[]"},{"internalType":"address","name":"pool","type":"address"},{"internalType":"bytes32[]","name":"poolIds","type":"bytes32[]"},{"internalType":"enum DEXAdapterV3.Exchange","name":"exchange","type":"uint8"}],"internalType":"struct DEXAdapterV3.SwapData","name":"_reserveAssetSwapData","type":"tuple"}],"name":"getRedeemAmountOut","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_minSetTokenAmount","type":"uint256"},{"internalType":"contract IERC20","name":"_inputToken","type":"address"},{"internalType":"uint256","name":"_inputTokenAmount","type":"uint256"},{"components":[{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"uint24[]","name":"fees","type":"uint24[]"},{"internalType":"address","name":"pool","type":"address"},{"internalType":"bytes32[]","name":"poolIds","type":"bytes32[]"},{"internalType":"enum DEXAdapterV3.Exchange","name":"exchange","type":"uint8"}],"internalType":"struct DEXAdapterV3.SwapData","name":"_reserveAssetSwapData","type":"tuple"}],"name":"issueSetFromExactERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_minSetTokenAmount","type":"uint256"},{"components":[{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"uint24[]","name":"fees","type":"uint24[]"},{"internalType":"address","name":"pool","type":"address"},{"internalType":"bytes32[]","name":"poolIds","type":"bytes32[]"},{"internalType":"enum DEXAdapterV3.Exchange","name":"exchange","type":"uint8"}],"internalType":"struct DEXAdapterV3.SwapData","name":"_reserveAssetSwapData","type":"tuple"}],"name":"issueSetFromExactETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"navIssuanceModule","outputs":[{"internalType":"contract INAVIssuanceModule","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_setTokenAmount","type":"uint256"},{"internalType":"contract IERC20","name":"_outputToken","type":"address"},{"internalType":"uint256","name":"_minOutputTokenAmount","type":"uint256"},{"components":[{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"uint24[]","name":"fees","type":"uint24[]"},{"internalType":"address","name":"pool","type":"address"},{"internalType":"bytes32[]","name":"poolIds","type":"bytes32[]"},{"internalType":"enum DEXAdapterV3.Exchange","name":"exchange","type":"uint8"}],"internalType":"struct DEXAdapterV3.SwapData","name":"_reserveAssetSwapData","type":"tuple"}],"name":"redeemExactSetForERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISetToken","name":"_setToken","type":"address"},{"internalType":"uint256","name":"_setTokenAmount","type":"uint256"},{"internalType":"uint256","name":"_minEthAmount","type":"uint256"},{"components":[{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"uint24[]","name":"fees","type":"uint24[]"},{"internalType":"address","name":"pool","type":"address"},{"internalType":"bytes32[]","name":"poolIds","type":"bytes32[]"},{"internalType":"enum DEXAdapterV3.Exchange","name":"exchange","type":"uint8"}],"internalType":"struct DEXAdapterV3.SwapData","name":"_reserveAssetSwapData","type":"tuple"}],"name":"redeemExactSetForETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"setController","outputs":[{"internalType":"contract IController","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20[]","name":"_tokens","type":"address[]"},{"internalType":"address payable","name":"_to","type":"address"}],"name":"withdrawTokens","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/src/constants/contracts.ts b/src/constants/contracts.ts index 73a9c38a..dc3771df 100644 --- a/src/constants/contracts.ts +++ b/src/constants/contracts.ts @@ -3,8 +3,10 @@ import { ChainId } from 'constants/chains' // eslint-disable-next-line @typescript-eslint/no-explicit-any export const Contracts: { [key: number]: any } = { [ChainId.Mainnet]: { + CustomOracleNavIssuanceModule: '0x2344674B23aD076908FD2396373CfE9cd48A1ba3', DebtIssuanceModuleV3: '0x86B7C605C03B9bbb0F6A25FBBb63baF15d875193', FlashMintHyEthV3: '0xCb1eEA349f25288627f008C5e2a69b684bddDf49', + FlashMintNav: '0x62F160391d2f1e3a176f32F51ADE6Ed8BB2ea1cf', FlashMintWrapped: '0x7ddE626dE8CE73229838B5c2F9A71bc7ac207801', }, [ChainId.Arbitrum]: { @@ -14,6 +16,7 @@ export const Contracts: { [key: number]: any } = { [ChainId.Base]: { DebtIssuanceModuleV3: '0xa30E87311407dDcF1741901A8F359b6005252F22', FlashMintLeveragedExtended: '0xE6c18c4C9FC6909EDa546649EBE33A8159256CBE', + FlashMintWrapped: '0xb929Ca7279B193d2B5428eED0AB685ECA9Ed567A', }, } diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts index ecde8fc0..7b680d3f 100644 --- a/src/constants/tokens.ts +++ b/src/constants/tokens.ts @@ -1,5 +1,6 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { getIndexTokenData } from '@indexcoop/tokenlists' +import { getTokenByChainAndSymbol } from '@indexcoop/tokenlists' + +import { ChainId } from 'constants/chains' export interface Token { symbol: string @@ -54,7 +55,7 @@ export const HighYieldETHIndex: Token = { } export const ic21: Token = { - ...getIndexTokenData('ic21', 1)!, + ...getTokenByChainAndSymbol(1, 'ic21'), } export const IndexCoopBitcoin2xIndex: Token = { @@ -108,7 +109,8 @@ export const RealWorldAssetIndex: Token = { } export const TheUSDCYieldIndex: Token = { - ...getIndexTokenData('icUSD', 1)!, + ...getTokenByChainAndSymbol(ChainId.Mainnet, 'icUSD'), + addressBase: getTokenByChainAndSymbol(ChainId.Base, 'icUSD').address, } // Other diff --git a/src/flashmint/builders/index.ts b/src/flashmint/builders/index.ts index 03c3132b..dc75cbf4 100644 --- a/src/flashmint/builders/index.ts +++ b/src/flashmint/builders/index.ts @@ -2,5 +2,6 @@ export * from './hyeth' export * from './interface' export * from './leveraged' export * from './leveraged-extended' +export * from './nav' export * from './wrapped' export * from './zeroex' diff --git a/src/flashmint/builders/leveraged-extended.test.ts b/src/flashmint/builders/leveraged-extended.test.ts index 90f41c5c..a838b8aa 100644 --- a/src/flashmint/builders/leveraged-extended.test.ts +++ b/src/flashmint/builders/leveraged-extended.test.ts @@ -5,12 +5,7 @@ import { ChainId } from 'constants/chains' import { Contracts } from 'constants/contracts' import { IndexCoopEthereum2xIndex, USDC, WETH } from 'constants/tokens' import { noopSwapData } from 'constants/swapdata' -import { - LocalhostProviderArbitrum, - LocalhostProviderBase, - LocalhostProviderUrlArbitrum, - LocalhostProviderUrlBase, -} from 'tests/utils' +import { getLocalHostProviderUrl } from 'tests/utils' import { getFlashMintLeveragedContractForToken } from 'utils/contracts' import { wei } from 'utils/numbers' import { Exchange } from 'utils' @@ -19,11 +14,12 @@ import { FlashMintLeveragedExtendedBuildRequest, LeveragedExtendedTransactionBuilder, } from './leveraged-extended' +import { getRpcProvider } from 'utils/rpc-provider' -const provider = LocalhostProviderArbitrum -const providerBase = LocalhostProviderBase -const rpcUrl = LocalhostProviderUrlArbitrum -const rpcUrlBase = LocalhostProviderUrlBase +const rpcUrl = getLocalHostProviderUrl(ChainId.Arbitrum) +const rpcUrlBase = getLocalHostProviderUrl(ChainId.Base) +const provider = getRpcProvider(rpcUrl) +const providerBase = getRpcProvider(rpcUrlBase) const eth = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' const usdcAddress = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' diff --git a/src/flashmint/builders/nav.test.ts b/src/flashmint/builders/nav.test.ts new file mode 100644 index 00000000..270ec49a --- /dev/null +++ b/src/flashmint/builders/nav.test.ts @@ -0,0 +1,247 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { BigNumber } from '@ethersproject/bignumber' + +import { ChainId } from 'constants/chains' +import { Contracts } from 'constants/contracts' +import { + HighYieldETHIndex, + TheUSDCYieldIndex, + USDC, + WETH, +} from 'constants/tokens' +import { LocalhostProvider, LocalhostProviderUrl } from 'tests/utils' +import { getFlashMintNavContract } from 'utils/contracts' +import { wei } from 'utils/numbers' +import { Exchange } from 'utils' + +import { + FlashMintNavBuildRequest, + FlashMintNavTransactionBuilder, +} from 'flashmint/builders/nav' + +const provider = LocalhostProvider +const rpcUrl = LocalhostProviderUrl + +const eth = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' +const usdcAddress = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' + +const FlashMintNavAddress = Contracts[ChainId.Mainnet].FlashMintNav + +describe('FlashMintNavTransactionBuilder()', () => { + const contract = getFlashMintNavContract(provider) + + test('returns null for invalid request (no index token)', async () => { + const buildRequest = createBuildRequest() + buildRequest.isMinting = true + buildRequest.outputToken = '' + const builder = new FlashMintNavTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + expect(tx).toBeNull() + }) + + test('returns null for invalid request (no input/output token)', async () => { + const buildRequest = createBuildRequest() + buildRequest.inputToken = '' + const builder = new FlashMintNavTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + expect(tx).toBeNull() + }) + + test('returns null for invalid request (inputTokenAmount = 0)', async () => { + const buildRequest = createBuildRequest() + buildRequest.outputTokenAmount = BigNumber.from(0) + const builder = new FlashMintNavTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + expect(tx).toBeNull() + }) + + test('returns null for invalid request (outputTokenAmount = 0)', async () => { + const buildRequest = createBuildRequest() + buildRequest.inputTokenAmount = BigNumber.from(0) + const builder = new FlashMintNavTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + expect(tx).toBeNull() + }) + + test('returns null for invalid request (invalid swap data debt collateral - no pool)', async () => { + const buildRequest = createBuildRequest() + buildRequest.reserveAssetSwapData = { + exchange: 1, + path: ['', ''], + fees: [], + poolIds: [], + pool: '', + } + const builder = new FlashMintNavTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + expect(tx).toBeNull() + }) + + test('returns null for invalid request (invalid swap data input/output token - no paths)', async () => { + const buildRequest = createBuildRequest() + buildRequest.reserveAssetSwapData = { + exchange: 1, + path: [], + fees: [], + poolIds: [], + pool: '0x0000000000000000000000000000000000000000', + } + const builder = new FlashMintNavTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + expect(tx).toBeNull() + }) + + test('returns null for invalid request (invalid swap data input/output token - univ3 fees)', async () => { + const buildRequest = createBuildRequest() + buildRequest.reserveAssetSwapData = { + exchange: 3, + path: ['', '', ''], + // For UniV3 fees.length has to be path.length - 1 + fees: [3000], + poolIds: [], + pool: '0x0000000000000000000000000000000000000000', + } + const builder = new FlashMintNavTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + expect(tx).toBeNull() + }) + + test('returns null for invalid request (invalid path - exchange type none)', async () => { + const buildRequest = createBuildRequest() + buildRequest.reserveAssetSwapData = { + exchange: 0, + path: [], + fees: [500], + poolIds: [], + pool: '0x00000000000000000000000000000000000000', + } + const builder = new FlashMintNavTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + expect(tx).toBeNull() + }) + + test('returns tx for correct swap data with exchange type none', async () => { + const buildRequest = createBuildRequest() + buildRequest.reserveAssetSwapData = { + exchange: 0, + path: [], + fees: [500], + poolIds: [], + pool: '0x0000000000000000000000000000000000000000', + } + const builder = new FlashMintNavTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + expect(tx).not.toBeNull() + }) + + test('returns a tx for minting icUSD (ERC20)', async () => { + const buildRequest = createBuildRequest() + buildRequest.isMinting = true + const indexToken = buildRequest.outputToken + const refTx = await contract.populateTransaction.issueSetFromExactERC20( + indexToken, + buildRequest.outputTokenAmount, + buildRequest.inputToken, + buildRequest.inputTokenAmount, + buildRequest.reserveAssetSwapData + ) + const builder = new FlashMintNavTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + if (!tx) fail() + expect(tx.to).toBe(FlashMintNavAddress) + expect(tx.data).toEqual(refTx.data) + }) + + test('returns a tx for minting icUSD (ETH)', async () => { + const buildRequest = createBuildRequest(true, eth, 'ETH') + const indexToken = buildRequest.outputToken + const refTx = await contract.populateTransaction.issueSetFromExactETH( + indexToken, + buildRequest.outputTokenAmount, + buildRequest.reserveAssetSwapData, + { value: buildRequest.inputTokenAmount } + ) + const builder = new FlashMintNavTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + if (!tx) fail() + expect(tx.to).toBe(FlashMintNavAddress) + expect(tx.data).toEqual(refTx.data) + expect(tx.value).toEqual(buildRequest.inputTokenAmount) + }) + + test('returns a tx for redeeming icUSD (ERC20)', async () => { + const buildRequest = createBuildRequest( + false, + TheUSDCYieldIndex.address!, + TheUSDCYieldIndex.symbol, + usdcAddress, + 'USDC' + ) + const refTx = await contract.populateTransaction.redeemExactSetForERC20( + buildRequest.inputToken, + buildRequest.inputTokenAmount, + buildRequest.outputToken, + buildRequest.outputTokenAmount, + buildRequest.reserveAssetSwapData + ) + const builder = new FlashMintNavTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + if (!tx) fail() + expect(tx.to).toBe(FlashMintNavAddress) + expect(tx.data).toEqual(refTx.data) + }) + + test('returns a tx for redeeming icUSD (ETH)', async () => { + const buildRequest = createBuildRequest( + false, + TheUSDCYieldIndex.address!, + TheUSDCYieldIndex.symbol, + eth, + 'ETH' + ) + const refTx = await contract.populateTransaction.redeemExactSetForETH( + buildRequest.inputToken, + buildRequest.inputTokenAmount, + buildRequest.outputTokenAmount, + buildRequest.reserveAssetSwapData + ) + const builder = new FlashMintNavTransactionBuilder(rpcUrl) + const tx = await builder.build(buildRequest) + if (!tx) fail() + expect(tx.to).toBe(FlashMintNavAddress) + expect(tx.data).toEqual(refTx.data) + }) +}) + +function createBuildRequest( + isMinting = true, + inputToken: string = usdcAddress, + inputTokenSymbol = 'USDC', + outputToken: string = HighYieldETHIndex.address!, + outputTokenSymbol: string = HighYieldETHIndex.symbol +): FlashMintNavBuildRequest { + const inputSwapData = { + exchange: Exchange.UniV3, + path: [USDC.address!, WETH.address!], + fees: [500], + poolIds: [], + pool: '0xDC24316b9AE028F1497c275EB9192a3Ea0f67022', + } + const outputSwapData = { + exchange: Exchange.UniV3, + path: [WETH.address!, USDC.address!], + fees: [500], + poolIds: [], + pool: '0xDC24316b9AE028F1497c275EB9192a3Ea0f67022', + } + return { + isMinting, + inputToken, + inputTokenSymbol, + outputToken, + outputTokenSymbol, + inputTokenAmount: BigNumber.from(194235680), + outputTokenAmount: wei(1), + reserveAssetSwapData: isMinting ? inputSwapData : outputSwapData, + } +} diff --git a/src/flashmint/builders/nav.ts b/src/flashmint/builders/nav.ts new file mode 100644 index 00000000..d40c4863 --- /dev/null +++ b/src/flashmint/builders/nav.ts @@ -0,0 +1,117 @@ +import { TransactionRequest } from '@ethersproject/abstract-provider' +import { BigNumber } from '@ethersproject/bignumber' + +import { getRpcProvider } from 'utils/rpc-provider' +import { Exchange, SwapDataV3 } from 'utils/swap-data' + +import { getFlashMintNavContract } from '../../utils/contracts' +import { TransactionBuilder } from './interface' +import { isEmptyString, isInvalidAmount } from './utils' + +export interface FlashMintNavBuildRequest { + isMinting: boolean + inputToken: string + inputTokenSymbol: string + outputToken: string + outputTokenSymbol: string + inputTokenAmount: BigNumber + outputTokenAmount: BigNumber + reserveAssetSwapData: SwapDataV3 +} + +export class FlashMintNavTransactionBuilder + implements TransactionBuilder +{ + constructor(private readonly rpcUrl: string) {} + + async build( + request: FlashMintNavBuildRequest + ): Promise { + if (!this.isValidRequest(request)) return null + const provider = getRpcProvider(this.rpcUrl) + const { + inputToken, + inputTokenAmount, + inputTokenSymbol, + outputToken, + outputTokenAmount, + outputTokenSymbol, + isMinting, + reserveAssetSwapData, + } = request + const contract = getFlashMintNavContract(provider) + const indexTokenAmount = isMinting ? outputTokenAmount : inputTokenAmount + if (isMinting) { + const inputTokenIsEth = inputTokenSymbol === 'ETH' + if (inputTokenIsEth) { + return await contract.populateTransaction.issueSetFromExactETH( + outputToken, + indexTokenAmount, // _minSetTokenAmount + reserveAssetSwapData, + { value: inputTokenAmount } + ) + } else { + return await contract.populateTransaction.issueSetFromExactERC20( + outputToken, + indexTokenAmount, // _minSetTokenAmount + inputToken, + inputTokenAmount, // _maxAmountInputToken + reserveAssetSwapData + ) + } + } else { + const outputTokenIsEth = outputTokenSymbol === 'ETH' + if (outputTokenIsEth) { + return await contract.populateTransaction.redeemExactSetForETH( + inputToken, + indexTokenAmount, + outputTokenAmount, // _minEthAmount + reserveAssetSwapData + ) + } else { + return await contract.populateTransaction.redeemExactSetForERC20( + inputToken, + indexTokenAmount, + outputToken, + outputTokenAmount, // _minOutputTokenAmount + reserveAssetSwapData + ) + } + } + } + + private isValidSwapData(swapData: SwapDataV3): boolean { + if (swapData.exchange === Exchange.None) { + if (swapData.pool.length !== 42) return false + return true + } + if ( + swapData.exchange === Exchange.UniV3 && + swapData.fees.length !== swapData.path.length - 1 + ) + return false + if (swapData.path.length === 0) return false + if (swapData.pool.length !== 42) return false + return true + } + + private isValidRequest(request: FlashMintNavBuildRequest): boolean { + const { + inputToken, + inputTokenSymbol, + outputToken, + outputTokenSymbol, + inputTokenAmount, + outputTokenAmount, + reserveAssetSwapData, + } = request + if (isEmptyString(inputToken)) return false + if (isEmptyString(outputToken)) return false + if (isEmptyString(inputTokenSymbol)) return false + if (isEmptyString(outputTokenSymbol)) return false + if (isInvalidAmount(inputTokenAmount)) return false + if (isInvalidAmount(outputTokenAmount)) return false + if (!this.isValidSwapData(reserveAssetSwapData)) return false + return true + } +} diff --git a/src/flashmint/builders/wrapped.test.ts b/src/flashmint/builders/wrapped.test.ts index be0e2ed3..b8048d8f 100644 --- a/src/flashmint/builders/wrapped.test.ts +++ b/src/flashmint/builders/wrapped.test.ts @@ -2,7 +2,7 @@ import { BigNumber } from '@ethersproject/bignumber' import { ChainId } from 'constants/chains' import { Contracts } from 'constants/contracts' -import { LocalhostProviderUrl, QuoteTokens } from 'tests/utils' +import { getLocalHostProviderUrl } from 'tests/utils' import { getFlashMintWrappedContract } from 'utils/contracts' import { wei } from 'utils/numbers' import { getRpcProvider } from 'utils/rpc-provider' @@ -12,14 +12,16 @@ import { FlashMintWrappedBuildRequest, WrappedTransactionBuilder, } from './wrapped' +import { getTokenByChainAndSymbol } from '@indexcoop/tokenlists' -const rpcUrl = LocalhostProviderUrl -const ZERO_BYTES = '0x0000000000000000000000000000000000000000' +const chainId = ChainId.Base +const rpcUrl = getLocalHostProviderUrl(chainId) -const FlashMintWrappedAddress = Contracts[ChainId.Mainnet].FlashMintWrapped +const FlashMintWrappedAddress = Contracts[chainId].FlashMintWrapped const eth = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' -const indexToken = QuoteTokens.icusd.address -const usdcAddress = QuoteTokens.usdc.address +const indexToken = getTokenByChainAndSymbol(chainId, 'icUSD').address +const usdcAddress = getTokenByChainAndSymbol(chainId, 'USDC').address +const ZERO_BYTES = '0x0000000000000000000000000000000000000000' describe('WrappedTransactionBuilder()', () => { test('returns null for invalid request (no index token)', async () => { @@ -78,7 +80,7 @@ describe('WrappedTransactionBuilder()', () => { expect(tx).toBeNull() }) - test('returns a tx for icUSD MMI (ERC20)', async () => { + test('returns a tx for icUSD (ERC20)', async () => { const buildRequest = getDefaultBuildRequest() const provider = getRpcProvider(rpcUrl) const contract = getFlashMintWrappedContract(provider) @@ -164,6 +166,7 @@ function getDefaultBuildRequest( wrapData: ZERO_BYTES, } return { + chainId: ChainId.Base, isMinting, indexToken, inputOutputToken, diff --git a/src/flashmint/builders/wrapped.ts b/src/flashmint/builders/wrapped.ts index 1196303d..909fdbc9 100644 --- a/src/flashmint/builders/wrapped.ts +++ b/src/flashmint/builders/wrapped.ts @@ -11,6 +11,7 @@ import { TransactionBuilder } from './interface' import { isEmptyString, isInvalidAmount } from './utils' export interface FlashMintWrappedBuildRequest { + chainId: number isMinting: boolean indexToken: string inputOutputToken: string @@ -34,6 +35,7 @@ export class WrappedTransactionBuilder if (!isValidRequest) return null const provider = getRpcProvider(this.rpcUrl) const { + chainId, componentSwapData, componentWrapData, indexToken, @@ -44,7 +46,7 @@ export class WrappedTransactionBuilder isMinting, } = request const inputOutputTokenIsEth = inputOutputTokenSymbol === 'ETH' - const contract = getFlashMintWrappedContract(provider) + const contract = getFlashMintWrappedContract(provider, chainId) let tx: PopulatedTransaction | null = null if (isMinting) { if (inputOutputTokenIsEth) { diff --git a/src/quote/flashmint/hyeth/component-quotes/pendle.ts b/src/quote/flashmint/hyeth/component-quotes/pendle.ts index e362d7df..9d3471f2 100644 --- a/src/quote/flashmint/hyeth/component-quotes/pendle.ts +++ b/src/quote/flashmint/hyeth/component-quotes/pendle.ts @@ -57,14 +57,12 @@ export class PendleQuoteProvider { const fmHyEth = this.getFlashMintHyEth() const market = await fmHyEth.pendleMarkets(component) const marketData = await fmHyEth.pendleMarketData(market) - console.log(marketData) const ptContract = this.getPtContract(component) const sy = await ptContract.SY() const syContract = this.getSyContract(sy) const routerContract = this.getRouterStatic(this.routerStaticMainnet) const assetRate: BigNumber = await routerContract.getPtToAssetRate(market) let ethAmount = (position * assetRate.toBigInt()) / BigInt(1e18) - console.log(component, syContract.address, ethAmount.toString()) const syAmountPreview: BigNumber = await syContract.previewDeposit( AddressZero, ethAmount @@ -82,7 +80,6 @@ export class PendleQuoteProvider { outputAmount: ethAmount.toString(), }) if (!quote) return null - console.log('quote', component, quote.inputAmount) return BigInt(quote.inputAmount) } catch (error) { console.log(component) diff --git a/src/quote/flashmint/hyeth/provider.test.ts b/src/quote/flashmint/hyeth/provider.test.ts index 93b50719..f8f39765 100644 --- a/src/quote/flashmint/hyeth/provider.test.ts +++ b/src/quote/flashmint/hyeth/provider.test.ts @@ -1,20 +1,21 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { AddressZero, EthAddress } from 'constants/addresses' +import { ChainId } from 'constants/chains' import { noopSwapData } from 'constants/swapdata' import { USDC, WETH } from 'constants/tokens' import { wei } from 'utils/numbers' import { Exchange } from 'utils' import { - IndexZeroExSwapQuoteProvider, - LocalhostProviderUrl, + getLocalHostProviderUrl, + getZeroExSwapQuoteProvider, QuoteTokens, } from 'tests/utils' import { FlashMintHyEthQuoteProvider } from './provider' -const rpcUrl = LocalhostProviderUrl -const swapQuoteProvider = IndexZeroExSwapQuoteProvider +const rpcUrl = getLocalHostProviderUrl(ChainId.Mainnet) +const swapQuoteProvider = getZeroExSwapQuoteProvider(ChainId.Mainnet) const { eth, hyeth, usdc, weth } = QuoteTokens const indexToken = hyeth diff --git a/src/quote/flashmint/hyeth/provider.ts b/src/quote/flashmint/hyeth/provider.ts index 68653498..380ade91 100644 --- a/src/quote/flashmint/hyeth/provider.ts +++ b/src/quote/flashmint/hyeth/provider.ts @@ -88,10 +88,6 @@ export class FlashMintHyEthQuoteProvider slippage, isMinting ) - console.log( - inputOutputTokenAmount.toString(), - quoteResult.inputOutputTokenAmount.toString() - ) return { indexTokenAmount, inputOutputTokenAmount: inputOutputTokenAmount.toBigInt(), diff --git a/src/quote/flashmint/leveraged-extended/provider.test.ts b/src/quote/flashmint/leveraged-extended/provider.test.ts index aed084ae..9a9b7414 100644 --- a/src/quote/flashmint/leveraged-extended/provider.test.ts +++ b/src/quote/flashmint/leveraged-extended/provider.test.ts @@ -1,23 +1,21 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { ChainId } from 'constants/chains' import { noopSwapData } from 'constants/swapdata' import { IndexCoopEthereum2xIndex, USDC, WETH } from 'constants/tokens' import { Exchange } from 'utils' import { wei } from 'utils/numbers' import { - IndexZeroExSwapQuoteProviderArbitrum, - IndexZeroExSwapQuoteProviderBase, - LocalhostProviderUrlArbitrum, - LocalhostProviderUrlBase, + getLocalHostProviderUrl, + getZeroExSwapQuoteProvider, QuoteTokens, } from 'tests/utils' import { LeveragedExtendedQuoteProvider } from './provider' - -const rpcUrl = LocalhostProviderUrlArbitrum -const rpcUrlBase = LocalhostProviderUrlBase -const swapQuoteProvider = IndexZeroExSwapQuoteProviderArbitrum -const swapQuoteProviderBase = IndexZeroExSwapQuoteProviderBase +const rpcUrl = getLocalHostProviderUrl(ChainId.Arbitrum) +const rpcUrlBase = getLocalHostProviderUrl(ChainId.Base) +const swapQuoteProvider = getZeroExSwapQuoteProvider(ChainId.Arbitrum) +const swapQuoteProviderBase = getZeroExSwapQuoteProvider(ChainId.Base) const { eth, usdc } = QuoteTokens diff --git a/src/quote/flashmint/leveraged-extended/provider.ts b/src/quote/flashmint/leveraged-extended/provider.ts index 7273c88b..2bf0af36 100644 --- a/src/quote/flashmint/leveraged-extended/provider.ts +++ b/src/quote/flashmint/leveraged-extended/provider.ts @@ -52,7 +52,6 @@ export class LeveragedExtendedQuoteProvider const sources = [Exchange.Sushiswap, Exchange.UniV3] const network = await provider.getNetwork() const chainId = network.chainId - console.log('chainId:', chainId) const leveragedTokenData = await getLeveragedTokenData( indexToken.address, indexTokenAmount, diff --git a/src/quote/flashmint/leveraged/provider.test.ts b/src/quote/flashmint/leveraged/provider.test.ts index 8a70c9e6..eefe701e 100644 --- a/src/quote/flashmint/leveraged/provider.test.ts +++ b/src/quote/flashmint/leveraged/provider.test.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { ChainId } from 'constants/chains' import { collateralDebtSwapData, debtCollateralSwapData, @@ -7,15 +8,15 @@ import { } from 'constants/swapdata' import { wei } from 'utils/numbers' import { - IndexZeroExSwapQuoteProvider, - LocalhostProviderUrl, + getLocalHostProviderUrl, + getZeroExSwapQuoteProvider, QuoteTokens, } from 'tests/utils' import { LeveragedQuoteProvider } from './provider' -const rpcUrl = LocalhostProviderUrl -const swapQuoteProvider = IndexZeroExSwapQuoteProvider +const rpcUrl = getLocalHostProviderUrl(ChainId.Mainnet) +const swapQuoteProvider = getZeroExSwapQuoteProvider(ChainId.Mainnet) const { eth, iceth } = QuoteTokens diff --git a/src/quote/flashmint/nav/index.ts b/src/quote/flashmint/nav/index.ts new file mode 100644 index 00000000..6f29423e --- /dev/null +++ b/src/quote/flashmint/nav/index.ts @@ -0,0 +1 @@ +export * from './provider' diff --git a/src/quote/flashmint/nav/provider.test.ts b/src/quote/flashmint/nav/provider.test.ts new file mode 100644 index 00000000..53244490 --- /dev/null +++ b/src/quote/flashmint/nav/provider.test.ts @@ -0,0 +1,137 @@ +import { getTokenByChainAndSymbol } from '@indexcoop/tokenlists' + +import { AddressZero } from 'constants/addresses' +import { ChainId } from 'constants/chains' +import { + getLocalHostProviderUrl, + getZeroExSwapQuoteProvider, + QuoteTokens, +} from 'tests/utils' +import { wei } from 'utils/numbers' +import { Exchange, isSameAddress } from 'utils' + +import { FlashMintNavQuoteRequest, FlashMintNavQuoteProvider } from './provider' + +describe('FlashMintNavQuoteProvider()', () => { + const { usdc, weth } = QuoteTokens + const chainId = 1 + const indexToken = getTokenByChainAndSymbol(chainId, 'icUSD') + const provider = getLocalHostProviderUrl(ChainId.Mainnet) + const swapQuoteProvider = getZeroExSwapQuoteProvider(ChainId.Mainnet) + + test('returns a quote for minting icUSD', async () => { + const request: FlashMintNavQuoteRequest = { + chainId, + isMinting: true, + inputToken: usdc, + outputToken: indexToken, + inputTokenAmount: wei(100, 6), + slippage: 0.5, + } + const quoteProvider = new FlashMintNavQuoteProvider( + provider, + swapQuoteProvider + ) + const quote = await quoteProvider.getQuote(request) + if (!quote) fail() + expect(quote.inputTokenAmount).toEqual(request.inputTokenAmount) + expect(quote.outputTokenAmount.gt(0)).toEqual(true) + expect(quote.reserveAssetSwapData).toEqual({ + exchange: Exchange.None, + fees: [], + path: [], + poolIds: [], + pool: AddressZero, + }) + }) + + test('returns a quote for minting icUSD w/ WETH', async () => { + const request: FlashMintNavQuoteRequest = { + chainId, + isMinting: true, + inputToken: weth, + outputToken: indexToken, + inputTokenAmount: wei(1), + slippage: 0.5, + } + const quoteProvider = new FlashMintNavQuoteProvider( + provider, + swapQuoteProvider + ) + const quote = await quoteProvider.getQuote(request) + if (!quote) fail() + expect(quote.inputTokenAmount).toEqual(request.inputTokenAmount) + expect(quote.outputTokenAmount.gt(0)).toEqual(true) + // Testing for individual params as changing market conditions could affect + // the swap data contents and the test results + const { reserveAssetSwapData } = quote + expect(reserveAssetSwapData.exchange).not.toBe(Exchange.None) + expect(reserveAssetSwapData.fees.length).toBeGreaterThanOrEqual(1) + expect(isSameAddress(reserveAssetSwapData.path[0], weth.address)).toBe(true) + expect( + isSameAddress( + reserveAssetSwapData.path[reserveAssetSwapData.path.length - 1], + usdc.address + ) + ).toBe(true) + expect(reserveAssetSwapData.poolIds).toEqual([]) + }) + + test('returns a quote redeeming icUSD for USDC', async () => { + const request: FlashMintNavQuoteRequest = { + chainId, + isMinting: false, + inputToken: indexToken, + outputToken: usdc, + inputTokenAmount: wei(1), + slippage: 0.5, + } + const quoteProvider = new FlashMintNavQuoteProvider( + provider, + swapQuoteProvider + ) + const quote = await quoteProvider.getQuote(request) + if (!quote) fail() + expect(quote.inputTokenAmount).toEqual(request.inputTokenAmount) + expect(quote.outputTokenAmount.gt(0)).toEqual(true) + expect(quote.reserveAssetSwapData).toEqual({ + exchange: Exchange.None, + fees: [], + path: [], + poolIds: [], + pool: AddressZero, + }) + }) + + test('returns a quote for redeeming icUSD for WETH', async () => { + const request: FlashMintNavQuoteRequest = { + chainId, + isMinting: false, + inputToken: indexToken, + outputToken: weth, + inputTokenAmount: wei(1), + slippage: 0.5, + } + const quoteProvider = new FlashMintNavQuoteProvider( + provider, + swapQuoteProvider + ) + const quote = await quoteProvider.getQuote(request) + if (!quote) fail() + expect(quote.inputTokenAmount).toEqual(request.inputTokenAmount) + expect(quote.outputTokenAmount.gt(0)).toEqual(true) + // Testing for individual params as changing market conditions could affect + // the swap data contents and the test results + const { reserveAssetSwapData } = quote + expect(reserveAssetSwapData.exchange).not.toBe(Exchange.None) + expect(reserveAssetSwapData.fees.length).toBeGreaterThanOrEqual(1) + expect(isSameAddress(reserveAssetSwapData.path[0], usdc.address)).toBe(true) + expect( + isSameAddress( + reserveAssetSwapData.path[reserveAssetSwapData.path.length - 1], + weth.address + ) + ).toBe(true) + expect(reserveAssetSwapData.poolIds).toEqual([]) + }) +}) diff --git a/src/quote/flashmint/nav/provider.ts b/src/quote/flashmint/nav/provider.ts new file mode 100644 index 00000000..9c8cddcc --- /dev/null +++ b/src/quote/flashmint/nav/provider.ts @@ -0,0 +1,131 @@ +import { BigNumber } from '@ethersproject/bignumber' +import { getTokenByChainAndSymbol } from '@indexcoop/tokenlists' +import { Address } from 'viem' + +import { AddressZero } from 'constants/addresses' +import { SwapQuoteProvider } from 'quote/swap' +import { + Exchange, + getFlashMintNavContract, + isSameAddress, + slippageAdjustedTokenAmount, + SwapDataV3, +} from 'utils' +import { getExpectedReserveRedeemQuantity } from 'utils/custom-oracle-nav-issuance-module' +import { getRpcProvider } from 'utils/rpc-provider' + +import { QuoteProvider, QuoteToken } from '../../interfaces' + +export interface FlashMintNavQuoteRequest { + chainId: number + isMinting: boolean + inputToken: QuoteToken + outputToken: QuoteToken + // In contrast to other quote providers, we always need the input amount (not the index amount) + inputTokenAmount: BigNumber + slippage: number +} + +export interface FlashMintNavQuoteQuote { + inputTokenAmount: BigNumber + outputTokenAmount: BigNumber + reserveAssetSwapData: SwapDataV3 +} + +export class FlashMintNavQuoteProvider + implements QuoteProvider +{ + constructor( + private readonly rpcUrl: string, + private readonly swapQuoteProvider: SwapQuoteProvider + ) {} + + async getQuote( + request: FlashMintNavQuoteRequest + ): Promise { + const { + chainId, + inputToken, + inputTokenAmount, + isMinting, + outputToken, + slippage, + } = request + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const usdc = getTokenByChainAndSymbol(chainId, 'USDC')!.address + + const swapQuoteRequest = { + chainId, + inputToken: isMinting ? inputToken.address : usdc, + outputToken: isMinting ? usdc : outputToken.address, + inputAmount: inputTokenAmount.toString(), + // TODO: + sources: [Exchange.UniV3], + slippage, + } + + let reserveAssetSwapData: SwapDataV3 = { + exchange: Exchange.None, + fees: [], + path: [], + poolIds: [], + pool: AddressZero, + } + if ( + !isSameAddress(swapQuoteRequest.inputToken, swapQuoteRequest.outputToken) + ) { + if (!isMinting) { + const indexToken = isMinting ? outputToken.address : inputToken.address + // When redeeming we need to swap the reserve asset (USDC) for the user's + // chosen output token (ex. WETH). So the `inputAmount` determines how + // much USDC we need to swap into WETH. + const usdcAmountToSwap = await getExpectedReserveRedeemQuantity( + chainId, + indexToken as Address, + usdc as Address, + inputTokenAmount.toBigInt() + ) + swapQuoteRequest.inputAmount = usdcAmountToSwap.toString() + } + const res = await this.swapQuoteProvider.getSwapQuote(swapQuoteRequest) + if (!res || !res?.swapData) return null + reserveAssetSwapData = { + ...res.swapData, + poolIds: [], + } + } + + let estimatedOutputAmount: BigNumber = BigNumber.from(0) + const provider = getRpcProvider(this.rpcUrl) + const contract = getFlashMintNavContract(provider) + if (isMinting) { + estimatedOutputAmount = await contract.callStatic.getIssueAmount( + outputToken.address, + inputToken.address, + inputTokenAmount, + reserveAssetSwapData + ) + } else { + estimatedOutputAmount = await contract.callStatic.getRedeemAmountOut( + inputToken.address, + inputTokenAmount, + outputToken.address, + reserveAssetSwapData + ) + } + const outputTokenAmount = slippageAdjustedTokenAmount( + estimatedOutputAmount, + outputToken.decimals, + slippage, + // Usually, this function is used to have either input/output amount slippage + // adjusted but since FlastMintNav only uses an input amount, we always have + // the output amount as result. So we always want to substract slippage. + false + ) + return { + inputTokenAmount, + outputTokenAmount, + reserveAssetSwapData, + } + } +} diff --git a/src/quote/flashmint/wrapped/provider.test.ts b/src/quote/flashmint/wrapped/provider.test.ts index dfee220e..32da61be 100644 --- a/src/quote/flashmint/wrapped/provider.test.ts +++ b/src/quote/flashmint/wrapped/provider.test.ts @@ -1,19 +1,24 @@ +import { getTokenByChainAndSymbol } from '@indexcoop/tokenlists' + +import { ChainId } from 'constants/chains' import { - IndexZeroExSwapQuoteProvider, - LocalhostProviderUrl, - QuoteTokens, + getLocalHostProviderUrl, + getZeroExSwapQuoteProvider, } from 'tests/utils' import { wei } from 'utils/numbers' + import { FlashMintWrappedQuoteRequest, WrappedQuoteProvider } from '.' -const { icusd, usdc, weth } = QuoteTokens -const indexToken = icusd -const chainId = 1 -const provider = LocalhostProviderUrl -const swapQuoteProvider = IndexZeroExSwapQuoteProvider +const chainId = ChainId.Base +const indexToken = getTokenByChainAndSymbol(chainId, 'icUSD') +const usdc = getTokenByChainAndSymbol(chainId, 'USDC') +const weth = getTokenByChainAndSymbol(chainId, 'WETH') +const provider = getLocalHostProviderUrl(chainId) +const swapQuoteProvider = getZeroExSwapQuoteProvider(chainId) describe('WrappedQuoteProvider()', () => { - test('returns a quote for minting icUSD', async () => { + const expectedSwapDataLength = 8 + test('returns a quote for minting icUSD w/ USDC', async () => { const inputToken = usdc const request: FlashMintWrappedQuoteRequest = { chainId, @@ -28,8 +33,8 @@ describe('WrappedQuoteProvider()', () => { if (!quote) fail() expect(quote.indexTokenAmount).toEqual(request.indexTokenAmount) expect(quote.inputOutputTokenAmount.gt(0)).toEqual(true) - expect(quote.componentSwapData.length).toEqual(1) - expect(quote.componentWrapData.length).toEqual(1) + expect(quote.componentSwapData.length).toEqual(expectedSwapDataLength) + expect(quote.componentWrapData.length).toEqual(expectedSwapDataLength) }) test('returns a quote for minting icUSD w/ WETH', async () => { @@ -47,8 +52,8 @@ describe('WrappedQuoteProvider()', () => { if (!quote) fail() expect(quote.indexTokenAmount).toEqual(request.indexTokenAmount) expect(quote.inputOutputTokenAmount.gt(0)).toEqual(true) - expect(quote.componentSwapData.length).toEqual(1) - expect(quote.componentWrapData.length).toEqual(1) + expect(quote.componentSwapData.length).toEqual(expectedSwapDataLength) + expect(quote.componentWrapData.length).toEqual(expectedSwapDataLength) }) test('returns a quote redeeming icUSD for USDC', async () => { @@ -66,8 +71,8 @@ describe('WrappedQuoteProvider()', () => { if (!quote) fail() expect(quote.indexTokenAmount).toEqual(request.indexTokenAmount) expect(quote.inputOutputTokenAmount.gt(0)).toEqual(true) - expect(quote.componentSwapData.length).toEqual(1) - expect(quote.componentWrapData.length).toEqual(1) + expect(quote.componentSwapData.length).toEqual(expectedSwapDataLength) + expect(quote.componentWrapData.length).toEqual(expectedSwapDataLength) }) test('returns a quote for redeeming icUSD for WETH', async () => { @@ -85,7 +90,7 @@ describe('WrappedQuoteProvider()', () => { if (!quote) fail() expect(quote.indexTokenAmount).toEqual(request.indexTokenAmount) expect(quote.inputOutputTokenAmount.gt(0)).toEqual(true) - expect(quote.componentSwapData.length).toEqual(1) - expect(quote.componentWrapData.length).toEqual(1) + expect(quote.componentSwapData.length).toEqual(expectedSwapDataLength) + expect(quote.componentWrapData.length).toEqual(expectedSwapDataLength) }) }) diff --git a/src/quote/flashmint/wrapped/provider.ts b/src/quote/flashmint/wrapped/provider.ts index 35d48559..74e8ddfc 100644 --- a/src/quote/flashmint/wrapped/provider.ts +++ b/src/quote/flashmint/wrapped/provider.ts @@ -78,7 +78,7 @@ export class WrappedQuoteProvider if (componentSwapData.length !== componentWrapData.length) return null let estimatedInputOutputAmount: BigNumber = BigNumber.from(0) const provider = getRpcProvider(this.rpcUrl) - const contract = getFlashMintWrappedContract(provider) + const contract = getFlashMintWrappedContract(provider, chainId) if (isMinting) { estimatedInputOutputAmount = await contract.callStatic.getIssueExactSet( indexToken.address, @@ -94,6 +94,7 @@ export class WrappedQuoteProvider componentSwapData ) } + // Apply slippage to the quote amount (minting: inputToken, redeeming: outputToken) const inputOutputTokenAmount = slippageAdjustedTokenAmount( estimatedInputOutputAmount, isMinting ? inputToken.decimals : outputToken.decimals, diff --git a/src/quote/flashmint/zeroEx/provider.test.ts b/src/quote/flashmint/zeroEx/provider.test.ts index 3f687876..fd49275d 100644 --- a/src/quote/flashmint/zeroEx/provider.test.ts +++ b/src/quote/flashmint/zeroEx/provider.test.ts @@ -1,16 +1,17 @@ import 'dotenv/config' +import { ChainId } from 'constants/chains' import { - IndexZeroExSwapQuoteProvider, - LocalhostProviderUrl, + getLocalHostProviderUrl, + getZeroExSwapQuoteProvider, QuoteTokens, } from 'tests/utils' import { wei } from 'utils' import { ZeroExQuoteProvider } from './provider' -const rpcUrl = LocalhostProviderUrl -const swapQuoteProvider = IndexZeroExSwapQuoteProvider +const rpcUrl = getLocalHostProviderUrl(ChainId.Mainnet) +const swapQuoteProvider = getZeroExSwapQuoteProvider(ChainId.Mainnet) const { dpi, dseth, eth, mvi } = QuoteTokens diff --git a/src/quote/provider/icusd.ts b/src/quote/provider/icusd.ts new file mode 100644 index 00000000..8052b12a --- /dev/null +++ b/src/quote/provider/icusd.ts @@ -0,0 +1,136 @@ +import { BigNumber } from '@ethersproject/bignumber' +import { getTokenByChainAndSymbol } from '@indexcoop/tokenlists' + +import { + FlashMintWrappedBuildRequest, + WrappedTransactionBuilder, +} from 'flashmint' + +import { WrappedQuoteProvider } from '../flashmint/wrapped' +import { QuoteProvider, QuoteToken } from '../interfaces' +import { + FlashMintContractType, + FlashMintQuote, + FlashMintQuoteRequest, +} from '../provider' +import { SwapQuoteProvider } from '../swap' +import { buildQuoteResponse } from './utils' + +export interface IcUsdQuoteRequest extends FlashMintQuoteRequest { + chainId: number + isMinting: boolean + inputToken: QuoteToken + outputToken: QuoteToken + indexTokenAmount: BigNumber + // FlashMintNav always needs the input amount + inputTokenAmount: BigNumber + slippage: number +} + +export class IcUsdQuoteRouter + implements QuoteProvider +{ + constructor( + private readonly rpcUrl: string, + private readonly swapQuoteProvider: SwapQuoteProvider + ) {} + + async getQuote(request: IcUsdQuoteRequest): Promise { + const { chainId } = request + const usdc = getTokenByChainAndSymbol(chainId, 'USDC') + const icUsd = getTokenByChainAndSymbol(chainId, 'icUSD') + if (!usdc || !icUsd) { + throw new Error(`icUSD not supported on chainId: ${chainId}`) + } + // For now we only use FlashMintWrapped + // if (request.isMinting) { + // return await this.getFlashMintNavQuote(request) + // } else { + // const usdcBalance = await getBalanceOf( + // usdc.address, + // icUsd.address as Address, + // chainId + // ) + // const usdcInputAmount = await getExpectedReserveRedeemQuantity( + // icUsd.address as Address, + // usdc.address, + // indexTokenAmount.toBigInt() + // ) + // console.log(usdcBalance.toString(), 'USDC') + // // 80% of the USDC balance of icUSD + // const threshold = (usdcBalance * 80n) / 100n + // const useFlashMintNav = usdcInputAmount < threshold + // if (useFlashMintNav) return await this.getFlashMintNavQuote(request) + return await this.getFlashMintWrappedQuote(request) + // } + } + + // private async getFlashMintNavQuote(request: IcUsdQuoteRequest) { + // const { chainId, inputToken, isMinting, outputToken } = request + // const flashMintNavQuoteProvider = new FlashMintNavQuoteProvider( + // this.rpcUrl, + // this.swapQuoteProvider + // ) + // const fmNavQuote = await flashMintNavQuoteProvider.getQuote({ + // ...request, + // }) + // if (!fmNavQuote) return null + // const builder = new FlashMintNavTransactionBuilder(this.rpcUrl) + // const txRequest = { + // isMinting, + // inputToken: inputToken.address, + // inputTokenSymbol: inputToken.symbol, + // outputToken: outputToken.address, + // outputTokenSymbol: outputToken.symbol, + // inputTokenAmount: fmNavQuote.inputTokenAmount, + // outputTokenAmount: fmNavQuote.outputTokenAmount, + // reserveAssetSwapData: fmNavQuote.reserveAssetSwapData, + // } + // const tx = await builder.build(txRequest) + // if (!tx) return null + // return buildQuoteResponse( + // request, + // chainId, + // FlashMintContractType.nav, + // isMinting ? fmNavQuote.inputTokenAmount : fmNavQuote.outputTokenAmount, + // tx + // ) + // } + + private async getFlashMintWrappedQuote(request: IcUsdQuoteRequest) { + const { chainId, indexTokenAmount, inputToken, isMinting, outputToken } = + request + const indexToken = isMinting ? outputToken : inputToken + const inputOutputToken = isMinting ? inputToken : outputToken + const wrappedQuoteProvider = new WrappedQuoteProvider( + this.rpcUrl, + this.swapQuoteProvider + ) + const wrappedQuote = await wrappedQuoteProvider.getQuote({ + ...request, + chainId, + }) + if (!wrappedQuote) return null + const builder = new WrappedTransactionBuilder(this.rpcUrl) + const txRequest: FlashMintWrappedBuildRequest = { + chainId, + isMinting, + indexToken: indexToken.address, + indexTokenAmount, + inputOutputToken: inputOutputToken.address, + inputOutputTokenSymbol: inputOutputToken.symbol, + inputOutputTokenAmount: wrappedQuote.inputOutputTokenAmount, + componentSwapData: wrappedQuote.componentSwapData, + componentWrapData: wrappedQuote.componentWrapData, + } + const tx = await builder.build(txRequest) + if (!tx) return null + return buildQuoteResponse( + request, + chainId, + FlashMintContractType.wrapped, + wrappedQuote.inputOutputTokenAmount, + tx + ) + } +} diff --git a/src/quote/provider/index.test.ts b/src/quote/provider/index.test.ts index 7dbbb8cc..5a1519f7 100644 --- a/src/quote/provider/index.test.ts +++ b/src/quote/provider/index.test.ts @@ -1,16 +1,15 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { getTokenByChainAndSymbol } from '@indexcoop/tokenlists' + import { ChainId } from 'constants/chains' import { Contracts } from 'constants/contracts' import { IndexCoopEthereum2xIndex } from 'constants/tokens' import { getFlashMintLeveragedContractForToken, wei } from 'utils' +import { getRpcProvider } from 'utils/rpc-provider' import { - IndexZeroExSwapQuoteProvider, - IndexZeroExSwapQuoteProviderArbitrum, - LocalhostProvider, - LocalhostProviderArbitrum, - LocalhostProviderUrl, - LocalhostProviderUrlArbitrum, + getLocalHostProviderUrl, + getZeroExSwapQuoteProvider, QuoteTokens, } from 'tests/utils' @@ -20,12 +19,13 @@ import { FlashMintQuoteRequest, } from '.' -const rpcUrl = LocalhostProviderUrl -const provider = LocalhostProvider -const zeroexSwapQuoteProvider = IndexZeroExSwapQuoteProvider +const chainId = ChainId.Mainnet +const rpcUrl = getLocalHostProviderUrl(chainId) +const provider = getRpcProvider(rpcUrl) +const zeroexSwapQuoteProvider = getZeroExSwapQuoteProvider(chainId) const FlashMintHyEthAddress = Contracts[ChainId.Mainnet].FlashMintHyEthV3 -const { eth, eth2x, hyeth, iceth, icusd, usdc } = QuoteTokens +const { eth, eth2x, hyeth, iceth, usdc } = QuoteTokens describe('FlashMintQuoteProvider()', () => { test('throws if token is unsupported', async () => { @@ -52,7 +52,8 @@ describe('FlashMintQuoteProvider()', () => { }) test('returns a quote for minting ETH2X', async () => { - const arbitrumProvider = LocalhostProviderArbitrum + const rpcUrl = getLocalHostProviderUrl(ChainId.Arbitrum) + const arbitrumProvider = getRpcProvider(rpcUrl) const inputToken = usdc const outputToken = { address: IndexCoopEthereum2xIndex.addressArbitrum!, @@ -72,8 +73,8 @@ describe('FlashMintQuoteProvider()', () => { slippage: 0.5, } const quoteProvider = new FlashMintQuoteProvider( - LocalhostProviderUrlArbitrum, - IndexZeroExSwapQuoteProviderArbitrum + rpcUrl, + getZeroExSwapQuoteProvider(ChainId.Arbitrum) ) const quote = await quoteProvider.getQuote(request) if (!quote) fail() @@ -103,8 +104,8 @@ describe('FlashMintQuoteProvider()', () => { slippage: 0.5, } const quoteProvider = new FlashMintQuoteProvider( - LocalhostProviderUrl, - IndexZeroExSwapQuoteProvider + rpcUrl, + zeroexSwapQuoteProvider ) const quote = await quoteProvider.getQuote(request) if (!quote) fail() @@ -126,39 +127,41 @@ describe('FlashMintQuoteProvider()', () => { }) test('returns a quote for minting icUSD', async () => { + const chainId = ChainId.Base const request: FlashMintQuoteRequest = { isMinting: true, - inputToken: usdc, - outputToken: icusd, + inputToken: getTokenByChainAndSymbol(chainId, 'USDC'), + outputToken: getTokenByChainAndSymbol(chainId, 'icUSD'), indexTokenAmount: wei(1), + inputTokenAmount: wei(100, 6), slippage: 0.5, } const quoteProvider = new FlashMintQuoteProvider( - LocalhostProviderUrl, - IndexZeroExSwapQuoteProvider + getLocalHostProviderUrl(chainId), + getZeroExSwapQuoteProvider(chainId) ) const quote = await quoteProvider.getQuote(request) if (!quote) fail() - const FlashMintWrappedAddress = Contracts[ChainId.Mainnet].FlashMintWrapped - const chainId = (await provider.getNetwork()).chainId + const FlashMintNavAddress = Contracts[chainId].FlashMintWrapped expect(quote.chainId).toEqual(chainId) expect(quote.contractType).toEqual(FlashMintContractType.wrapped) - expect(quote.contract).toEqual(FlashMintWrappedAddress) + expect(quote.contract).toEqual(FlashMintNavAddress) expect(quote.isMinting).toEqual(request.isMinting) expect(quote.inputToken).toEqual(request.inputToken) expect(quote.outputToken).toEqual(request.outputToken) expect(quote.outputToken).toEqual(request.outputToken) expect(quote.inputAmount).toEqual(quote.inputOutputAmount) - expect(quote.indexTokenAmount).toEqual(request.indexTokenAmount) + expect(quote.indexTokenAmount).toEqual(quote.outputAmount) expect(quote.inputOutputAmount.gt(0)).toBe(true) expect(quote.slippage).toEqual(request.slippage) expect(quote.tx).not.toBeNull() - expect(quote.tx.to).toBe(FlashMintWrappedAddress) + expect(quote.tx.to).toBe(FlashMintNavAddress) expect(quote.tx.data?.length).toBeGreaterThan(0) }) test('returns a quote for redeeming ETH2X', async () => { - const arbitrumProvider = LocalhostProviderArbitrum + const rpcUrl = getLocalHostProviderUrl(ChainId.Arbitrum) + const arbitrumProvider = getRpcProvider(rpcUrl) const inputToken = { address: IndexCoopEthereum2xIndex.addressArbitrum!, decimals: eth2x.decimals, @@ -178,8 +181,8 @@ describe('FlashMintQuoteProvider()', () => { slippage: 0.5, } const quoteProvider = new FlashMintQuoteProvider( - LocalhostProviderUrlArbitrum, - IndexZeroExSwapQuoteProviderArbitrum + rpcUrl, + getZeroExSwapQuoteProvider(ChainId.Arbitrum) ) const quote = await quoteProvider.getQuote(request) if (!quote) fail() @@ -209,8 +212,8 @@ describe('FlashMintQuoteProvider()', () => { slippage: 0.5, } const quoteProvider = new FlashMintQuoteProvider( - LocalhostProviderUrl, - IndexZeroExSwapQuoteProvider + rpcUrl, + zeroexSwapQuoteProvider ) const quote = await quoteProvider.getQuote(request) if (!quote) fail() @@ -270,24 +273,27 @@ describe('FlashMintQuoteProvider()', () => { }) test('returns a quote for redeeming icUSD', async () => { + const chainId = ChainId.Base const request: FlashMintQuoteRequest = { isMinting: false, - inputToken: icusd, - outputToken: usdc, + inputToken: getTokenByChainAndSymbol(chainId, 'icUSD'), + outputToken: getTokenByChainAndSymbol(chainId, 'USDC'), indexTokenAmount: wei(1), + // Note that input token amount is essential to determine here if the test + // fails or not. For example larger amounts might return FlashMintWrapped instead of (FMNav) + inputTokenAmount: wei(1), slippage: 0.5, } const quoteProvider = new FlashMintQuoteProvider( - LocalhostProviderUrl, - IndexZeroExSwapQuoteProvider + getLocalHostProviderUrl(chainId), + getZeroExSwapQuoteProvider(chainId) ) const quote = await quoteProvider.getQuote(request) if (!quote) fail() - const FlashMintWrappedAddress = Contracts[ChainId.Mainnet].FlashMintWrapped - const chainId = (await provider.getNetwork()).chainId + const FlashMintContractAddress = Contracts[chainId].FlashMintWrapped expect(quote.chainId).toEqual(chainId) expect(quote.contractType).toEqual(FlashMintContractType.wrapped) - expect(quote.contract).toEqual(FlashMintWrappedAddress) + expect(quote.contract).toEqual(FlashMintContractAddress) expect(quote.isMinting).toEqual(request.isMinting) expect(quote.inputToken).toEqual(request.inputToken) expect(quote.outputToken).toEqual(request.outputToken) @@ -297,7 +303,7 @@ describe('FlashMintQuoteProvider()', () => { expect(quote.inputOutputAmount.gt(0)).toBe(true) expect(quote.slippage).toEqual(request.slippage) expect(quote.tx).not.toBeNull() - expect(quote.tx.to).toBe(FlashMintWrappedAddress) + expect(quote.tx.to).toBe(FlashMintContractAddress) expect(quote.tx.data?.length).toBeGreaterThan(0) }) }) diff --git a/src/quote/provider/index.ts b/src/quote/provider/index.ts index 31f89910..63238901 100644 --- a/src/quote/provider/index.ts +++ b/src/quote/provider/index.ts @@ -1,6 +1,7 @@ import { TransactionRequest } from '@ethersproject/abstract-provider' import { BigNumber } from '@ethersproject/bignumber' +import { TheUSDCYieldIndex } from 'constants/tokens' import { FlashMintHyEthTransactionBuilder, FlashMintLeveragedBuildRequest, @@ -23,12 +24,14 @@ import { ZeroExQuoteProvider } from '../flashmint/zeroEx' import { QuoteProvider, QuoteToken } from '../interfaces' import { SwapQuoteProvider } from '../swap' +import { IcUsdQuoteRouter } from './icusd' import { buildQuoteResponse, getContractType } from './utils' export enum FlashMintContractType { hyeth, leveraged, leveragedExtended, + nav, wrapped, zeroEx, } @@ -38,6 +41,7 @@ export interface FlashMintQuoteRequest { inputToken: QuoteToken outputToken: QuoteToken indexTokenAmount: BigNumber + inputTokenAmount?: BigNumber slippage: number } @@ -69,12 +73,30 @@ export class FlashMintQuoteProvider ): Promise { const { rpcUrl, swapQuoteProvider } = this const provider = getRpcProvider(rpcUrl) - const { indexTokenAmount, inputToken, isMinting, outputToken, slippage } = - request + const { + indexTokenAmount, + inputTokenAmount, + inputToken, + isMinting, + outputToken, + slippage, + } = request const indexToken = isMinting ? outputToken : inputToken const inputOutputToken = isMinting ? inputToken : outputToken const network = await provider.getNetwork() const chainId = network.chainId + // As icUSD needs custom routing we return early using the custom router + if (indexToken.symbol === TheUSDCYieldIndex.symbol) { + if (!inputTokenAmount) { + throw new Error('Must set `inputTokenAmount` for icUSD quote request') + } + const icUsdRouter = new IcUsdQuoteRouter(rpcUrl, swapQuoteProvider) + return await icUsdRouter.getQuote({ + ...request, + chainId, + inputTokenAmount, + }) + } const contractType = getContractType(indexToken.symbol, chainId) if (contractType === null) { throw new Error('Index token not supported') @@ -171,9 +193,10 @@ export class FlashMintQuoteProvider swapDataDebtCollateral: leveragedExtendedQuote.swapDataDebtCollateral, swapDataInputOutputToken: leveragedExtendedQuote.swapDataPaymentToken, swapDataInputTokenForETH: - leveragedExtendedQuote.swapDataDebtCollateral, // TODO: check - priceEstimateInflator: wei(0.9), // TODO: For the price estimate inflator, increasing it towards 1.0 (but always slightly less) should reduce gas costs but can also lead to revertions. - maxDust: wei(0.00001), // TODO: maxDust = 0.01 % * inputTokenAmount + leveragedExtendedQuote.swapDataDebtCollateral, + // Below not used for now + priceEstimateInflator: wei(0.9), // For the price estimate inflator, increasing it towards 1.0 (but always slightly less) should reduce gas costs but can also lead to revertions. + maxDust: wei(0.00001), // maxDust = 0.01 % * inputTokenAmount } const tx = await builder.build(txRequest) if (!tx) return null @@ -197,6 +220,7 @@ export class FlashMintQuoteProvider if (!wrappedQuote) return null const builder = new WrappedTransactionBuilder(rpcUrl) const txRequest: FlashMintWrappedBuildRequest = { + chainId, isMinting, indexToken: indexToken.address, indexTokenAmount, diff --git a/src/quote/swap/adapters/lifi/index.ts b/src/quote/swap/adapters/lifi/index.ts index 73ebb4a7..814f5a8b 100644 --- a/src/quote/swap/adapters/lifi/index.ts +++ b/src/quote/swap/adapters/lifi/index.ts @@ -81,7 +81,7 @@ export class LiFiSwapQuoteProvider implements SwapQuoteProvider { swapData, } } catch (error) { - console.log('Error getting LiFi swap quote:') + console.warn('Error getting LiFi swap quote:') console.log(error) return null } diff --git a/src/quote/swap/adapters/uniswap/index.ts b/src/quote/swap/adapters/uniswap/index.ts index fd924552..f23624b3 100644 --- a/src/quote/swap/adapters/uniswap/index.ts +++ b/src/quote/swap/adapters/uniswap/index.ts @@ -1,5 +1,8 @@ import axios from 'axios' -import { getTokenData, getTokenDataByAddress } from '@indexcoop/tokenlists' +import { + getTokenByChainAndAddress, + getTokenByChainAndSymbol, +} from '@indexcoop/tokenlists' import { Token } from '@uniswap/sdk-core' import { AddressZero, EthAddress } from 'constants/addresses' @@ -12,7 +15,7 @@ import { Exchange, isSameAddress } from 'utils' function changeToWethIfNecessary(token: string, chainId: number): string { if (isSameAddress(token, EthAddress)) { - const weth = getTokenData('WETH', chainId) + const weth = getTokenByChainAndSymbol(chainId, 'WETH') return weth?.address ?? token } return token @@ -43,9 +46,9 @@ export class UniswapSwapQuoteProvider implements SwapQuoteProvider { throw new Error('Error - either input or output amount must be set') } - const weth = getTokenData('WETH', chainId) - const inputTokenData = getTokenDataByAddress(inputToken, chainId) - const outputTokenData = getTokenDataByAddress(outputToken, chainId) + const weth = getTokenByChainAndSymbol(chainId, 'WETH') + const inputTokenData = getTokenByChainAndAddress(chainId, inputToken) + const outputTokenData = getTokenByChainAndAddress(chainId, outputToken) if (!inputTokenData || !outputTokenData || !weth) { throw new Error('Error - either input or output token is invalid') @@ -112,7 +115,7 @@ export class UniswapSwapQuoteProvider implements SwapQuoteProvider { }, } } catch (error) { - console.log('Error getting Uniswap swap quote:') + console.warn('Error getting Uniswap swap quote:') console.log(error) return null } diff --git a/src/tests/base/eth2x.test.ts b/src/tests/base/eth2x.test.ts index 41dd0489..008c0ea7 100644 --- a/src/tests/base/eth2x.test.ts +++ b/src/tests/base/eth2x.test.ts @@ -1,13 +1,15 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { ChainId } from 'constants/chains' import { IndexCoopEthereum2xIndex } from 'constants/tokens' import { getBaseTestFactory, + getLocalHostProviderUrl, getSignerAccount, - LocalhostProviderBase, QuoteTokens, TestFactory, wei, } from 'tests/utils' +import { getRpcProvider } from 'utils/rpc-provider' const { eth } = QuoteTokens const eth2x = { @@ -19,7 +21,7 @@ const eth2x = { describe('ETH2X (Base)', () => { let factory: TestFactory beforeEach(async () => { - const provider = LocalhostProviderBase + const provider = getRpcProvider(getLocalHostProviderUrl(ChainId.Base)) const signer = getSignerAccount(0, provider) factory = getBaseTestFactory(signer) }) diff --git a/src/tests/base/eth3x.test.ts b/src/tests/base/eth3x.test.ts index da2be9c1..525661d8 100644 --- a/src/tests/base/eth3x.test.ts +++ b/src/tests/base/eth3x.test.ts @@ -1,13 +1,15 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { ChainId } from 'constants/chains' import { IndexCoopEthereum3xIndex } from 'constants/tokens' import { getBaseTestFactory, + getLocalHostProviderUrl, getSignerAccount, - LocalhostProviderBase, QuoteTokens, TestFactory, wei, } from 'tests/utils' +import { getRpcProvider } from 'utils/rpc-provider' const { eth } = QuoteTokens const eth3x = { @@ -19,7 +21,7 @@ const eth3x = { describe.skip('ETH3X (Base)', () => { let factory: TestFactory beforeEach(async () => { - const provider = LocalhostProviderBase + const provider = getRpcProvider(getLocalHostProviderUrl(ChainId.Base)) const signer = getSignerAccount(2, provider) factory = getBaseTestFactory(signer) }) diff --git a/src/tests/icusd.test.ts b/src/tests/icusd.test.ts index dd029f8a..0dab04b7 100644 --- a/src/tests/icusd.test.ts +++ b/src/tests/icusd.test.ts @@ -1,20 +1,29 @@ +import { BigNumber } from '@ethersproject/bignumber' +import { getTokenByChainAndSymbol } from '@indexcoop/tokenlists' + +import { ChainId } from 'constants/chains' +import { getRpcProvider } from 'utils/rpc-provider' import { - getMainnetTestFactory, - QuoteTokens, - SignerAccount4, + getBaseTestFactory, + getLocalHostProviderUrl, + getSignerAccount, TestFactory, transferFromWhale, wei, + wrapETH, } from './utils' -const { icusd, usdc } = QuoteTokens - -describe('icUSD (mainnet)', () => { - const indexToken = icusd - const signer = SignerAccount4 +describe('icUSD (Base)', () => { + const chainId = ChainId.Base + const indexToken = getTokenByChainAndSymbol(chainId, 'icUSD') + const usdc = getTokenByChainAndSymbol(chainId, 'USDC') + const weth = getTokenByChainAndSymbol(chainId, 'WETH') + const usdcWhale = '0x8dB0f952B8B6A462445C732C41Ec2937bCae9c35' + const provider = getRpcProvider(getLocalHostProviderUrl(chainId)) + const signer = getSignerAccount(4, provider) let factory: TestFactory beforeEach(async () => { - factory = getMainnetTestFactory(signer) + factory = getBaseTestFactory(signer) }) test('can mint with USDC', async () => { @@ -22,14 +31,53 @@ describe('icUSD (mainnet)', () => { isMinting: true, inputToken: usdc, outputToken: indexToken, - indexTokenAmount: wei('1'), + indexTokenAmount: wei(1), + // Irrelevant - as right now we don't use FlashMintNav + inputTokenAmount: BigNumber.from(0), slippage: 0.5, }) - const usdcWhale = '0x7713974908Be4BEd47172370115e8b1219F4A5f0' await transferFromWhale( usdcWhale, factory.getSigner().address, - wei('100000', quote.inputToken.decimals), + wei('10000', quote.inputToken.decimals), + quote.inputToken.address, + factory.getProvider() + ) + await factory.executeTx() + }) + + test('can mint with WETH', async () => { + const quote = await factory.fetchQuote({ + isMinting: true, + inputToken: weth, + outputToken: indexToken, + indexTokenAmount: wei(1), + // Irrelevant - as right now we don't use FlashMintNav + inputTokenAmount: BigNumber.from(0), + slippage: 0.5, + }) + await wrapETH( + BigNumber.from(quote.inputAmount.mul(BigNumber.from('2'))), + factory.getSigner(), + chainId + ) + await factory.executeTx() + }) + + test('can mint with DAI', async () => { + const quote = await factory.fetchQuote({ + isMinting: true, + inputToken: getTokenByChainAndSymbol(chainId, 'DAI'), + outputToken: indexToken, + indexTokenAmount: wei(1), + // Irrelevant - as right now we don't use FlashMintNav + inputTokenAmount: BigNumber.from(0), + slippage: 0.5, + }) + await transferFromWhale( + '0x9646D8F3F59bd7882237eE0EE1c00d483552397D', + factory.getSigner().address, + wei('10000', quote.inputToken.decimals), quote.inputToken.address, factory.getProvider() ) @@ -41,9 +89,74 @@ describe('icUSD (mainnet)', () => { isMinting: false, inputToken: indexToken, outputToken: usdc, + // In case of redeeming input and index token amount are the same + indexTokenAmount: wei('1'), + inputTokenAmount: wei('1'), + slippage: 0.5, + }) + await factory.executeTx() + }) + + test('can redeem to WETH', async () => { + await factory.fetchQuote({ + isMinting: false, + inputToken: indexToken, + outputToken: weth, + // In case of redeeming input and index token amount are the same indexTokenAmount: wei('1'), + inputTokenAmount: wei('1'), slippage: 0.5, }) await factory.executeTx() }) + + // Readd once we use FMWrapped and FMNav again + // test.skip('can mint with USDC', async () => { + // const usdcBalance = await getBalanceOf( + // usdc.address as Address, + // indexToken.address as Address, + // chainId + // ) + // // Minting enough tokens for the redemption tests + // const inputAmountGreaterThreshold = (usdcBalance * BigInt(90)) / BigInt(100) + // const quote = await factory.fetchQuote({ + // isMinting: true, + // inputToken: usdc, + // outputToken: indexToken, + // // Index token amount will be ignored for minting + // indexTokenAmount: wei(formatUnits(inputAmountGreaterThreshold, 6)), + // inputTokenAmount: BigNumber.from(inputAmountGreaterThreshold.toString()), + // slippage: 0.5, + // }) + // await transferFromWhale( + // usdcWhale, + // factory.getSigner().address, + // wei('10000', quote.inputToken.decimals), + // quote.inputToken.address, + // factory.getProvider() + // ) + // await factory.executeTx() + // }) + + // Readd once we use FMWrapped and FMNav again + // test.skip('can redeem to USDC (via FMWrapped)', async () => { + // const usdcBalance = await getBalanceOf( + // usdc.address as Address, + // indexToken.address as Address, + // chainId + // ) + // // To test that the FM Wrapped contract is used for redeeming, get an input + // // amount greater than the internal threshold (80%). + // const inputAmountGreaterThreshold = (usdcBalance * BigInt(85)) / BigInt(100) + // await factory.fetchQuote({ + // isMinting: false, + // inputToken: indexToken, + // outputToken: usdc, + // // In case of redeeming input and index token amount are the same + // indexTokenAmount: wei(formatUnits(inputAmountGreaterThreshold, 6)), + // inputTokenAmount: wei(formatUnits(inputAmountGreaterThreshold, 6)), + // slippage: 0.5, + // }) + // await factory.executeTx() + // }) }) diff --git a/src/tests/utils/erc20.ts b/src/tests/utils/erc20.ts new file mode 100644 index 00000000..1bdd8020 --- /dev/null +++ b/src/tests/utils/erc20.ts @@ -0,0 +1,76 @@ +import { BigNumber } from '@ethersproject/bignumber' +import { Contract } from '@ethersproject/contracts' +import { JsonRpcProvider, JsonRpcSigner } from '@ethersproject/providers' +import { Wallet } from '@ethersproject/wallet' + +export { wei } from 'utils/numbers' + +export function createERC20Contract( + erc20Address: string, + providerOrSigner: JsonRpcProvider | JsonRpcSigner | Wallet +): Contract { + const abi = [ + // Read-Only Functions + 'function allowance(address account, address spender) external view returns (uint)', + 'function balanceOf(address owner) view returns (uint256)', + 'function decimals() view returns (uint8)', + 'function symbol() view returns (string)', + // Authenticated Functions + 'function approve(address spender, uint rawAmount) external returns (bool)', + 'function transfer(address to, uint amount) returns (bool)', + ] + return new Contract(erc20Address, abi, providerOrSigner) +} + +export async function approveErc20( + erc20Address: string, + spender: string, + amount: BigNumber, + signer: Wallet +) { + const contract = createERC20Contract(erc20Address, signer) + const approveTx = await contract.approve(spender, amount, { + gasLimit: 100_000, + }) + await approveTx.wait() +} + +export async function allowanceOf( + erc20Address: string, + spender: string, + signer: Wallet +): Promise { + const contract = createERC20Contract(erc20Address, signer) + return await contract.allowance(signer.address, spender) +} + +export async function balanceOf( + signer: Wallet, + erc20Address: string +): Promise { + const contract = createERC20Contract(erc20Address, signer) + return await contract.balanceOf(signer.address) +} + +export async function transferFromWhale( + whale: string, + to: string, + amount: BigNumber, + erc20Address: string, + provider: JsonRpcProvider +) { + const signer = await provider.getSigner(whale) + const contract = createERC20Contract(erc20Address, signer) + const balance = await contract.balanceOf(whale) + if (balance.lt(amount)) { + throw new Error( + `Not enough balance to steal ${amount} ${erc20Address} from ${whale}: ${balance}` + ) + } + await provider.send('hardhat_impersonateAccount', [whale]) + const transferTx = await contract.transfer(to, amount, { + gasLimit: 100_000, + }) + await transferTx.wait() + await provider.send('hardhat_stopImpersonatingAccount', [whale]) +} diff --git a/src/tests/utils/factories.ts b/src/tests/utils/factories.ts index e101c066..1e37cbb0 100644 --- a/src/tests/utils/factories.ts +++ b/src/tests/utils/factories.ts @@ -1,129 +1,49 @@ -import { BigNumber } from '@ethersproject/bignumber' -import { JsonRpcProvider } from '@ethersproject/providers' -import { Wallet } from '@ethersproject/wallet' - +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { ChainId } from 'constants/chains' +import { IndexSwapQuoteProvider } from 'quote' import { - FlashMintQuote, - FlashMintQuoteProvider, - QuoteToken, - SwapQuoteProvider, -} from 'quote' - -import { approveErc20, balanceOf } from './' - -class TxTestFactory { - constructor(readonly provider: JsonRpcProvider, readonly signer: Wallet) {} - - /** - * Tests minting a given flash mint quote. - * @param quote a flash mint quote - */ - async testMinting(quote: FlashMintQuote) { - const { signer } = this - const indexToken = quote.outputToken - const tx = quote.tx - if (!tx) fail() - const balanceBefore: BigNumber = await balanceOf(signer, indexToken.address) - if (quote.inputToken.symbol !== 'ETH') { - await approveErc20( - quote.inputToken.address, - quote.contract, - quote.inputOutputAmount, - signer - ) - } - // Automatically adding from as it seems like estimateGas won't recognize - // the impersonated balance if `from` is not set. - tx.from = this.signer.address - const gasEstimate = await this.provider.estimateGas(tx) - tx.gasLimit = gasEstimate - const res = await signer.sendTransaction(tx) - res.wait() - const balanceAfter: BigNumber = await balanceOf(signer, indexToken.address) - expect(balanceAfter.gte(balanceBefore.add(quote.indexTokenAmount))).toBe( - true - ) - } - - /** - * Tests redeeming a given flash mint quote. - * @param quote a flash mint quote - * @param gasLimit override gas limit - */ - async testRedeeming(quote: FlashMintQuote, gasLimit?: BigNumber) { - const { signer } = this - const indexToken = quote.inputToken - const balanceBefore: BigNumber = await balanceOf(signer, indexToken.address) - await approveErc20( - indexToken.address, - quote.contract, - quote.indexTokenAmount, - signer - ) - const tx = quote.tx - if (!tx) fail() - // Automatically, adding from as it seems like estimateGas won't recognize - // the impersonated balance if `from` is not set. - tx.from = this.signer.address - tx.gasLimit = gasLimit - if (!gasLimit) { - const gasEstimate = await this.provider.estimateGas(tx) - tx.gasLimit = gasEstimate - } - const res = await signer.sendTransaction(tx) - res.wait() - const balanceAfter: BigNumber = await balanceOf(signer, indexToken.address) - expect(balanceAfter.lte(balanceBefore.sub(quote.indexTokenAmount))).toBe( - true - ) - } + getLocalHostProviderUrl, + getZeroExSwapQuoteProvider, +} from 'tests/utils' +import { TestFactory } from 'tests/utils/test-factory' + +// Pre-configured TestFactories +export function getArbitrumTestFactory( + signer: any, + rpcUrl: string = getLocalHostProviderUrl(ChainId.Arbitrum) +) { + const swapQuoteProvider = getZeroExSwapQuoteProvider(ChainId.Arbitrum) + return new TestFactory(rpcUrl, signer, swapQuoteProvider) } -export class TestFactory { - private quote: FlashMintQuote | null = null - private quoteProvider: FlashMintQuoteProvider - private txFactory: TxTestFactory - constructor( - rpcUrl: string, - signer: Wallet, - swapQuoteProvider: SwapQuoteProvider - ) { - const provider = new JsonRpcProvider(rpcUrl) - this.quoteProvider = new FlashMintQuoteProvider(rpcUrl, swapQuoteProvider) - this.txFactory = new TxTestFactory(provider, signer) - } - - async executeTx(gasLimit?: BigNumber) { - if (!this.quote) fail() - if (this.quote.isMinting) { - await this.txFactory.testMinting(this.quote) - return - } - await this.txFactory.testRedeeming(this.quote, gasLimit) - } +export function getArbitrumTestFactoryUniswap( + signer: any, + rpcUrl: string = getLocalHostProviderUrl(ChainId.Arbitrum) +) { + const swapQuoteProvider = new IndexSwapQuoteProvider(rpcUrl) + return new TestFactory(rpcUrl, signer, swapQuoteProvider) +} - async fetchQuote(config: { - inputToken: QuoteToken - outputToken: QuoteToken - indexTokenAmount: BigNumber - isMinting: boolean - slippage: number - }): Promise { - const quote = await this.quoteProvider.getQuote(config) - expect(quote).toBeDefined() - if (!quote) fail() - expect(quote.isMinting).toEqual(config.isMinting) - expect(quote.indexTokenAmount).toEqual(config.indexTokenAmount) - expect(quote.inputOutputAmount.gt(0)).toBe(true) - this.quote = quote - return quote - } +export function getBaseTestFactory( + signer: any, + rpcUrl: string = getLocalHostProviderUrl(ChainId.Base) +) { + const swapQuoteProvider = getZeroExSwapQuoteProvider(ChainId.Base) + return new TestFactory(rpcUrl, signer, swapQuoteProvider) +} - getProvider(): JsonRpcProvider { - return this.txFactory.provider - } +export function getMainnetTestFactory( + signer: any, + rpcUrl: string = getLocalHostProviderUrl(ChainId.Mainnet) +) { + const swapQuoteProvider = getZeroExSwapQuoteProvider(ChainId.Mainnet) + return new TestFactory(rpcUrl, signer, swapQuoteProvider) +} - getSigner(): Wallet { - return this.txFactory.signer - } +export function getMainnetTestFactoryUniswap( + signer: any, + rpcUrl: string = getLocalHostProviderUrl(ChainId.Mainnet) +) { + const swapQuoteProvider = new IndexSwapQuoteProvider(rpcUrl) + return new TestFactory(rpcUrl, signer, swapQuoteProvider) } diff --git a/src/tests/utils/index.ts b/src/tests/utils/index.ts index 07ca8fd9..c49948de 100644 --- a/src/tests/utils/index.ts +++ b/src/tests/utils/index.ts @@ -1,21 +1,19 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* eslint-disable @typescript-eslint/no-explicit-any */ import 'dotenv/config' +import { JsonRpcProvider } from '@ethersproject/providers' -import { BigNumber } from '@ethersproject/bignumber' -import { Contract } from '@ethersproject/contracts' -import { JsonRpcProvider, JsonRpcSigner } from '@ethersproject/providers' -import { Wallet } from '@ethersproject/wallet' - -import { WETH } from 'constants/tokens' -import { IndexSwapQuoteProvider, ZeroExSwapQuoteProvider } from 'quote' -import { TestFactory } from './factories' +import { ChainId } from 'constants/chains' +import { ZeroExSwapQuoteProvider } from 'quote' export { wei } from 'utils/numbers' +export * from './erc20' export * from './factories' export { QuoteTokens } from './quoteTokens' export * from './lido' +export * from './signers' +export * from './test-factory' export * from './uniswap' +export * from './weth' // Alchemy export const AlchemyProviderUrl = process.env.MAINNET_ALCHEMY_API! @@ -23,241 +21,43 @@ export const AlchemyProviderUrlArbitrum = process.env.ARBITRUM_ALCHEMY_API! export const AlchemyProvider = new JsonRpcProvider(AlchemyProviderUrl, 1) // Hardhat +// Try avoiding these single consts in the future and rather use convenience functions below export const LocalhostProviderUrl = 'http://127.0.0.1:8545/' -export const LocalhostProviderUrlArbitrum = 'http://127.0.0.1:8548/' -export const LocalhostProviderUrlBase = 'http://127.0.0.1:8453/' export const LocalhostProvider = new JsonRpcProvider(LocalhostProviderUrl) +const LocalhostProviderUrlArbitrum = 'http://127.0.0.1:8548/' export const LocalhostProviderArbitrum = new JsonRpcProvider( LocalhostProviderUrlArbitrum ) -export const LocalhostProviderBase = new JsonRpcProvider( - LocalhostProviderUrlBase -) - -// Pre-configured TestFactories -export function getArbitrumTestFactory( - signer: any, - rpcUrl: string = LocalhostProviderUrlArbitrum -) { - return new TestFactory(rpcUrl, signer, IndexZeroExSwapQuoteProviderArbitrum) -} -export function getArbitrumTestFactoryUniswap( - signer: any, - rpcUrl: string = LocalhostProviderUrlArbitrum -) { - const swapQuoteProvider = new IndexSwapQuoteProvider(rpcUrl) - return new TestFactory(rpcUrl, signer, swapQuoteProvider) -} -export function getBaseTestFactory( - signer: any, - rpcUrl: string = LocalhostProviderUrlBase -) { - return new TestFactory(rpcUrl, signer, IndexZeroExSwapQuoteProviderBase) -} - -export function getMainnetTestFactory( - signer: any, - rpcUrl: string = LocalhostProviderUrl -) { - return new TestFactory(rpcUrl, signer, IndexZeroExSwapQuoteProvider) -} -export function getMainnetTestFactoryUniswap( - signer: any, - rpcUrl: string = LocalhostProviderUrl -) { - const swapQuoteProvider = new IndexSwapQuoteProvider(rpcUrl) - return new TestFactory(rpcUrl, signer, swapQuoteProvider) -} - -export function getSignerAccount(num = 0, provider: JsonRpcProvider) { - let privateKey = - '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' - switch (num) { - case 1: - privateKey = - '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d' - break - case 2: - privateKey = - '0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6' - break - case 3: - privateKey = - '0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6' - break - case 4: - privateKey = - '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a' - break - case 5: - privateKey = - '0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba' - break +// Try to use these more together with the `getRpcProvider` util function +export function getLocalHostProviderUrl(chainId: number) { + switch (chainId) { + case ChainId.Arbitrum: + return 'http://127.0.0.1:8548/' + case ChainId.Base: + return 'http://127.0.0.1:8453/' + default: + return 'http://127.0.0.1:8545/' } - return new Wallet(privateKey, provider) } -// Hardhat Account #0 -export const SignerAccount0 = new Wallet( - '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', - LocalhostProvider -) - -// Hardhat Account #1 -export const SignerAccount1 = new Wallet( - '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d', - LocalhostProvider -) - -// Hardhat Account #2 -export const SignerAccount2 = new Wallet( - '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a', - LocalhostProvider -) - -// Hardhat Account #3 -export const SignerAccount3 = new Wallet( - '0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6', - LocalhostProvider -) - -// Hardhat Account #4 -export const SignerAccount4 = new Wallet( - '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a', - LocalhostProvider -) - -// Hardhat Account #5 -export const SignerAccount5 = new Wallet( - '0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba', - LocalhostProvider -) - -// Hardhat Account #17 -export const SignerAccount17 = new Wallet( - '0x689af8efa8c651a91ad287602527f3af2fe9f6501a7ac4b061667b5a93e037fd', - LocalhostProvider -) - -export async function resetHardhat( - provider: JsonRpcProvider, - blockNumber: number -) { - await provider.send('hardhat_reset', [ - { - forking: { - jsonRpcUrl: process.env.MAINNET_ALCHEMY_API!, - blockNumber, - }, - }, - ]) -} - -// ZeroExApi -const index0xApiBaseUrl = process.env.INDEX_0X_API -export const IndexZeroExSwapQuoteProvider = new ZeroExSwapQuoteProvider( - index0xApiBaseUrl, - '', - { 'X-INDEXCOOP-API-KEY': process.env.INDEX_0X_API_KEY! }, - '/mainnet/swap/v1/quote' -) - -export const IndexZeroExSwapQuoteProviderArbitrum = new ZeroExSwapQuoteProvider( - index0xApiBaseUrl, - '', - { 'X-INDEXCOOP-API-KEY': process.env.INDEX_0X_API_KEY! }, - '/arbitrum/swap/v1/quote' -) - -export const IndexZeroExSwapQuoteProviderBase = new ZeroExSwapQuoteProvider( - index0xApiBaseUrl, - '', - { 'X-INDEXCOOP-API-KEY': process.env.INDEX_0X_API_KEY! }, - '/base/swap/v1/quote' -) - -// Balance - -export async function transferFromWhale( - whale: string, - to: string, - amount: BigNumber, - erc20Address: string, - provider: JsonRpcProvider -) { - const signer = await provider.getSigner(whale) - const contract = createERC20Contract(erc20Address, signer) - const balance = await contract.balanceOf(whale) - if (balance.lt(amount)) { - throw new Error( - `Not enough balance to steal ${amount} ${erc20Address} from ${whale}: ${balance}` - ) +function get0xSwapPathOverride(chainId: number) { + switch (chainId) { + case ChainId.Arbitrum: + return '/arbitrum/swap/v1/quote' + case ChainId.Base: + return '/base/swap/v1/quote' + default: + return '/mainnet/swap/v1/quote' } - await provider.send('hardhat_impersonateAccount', [whale]) - const transferTx = await contract.transfer(to, amount, { - gasLimit: 100_000, - }) - await transferTx.wait() - await provider.send('hardhat_stopImpersonatingAccount', [whale]) -} - -// ERC20 -export function createERC20Contract( - erc20Address: string, - providerOrSigner: JsonRpcProvider | JsonRpcSigner | Wallet -): Contract { - const abi = [ - // Read-Only Functions - 'function allowance(address account, address spender) external view returns (uint)', - 'function balanceOf(address owner) view returns (uint256)', - 'function decimals() view returns (uint8)', - 'function symbol() view returns (string)', - // Authenticated Functions - 'function approve(address spender, uint rawAmount) external returns (bool)', - 'function transfer(address to, uint amount) returns (bool)', - ] - return new Contract(erc20Address, abi, providerOrSigner) -} - -export async function approveErc20( - erc20Address: string, - spender: string, - amount: BigNumber, - signer: Wallet -) { - const contract = createERC20Contract(erc20Address, signer) - const approveTx = await contract.approve(spender, amount, { - gasLimit: 100_000, - }) - await approveTx.wait() -} - -export async function allowanceOf( - erc20Address: string, - spender: string, - signer: Wallet -): Promise { - const contract = createERC20Contract(erc20Address, signer) - return await contract.allowance(signer.address, spender) -} - -export async function balanceOf( - signer: Wallet, - erc20Address: string -): Promise { - const contract = createERC20Contract(erc20Address, signer) - return await contract.balanceOf(signer.address) } -// WETH -export async function wrapETH(amount: BigNumber, signer: Wallet) { - const abi = ['function deposit() public payable'] - const WETH9 = WETH.address! - const contract = new Contract(WETH9, abi, signer) - const depositTokenInTx = await contract.deposit({ - gasLimit: 50_000, - value: amount, - }) - await depositTokenInTx.wait() +export function getZeroExSwapQuoteProvider(chainId: number) { + const index0xApiBaseUrl = process.env.INDEX_0X_API + return new ZeroExSwapQuoteProvider( + index0xApiBaseUrl, + '', + { 'X-INDEXCOOP-API-KEY': process.env.INDEX_0X_API_KEY! }, + get0xSwapPathOverride(chainId) + ) } diff --git a/src/tests/utils/quoteTokens.ts b/src/tests/utils/quoteTokens.ts index 2f856e7e..7b16d6d5 100644 --- a/src/tests/utils/quoteTokens.ts +++ b/src/tests/utils/quoteTokens.ts @@ -11,7 +11,6 @@ import { InterestCompoundingETHIndex, MetaverseIndex, RETH, - TheUSDCYieldIndex, USDC, USDT, WETH, @@ -82,12 +81,6 @@ const iceth: QuoteToken = { address: InterestCompoundingETHIndex.address!, } -const icusd: QuoteToken = { - address: TheUSDCYieldIndex.address!, - decimals: 18, - symbol: TheUSDCYieldIndex.symbol, -} - const mvi: QuoteToken = { address: MetaverseIndex.address!, decimals: 18, @@ -147,7 +140,6 @@ export const QuoteTokens = { hyeth, ic21, iceth, - icusd, mvi, reth, seth2, diff --git a/src/tests/utils/signers.ts b/src/tests/utils/signers.ts new file mode 100644 index 00000000..6f7881c7 --- /dev/null +++ b/src/tests/utils/signers.ts @@ -0,0 +1,79 @@ +import { JsonRpcProvider } from '@ethersproject/providers' +import { Wallet } from '@ethersproject/wallet' +import { getLocalHostProviderUrl } from 'tests/utils' +import { getRpcProvider } from 'utils/rpc-provider' + +export { wei } from 'utils/numbers' + +export function getSignerAccount(num = 0, provider: JsonRpcProvider) { + let privateKey = + '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' + switch (num) { + case 1: + privateKey = + '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d' + break + case 2: + privateKey = + '0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6' + break + case 3: + privateKey = + '0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6' + break + case 4: + privateKey = + '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a' + break + case 5: + privateKey = + '0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba' + break + } + return new Wallet(privateKey, provider) +} + +// Mainnet only +const LocalhostProvider = getRpcProvider(getLocalHostProviderUrl(1)) + +// Hardhat Account #0 +export const SignerAccount0 = new Wallet( + '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', + LocalhostProvider +) + +// Hardhat Account #1 +export const SignerAccount1 = new Wallet( + '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d', + LocalhostProvider +) + +// Hardhat Account #2 +export const SignerAccount2 = new Wallet( + '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a', + LocalhostProvider +) + +// Hardhat Account #3 +export const SignerAccount3 = new Wallet( + '0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6', + LocalhostProvider +) + +// Hardhat Account #4 +export const SignerAccount4 = new Wallet( + '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a', + LocalhostProvider +) + +// Hardhat Account #5 +export const SignerAccount5 = new Wallet( + '0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba', + LocalhostProvider +) + +// Hardhat Account #17 +export const SignerAccount17 = new Wallet( + '0x689af8efa8c651a91ad287602527f3af2fe9f6501a7ac4b061667b5a93e037fd', + LocalhostProvider +) diff --git a/src/tests/utils/test-factory.ts b/src/tests/utils/test-factory.ts new file mode 100644 index 00000000..57c84d42 --- /dev/null +++ b/src/tests/utils/test-factory.ts @@ -0,0 +1,123 @@ +import { BigNumber } from '@ethersproject/bignumber' +import { JsonRpcProvider } from '@ethersproject/providers' +import { Wallet } from '@ethersproject/wallet' + +import { + FlashMintQuote, + FlashMintQuoteProvider, + FlashMintQuoteRequest, + SwapQuoteProvider, +} from 'quote' + +import { approveErc20, balanceOf } from './' + +class TxTestFactory { + constructor(readonly provider: JsonRpcProvider, readonly signer: Wallet) {} + + /** + * Tests minting a given flash mint quote. + * @param quote a flash mint quote + */ + async testMinting(quote: FlashMintQuote) { + const { signer } = this + const indexToken = quote.outputToken + const tx = quote.tx + if (!tx) fail() + const balanceBefore: BigNumber = await balanceOf(signer, indexToken.address) + if (quote.inputToken.symbol !== 'ETH') { + await approveErc20( + quote.inputToken.address, + quote.contract, + quote.inputOutputAmount, + signer + ) + } + // Automatically adding from as it seems like estimateGas won't recognize + // the impersonated balance if `from` is not set. + tx.from = this.signer.address + const gasEstimate = await this.provider.estimateGas(tx) + tx.gasLimit = gasEstimate + const res = await signer.sendTransaction(tx) + res.wait() + const balanceAfter: BigNumber = await balanceOf(signer, indexToken.address) + expect(balanceAfter.gte(balanceBefore.add(quote.indexTokenAmount))).toBe( + true + ) + } + + /** + * Tests redeeming a given flash mint quote. + * @param quote a flash mint quote + * @param gasLimit override gas limit + */ + async testRedeeming(quote: FlashMintQuote, gasLimit?: BigNumber) { + const { signer } = this + const indexToken = quote.inputToken + const balanceBefore: BigNumber = await balanceOf(signer, indexToken.address) + await approveErc20( + indexToken.address, + quote.contract, + quote.indexTokenAmount, + signer + ) + const tx = quote.tx + if (!tx) fail() + // Automatically, adding from as it seems like estimateGas won't recognize + // the impersonated balance if `from` is not set. + tx.from = this.signer.address + tx.gasLimit = gasLimit + if (!gasLimit) { + const gasEstimate = await this.provider.estimateGas(tx) + tx.gasLimit = gasEstimate + } + const res = await signer.sendTransaction(tx) + res.wait() + const balanceAfter: BigNumber = await balanceOf(signer, indexToken.address) + expect(balanceAfter.lte(balanceBefore.sub(quote.indexTokenAmount))).toBe( + true + ) + } +} + +export class TestFactory { + private quote: FlashMintQuote | null = null + private quoteProvider: FlashMintQuoteProvider + private txFactory: TxTestFactory + constructor( + rpcUrl: string, + signer: Wallet, + swapQuoteProvider: SwapQuoteProvider + ) { + const provider = new JsonRpcProvider(rpcUrl) + this.quoteProvider = new FlashMintQuoteProvider(rpcUrl, swapQuoteProvider) + this.txFactory = new TxTestFactory(provider, signer) + } + + async executeTx(gasLimit?: BigNumber) { + if (!this.quote) fail() + if (this.quote.isMinting) { + await this.txFactory.testMinting(this.quote) + return + } + await this.txFactory.testRedeeming(this.quote, gasLimit) + } + + async fetchQuote(config: FlashMintQuoteRequest): Promise { + const quote = await this.quoteProvider.getQuote(config) + expect(quote).toBeDefined() + if (!quote) fail() + expect(quote.isMinting).toEqual(config.isMinting) + expect(quote.indexTokenAmount).toEqual(config.indexTokenAmount) + expect(quote.inputOutputAmount.gt(0)).toBe(true) + this.quote = quote + return quote + } + + getProvider(): JsonRpcProvider { + return this.txFactory.provider + } + + getSigner(): Wallet { + return this.txFactory.signer + } +} diff --git a/src/tests/utils/weth.ts b/src/tests/utils/weth.ts new file mode 100644 index 00000000..59d33440 --- /dev/null +++ b/src/tests/utils/weth.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { BigNumber } from '@ethersproject/bignumber' +import { Contract } from '@ethersproject/contracts' +import { Wallet } from '@ethersproject/wallet' +import { getTokenByChainAndSymbol } from '@indexcoop/tokenlists' + +import { ChainId } from 'constants/chains' +export { wei } from 'utils/numbers' + +export async function wrapETH( + amount: BigNumber, + signer: Wallet, + chainId: number = ChainId.Mainnet +) { + const abi = ['function deposit() public payable'] + const WETH9 = getTokenByChainAndSymbol(chainId, 'WETH')!.address + const contract = new Contract(WETH9, abi, signer) + const depositTokenInTx = await contract.deposit({ + gasLimit: 50_000, + value: amount, + }) + await depositTokenInTx.wait() +} diff --git a/src/utils/clients.ts b/src/utils/clients.ts index 00b02012..60fcae17 100644 --- a/src/utils/clients.ts +++ b/src/utils/clients.ts @@ -1,9 +1,15 @@ import { createPublicClient, http } from 'viem' -import { mainnet } from 'viem/chains' +import { base, mainnet } from 'viem/chains' import { ChainId } from 'constants/chains' export function createClient(chainId: number) { + if (chainId === ChainId.Base) { + return createPublicClient({ + chain: base, + transport: http(process.env.BASE_ALCHEMY_API), + }) + } if (chainId !== ChainId.Mainnet) return null return createPublicClient({ chain: mainnet, diff --git a/src/utils/component-swap-data.test.ts b/src/utils/component-swap-data.test.ts index 5a2168db..f2b76fca 100644 --- a/src/utils/component-swap-data.test.ts +++ b/src/utils/component-swap-data.test.ts @@ -1,23 +1,29 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { AddressZero } from 'constants/addresses' -import { TheUSDCYieldIndex, USDC, WETH } from 'constants/tokens' +import { ChainId } from 'constants/chains' + import { getIssuanceComponentSwapData, getRedemptionComponentSwapData, } from 'utils/component-swap-data' import { wei } from 'utils/numbers' -import { IndexZeroExSwapQuoteProvider, LocalhostProviderUrl } from 'tests/utils' +import { + getLocalHostProviderUrl, + getZeroExSwapQuoteProvider, +} from 'tests/utils' import { isSameAddress } from 'utils/addresses' import { Exchange } from 'utils/swap-data' +import { getTokenByChainAndSymbol } from '@indexcoop/tokenlists' +import { BigNumber } from '@ethersproject/bignumber' -const chainId = 1 -const rpcUrl = LocalhostProviderUrl -const swapQuoteProvider = IndexZeroExSwapQuoteProvider +const chainId = ChainId.Base +const rpcUrl = getLocalHostProviderUrl(chainId) +const swapQuoteProvider = getZeroExSwapQuoteProvider(chainId) -const indexTokenSymbol = TheUSDCYieldIndex.symbol -const indexToken = TheUSDCYieldIndex.address! -const usdc = USDC.address! -const weth = WETH.address! +const icUsd = getTokenByChainAndSymbol(chainId, 'icUSD') +const indexTokenSymbol = icUsd.symbol +const indexToken = icUsd.address +const usdc = getTokenByChainAndSymbol(chainId, 'USDC').address +const weth = getTokenByChainAndSymbol(chainId, 'WETH').address describe('getIssuanceComponentSwapData()', () => { test('returns correct swap data based on input token USDC', async () => { @@ -32,8 +38,7 @@ describe('getIssuanceComponentSwapData()', () => { rpcUrl, swapQuoteProvider ) - // TODO: update once rebalanced into components - expect(componentSwapData.length).toBe(1) + expect(componentSwapData.length).toBe(8) for (let i = 0; i < componentSwapData.length; i++) { expect(isSameAddress(componentSwapData[i].underlyingERC20, usdc)).toBe( true @@ -45,9 +50,10 @@ describe('getIssuanceComponentSwapData()', () => { expect(dexData.path).toEqual([]) expect(dexData.pool).toEqual(AddressZero) expect(dexData.poolIds).toEqual([]) + expect( + componentSwapData[i].buyUnderlyingAmount.gt(BigNumber.from(0)) + ).toBe(true) } - // TODO: update once rebalanced into components - expect(componentSwapData[0].buyUnderlyingAmount.toString()).toBe('1000010') }) test('returns correct swap data based when input token is WETH', async () => { @@ -62,25 +68,22 @@ describe('getIssuanceComponentSwapData()', () => { rpcUrl, swapQuoteProvider ) - // TODO: update once rebalanced into components - expect(componentSwapData.length).toBe(1) + expect(componentSwapData.length).toBe(8) for (let i = 0; i < componentSwapData.length; i++) { expect(isSameAddress(componentSwapData[i].underlyingERC20, usdc)).toBe( true ) - // Should be empty as input token is equal to output token (underlying erc20) const dexData = componentSwapData[i].dexData expect(dexData.exchange).toEqual(Exchange.UniV3) expect(dexData.fees.length).toBeGreaterThan(0) - expect(dexData.path).toEqual([ - '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - ]) + expect(dexData.path[0]).toEqual(weth) + expect(dexData.path[dexData.path.length - 1]).toEqual(usdc.toLowerCase()) expect(dexData.pool).toEqual(AddressZero) expect(dexData.poolIds).toEqual([]) + expect( + componentSwapData[i].buyUnderlyingAmount.gt(BigNumber.from(0)) + ).toBe(true) } - // TODO: update once rebalanced into components - expect(componentSwapData[0].buyUnderlyingAmount.toString()).toBe('1000010') }) }) @@ -97,8 +100,7 @@ describe('getRedemptionComponentSwapData()', () => { rpcUrl, swapQuoteProvider ) - // TODO: update once rebalanced into components - expect(componentSwapData.length).toBe(1) + expect(componentSwapData.length).toBe(8) for (let i = 0; i < componentSwapData.length; i++) { expect(isSameAddress(componentSwapData[i].underlyingERC20, usdc)).toBe( true @@ -110,9 +112,10 @@ describe('getRedemptionComponentSwapData()', () => { expect(dexData.path).toEqual([]) expect(dexData.pool).toEqual(AddressZero) expect(dexData.poolIds).toEqual([]) + expect( + componentSwapData[i].buyUnderlyingAmount.gt(BigNumber.from(0)) + ).toBe(true) } - // TODO: update once rebalanced into components - expect(componentSwapData[0].buyUnderlyingAmount.toString()).toBe('999990') }) test('returns correct swap data when output token is WETH', async () => { @@ -127,24 +130,20 @@ describe('getRedemptionComponentSwapData()', () => { rpcUrl, swapQuoteProvider ) - // TODO: update once rebalanced into components - expect(componentSwapData.length).toBe(1) + expect(componentSwapData.length).toBe(8) for (let i = 0; i < componentSwapData.length; i++) { expect(isSameAddress(componentSwapData[i].underlyingERC20, usdc)).toBe( true ) - // Should be empty as input token is equal to output token (underlying erc20) const dexData = componentSwapData[i].dexData expect(dexData.exchange).toEqual(Exchange.UniV3) expect(dexData.fees.length).toBeGreaterThan(0) - expect(dexData.path).toEqual([ - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - ]) + expect(dexData.path).toEqual([usdc.toLowerCase(), weth]) expect(dexData.pool).toEqual(AddressZero) expect(dexData.poolIds).toEqual([]) + expect( + componentSwapData[i].buyUnderlyingAmount.gt(BigNumber.from(0)) + ).toBe(true) } - // TODO: update once rebalanced into components - expect(componentSwapData[0].buyUnderlyingAmount.toString()).toBe('999990') }) }) diff --git a/src/utils/component-swap-data.ts b/src/utils/component-swap-data.ts index 304dc5ac..427bc300 100644 --- a/src/utils/component-swap-data.ts +++ b/src/utils/component-swap-data.ts @@ -1,9 +1,9 @@ import { BigNumber } from '@ethersproject/bignumber' import { Contract } from '@ethersproject/contracts' +import { getTokenByChainAndSymbol, isAddressEqual } from '@indexcoop/tokenlists' import { Address, parseAbi } from 'viem' import { AddressZero } from 'constants/addresses' -import { USDC } from 'constants/tokens' import { SwapQuote, SwapQuoteProvider } from 'quote' import { isSameAddress } from 'utils/addresses' import { createClient } from 'utils/clients' @@ -65,7 +65,7 @@ export async function getIssuanceComponentSwapData( indexTokenAmount, inputToken, } = request - const issuance = getIssuanceContract(indexTokenSymbol, rpcUrl) + const issuance = getIssuanceContract(chainId, indexTokenSymbol, rpcUrl) const [issuanceComponents, issuanceUnits] = await issuance.getRequiredComponentIssuanceUnits( indexToken, @@ -75,26 +75,17 @@ export async function getIssuanceComponentSwapData( issuanceComponents.map((component: string) => getUnderlyingErc20(component, chainId) ) + const units = issuanceUnits.map((unit: BigNumber) => unit.toString()) const amountPromises = issuanceComponents.map( (component: Address, index: number) => - getAmount(component, issuanceUnits[index], chainId) + getAmount(true, component, BigInt(units[index]), chainId) ) const wrappedTokens = await Promise.all(underlyingERC20sPromises) - const amounts = await Promise.all(amountPromises) + const amounts: bigint[] = await Promise.all(amountPromises) const swapPromises: Promise[] = issuanceComponents.map( (_: string, index: number) => { const wrappedToken = wrappedTokens[index] const underlyingERC20 = wrappedToken.underlyingErc20 - console.log( - underlyingERC20.symbol === USDC.symbol, - underlyingERC20.symbol, - USDC.symbol - ) - console.log( - isSameAddress(underlyingERC20.address, inputToken), - underlyingERC20.address, - inputToken - ) if (isSameAddress(underlyingERC20.address, inputToken)) return null return swapQuoteProvider.getSwapQuote({ chainId, @@ -130,7 +121,7 @@ export async function getRedemptionComponentSwapData( indexTokenAmount, outputToken, } = request - const issuance = getIssuanceContract(indexTokenSymbol, rpcUrl) + const issuance = getIssuanceContract(chainId, indexTokenSymbol, rpcUrl) const [issuanceComponents, issuanceUnits] = await issuance.getRequiredComponentRedemptionUnits( indexToken, @@ -142,11 +133,10 @@ export async function getRedemptionComponentSwapData( ) const amountPromises = issuanceComponents.map( (component: Address, index: number) => - getAmount(component, issuanceUnits[index], chainId) + getAmount(false, component, issuanceUnits[index].toBigInt(), chainId) ) const wrappedTokens = await Promise.all(underlyingERC20sPromises) const amounts = await Promise.all(amountPromises) - console.log(wrappedTokens) const swapPromises: Promise[] = issuanceComponents.map( (_: string, index: number) => { const wrappedToken = wrappedTokens[index] @@ -173,12 +163,12 @@ export async function getRedemptionComponentSwapData( function buildComponentSwapData( issuanceComponents: string[], wrappedTokens: WrappedToken[], - buyAmounts: BigNumber[], + buyAmounts: bigint[], swapDataResults: (SwapQuote | null)[] ): ComponentSwapData[] { return issuanceComponents.map((_: string, index: number) => { const wrappedToken = wrappedTokens[index] - const buyUnderlyingAmount = buyAmounts[index] + const buyUnderlyingAmount = BigNumber.from(buyAmounts[index].toString()) const swapData = swapDataResults[index]?.swapData const dexData: SwapDataV3 = swapData ? { @@ -198,6 +188,7 @@ function buildComponentSwapData( } async function getAmount( + isMinting: boolean, component: Address, issuanceUnits: bigint, chainId: number @@ -207,49 +198,31 @@ async function getAmount( const publicClient = createClient(chainId)! const erc4626Abi = [ 'function convertToAssets(uint256 shares) view returns (uint256 assets)', - 'function previewDeposit(uint256 assets) view returns (uint256)', - 'function previewMint(uint256 shares) view returns (uint256)', - 'function previewRedeem(uint256 shares) view returns (uint256)', - 'function previewWithdraw(uint256 assets) view returns (uint256)', + 'function previewMint(uint256 shares) view returns (uint256 assets)', + 'function previewRedeem(uint256 shares) view returns (uint256 assets)', ] - const assets: bigint = (await publicClient.readContract({ + const preview: bigint = (await publicClient.readContract({ address: component as Address, - abi: erc4626Abi, - functionName: 'convertToAssets', + abi: parseAbi(erc4626Abi), + functionName: isMinting ? 'previewMint' : 'previewRedeem', args: [issuanceUnits], })) as bigint - return assets + return preview } catch { - // TODO: apply slippage to issuance units amount (for all none erc4262) - return issuanceUnits + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const usdc = getTokenByChainAndSymbol(chainId, 'USDC')! + if (isAddressEqual(component, usdc)) return issuanceUnits + // Apply slippage to issuance units amount (for all none erc4262) + if (isMinting) { + return (issuanceUnits * BigInt(1005)) / BigInt(1000) + } else { + return (issuanceUnits * BigInt(995)) / BigInt(1000) + } } - // const isFCASH = (address: string) => - // [ - // '0x278039398A5eb29b6c2FB43789a38A84C6085266', - // '0xe09B1968851478f20a43959d8a212051367dF01A', - // ].includes(address) - // const getAmountOfAssetToObtainShares = async () => - // component: string, - // shares: BigNumber, - // provider: JsonRpcProvider - // slippage = DEFAULT_SLIPPAGE // 1 = 1% - // { - // const componentContract = new Contract(component, erc4626Abi, provider) - // // Convert slippage to a BigNumber, do rounding to avoid weird JS precision loss - // const defaultSlippageBN = BigNumber.from(Math.round(slippage * 10000)) - // // if FCASH, increase slippage x3 - // const slippageBigNumber = isFCASH(component) - // ? defaultSlippageBN.mul(3) - // : defaultSlippageBN - // // Calculate the multiplier (1 + slippage) - // const multiplier = BigNumber.from(10000).add(slippageBigNumber) - // const buyUnderlyingAmount: BigNumber = - // await componentContract.convertToAssets(shares) - // return buyUnderlyingAmount.mul(multiplier).div(10000) - // } } function getIssuanceContract( + chainId: number, indexTokenSymbol: string, rpcUrl: string ): Contract { @@ -258,7 +231,7 @@ function getIssuanceContract( 'function getRequiredComponentRedemptionUnits(address _setToken, uint256 _quantity) external view returns (address[] memory, uint256[] memory, uint256[] memory)', ] const provider = getRpcProvider(rpcUrl) - const issuanceModule = getIssuanceModule(indexTokenSymbol) + const issuanceModule = getIssuanceModule(indexTokenSymbol, chainId) return new Contract(issuanceModule.address, abi, provider) } @@ -273,14 +246,15 @@ async function getUnderlyingErc20( abi: parseAbi(['function decimals() view returns (uint8)']), functionName: 'decimals', }) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const usdc = getTokenByChainAndSymbol(chainId, 'USDC')! return { address: token, decimals, underlyingErc20: { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - address: USDC.address!, - decimals: 6, - symbol: USDC.symbol, + address: usdc.address, + decimals: usdc.decimals, + symbol: usdc.symbol, }, } } diff --git a/src/utils/contracts.test.ts b/src/utils/contracts.test.ts index 6b0e0aa0..ad82c173 100644 --- a/src/utils/contracts.test.ts +++ b/src/utils/contracts.test.ts @@ -32,6 +32,7 @@ import { getFlashMintLeveragedContractForToken, getFlashMintHyEthContract, getFlashMintWrappedContract, + getFlashMintNavContract, } from './contracts' describe('getExchangeIssuanceLeveragedContractAddress()', () => { @@ -141,6 +142,20 @@ describe('getFlashMintLeveragedContractForToken()', () => { }) }) +describe('getFlashMintNavContract()', () => { + test('returns correct contract', async () => { + const expectedAddress = Contracts[ChainId.Mainnet].FlashMintNav + const contract = getFlashMintNavContract(undefined) + expect(contract.address).toEqual(expectedAddress) + expect(contract.functions.getIssueAmount).toBeDefined() + expect(contract.functions.getRedeemAmountOut).toBeDefined() + expect(contract.functions.issueSetFromExactERC20).toBeDefined() + expect(contract.functions.issueSetFromExactETH).toBeDefined() + expect(contract.functions.redeemExactSetForERC20).toBeDefined() + expect(contract.functions.redeemExactSetForETH).toBeDefined() + }) +}) + describe('getFlashMintWrappedContract()', () => { test('returns correct contract', async () => { const expectedAddress = Contracts[ChainId.Mainnet].FlashMintWrapped @@ -153,6 +168,18 @@ describe('getFlashMintWrappedContract()', () => { expect(contract.functions.redeemExactSetForERC20).toBeDefined() expect(contract.functions.redeemExactSetForETH).toBeDefined() }) + + test('returns correct contract for Base', async () => { + const expectedAddress = Contracts[ChainId.Base].FlashMintWrapped + const contract = getFlashMintWrappedContract(undefined, ChainId.Base) + expect(contract.address).toEqual(expectedAddress) + expect(contract.functions.getIssueExactSet).toBeDefined() + expect(contract.functions.getRedeemExactSet).toBeDefined() + expect(contract.functions.issueExactSetFromERC20).toBeDefined() + expect(contract.functions.issueExactSetFromETH).toBeDefined() + expect(contract.functions.redeemExactSetForERC20).toBeDefined() + expect(contract.functions.redeemExactSetForETH).toBeDefined() + }) }) describe('getExchangeIssuanceZeroExContractAddress()', () => { diff --git a/src/utils/contracts.ts b/src/utils/contracts.ts index e2f421c9..3c3646e9 100644 --- a/src/utils/contracts.ts +++ b/src/utils/contracts.ts @@ -7,6 +7,7 @@ import EXCHANGE_ISSUANCE_ZERO_EX_ABI from '../constants/abis/ExchangeIssuanceZer import FLASHMINT_HYETH_ABI from '../constants/abis/FlashMintHyEth.json' import FLASHMINT_LEVERAGED_COMPOUND from '../constants/abis/FlashMintLeveragedForCompound.json' import FLASHMINT_LEVERAGED_EXTENDED_ABI from '../constants/abis/FlashMintLeveragedExtended.json' +import FLASHMINT_NAV_ABI from '../constants/abis/FlashMintNav.json' import FLASHMINT_WRAPPED_ABI from '../constants/abis/FlashMintWrapped.json' import FLASHMINT_ZEROEX_ABI from '../constants/abis/FlashMintZeroEx.json' @@ -187,20 +188,34 @@ export const getFlashMintLeveragedContractForToken = ( } /** - * Returns an instance of a FlasthMintWrapped contract (mainnet only). + * Returns an instance of a FlashMintNav contract (mainnet only). * @param signerOrProvider A signer or provider. - * @returns An instance of a FlasthMintWrapped contract. + * @returns An instance of a FlashMintNav contract. */ -export const getFlashMintWrappedContract = ( +export const getFlashMintNavContract = ( signerOrProvider: Signer | Provider | undefined ): Contract => { return new Contract( - Contracts[ChainId.Mainnet].FlashMintWrapped, - FLASHMINT_WRAPPED_ABI, + Contracts[ChainId.Mainnet].FlashMintNav, + FLASHMINT_NAV_ABI, signerOrProvider ) } +/** + * Returns an instance of a FlasthMintWrapped contract. + * @param signerOrProvider A signer or provider. + * @param chainId A supported chainId. + * @returns An instance of a FlasthMintWrapped contract. + */ +export const getFlashMintWrappedContract = ( + signerOrProvider: Signer | Provider | undefined, + chainId: number = ChainId.Mainnet +): Contract => { + const contractAddress = Contracts[chainId].FlashMintWrapped + return new Contract(contractAddress, FLASHMINT_WRAPPED_ABI, signerOrProvider) +} + export function getExchangeIssuanceZeroExContractAddress( chainId: number = ChainId.Mainnet ): string { diff --git a/src/utils/custom-oracle-nav-issuance-module.ts b/src/utils/custom-oracle-nav-issuance-module.ts new file mode 100644 index 00000000..2e866231 --- /dev/null +++ b/src/utils/custom-oracle-nav-issuance-module.ts @@ -0,0 +1,23 @@ +import { Address, parseAbi } from 'viem' + +import { Contracts } from 'constants/contracts' +import { createClient } from 'utils/clients' + +export async function getExpectedReserveRedeemQuantity( + chainId: number, + indexToken: Address, + reserveAsset: Address, + indexTokenAmount: bigint +): Promise { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const publicClient = createClient(chainId)! + const amount: bigint = (await publicClient.readContract({ + address: Contracts[chainId].CustomOracleNavIssuanceModule, + abi: parseAbi([ + 'function getExpectedReserveRedeemQuantity(address _setToken, address _reserveAsset, uint256 _setTokenQuantity) view returns (uint256)', + ]), + functionName: 'getExpectedReserveRedeemQuantity', + args: [indexToken, reserveAsset, indexTokenAmount], + })) as bigint + return amount +} diff --git a/src/utils/erc20.ts b/src/utils/erc20.ts new file mode 100644 index 00000000..27ab9533 --- /dev/null +++ b/src/utils/erc20.ts @@ -0,0 +1,20 @@ +import { Address, parseAbi } from 'viem' +import { createClient } from './clients' + +export async function getBalanceOf( + token: Address, + account: Address, + chainId: number +) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const publicClient = createClient(chainId)! + const amount: bigint = (await publicClient.readContract({ + address: token, + abi: parseAbi([ + 'function balanceOf(address account) view returns (uint256)', + ]), + functionName: 'balanceOf', + args: [account], + })) as bigint + return amount +} diff --git a/src/utils/leveraged-token-data.ts b/src/utils/leveraged-token-data.ts index 69573f1a..ddb95b75 100644 --- a/src/utils/leveraged-token-data.ts +++ b/src/utils/leveraged-token-data.ts @@ -31,7 +31,7 @@ export async function getLeveragedTokenData( isIssuance ) } catch (error) { - // TODO: should this just always fail cause it means there is something wrongly configured? + // Should this just always fail cause it means there is something wrongly configured? console.error('Error getting leveraged token data', error) return null } diff --git a/src/utils/slippage.ts b/src/utils/slippage.ts index 4451a97b..b2a1eb2f 100644 --- a/src/utils/slippage.ts +++ b/src/utils/slippage.ts @@ -20,7 +20,6 @@ export function slippageAdjustedTokenAmount( .mul(wei(100, tokenDecimals)) .div(wei(100 - slippage, tokenDecimals)) } - return tokenAmount .mul(wei(100, tokenDecimals)) .div(wei(100 + slippage, tokenDecimals)) diff --git a/src/utils/wrap-data.test.ts b/src/utils/wrap-data.test.ts index 8d556a92..ff1def94 100644 --- a/src/utils/wrap-data.test.ts +++ b/src/utils/wrap-data.test.ts @@ -8,15 +8,17 @@ describe('getWrapData()', () => { test.only('returns correct wrap data for icUSD', async () => { const wrapData = getWrapData('icUSD') - // TODO: update for components (after presale/rebalance) - expect(wrapData.length).toBe(1) - // expect(wrapData[0].integrationName).toBe('') - // expect(wrapData[1].integrationName).toBe('Aave_V3_Wrap_V2_Adapter') - // expect(wrapData[2].integrationName).toBe('Compound_V3_USDC_Wrap_V2_Adapter') - // expect(wrapData[3].integrationName).toBe('Aave_V2_Wrap_V2_Adapter') - // expect(wrapData[4].integrationName).toBe('ERC4626_Wrap_V2_Adapter') - // wrapData.forEach((data) => { - // expect(data.wrapData).toBe('0x0000000000000000000000000000000000000000') - // }) + expect(wrapData.length).toBe(8) + expect(wrapData[0].integrationName).toBe('AaveV3WrapV2Adapter') + expect(wrapData[1].integrationName).toBe('CompoundV3WrapV2Adapter') + expect(wrapData[2].integrationName).toBe('ERC4626WrapV2Adapter') + expect(wrapData[3].integrationName).toBe('ERC4626WrapV2Adapter') + expect(wrapData[4].integrationName).toBe('ERC4626WrapV2Adapter') + expect(wrapData[5].integrationName).toBe('') + expect(wrapData[6].integrationName).toBe('ERC4626WrapV2Adapter') + expect(wrapData[7].integrationName).toBe('ERC4626WrapV2Adapter') + wrapData.forEach((data) => { + expect(data.wrapData).toBe('0x0000000000000000000000000000000000000000') + }) }) }) diff --git a/src/utils/wrap-data.ts b/src/utils/wrap-data.ts index dc6fdc5f..1a80d033 100644 --- a/src/utils/wrap-data.ts +++ b/src/utils/wrap-data.ts @@ -1,5 +1,6 @@ import { TheUSDCYieldIndex } from 'constants/tokens' +// https://github.com/IndexCoop/index-deployments/pull/299/files#diff-dbc080e96cf11ca994e727d3b77db7b178aa38a33d24d82850e934c018e4d545 enum IntegrationName { aaveV2WrapV2Adapter = 'AaveV2WrapV2Adapter', aaveV3WrapV2Adapter = 'AaveV3WrapV2Adapter', @@ -20,26 +21,37 @@ const ZERO_BYTES = '0x0000000000000000000000000000000000000000' export function getWrapData(tokenSymbol: string): ComponentWrapData[] { if (tokenSymbol !== TheUSDCYieldIndex.symbol) return [] return [ + { + integrationName: IntegrationName.aaveV3WrapV2Adapter, + wrapData: ZERO_BYTES, + }, + { + integrationName: IntegrationName.compoundV3UsdcWrapV2Adapter, + wrapData: ZERO_BYTES, + }, + { + integrationName: IntegrationName.erc4626WrapV2Adapter, + wrapData: ZERO_BYTES, + }, + { + integrationName: IntegrationName.erc4626WrapV2Adapter, + wrapData: ZERO_BYTES, + }, + { + integrationName: IntegrationName.erc4626WrapV2Adapter, + wrapData: ZERO_BYTES, + }, { integrationName: '', wrapData: ZERO_BYTES, }, - // TODO: update once rebalanced - // { - // integrationName: aaveV3WrapV2AdapterName, - // wrapData: ZERO_BYTES, - // }, - // { - // integrationName: compoundV3WrapV2AdapterName, - // wrapData: ZERO_BYTES, - // }, - // { - // integrationName: aaveV2WrapV2AdapterName, - // wrapData: ZERO_BYTES, - // }, - // { - // integrationName: erc4626WrapV2AdapterName, - // wrapData: ZERO_BYTES, - // }, + { + integrationName: IntegrationName.erc4626WrapV2Adapter, + wrapData: ZERO_BYTES, + }, + { + integrationName: IntegrationName.erc4626WrapV2Adapter, + wrapData: ZERO_BYTES, + }, ] }