diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 712f80f3..5f242766 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -52,9 +52,9 @@ jobs: npm install -g yarn yarn - # - name: Run nx reset workspace - # run: | - # yarn nx reset + - name: Run nx reset workspace + run: | + yarn nx reset - name: Run test run: | diff --git a/jest.config.js b/jest.config.js index 3c3b8fb5..7988c582 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,7 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ +// jest.config.js module.exports = { transform: { - "^.+\\.ts?$": ["ts-jest", { isolatedModules: true }] + "^.+\\.ts?$": ["@swc/jest"] }, testEnvironment: "node", modulePathIgnorePatterns: ["/dist/", "/packages/ibc-routing"], diff --git a/package.json b/package.json index 2fa2c47e..a3150d05 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,11 @@ "@oraichain/common-contracts-sdk": "1.0.31" }, "devDependencies": { + "@babel/traverse": "7.24.1", "@cosmjs/encoding": "0.31.3", "@oraichain/cw-simulate": "^2.8.68", + "@swc/core": "^1.4.11", + "@swc/jest": "^0.2.36", "@types/jest": "^29.5.12", "@types/lodash": "^4.17.0", "@types/node": "^20.11.30", @@ -55,8 +58,7 @@ "ts-node": "^10.9.2", "ts-node-dev": "^2.0.0", "typedoc": "^0.25.12", - "typescript": "5.3.2", - "@babel/traverse": "7.24.1" + "typescript": "5.3.2" }, "version": "1.0.1" } diff --git a/packages/oraidex-common-ui/package.json b/packages/oraidex-common-ui/package.json index b6fe3986..eb65ef4f 100644 --- a/packages/oraidex-common-ui/package.json +++ b/packages/oraidex-common-ui/package.json @@ -24,16 +24,19 @@ "react-use-websocket": "^4.5.0" }, "devDependencies": { + "@babel/preset-env": "^7.24.4", + "@babel/preset-react": "^7.24.1", + "@babel/preset-typescript": "^7.24.1", "@storybook/addon-essentials": "7.5.3", "@storybook/addon-interactions": "7.5.3", "@storybook/addon-links": "7.5.3", "@storybook/react": "7.5.3", - "@storybook/react-webpack5": "^7.5.3", - "@types/react": "^18.2.2", - "css-minimizer-webpack-plugin": "^4.1.0", + "@storybook/react-webpack5": "7.5.3", + "@types/react": "18.2.2", + "css-minimizer-webpack-plugin": "6.0.0", "mini-css-extract-plugin": "^2.8.1", "storybook": "7.5.3", - "terser-webpack-plugin": "5.3.6", + "terser-webpack-plugin": "5.3.10", "webpack": "5.89.0", "webpack-cli": "^5.1.4" }, diff --git a/packages/oraidex-common/package.json b/packages/oraidex-common/package.json index 39a591ce..1ef4be81 100644 --- a/packages/oraidex-common/package.json +++ b/packages/oraidex-common/package.json @@ -1,6 +1,6 @@ { "name": "@oraichain/oraidex-common", - "version": "1.0.81", + "version": "1.0.83", "main": "build/index.js", "files": [ "build/" diff --git a/packages/oraidex-common/src/helper.ts b/packages/oraidex-common/src/helper.ts index c17fefba..b9aaa718 100644 --- a/packages/oraidex-common/src/helper.ts +++ b/packages/oraidex-common/src/helper.ts @@ -19,7 +19,7 @@ import { MULTIPLIER, CW20_DECIMALS } from "./constant"; -import { CoinGeckoId, NetworkChainId } from "./network"; +import { CoinGeckoId, NetworkChainId, cosmosChains } from "./network"; import { AmountDetails, TokenInfo, @@ -34,6 +34,7 @@ import { StargateMsg, Tx } from "./tx"; import { BigDecimal } from "./bigdecimal"; import { TextProposal } from "cosmjs-types/cosmos/gov/v1beta1/gov"; import { defaultRegistryTypes as defaultStargateTypes, IndexedTx, logs, StargateClient } from "@cosmjs/stargate"; +import TronWeb from "tronweb"; export const getEvmAddress = (bech32Address: string) => { if (!bech32Address) throw new Error("bech32 address is empty"); @@ -489,3 +490,116 @@ export const parseTxToMsgsAndEvents = (indexedTx: Tx, eventsParser?: (events: re return { attrs, message: messages[index] }; }); }; + +export const validateAndIdentifyCosmosAddress = (address: string, network: string) => { + try { + const cosmosAddressRegex = /^[a-z]{1,6}[0-9a-z]{0,64}$/; + if (!cosmosAddressRegex.test(address)) { + throw new Error("Invalid address"); + } + + const decodedAddress = bech32.decode(address); + const prefix = decodedAddress.prefix; + + let chainInfo; + const networkMap = cosmosChains.reduce((acc, cur) => { + if (cur.chainId === network) chainInfo = cur; + return { + ...acc, + [cur.bech32Config.bech32PrefixAccAddr]: true + }; + }, {}); + + if (chainInfo && chainInfo.bech32Config.bech32PrefixAccAddr !== prefix) { + throw new Error("Network doesn't match"); + } + + if (networkMap.hasOwnProperty(prefix)) { + return { + isValid: true, + network + }; + } else { + throw new Error("Unsupported address network"); + } + } catch (error) { + console.log("error:", error); + return { + isValid: false, + error: error.message + }; + } +}; + +export const validateEvmAddress = (address: string, network: string) => { + try { + const isEvm = ethers.utils.isAddress(address); + + if (isEvm) { + return { + isValid: true, + network + }; + } + + return { + isValid: false + }; + } catch (error) { + return { + isValid: false + }; + } +}; + +export const validateTronAddress = (address: string, network: string) => { + try { + if (!/T[a-zA-Z0-9]{32}/.test(address)) { + throw new Error("Invalid tron address"); + } + + return { + isValid: true, + network + }; + + // const tronWeb = new TronWeb({ + // fullHost: "https://api.trongrid.io" + // }); + + // tronWeb.trx.getAccount(address).then((isValid) => { + // if (isValid) { + // return { + // isValid: true, + // network: "0x2b6653dc" //"tron" + // }; + // } else { + // console.error("Invalid address"); + + // return { + // isValid: false, + // network: "0x2b6653dc" //"tron" + // }; + // } + // }); + } catch (error) { + return { + isValid: false + }; + } +}; + +export const checkValidateAddressWithNetwork = (address: string, network: NetworkChainId) => { + switch (network) { + case "0x01": + case "0x38": + return validateEvmAddress(address, network); + + // tron + case "0x2b6653dc": + return validateTronAddress(address, network); + + default: + return validateAndIdentifyCosmosAddress(address, network); + } +}; diff --git a/packages/oraidex-common/tests/helper.spec.ts b/packages/oraidex-common/tests/helper.spec.ts index 2667ea78..65d5aba3 100644 --- a/packages/oraidex-common/tests/helper.spec.ts +++ b/packages/oraidex-common/tests/helper.spec.ts @@ -7,6 +7,7 @@ import { AIRI_CONTRACT, AVERAGE_COSMOS_GAS_PRICE, MILKYBSC_ORAICHAIN_DENOM, ORAI import { calculateMinReceive, calculateTimeoutTimestamp, + checkValidateAddressWithNetwork, decodeProto, ethToTronAddress, findToTokenOnOraiBridge, @@ -29,11 +30,16 @@ import { toDisplay, toTokenInfo, tronToEthAddress, - validateNumber + validateAndIdentifyCosmosAddress, + validateEvmAddress, + validateNumber, + validateTronAddress } from "../src/helper"; import { CoinGeckoId, NetworkChainId } from "../src/network"; import { isFactoryV1 } from "../src/pairs"; import { AmountDetails, TokenItemType, cosmosTokens, flattenTokens, oraichainTokens } from "../src/token"; +import fs from "fs"; +import path from "path"; describe("should helper functions in helper run exactly", () => { const amounts: AmountDetails = { @@ -388,11 +394,10 @@ describe("should helper functions in helper run exactly", () => { expect(reuslt).toEqual([]); // case 2: real tx with multiple msgs and multiple contract calls - const client = await StargateClient.connect("wss://rpc.orai.io"); - const indexedTx = await client.getTx("9B435E4014DEBA5AB80D4BB8F52D766A6C14BFCAC21F821CDB96F4ABB4E29B17"); - client.disconnect(); - - const data = parseTxToMsgsAndEvents(indexedTx!); + // got data from tx hash 9B435E4014DEBA5AB80D4BB8F52D766A6C14BFCAC21F821CDB96F4ABB4E29B17 Oraichain. + const rawLog = fs.readFileSync(path.join(__dirname, "indexed-tx-raw-log.json")).toString(); + const tx = Buffer.from(fs.readFileSync(path.join(__dirname, "indexed-tx-tx.json")).toString(), "base64"); + const data = parseTxToMsgsAndEvents({ rawLog, tx } as any); expect(data.length).toEqual(2); expect(data[0].message).toMatchObject({ sender: "orai16hv74w3eu3ek0muqpgp4fekhrqgpzl3hd3qeqk", @@ -540,4 +545,112 @@ describe("should helper functions in helper run exactly", () => { ])("test-parseWasmEvents-with-case: %p", (_case, input, expectedOutput) => { expect(parseWasmEvents(input)).toEqual(expectedOutput); }); + + it.each<[string, NetworkChainId, { isValid: boolean; network?: string; error?: string }]>([ + [ + "0x1CE09E54A5d7432ecabf3b085BAda7920aeb7dab", + "0x01", + { + isValid: true, + network: "0x01" + } + ], + [ + "0x1CE09E54A5d7432ecabf3b085BAda7920aeb7dab", + "0x38", + { + isValid: true, + network: "0x38" + } + ], + [ + "TPF97BNTx2pyNayUhz6B88JSzfdz5SHDbm", + "0x2b6653dc", + { + isValid: true, + network: "0x2b6653dc" + } + ], + [ + "orai1hvr9d72r5um9lvt0rpkd4r75vrsqtw6yujhqs2", + "Oraichain", + { + isValid: true, + network: "Oraichain" + } + ], + [ + "osmo1hvr9d72r5um9lvt0rpkd4r75vrsqtw6y86jn8t", + "0x38", + { + isValid: false + } + ], + [ + "osmo1hvr9d72r5um9lvt0rpkd4r75vrsqtw6y86jn8t", + "Oraichain", + { + isValid: false, + error: "Network doesn't match" + } + ], + [ + "TPF97BNTx2pyNayUhz6B88JSzfdz5SHDbm", + "Oraichain", + { + isValid: false, + error: "Invalid address" + } + ] + ])("test-check-validate-address-wallet-with-network", (address, network, expected) => { + const check = checkValidateAddressWithNetwork(address, network); + + expect(check).toEqual(expected); + }); + + it.each([ + ["0x1CE09E54A5d7432ecabf3b085BAda7920aeb7dab", "0x01", true], + ["TEu6u8JLCFs6x1w5s8WosNqYqVx2JMC5hQ", "0x2b6653dc", false], + ["TEu6u8JLCFs6x1w5s8WosNqYqVx2JMC5hQ", "0x01", false], + ["0x1", "0x38", false], + ["", "0x38", false] + ])("test-validateEvmAddress", (value, network, expectation) => { + try { + const { isValid } = validateEvmAddress(value, network); + expect(isValid).toEqual(expectation); + } catch (error) { + expect(expectation).toEqual(false); + } + }); + + it.each([ + ["TEu6u8JLCFs6x1w5s8WosNqYqVx2JMC5hQ", "0x2b6653dc", true], + ["0x1CE09E54A5d7432ecabf3b085BAda7920aeb7dab", "0x01", false], + ["TEu6u8JLCFs6x1w5s8WosNqYqVx2JMC5hQ", "0x01", false], + ["TE", "0x2b6653dc", false], + ["", "0x2b6653dc", false] + ])("test-validateTronAddress", (value, network, expectation) => { + try { + const { isValid } = validateTronAddress(value, network); + expect(isValid).toEqual(expectation); + } catch (error) { + expect(expectation).toEqual(false); + } + }); + + it.each([ + ["orai12zyu8w93h0q2lcnt50g3fn0w3yqnhy4fvawaqz", "Oraichain", true], + ["orai1", "Oraichain", false], + ["", "Oraichain", false], + ["cosmos12zyu8w93h0q2lcnt50g3fn0w3yqnhy4flwc7p3", "cosmoshub-4", true], + ["cosmos12", "cosmoshub-4", false], + ["", "cosmoshub-4", false] + ])("test-validateTronAddress", (value, network, expectation) => { + try { + const { isValid } = validateAndIdentifyCosmosAddress(value, network); + expect(isValid).toEqual(expectation); + } catch (error) { + expect(expectation).toEqual(false); + } + }); }); diff --git a/packages/oraidex-common/tests/indexed-tx-raw-log.json b/packages/oraidex-common/tests/indexed-tx-raw-log.json new file mode 100644 index 00000000..5f3193cd --- /dev/null +++ b/packages/oraidex-common/tests/indexed-tx-raw-log.json @@ -0,0 +1,469 @@ +[ + { + "events": [ + { + "type": "execute", + "attributes": [ + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "_contract_address", "value": "orai1lplapmgqnelqn253stz6kmvm3ulgdaytn89a8mz9y85xq8wd684s6xl3lt" }, + { "key": "_contract_address", "value": "orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh" } + ] + }, + { + "type": "message", + "attributes": [ + { "key": "action", "value": "/cosmwasm.wasm.v1.MsgExecuteContract" }, + { "key": "module", "value": "wasm" }, + { "key": "sender", "value": "orai16hv74w3eu3ek0muqpgp4fekhrqgpzl3hd3qeqk" } + ] + }, + { + "type": "wasm", + "attributes": [ + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "action", "value": "execute_orderbook_pair" }, + { + "key": "pair", + "value": "orai1lplapmgqnelqn253stz6kmvm3ulgdaytn89a8mz9y85xq8wd684s6xl3lt - orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh" + }, + { "key": "total_matched_orders", "value": "2" }, + { "key": "executor_reward", "value": "[]" }, + { "key": "_contract_address", "value": "orai1lplapmgqnelqn253stz6kmvm3ulgdaytn89a8mz9y85xq8wd684s6xl3lt" }, + { "key": "action", "value": "transfer" }, + { "key": "amount", "value": "10089600" }, + { "key": "from", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "to", "value": "orai1rvs4kypz8vygvfds0mzzssg36d9nevwv65qe5a" }, + { "key": "_contract_address", "value": "orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh" }, + { "key": "action", "value": "transfer" }, + { "key": "amount", "value": "5216324" }, + { "key": "from", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "to", "value": "orai1sdyqpt0y29eg2fzse85cd7r0klahqufujvaeve" } + ] + }, + { + "type": "wasm-matched_order", + "attributes": [ + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "PartialFilled" }, + { "key": "bidder_addr", "value": "orai1rvs4kypz8vygvfds0mzzssg36d9nevwv65qe5a" }, + { "key": "order_id", "value": "3808595" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "355243371" }, + { "key": "filled_offer_amount", "value": "159665374" }, + { "key": "ask_amount", "value": "684476630" }, + { "key": "filled_ask_amount", "value": "307640405" }, + { "key": "reward_fee", "value": "10099" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "5241900" }, + { "key": "filled_ask_this_round", "value": "10099999" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1sdyqpt0y29eg2fzse85cd7r0klahqufujvaeve" }, + { "key": "order_id", "value": "3809108" }, + { "key": "direction", "value": "Sell" }, + { "key": "offer_amount", "value": "10100000" }, + { "key": "filled_offer_amount", "value": "10099999" }, + { "key": "ask_amount", "value": "5221700" }, + { "key": "filled_ask_amount", "value": "5221700" }, + { "key": "reward_fee", "value": "5221" }, + { "key": "relayer_fee", "value": "155" }, + { "key": "filled_offer_this_round", "value": "10099999" }, + { "key": "filled_ask_this_round", "value": "5221700" } + ] + } + ] + }, + { + "msg_index": 1, + "events": [ + { + "type": "coin_received", + "attributes": [ + { "key": "receiver", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "amount", "value": "92968937orai" } + ] + }, + { + "type": "coin_spent", + "attributes": [ + { "key": "spender", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "amount", "value": "92968937orai" } + ] + }, + { + "type": "execute", + "attributes": [ + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "_contract_address", "value": "orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh" }, + { "key": "_contract_address", "value": "orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh" } + ] + }, + { + "type": "message", + "attributes": [ + { "key": "action", "value": "/cosmwasm.wasm.v1.MsgExecuteContract" }, + { "key": "module", "value": "wasm" }, + { "key": "sender", "value": "orai16hv74w3eu3ek0muqpgp4fekhrqgpzl3hd3qeqk" } + ] + }, + { + "type": "transfer", + "attributes": [ + { "key": "recipient", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "sender", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "amount", "value": "92968937orai" } + ] + }, + { + "type": "wasm", + "attributes": [ + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "action", "value": "execute_orderbook_pair" }, + { "key": "pair", "value": "orai - orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh" }, + { "key": "total_matched_orders", "value": "25" }, + { "key": "executor_reward", "value": "[\"1883243orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh\"]" }, + { "key": "_contract_address", "value": "orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh" }, + { "key": "action", "value": "transfer" }, + { "key": "amount", "value": "1766572681" }, + { "key": "from", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "to", "value": "orai1ufd8cw6g3el5568mee9vrd7t58suqn8vvnew34" }, + { "key": "_contract_address", "value": "orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh" }, + { "key": "action", "value": "transfer" }, + { "key": "amount", "value": "1883243" }, + { "key": "from", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "to", "value": "orai16stq6f4pnrfpz75n9ujv6qg3czcfa4qyjux5en" } + ] + }, + { + "type": "wasm-matched_order", + "attributes": [ + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809077" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "5889977" }, + { "key": "filled_offer_amount", "value": "5889977" }, + { "key": "ask_amount", "value": "309784" }, + { "key": "filled_ask_amount", "value": "309784" }, + { "key": "reward_fee", "value": "309" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "5889977" }, + { "key": "filled_ask_this_round", "value": "309784" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809078" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "11779954" }, + { "key": "filled_offer_amount", "value": "11779954" }, + { "key": "ask_amount", "value": "619600" }, + { "key": "filled_ask_amount", "value": "619600" }, + { "key": "reward_fee", "value": "619" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "11779954" }, + { "key": "filled_ask_this_round", "value": "619600" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809079" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "17669931" }, + { "key": "filled_offer_amount", "value": "17669931" }, + { "key": "ask_amount", "value": "929449" }, + { "key": "filled_ask_amount", "value": "929449" }, + { "key": "reward_fee", "value": "929" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "17669931" }, + { "key": "filled_ask_this_round", "value": "929449" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809080" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "23559909" }, + { "key": "filled_offer_amount", "value": "23559909" }, + { "key": "ask_amount", "value": "1239331" }, + { "key": "filled_ask_amount", "value": "1239331" }, + { "key": "reward_fee", "value": "1239" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "23559909" }, + { "key": "filled_ask_this_round", "value": "1239331" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809081" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "29449886" }, + { "key": "filled_offer_amount", "value": "29449886" }, + { "key": "ask_amount", "value": "1549245" }, + { "key": "filled_ask_amount", "value": "1549245" }, + { "key": "reward_fee", "value": "1549" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "29449886" }, + { "key": "filled_ask_this_round", "value": "1549245" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809082" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "35339863" }, + { "key": "filled_offer_amount", "value": "35339863" }, + { "key": "ask_amount", "value": "1859192" }, + { "key": "filled_ask_amount", "value": "1859192" }, + { "key": "reward_fee", "value": "1859" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "35339863" }, + { "key": "filled_ask_this_round", "value": "1859192" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809083" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "41229840" }, + { "key": "filled_offer_amount", "value": "41229840" }, + { "key": "ask_amount", "value": "2169171" }, + { "key": "filled_ask_amount", "value": "2169171" }, + { "key": "reward_fee", "value": "2169" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "41229840" }, + { "key": "filled_ask_this_round", "value": "2169171" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809084" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "47119817" }, + { "key": "filled_offer_amount", "value": "47119817" }, + { "key": "ask_amount", "value": "2479183" }, + { "key": "filled_ask_amount", "value": "2479183" }, + { "key": "reward_fee", "value": "2479" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "47119817" }, + { "key": "filled_ask_this_round", "value": "2479183" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809085" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "53009794" }, + { "key": "filled_offer_amount", "value": "53009794" }, + { "key": "ask_amount", "value": "2789228" }, + { "key": "filled_ask_amount", "value": "2789228" }, + { "key": "reward_fee", "value": "2789" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "53009794" }, + { "key": "filled_ask_this_round", "value": "2789228" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809086" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "58899771" }, + { "key": "filled_offer_amount", "value": "58899771" }, + { "key": "ask_amount", "value": "3099305" }, + { "key": "filled_ask_amount", "value": "3099305" }, + { "key": "reward_fee", "value": "3099" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "58899771" }, + { "key": "filled_ask_this_round", "value": "3099305" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809087" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "64789748" }, + { "key": "filled_offer_amount", "value": "64789748" }, + { "key": "ask_amount", "value": "3409415" }, + { "key": "filled_ask_amount", "value": "3409415" }, + { "key": "reward_fee", "value": "3409" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "64789748" }, + { "key": "filled_ask_this_round", "value": "3409415" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809088" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "70679726" }, + { "key": "filled_offer_amount", "value": "70679726" }, + { "key": "ask_amount", "value": "3719558" }, + { "key": "filled_ask_amount", "value": "3719558" }, + { "key": "reward_fee", "value": "3719" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "70679726" }, + { "key": "filled_ask_this_round", "value": "3719558" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809089" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "76569703" }, + { "key": "filled_offer_amount", "value": "76569703" }, + { "key": "ask_amount", "value": "4029733" }, + { "key": "filled_ask_amount", "value": "4029733" }, + { "key": "reward_fee", "value": "4029" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "76569703" }, + { "key": "filled_ask_this_round", "value": "4029733" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809069" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "124264565" }, + { "key": "filled_offer_amount", "value": "124264565" }, + { "key": "ask_amount", "value": "6539841" }, + { "key": "filled_ask_amount", "value": "6539840" }, + { "key": "reward_fee", "value": "3435" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "65279075" }, + { "key": "filled_ask_this_round", "value": "3435531" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809090" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "82459680" }, + { "key": "filled_offer_amount", "value": "82459680" }, + { "key": "ask_amount", "value": "4339941" }, + { "key": "filled_ask_amount", "value": "4339941" }, + { "key": "reward_fee", "value": "4339" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "82459680" }, + { "key": "filled_ask_this_round", "value": "4339941" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809070" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "129235147" }, + { "key": "filled_offer_amount", "value": "129235147" }, + { "key": "ask_amount", "value": "6801793" }, + { "key": "filled_ask_amount", "value": "6801793" }, + { "key": "reward_fee", "value": "6801" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "129235147" }, + { "key": "filled_ask_this_round", "value": "6801793" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809091" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "88349657" }, + { "key": "filled_offer_amount", "value": "88349657" }, + { "key": "ask_amount", "value": "4650182" }, + { "key": "filled_ask_amount", "value": "4650182" }, + { "key": "reward_fee", "value": "4650" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "88349657" }, + { "key": "filled_ask_this_round", "value": "4650182" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809071" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "134205730" }, + { "key": "filled_offer_amount", "value": "134205730" }, + { "key": "ask_amount", "value": "7063772" }, + { "key": "filled_ask_amount", "value": "7063772" }, + { "key": "reward_fee", "value": "7063" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "134205730" }, + { "key": "filled_ask_this_round", "value": "7063772" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809092" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "94239634" }, + { "key": "filled_offer_amount", "value": "94239634" }, + { "key": "ask_amount", "value": "4960455" }, + { "key": "filled_ask_amount", "value": "4960455" }, + { "key": "reward_fee", "value": "4960" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "94239634" }, + { "key": "filled_ask_this_round", "value": "4960455" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809072" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "139176312" }, + { "key": "filled_offer_amount", "value": "139176312" }, + { "key": "ask_amount", "value": "7325778" }, + { "key": "filled_ask_amount", "value": "7325778" }, + { "key": "reward_fee", "value": "7325" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "139176312" }, + { "key": "filled_ask_this_round", "value": "7325778" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809093" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "100129611" }, + { "key": "filled_offer_amount", "value": "100129611" }, + { "key": "ask_amount", "value": "5270761" }, + { "key": "filled_ask_amount", "value": "5270761" }, + { "key": "reward_fee", "value": "5270" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "100129611" }, + { "key": "filled_ask_this_round", "value": "5270761" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809073" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "144146895" }, + { "key": "filled_offer_amount", "value": "144146895" }, + { "key": "ask_amount", "value": "7587813" }, + { "key": "filled_ask_amount", "value": "7587813" }, + { "key": "reward_fee", "value": "7587" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "144146895" }, + { "key": "filled_ask_this_round", "value": "7587813" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809094" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "106019588" }, + { "key": "filled_offer_amount", "value": "106019588" }, + { "key": "ask_amount", "value": "5581099" }, + { "key": "filled_ask_amount", "value": "5581099" }, + { "key": "reward_fee", "value": "5581" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "106019588" }, + { "key": "filled_ask_this_round", "value": "5581099" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "Fulfilled" }, + { "key": "bidder_addr", "value": "orai1gqew39xtnshrrt8nmk0qy4gqkup5yhmnaryfp7" }, + { "key": "order_id", "value": "3809074" }, + { "key": "direction", "value": "Buy" }, + { "key": "offer_amount", "value": "149117477" }, + { "key": "filled_offer_amount", "value": "149117477" }, + { "key": "ask_amount", "value": "7849875" }, + { "key": "filled_ask_amount", "value": "7849875" }, + { "key": "reward_fee", "value": "7849" }, + { "key": "relayer_fee", "value": "300" }, + { "key": "filled_offer_this_round", "value": "149117477" }, + { "key": "filled_ask_this_round", "value": "7849875" }, + { "key": "_contract_address", "value": "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp" }, + { "key": "status", "value": "PartialFilled" }, + { "key": "bidder_addr", "value": "orai1ufd8cw6g3el5568mee9vrd7t58suqn8vvnew34" }, + { "key": "order_id", "value": "3809107" }, + { "key": "direction", "value": "Sell" }, + { "key": "offer_amount", "value": "200000000" }, + { "key": "filled_offer_amount", "value": "93069194" }, + { "key": "ask_amount", "value": "3799037200" }, + { "key": "filled_ask_amount", "value": "1768346725" }, + { "key": "reward_fee", "value": "1768346" }, + { "key": "relayer_fee", "value": "5698" }, + { "key": "filled_offer_this_round", "value": "93069194" }, + { "key": "filled_ask_this_round", "value": "1768346725" } + ] + } + ] + } +] diff --git a/packages/oraidex-common/tests/indexed-tx-tx.json b/packages/oraidex-common/tests/indexed-tx-tx.json new file mode 100644 index 00000000..a9e8b542 --- /dev/null +++ b/packages/oraidex-common/tests/indexed-tx-tx.json @@ -0,0 +1 @@ +"CsAFCvsCCiQvY29zbXdhc20ud2FzbS52MS5Nc2dFeGVjdXRlQ29udHJhY3QS0gIKK29yYWkxNmh2NzR3M2V1M2VrMG11cXBncDRmZWtocnFncHpsM2hkM3FlcWsSP29yYWkxbnQ1OGdjdTRlNjN2N2s1NXBobnIzZ2F5bTl0dmszcTRhcHF6cWNjanV3cHBnanV5ank2c3hrOHl6cBrhAXsiZXhlY3V0ZV9vcmRlcl9ib29rX3BhaXIiOnsiYXNzZXRfaW5mb3MiOlt7InRva2VuIjp7ImNvbnRyYWN0X2FkZHIiOiJvcmFpMWxwbGFwbWdxbmVscW4yNTNzdHo2a212bTN1bGdkYXl0bjg5YThtejl5ODV4cTh3ZDY4NHM2eGwzbHQifX0seyJ0b2tlbiI6eyJjb250cmFjdF9hZGRyIjoib3JhaTEyaHpqeGZoNzd3bDU3MmdkemN0MmZ4djJhcnhjd2g2Z3lrYzdxaCJ9fV0sImxpbWl0IjoxMDB9fQq/AgokL2Nvc213YXNtLndhc20udjEuTXNnRXhlY3V0ZUNvbnRyYWN0EpYCCitvcmFpMTZodjc0dzNldTNlazBtdXFwZ3A0ZmVraHJxZ3B6bDNoZDNxZXFrEj9vcmFpMW50NThnY3U0ZTYzdjdrNTVwaG5yM2dheW05dHZrM3E0YXBxenFjY2p1d3BwZ2p1eWp5NnN4azh5enAapQF7ImV4ZWN1dGVfb3JkZXJfYm9va19wYWlyIjp7ImFzc2V0X2luZm9zIjpbeyJuYXRpdmVfdG9rZW4iOnsiZGVub20iOiJvcmFpIn19LHsidG9rZW4iOnsiY29udHJhY3RfYWRkciI6Im9yYWkxMmh6anhmaDc3d2w1NzJnZHpjdDJmeHYyYXJ4Y3doNmd5a2M3cWgifX1dLCJsaW1pdCI6MTAwfX0SZwpRCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAmXkJCfyrJuMvuO0gCHubPxMloMQxSdPyy0SZAWF+4ubEgQKAggBGLcPEhIKDAoEb3JhaRIEMTE1MhDBokYaQBfox2B/mwb3tt33UHXPPOo42HZSy4V7DZ/TiPYWjGe0R8P5O5TavytfG7llLqeXh5SR+XrvKivlAz1YG1y2sXo=" \ No newline at end of file diff --git a/packages/universal-swap/package.json b/packages/universal-swap/package.json index 6d5906ca..0c1aecef 100644 --- a/packages/universal-swap/package.json +++ b/packages/universal-swap/package.json @@ -1,6 +1,6 @@ { "name": "@oraichain/oraidex-universal-swap", - "version": "1.0.67", + "version": "1.0.71", "main": "build/index.js", "files": [ "build/" diff --git a/packages/universal-swap/src/handler.ts b/packages/universal-swap/src/handler.ts index cbbfcdbc..4f487f5a 100644 --- a/packages/universal-swap/src/handler.ts +++ b/packages/universal-swap/src/handler.ts @@ -35,23 +35,11 @@ import { tokenMap, AmountDetails, buildMultipleExecuteMessages, - ibcInfosOld + ibcInfosOld, + checkValidateAddressWithNetwork } from "@oraichain/oraidex-common"; import { ethers } from "ethers"; -import { - addOraiBridgeRoute, - checkBalanceChannelIbc, - checkBalanceIBCOraichain, - checkFeeRelayer, - generateConvertCw20Erc20Message, - generateConvertErc20Cw20Message, - generateSwapOperationMsgs, - getEvmSwapRoute, - getIbcInfo, - getRoute, - isEvmSwappable, - isSupportedNoPoolSwapEvm -} from "./helper"; +import { UniversalSwapHelper } from "./helper"; import { ConvertReverse, ConvertType, Type, UniversalSwapConfig, UniversalSwapData, UniversalSwapType } from "./types"; import { GasPrice } from "@cosmjs/stargate"; import { Height } from "cosmjs-types/ibc/core/client/v1/client"; @@ -71,7 +59,7 @@ export class UniversalSwapHandler { } public getIbcInfo(fromChainId: CosmosChainId, toChainId: NetworkChainId) { - const ibcInfo = getIbcInfo(fromChainId, toChainId); + const ibcInfo = UniversalSwapHelper.getIbcInfo(fromChainId, toChainId); if (!this.config.ibcInfoTestMode || !ibcInfo.testInfo) return ibcInfo; return ibcInfo.testInfo; } @@ -108,8 +96,20 @@ export class UniversalSwapHandler { return getEncodedExecuteContractMsgs(sender, msgSwap); } const ibcInfo: IBCInfo = this.getIbcInfo("Oraichain", toChainId); - const ibcReceiveAddr = await this.config.cosmosWallet.getKeplrAddr(toChainId as CosmosChainId); - if (!ibcReceiveAddr) throw generateError("Please login keplr!"); + + let ibcReceiveAddr = ""; + + if (this.swapData.recipientAddress) { + const isValidRecipient = checkValidateAddressWithNetwork(this.swapData.recipientAddress, toChainId); + + if (!isValidRecipient.isValid) throw generateError("Recipient address invalid!"); + ibcReceiveAddr = this.swapData.recipientAddress; + } else { + ibcReceiveAddr = await this.config.cosmosWallet.getKeplrAddr(toChainId as CosmosChainId); + } + + if (!ibcReceiveAddr) throw generateError("Please login cosmos wallet!"); + let toTokenInOrai = getTokenOnOraichain(toCoinGeckoId); const isSpecialChain = ["kawaii_6886-1", "injective-1"].includes(toChainId); const isSpecialCoingecko = ["kawaii-islands", "milky-token", "injective-protocol"].includes(toCoinGeckoId); @@ -148,7 +148,7 @@ export class UniversalSwapHandler { if (fromCoinGeckoId === toCoinGeckoId && isSpecialCoingecko) { const evmToken = tokenMap[toTokenInOrai.denom]; const evmAmount = coin(toAmount(this.swapData.fromAmount, evmToken.decimals).toString(), evmToken.denom); - const msgConvertReverses = generateConvertCw20Erc20Message( + const msgConvertReverses = UniversalSwapHelper.generateConvertCw20Erc20Message( this.swapData.amounts, getTokenOnOraichain(toCoinGeckoId), sender, @@ -195,9 +195,18 @@ export class UniversalSwapHandler { metamaskAddress: string, tronAddress: string, channel: string, - toToken: { chainId: string; prefix: string } + toToken: { chainId: string; prefix: string; originalChainId: NetworkChainId }, + recipientAddress?: string ) { - const transferAddress = this.getTranferAddress(metamaskAddress, tronAddress, channel); + let transferAddress; + if (recipientAddress) { + const isValidRecipient = checkValidateAddressWithNetwork(this.swapData.recipientAddress, toToken.originalChainId); + if (!isValidRecipient.isValid) throw generateError("Recipient address invalid!"); + transferAddress = recipientAddress; + } else { + transferAddress = this.getTranferAddress(metamaskAddress, tronAddress, channel); + } + return toToken.chainId === "oraibridge-subnet-2" ? toToken.prefix + transferAddress : ""; } @@ -207,7 +216,7 @@ export class UniversalSwapHandler { */ async combineMsgEvm(metamaskAddress: string, tronAddress: string) { let msgExecuteSwap: EncodeObject[] = []; - const { originalFromToken, originalToToken, sender } = this.swapData; + const { originalFromToken, originalToToken, sender, recipientAddress } = this.swapData; // if from and to dont't have same coingeckoId, create swap msg to combine with bridge msg if (originalFromToken.coinGeckoId !== originalToToken.coinGeckoId) { const msgSwap = this.generateMsgsSwap(); @@ -216,14 +225,22 @@ export class UniversalSwapHandler { // then find new _toToken in Oraibridge that have same coingeckoId with originalToToken. const newToToken = findToTokenOnOraiBridge(originalToToken.coinGeckoId, originalToToken.chainId); + const toAddress = await this.config.cosmosWallet.getKeplrAddr(newToToken.chainId as CosmosChainId); - if (!toAddress) throw generateError("Please login keplr!"); + if (!toAddress) throw generateError("Please login cosmos wallet!"); const ibcInfo = this.getIbcInfo(originalFromToken.chainId as CosmosChainId, newToToken.chainId); - const ibcMemo = this.getIbcMemo(metamaskAddress, tronAddress, ibcInfo.channel, { - chainId: newToToken.chainId, - prefix: newToToken.prefix - }); + const ibcMemo = this.getIbcMemo( + metamaskAddress, + tronAddress, + ibcInfo.channel, + { + chainId: newToToken.chainId, + prefix: newToToken.prefix, + originalChainId: originalToToken.chainId + }, + recipientAddress + ); let ibcInfos = ibcInfo; let getEncodedExecuteMsgs = []; @@ -232,7 +249,7 @@ export class UniversalSwapHandler { const toTokenInOrai = getTokenOnOraichain(originalToToken.coinGeckoId, IBC_DECIMALS); const evmToken = tokenMap[toTokenInOrai.denom]; const evmAmount = coin(toAmount(this.swapData.fromAmount, evmToken.decimals).toString(), evmToken.denom); - const msgConvertReverses = generateConvertCw20Erc20Message( + const msgConvertReverses = UniversalSwapHelper.generateConvertCw20Erc20Message( this.swapData.amounts, getTokenOnOraichain(originalToToken.coinGeckoId), this.swapData.sender.cosmos, @@ -326,7 +343,7 @@ export class UniversalSwapHandler { } else if (!toTokenContractAddr) { // Case 2: swap to native eth / bnb. Get evm route so that we can swap from token -> native eth / bnb const routerV2 = IUniswapV2Router02__factory.connect(routerV2Addr, signer); - const evmRoute = getEvmSwapRoute(fromToken.chainId, fromToken.contractAddress); + const evmRoute = UniversalSwapHelper.getEvmSwapRoute(fromToken.chainId, fromToken.contractAddress); result = await routerV2.swapExactTokensForETH( finalFromAmount, @@ -453,7 +470,7 @@ export class UniversalSwapHandler { case "oraichain-to-evm": const { evm: metamaskAddress, tron: tronAddress } = this.swapData.sender; const routerClient = new OraiswapRouterQueryClient(client, network.router); - const isSufficient = await checkFeeRelayer({ + const isSufficient = await UniversalSwapHelper.checkFeeRelayer({ originalFromToken: this.swapData.originalFromToken, fromAmount: this.swapData.fromAmount, relayerFee: this.swapData.relayerFee, @@ -469,7 +486,7 @@ export class UniversalSwapHandler { throw generateError(`Universal swap type ${universalSwapType} is wrong. Should not call this function!`); } const ibcInfo = this.getIbcInfo("Oraichain", originalToToken.chainId); - await checkBalanceChannelIbc( + await UniversalSwapHelper.checkBalanceChannelIbc( ibcInfo, originalFromToken, originalToToken, @@ -526,7 +543,7 @@ export class UniversalSwapHandler { // has to switch network to the correct chain id on evm since users can swap between network tokens if (!this.config.evmWallet.isTron(originalFromToken.chainId)) await this.config.evmWallet.switchNetwork(originalFromToken.chainId); - if (isEvmSwappable(swappableData)) return this.evmSwap(evmSwapData); + if (UniversalSwapHelper.isEvmSwappable(swappableData)) return this.evmSwap(evmSwapData); const toTokenSameFromChainId = getTokenOnSpecificChainId(originalToToken.coinGeckoId, originalFromToken.chainId); if (toTokenSameFromChainId) { @@ -538,11 +555,14 @@ export class UniversalSwapHandler { } // special case for tokens not having a pool on Oraichain. We need to swap on evm instead then transfer to Oraichain - if (isEvmSwappable(swappableData) && isSupportedNoPoolSwapEvm(originalFromToken.coinGeckoId)) { + if ( + UniversalSwapHelper.isEvmSwappable(swappableData) && + UniversalSwapHelper.isSupportedNoPoolSwapEvm(originalFromToken.coinGeckoId) + ) { return this.evmSwap(evmSwapData); } - await checkBalanceIBCOraichain( + await UniversalSwapHelper.checkBalanceIBCOraichain( originalToToken, originalFromToken, fromAmount, @@ -552,7 +572,7 @@ export class UniversalSwapHandler { ); const routerClient = new OraiswapRouterQueryClient(client, network.router); - const isSufficient = await checkFeeRelayer({ + const isSufficient = await UniversalSwapHelper.checkFeeRelayer({ originalFromToken, fromAmount, relayerFee, @@ -592,7 +612,8 @@ export class UniversalSwapHandler { // get swapRoute const oraiAddress = await this.config.cosmosWallet.getKeplrAddr("Oraichain"); - const { swapRoute } = getRoute( + + const { swapRoute } = UniversalSwapHelper.getRoute( this.swapData.originalFromToken, this.swapData.originalToToken, destinationReceiver, @@ -621,7 +642,17 @@ export class UniversalSwapHandler { // check if from chain is noble, use ibc-wasm instead of ibc-hooks if (originalFromToken.chainId === "noble-1") { - msgTransfer.receiver = oraiAddress; + if (this.swapData.recipientAddress) { + const isValidRecipient = checkValidateAddressWithNetwork(this.swapData.recipientAddress, "Oraichain"); + + if (!isValidRecipient.isValid || isValidRecipient.network !== "Oraichain") { + throw generateError("Recipient address invalid! Only support bridge to Oraichain"); + } + msgTransfer.receiver = this.swapData.recipientAddress; + } else { + msgTransfer.receiver = oraiAddress; + } + msgTransfer.memo = swapRoute; } @@ -634,12 +665,24 @@ export class UniversalSwapHandler { async processUniversalSwap() { const { cosmos, evm, tron } = this.swapData.sender; - const toAddress = await this.getUniversalSwapToAddress(this.swapData.originalToToken.chainId, { - metamaskAddress: evm, - tronAddress: tron - }); + let toAddress = ""; + const currentToNetwork = this.swapData.originalToToken.chainId; + + if (this.swapData.recipientAddress) { + const isValidRecipient = checkValidateAddressWithNetwork(this.swapData.recipientAddress, currentToNetwork); - const { swapRoute, universalSwapType } = addOraiBridgeRoute( + if (!isValidRecipient.isValid) { + throw generateError("Recipient address invalid!"); + } + toAddress = this.swapData.recipientAddress; + } else { + toAddress = await this.getUniversalSwapToAddress(this.swapData.originalToToken.chainId, { + metamaskAddress: evm, + tronAddress: tron + }); + } + + const { swapRoute, universalSwapType } = UniversalSwapHelper.addOraiBridgeRoute( cosmos, this.swapData.originalFromToken, this.swapData.originalToToken, @@ -663,13 +706,18 @@ export class UniversalSwapHandler { const toTokenInOrai = getTokenOnOraichain(originalToToken.coinGeckoId); try { const _fromAmount = toAmount(fromAmount, fromTokenOnOrai.decimals).toString(); - const msgConvertsFrom = generateConvertErc20Cw20Message(this.swapData.amounts, fromTokenOnOrai); - const msgConvertTo = generateConvertErc20Cw20Message(this.swapData.amounts, toTokenInOrai); + const msgConvertsFrom = UniversalSwapHelper.generateConvertErc20Cw20Message( + this.swapData.amounts, + fromTokenOnOrai + ); + const msgConvertTo = UniversalSwapHelper.generateConvertErc20Cw20Message(this.swapData.amounts, toTokenInOrai); const isValidSlippage = this.swapData.userSlippage || this.swapData.userSlippage === 0; - if (!this.swapData.simulatePrice || !isValidSlippage) + if (!this.swapData.simulatePrice || !isValidSlippage) { throw generateError( "Could not calculate the minimum receive value because there is no simulate price or user slippage" ); + } + const minimumReceive = calculateMinReceive( this.swapData.simulatePrice, _fromAmount, @@ -679,12 +727,27 @@ export class UniversalSwapHandler { const { fund: offerSentFund, info: offerInfo } = parseTokenInfo(fromTokenOnOrai, _fromAmount); const { fund: askSentFund, info: askInfo } = parseTokenInfo(toTokenInOrai); const funds = handleSentFunds(offerSentFund, askSentFund); + + if (this.swapData.recipientAddress) { + const isValidRecipient = checkValidateAddressWithNetwork( + this.swapData.recipientAddress, + this.swapData.originalToToken.chainId + ); + + if (!isValidRecipient.isValid) { + throw generateError("Recipient address invalid!"); + } + } + const to = this.swapData.recipientAddress; + const inputTemp = { execute_swap_operations: { - operations: generateSwapOperationMsgs(offerInfo, askInfo), - minimum_receive: minimumReceive + operations: UniversalSwapHelper.generateSwapOperationMsgs(offerInfo, askInfo), + minimum_receive: minimumReceive, + to } }; + // if cw20 => has to send through cw20 contract if (!fromTokenOnOrai.contractAddress) { input = inputTemp; diff --git a/packages/universal-swap/src/helper.ts b/packages/universal-swap/src/helper.ts index f6d021bc..27722994 100644 --- a/packages/universal-swap/src/helper.ts +++ b/packages/universal-swap/src/helper.ts @@ -42,7 +42,8 @@ import { handleSentFunds, tokenMap, oraib2oraichainTest, - getSubAmountDetails + getSubAmountDetails, + evmChains } from "@oraichain/oraidex-common"; import { ConvertReverse, @@ -58,12 +59,12 @@ import { AssetInfo, OraiswapRouterQueryClient, OraiswapRouterReadOnlyInterface, - OraiswapTokenQueryClient + OraiswapTokenQueryClient, + SwapOperation } from "@oraichain/oraidex-contracts-sdk"; -import { SwapOperation } from "@oraichain/oraidex-contracts-sdk"; import { isEqual } from "lodash"; import { ethers } from "ethers"; -import { Amount, CwIcs20LatestQueryClient, CwIcs20LatestReadOnlyInterface } from "@oraichain/common-contracts-sdk"; +import { Amount, CwIcs20LatestQueryClient, Uint128 } from "@oraichain/common-contracts-sdk"; import { CosmWasmClient, ExecuteInstruction, toBinary } from "@cosmjs/cosmwasm-stargate"; import { swapFromTokens, swapToTokens } from "./swap-filter"; import { parseToIbcHookMemo, parseToIbcWasmMemo } from "./proto/proto-gen"; @@ -73,801 +74,987 @@ const caseSwapNativeAndWrapNative = (fromCoingecko, toCoingecko) => { const arr = ["ethereum", "weth"]; return arr.includes(fromCoingecko) && arr.includes(toCoingecko); }; -// evm swap helpers -export const isSupportedNoPoolSwapEvm = (coingeckoId: CoinGeckoId) => { - switch (coingeckoId) { - case "wbnb": - case "binancecoin": - case "ethereum": - return true; - default: - return false; - } -}; -export const isEvmNetworkNativeSwapSupported = (chainId: NetworkChainId) => { - switch (chainId) { - case "0x01": - case "0x38": - return true; - default: - return false; - } +const splitOnce = (s: string, seperator: string) => { + const i = s.indexOf(seperator); + // cannot find seperator then return string + if (i === -1) return [s]; + return [s.slice(0, i), s.slice(i + 1)]; }; -export const swapEvmRoutes: { - [network: string]: { - [pair: string]: string[]; +export class UniversalSwapHelper { + // evm swap helpers + static isSupportedNoPoolSwapEvm = (coingeckoId: CoinGeckoId) => { + switch (coingeckoId) { + case "wbnb": + case "binancecoin": + case "ethereum": + return true; + default: + return false; + } }; -} = { - "0x38": { - [`${WRAP_BNB_CONTRACT}-${USDT_BSC_CONTRACT}`]: [WRAP_BNB_CONTRACT, USDT_BSC_CONTRACT], - [`${WRAP_BNB_CONTRACT}-${USDT_TRON_CONTRACT}`]: [WRAP_BNB_CONTRACT, USDT_BSC_CONTRACT], - [`${WRAP_BNB_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [WRAP_BNB_CONTRACT, ORAI_BSC_CONTRACT], - [`${WRAP_BNB_CONTRACT}-${ORAI_BSC_CONTRACT}`]: [WRAP_BNB_CONTRACT, ORAI_BSC_CONTRACT], - [`${WRAP_BNB_CONTRACT}-${AIRI_BSC_CONTRACT}`]: [WRAP_BNB_CONTRACT, AIRI_BSC_CONTRACT], - [`${USDT_BSC_CONTRACT}-${AIRI_BSC_CONTRACT}`]: [USDT_BSC_CONTRACT, WRAP_BNB_CONTRACT, AIRI_BSC_CONTRACT], - [`${USDT_BSC_CONTRACT}-${ORAI_BSC_CONTRACT}`]: [USDT_BSC_CONTRACT, WRAP_BNB_CONTRACT, ORAI_BSC_CONTRACT], - [`${ORAI_BSC_CONTRACT}-${AIRI_BSC_CONTRACT}`]: [ORAI_BSC_CONTRACT, WRAP_BNB_CONTRACT, AIRI_BSC_CONTRACT] - }, - "0x01": { - [`${WRAP_ETH_CONTRACT}-${USDC_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, USDC_ETH_CONTRACT], - [`${WRAP_ETH_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, ORAI_ETH_CONTRACT], - [`${WRAP_ETH_CONTRACT}-${USDT_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, USDT_ETH_CONTRACT], - // TODO: hardcode fix eth -> weth (oraichain) - [`${WRAP_ETH_CONTRACT}-${WRAP_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, WRAP_ETH_CONTRACT], - [`${USDC_ETH_CONTRACT}-${USDT_ETH_CONTRACT}`]: [USDC_ETH_CONTRACT, USDT_ETH_CONTRACT], - [`${USDC_ETH_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [USDC_ETH_CONTRACT, WRAP_ETH_CONTRACT, ORAI_ETH_CONTRACT], - [`${USDT_ETH_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [USDT_ETH_CONTRACT, WRAP_ETH_CONTRACT, ORAI_ETH_CONTRACT] - - // [`${WRAP_ETH_CONTRACT}-${ORAIX_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, ORAIX_ETH_CONTRACT] - // [`${ORAIX_ETH_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [ORAIX_ETH_CONTRACT, WRAP_ETH_CONTRACT, ORAI_ETH_CONTRACT], - // [`${ORAIX_ETH_CONTRACT}-${USDC_ETH_CONTRACT}`]: [ORAIX_ETH_CONTRACT, WRAP_ETH_CONTRACT, USDC_ETH_CONTRACT], - // [`${ORAIX_ETH_CONTRACT}-${USDT_ETH_CONTRACT}`]: [ORAIX_ETH_CONTRACT, WRAP_ETH_CONTRACT, USDT_ETH_CONTRACT] - } -}; -export const buildSwapRouterKey = (fromContractAddr: string, toContractAddr: string) => { - return `${fromContractAddr}-${toContractAddr}`; -}; + static isEvmNetworkNativeSwapSupported = (chainId: NetworkChainId) => { + switch (chainId) { + case "0x01": + case "0x38": + return true; + default: + return false; + } + }; -export const getEvmSwapRoute = ( - chainId: string, - fromContractAddr?: string, - toContractAddr?: string -): string[] | undefined => { - if (!isEvmNetworkNativeSwapSupported(chainId as EvmChainId)) return undefined; - if (!fromContractAddr && !toContractAddr) return undefined; - const chainRoutes = swapEvmRoutes[chainId]; - const fromAddr = fromContractAddr || proxyContractInfo[chainId].wrapNativeAddr; - const toAddr = toContractAddr || proxyContractInfo[chainId].wrapNativeAddr; - - // in case from / to contract addr is empty aka native eth or bnb without contract addr then we fallback to swap route with wrapped token - // because uniswap & pancakeswap do not support simulating with native directly - let route: string[] | undefined = chainRoutes[buildSwapRouterKey(fromAddr, toContractAddr)]; - if (route) return route; - // because the route can go both ways. Eg: WBNB->AIRI, if we want to swap AIRI->WBNB, then first we find route WBNB->AIRI, then we reverse the route - route = chainRoutes[buildSwapRouterKey(toAddr, fromContractAddr)]; - if (route) { - return [].concat(route).reverse(); - } - return undefined; -}; + static swapEvmRoutes: { + [network: string]: { + [pair: string]: string[]; + }; + } = { + "0x38": { + [`${WRAP_BNB_CONTRACT}-${USDT_BSC_CONTRACT}`]: [WRAP_BNB_CONTRACT, USDT_BSC_CONTRACT], + [`${WRAP_BNB_CONTRACT}-${USDT_TRON_CONTRACT}`]: [WRAP_BNB_CONTRACT, USDT_BSC_CONTRACT], + [`${WRAP_BNB_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [WRAP_BNB_CONTRACT, ORAI_BSC_CONTRACT], + [`${WRAP_BNB_CONTRACT}-${ORAI_BSC_CONTRACT}`]: [WRAP_BNB_CONTRACT, ORAI_BSC_CONTRACT], + [`${WRAP_BNB_CONTRACT}-${AIRI_BSC_CONTRACT}`]: [WRAP_BNB_CONTRACT, AIRI_BSC_CONTRACT], + [`${USDT_BSC_CONTRACT}-${AIRI_BSC_CONTRACT}`]: [USDT_BSC_CONTRACT, WRAP_BNB_CONTRACT, AIRI_BSC_CONTRACT], + [`${USDT_BSC_CONTRACT}-${ORAI_BSC_CONTRACT}`]: [USDT_BSC_CONTRACT, WRAP_BNB_CONTRACT, ORAI_BSC_CONTRACT], + [`${ORAI_BSC_CONTRACT}-${AIRI_BSC_CONTRACT}`]: [ORAI_BSC_CONTRACT, WRAP_BNB_CONTRACT, AIRI_BSC_CONTRACT] + }, + "0x01": { + [`${WRAP_ETH_CONTRACT}-${USDC_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, USDC_ETH_CONTRACT], + [`${WRAP_ETH_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, ORAI_ETH_CONTRACT], + [`${WRAP_ETH_CONTRACT}-${USDT_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, USDT_ETH_CONTRACT], + // TODO: hardcode fix eth -> weth (oraichain) + [`${WRAP_ETH_CONTRACT}-${WRAP_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, WRAP_ETH_CONTRACT], + [`${USDC_ETH_CONTRACT}-${USDT_ETH_CONTRACT}`]: [USDC_ETH_CONTRACT, USDT_ETH_CONTRACT], + [`${USDC_ETH_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [USDC_ETH_CONTRACT, WRAP_ETH_CONTRACT, ORAI_ETH_CONTRACT], + [`${USDT_ETH_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [USDT_ETH_CONTRACT, WRAP_ETH_CONTRACT, ORAI_ETH_CONTRACT] -// static functions -export const isEvmSwappable = (data: { - fromChainId: string; - toChainId: string; - fromContractAddr?: string; - toContractAddr?: string; -}): boolean => { - const { fromChainId, fromContractAddr, toChainId, toContractAddr } = data; - // cant swap if they are not on the same evm chain - if (fromChainId !== toChainId) return false; - // cant swap on evm if chain id is not eth or bsc - if (fromChainId !== "0x01" && fromChainId !== "0x38") return false; - // if the tokens do not have contract addresses then we skip - // if (!fromContractAddr || !toContractAddr) return false; - // only swappable if there's a route to swap from -> to - if (!getEvmSwapRoute(fromChainId, fromContractAddr, toContractAddr)) return false; - return true; -}; + // [`${WRAP_ETH_CONTRACT}-${ORAIX_ETH_CONTRACT}`]: [WRAP_ETH_CONTRACT, ORAIX_ETH_CONTRACT] + // [`${ORAIX_ETH_CONTRACT}-${ORAI_ETH_CONTRACT}`]: [ORAIX_ETH_CONTRACT, WRAP_ETH_CONTRACT, ORAI_ETH_CONTRACT], + // [`${ORAIX_ETH_CONTRACT}-${USDC_ETH_CONTRACT}`]: [ORAIX_ETH_CONTRACT, WRAP_ETH_CONTRACT, USDC_ETH_CONTRACT], + // [`${ORAIX_ETH_CONTRACT}-${USDT_ETH_CONTRACT}`]: [ORAIX_ETH_CONTRACT, WRAP_ETH_CONTRACT, USDT_ETH_CONTRACT] + } + }; -// ibc helpers -export const getIbcInfo = (fromChainId: CosmosChainId, toChainId: NetworkChainId): IBCInfo => { - if (!ibcInfos[fromChainId]) throw generateError("Cannot find ibc info"); - const ibcInfo = ibcInfos[fromChainId][toChainId]; - if (!ibcInfo) throw generateError(`Cannot find ibc info from ${fromChainId} to ${toChainId}`); - return ibcInfo; -}; + static buildSwapRouterKey = (fromContractAddr: string, toContractAddr: string) => { + return `${fromContractAddr}-${toContractAddr}`; + }; -export const buildIbcWasmPairKey = (ibcPort: string, ibcChannel: string, denom: string) => { - return `${ibcPort}/${ibcChannel}/${denom}`; -}; + static getEvmSwapRoute = ( + chainId: string, + fromContractAddr?: string, + toContractAddr?: string + ): string[] | undefined => { + if (!UniversalSwapHelper.isEvmNetworkNativeSwapSupported(chainId as EvmChainId)) return undefined; + if (!fromContractAddr && !toContractAddr) return undefined; + const chainRoutes = UniversalSwapHelper.swapEvmRoutes[chainId]; + const fromAddr = fromContractAddr || proxyContractInfo[chainId].wrapNativeAddr; + const toAddr = toContractAddr || proxyContractInfo[chainId].wrapNativeAddr; -/** - * This function converts the destination address (from BSC / ETH -> Oraichain) to an appropriate format based on the BSC / ETH token contract address - * @param oraiAddress - receiver address on Oraichain - * @param contractAddress - BSC / ETH token contract address - * @returns converted receiver address - */ -export const getSourceReceiver = ( - oraiAddress: string, - contractAddress?: string, - isSourceReceiverTest?: boolean -): string => { - let sourceReceiver = `${oraib2oraichain}/${oraiAddress}`; - // TODO: test retire v2 (change structure memo evm -> oraichain) - if (isSourceReceiverTest) { - sourceReceiver = `${oraib2oraichainTest}/${oraiAddress}`; - } - - // we only support the old oraibridge ibc channel <--> Oraichain for MILKY & KWT - if (contractAddress === KWT_BSC_CONTRACT || contractAddress === MILKY_BSC_CONTRACT) { - sourceReceiver = oraiAddress; - } - return sourceReceiver; -}; + // in case from / to contract addr is empty aka native eth or bnb without contract addr then we fallback to swap route with wrapped token + // because uniswap & pancakeswap do not support simulating with native directly + let route: string[] | undefined = chainRoutes[UniversalSwapHelper.buildSwapRouterKey(fromAddr, toContractAddr)]; + if (route) return route; + // because the route can go both ways. Eg: WBNB->AIRI, if we want to swap AIRI->WBNB, then first we find route WBNB->AIRI, then we reverse the route + route = chainRoutes[UniversalSwapHelper.buildSwapRouterKey(toAddr, fromContractAddr)]; + if (route) { + return [].concat(route).reverse(); + } + return undefined; + }; -/** - * This function receives fromToken and toToken as parameters to generate the destination memo for the receiver address - * @param from - from token - * @param to - to token - * @param destReceiver - destination destReceiver - * @returns destination in the format /: - */ -export const getRoute = ( - fromToken?: TokenItemType, - toToken?: TokenItemType, - destReceiver?: string, - receiverOnOrai?: string -): SwapRoute => { - if (!fromToken || !toToken || !destReceiver) - return { swapRoute: "", universalSwapType: "other-networks-to-oraichain" }; - // this is the simplest case. Both tokens on the same Oraichain network => simple swap with to token denom - if (fromToken.chainId === "Oraichain" && toToken.chainId === "Oraichain") { - return { swapRoute: "", universalSwapType: "oraichain-to-oraichain" }; - } - // we dont need to have any swapRoute for this case - if (fromToken.chainId === "Oraichain") { - if (cosmosTokens.some((t) => t.chainId === toToken.chainId)) - return { swapRoute: "", universalSwapType: "oraichain-to-cosmos" }; - return { swapRoute: "", universalSwapType: "oraichain-to-evm" }; - } - // TODO: support 1-step swap for kwt - if (fromToken.chainId === "kawaii_6886-1" || fromToken.chainId === "0x1ae6") { - throw new Error(`chain id ${fromToken.chainId} is currently not supported in universal swap`); - } - // cosmos to others case where from token is a cosmos token - // we have 2 cases: 1) Cosmos to Oraichain, 2) Cosmos to cosmos or evm - if (cosmosTokens.some((t) => t.chainId === fromToken.chainId)) { - let swapRoute = ""; - const dstChannel = toToken.chainId == "Oraichain" ? "" : ibcInfos["Oraichain"][toToken.chainId].channel; - // if from chain is noble, use ibc wasm instead of ibc hooks - if (fromToken.chainId == "noble-1") { - swapRoute = parseToIbcWasmMemo(destReceiver, dstChannel, parseTokenInfoRawDenom(toToken)); - } else { - swapRoute = parseToIbcHookMemo(receiverOnOrai, destReceiver, dstChannel, parseTokenInfoRawDenom(toToken)); + // static functions + static isEvmSwappable = (data: { + fromChainId: string; + toChainId: string; + fromContractAddr?: string; + toContractAddr?: string; + }): boolean => { + const { fromChainId, fromContractAddr, toChainId, toContractAddr } = data; + // cant swap if they are not on the same evm chain + if (fromChainId !== toChainId) return false; + // cant swap on evm if chain id is not eth or bsc + if (fromChainId !== "0x01" && fromChainId !== "0x38") return false; + // if the tokens do not have contract addresses then we skip + // if (!fromContractAddr || !toContractAddr) return false; + // only swappable if there's a route to swap from -> to + if (!UniversalSwapHelper.getEvmSwapRoute(fromChainId, fromContractAddr, toContractAddr)) return false; + return true; + }; + + // ibc helpers + static getIbcInfo = (fromChainId: CosmosChainId, toChainId: NetworkChainId): IBCInfo => { + if (!ibcInfos[fromChainId]) throw generateError("Cannot find ibc info"); + const ibcInfo = ibcInfos[fromChainId][toChainId]; + if (!ibcInfo) throw generateError(`Cannot find ibc info from ${fromChainId} to ${toChainId}`); + return ibcInfo; + }; + + static buildIbcWasmPairKey = (ibcPort: string, ibcChannel: string, denom: string) => { + return `${ibcPort}/${ibcChannel}/${denom}`; + }; + + /** + * This function converts the destination address (from BSC / ETH -> Oraichain) to an appropriate format based on the BSC / ETH token contract address + * @param oraiAddress - receiver address on Oraichain + * @param contractAddress - BSC / ETH token contract address + * @returns converted receiver address + */ + static getSourceReceiver = ( + oraiAddress: string, + contractAddress?: string, + isSourceReceiverTest?: boolean + ): string => { + let sourceReceiver = `${oraib2oraichain}/${oraiAddress}`; + // TODO: test retire v2 (change structure memo evm -> oraichain) + if (isSourceReceiverTest) { + sourceReceiver = `${oraib2oraichainTest}/${oraiAddress}`; } - return { swapRoute, universalSwapType: "cosmos-to-others" }; - } + // we only support the old oraibridge ibc channel <--> Oraichain for MILKY & KWT + if (contractAddress === KWT_BSC_CONTRACT || contractAddress === MILKY_BSC_CONTRACT) { + sourceReceiver = oraiAddress; + } + return sourceReceiver; + }; - if (toToken.chainId === "Oraichain") { - // if from token is 0x38 & to token chain id is Oraichain & from token is kawaii or milky - if (["0x38"].includes(fromToken.chainId) && ["milky-token", "kawaii-islands"].includes(fromToken.coinGeckoId)) + /** + * This function receives fromToken and toToken as parameters to generate the destination memo for the receiver address + * @param from - from token + * @param to - to token + * @param destReceiver - destination destReceiver + * @returns destination in the format /: + */ + static getRoute = ( + fromToken?: TokenItemType, + toToken?: TokenItemType, + destReceiver?: string, + receiverOnOrai?: string + ): SwapRoute => { + if (!fromToken || !toToken || !destReceiver) return { swapRoute: "", universalSwapType: "other-networks-to-oraichain" }; - // if to token chain id is Oraichain, then we dont need to care about ibc msg case - // first case, two tokens are the same, only different in network => simple swap - if (fromToken.coinGeckoId === toToken.coinGeckoId) - return { swapRoute: parseToIbcWasmMemo(destReceiver, "", ""), universalSwapType: "other-networks-to-oraichain" }; - // if they are not the same then we set dest denom - return { - // swapRoute: `${destReceiver}:${parseTokenInfoRawDenom(toToken)}`, - swapRoute: parseToIbcWasmMemo(destReceiver, "", parseTokenInfoRawDenom(toToken)), - universalSwapType: "other-networks-to-oraichain" - }; - } - // the remaining cases where we have to process ibc msg - const ibcInfo: IBCInfo = ibcInfos["Oraichain"][toToken.chainId]; // we get ibc channel that transfers toToken from Oraichain to the toToken chain - // getTokenOnOraichain is called to get the ibc denom / cw20 denom on Oraichain so that we can create an ibc msg using it - let receiverPrefix = ""; - // TODO: no need to use to token on Oraichain. Can simply use the swapRoute token directly. Fix this requires fixing the logic on ibc wasm as well - const toTokenOnOraichain = getTokenOnOraichain(toToken.coinGeckoId); - if (!toTokenOnOraichain) + // this is the simplest case. Both tokens on the same Oraichain network => simple swap with to token denom + if (fromToken.chainId === "Oraichain" && toToken.chainId === "Oraichain") { + return { swapRoute: "", universalSwapType: "oraichain-to-oraichain" }; + } + // we dont need to have any swapRoute for this case + if (fromToken.chainId === "Oraichain") { + if (cosmosTokens.some((t) => t.chainId === toToken.chainId)) + return { swapRoute: "", universalSwapType: "oraichain-to-cosmos" }; + return { swapRoute: "", universalSwapType: "oraichain-to-evm" }; + } + // TODO: support 1-step swap for kwt + if (fromToken.chainId === "kawaii_6886-1" || fromToken.chainId === "0x1ae6") { + throw new Error(`chain id ${fromToken.chainId} is currently not supported in universal swap`); + } + + let receiverPrefix = ""; + if (isEthAddress(destReceiver)) receiverPrefix = toToken.prefix; + const toDenom = receiverPrefix + parseTokenInfoRawDenom(toToken); + const finalDestReceiver = receiverPrefix + destReceiver; + // we get ibc channel that transfers toToken from Oraichain to the toToken chain + const dstChannel = toToken.chainId == "Oraichain" ? "" : ibcInfos["Oraichain"][toToken.chainId].channel; + + // cosmos to others case where from token is a cosmos token + // we have 2 cases: 1) Cosmos to Oraichain, 2) Cosmos to cosmos or evm + + if (cosmosTokens.some((t) => t.chainId === fromToken.chainId)) { + let swapRoute = ""; + // if from chain is noble, use ibc wasm instead of ibc hooks + if (fromToken.chainId == "noble-1") { + swapRoute = parseToIbcWasmMemo(finalDestReceiver, dstChannel, toDenom); + } else { + swapRoute = parseToIbcHookMemo(receiverOnOrai, finalDestReceiver, dstChannel, toDenom); + } + + return { swapRoute, universalSwapType: "cosmos-to-others" }; + } + + if (toToken.chainId === "Oraichain") { + // if from token is 0x38 & to token chain id is Oraichain & from token is kawaii or milky + if (["0x38"].includes(fromToken.chainId) && ["milky-token", "kawaii-islands"].includes(fromToken.coinGeckoId)) + return { swapRoute: "", universalSwapType: "other-networks-to-oraichain" }; + // if to token chain id is Oraichain, then we dont need to care about ibc msg case + // first case, two tokens are the same, only different in network => simple swap + if (fromToken.coinGeckoId === toToken.coinGeckoId) + return { + swapRoute: parseToIbcWasmMemo(destReceiver, "", ""), + universalSwapType: "other-networks-to-oraichain" + }; + // if they are not the same then we set dest denom + return { + swapRoute: parseToIbcWasmMemo(destReceiver, "", toDenom), + universalSwapType: "other-networks-to-oraichain" + }; + } + // the remaining cases where we have to process ibc msg + + // getTokenOnOraichain is called to get the ibc denom / cw20 denom on Oraichain so that we can create an ibc msg using it + // TODO: no need to use to token on Oraichain. Can simply use the swapRoute token directly. Fix this requires fixing the logic on ibc wasm as well + const toTokenOnOraichain = getTokenOnOraichain(toToken.coinGeckoId); + if (!toTokenOnOraichain) + return { + swapRoute: "", + universalSwapType: "other-networks-to-oraichain" + }; + return { - swapRoute: "", + swapRoute: parseToIbcWasmMemo(finalDestReceiver, dstChannel, toDenom), universalSwapType: "other-networks-to-oraichain" }; - if (isEthAddress(destReceiver)) receiverPrefix = toToken.prefix; - return { - // swapRoute: `${ibcInfo.channel}/${receiverPrefix}${destReceiver}:${parseTokenInfoRawDenom(toToken)}`, - swapRoute: parseToIbcWasmMemo(`${receiverPrefix}${destReceiver}`, ibcInfo.channel, parseTokenInfoRawDenom(toToken)), - universalSwapType: "other-networks-to-oraichain" }; -}; -export const addOraiBridgeRoute = ( - sourceReceiver: string, - fromToken: TokenItemType, - toToken: TokenItemType, - destReceiver?: string, - isSourceReceiverTest?: boolean -): SwapRoute => { - // TODO: recheck cosmos address undefined (other-chain -> oraichain) - if (!sourceReceiver) throw generateError(`Cannot get source if the sourceReceiver is empty!`); - const source = getSourceReceiver(sourceReceiver, fromToken.contractAddress, isSourceReceiverTest); - - const { swapRoute, universalSwapType } = getRoute(fromToken, toToken, destReceiver); - if (swapRoute.length > 0) return { swapRoute: `${source}:${swapRoute}`, universalSwapType }; - return { swapRoute: source, universalSwapType }; -}; + static addOraiBridgeRoute = ( + sourceReceiver: string, + fromToken: TokenItemType, + toToken: TokenItemType, + destReceiver?: string, + isSourceReceiverTest?: boolean + ): SwapRoute => { + // TODO: recheck cosmos address undefined (other-chain -> oraichain) + if (!sourceReceiver) throw generateError(`Cannot get source if the sourceReceiver is empty!`); + const source = UniversalSwapHelper.getSourceReceiver( + sourceReceiver, + fromToken.contractAddress, + isSourceReceiverTest + ); -export const splitOnce = (s: string, seperator: string) => { - const i = s.indexOf(seperator); - // cannot find seperator then return string - if (i === -1) return [s]; - return [s.slice(0, i), s.slice(i + 1)]; -}; + const { swapRoute, universalSwapType } = UniversalSwapHelper.getRoute(fromToken, toToken, destReceiver); + if (swapRoute.length > 0) return { swapRoute: `${source}:${swapRoute}`, universalSwapType }; + return { swapRoute: source, universalSwapType }; + }; -/** - * cases: - * /: - * : - * - * :/: - * : - * :: - * */ -export const unmarshalOraiBridgeRoute = (destination: string) => { - const routeData: OraiBridgeRouteData = { - oraiBridgeChannel: "", - oraiReceiver: "", - finalDestinationChannel: "", - finalReceiver: "", - tokenIdentifier: "" + /** + * cases: + * /: + * : + * + * :/: + * : + * :: + * */ + static unmarshalOraiBridgeRoute = (destination: string) => { + const routeData: OraiBridgeRouteData = { + oraiBridgeChannel: "", + oraiReceiver: "", + finalDestinationChannel: "", + finalReceiver: "", + tokenIdentifier: "" + }; + const splittedDestination = splitOnce(destination, ":"); + if (splittedDestination.length === 0 || splittedDestination.length > 2) + throw generateError(`The destination data is malformed: ${destination}`); + const firstDestination = splittedDestination[0].split("/"); + if (firstDestination.length === 2) { + routeData.oraiBridgeChannel = firstDestination[0]; + routeData.oraiReceiver = firstDestination[1]; + } else if (firstDestination.length === 1) { + routeData.oraiReceiver = firstDestination[0]; + } else throw generateError(`First destination ${JSON.stringify(firstDestination)} of ${destination} is malformed`); + // there's nothing we need to parse anymore + if (splittedDestination.length === 1) return routeData; + const finalDestinationData = splittedDestination[1].split(":"); + if (finalDestinationData.length === 1) routeData.finalReceiver = finalDestinationData[0]; + else if (finalDestinationData.length === 2) { + routeData.tokenIdentifier = finalDestinationData[1]; + const splittedFinalDestinationData = finalDestinationData[0].split("/"); + if (splittedFinalDestinationData.length === 1) routeData.finalReceiver = splittedFinalDestinationData[0]; + else if (splittedFinalDestinationData.length === 2) { + routeData.finalDestinationChannel = splittedFinalDestinationData[0]; + routeData.finalReceiver = splittedFinalDestinationData[1]; + } else + throw generateError( + `splitted final destination data ${JSON.stringify(splittedFinalDestinationData)} is malformed` + ); + } else throw generateError(`Final destination data ${JSON.stringify(finalDestinationData)} is malformed`); + return routeData; }; - const splittedDestination = splitOnce(destination, ":"); - if (splittedDestination.length === 0 || splittedDestination.length > 2) - throw generateError(`The destination data is malformed: ${destination}`); - const firstDestination = splittedDestination[0].split("/"); - if (firstDestination.length === 2) { - routeData.oraiBridgeChannel = firstDestination[0]; - routeData.oraiReceiver = firstDestination[1]; - } else if (firstDestination.length === 1) { - routeData.oraiReceiver = firstDestination[0]; - } else throw generateError(`First destination ${JSON.stringify(firstDestination)} of ${destination} is malformed`); - // there's nothing we need to parse anymore - if (splittedDestination.length === 1) return routeData; - const finalDestinationData = splittedDestination[1].split(":"); - if (finalDestinationData.length === 1) routeData.finalReceiver = finalDestinationData[0]; - else if (finalDestinationData.length === 2) { - routeData.tokenIdentifier = finalDestinationData[1]; - const splittedFinalDestinationData = finalDestinationData[0].split("/"); - if (splittedFinalDestinationData.length === 1) routeData.finalReceiver = splittedFinalDestinationData[0]; - else if (splittedFinalDestinationData.length === 2) { - routeData.finalDestinationChannel = splittedFinalDestinationData[0]; - routeData.finalReceiver = splittedFinalDestinationData[1]; - } else - throw generateError( - `splitted final destination data ${JSON.stringify(splittedFinalDestinationData)} is malformed` - ); - } else throw generateError(`Final destination data ${JSON.stringify(finalDestinationData)} is malformed`); - return routeData; -}; -export const generateSwapRoute = (offerAsset: AssetInfo, askAsset: AssetInfo, swapRoute: AssetInfo[]) => { - const swaps = []; - if (swapRoute.length === 0) { - swaps.push({ - orai_swap: { - offer_asset_info: offerAsset, - ask_asset_info: askAsset - } - }); - } else { - swaps.push({ - orai_swap: { - offer_asset_info: offerAsset, - ask_asset_info: swapRoute[0] + static generateSwapRoute = (offerAsset: AssetInfo, askAsset: AssetInfo, swapRoute: AssetInfo[]) => { + const swaps = []; + if (swapRoute.length === 0) { + swaps.push({ + orai_swap: { + offer_asset_info: offerAsset, + ask_asset_info: askAsset + } + }); + } else { + swaps.push({ + orai_swap: { + offer_asset_info: offerAsset, + ask_asset_info: swapRoute[0] + } + }); + for (let i = 0; i < swapRoute.length - 1; i++) { + swaps.push({ + orai_swap: { + offer_asset_info: swapRoute[i], + ask_asset_info: swapRoute[i + 1] + } + }); } - }); - for (let i = 0; i < swapRoute.length - 1; i++) { swaps.push({ orai_swap: { - offer_asset_info: swapRoute[i], - ask_asset_info: swapRoute[i + 1] + offer_asset_info: swapRoute[swapRoute.length - 1], + ask_asset_info: askAsset } }); } - swaps.push({ - orai_swap: { - offer_asset_info: swapRoute[swapRoute.length - 1], - ask_asset_info: askAsset - } + return swaps; + }; + + // generate messages + static generateSwapOperationMsgs = (offerInfo: AssetInfo, askInfo: AssetInfo): SwapOperation[] => { + const pairExist = PAIRS.some((pair) => { + const assetInfos = pair.asset_infos; + return ( + (isEqual(assetInfos[0], offerInfo) && isEqual(assetInfos[1], askInfo)) || + (isEqual(assetInfos[1], offerInfo) && isEqual(assetInfos[0], askInfo)) + ); }); - } - return swaps; -}; -// generate messages -export const generateSwapOperationMsgs = (offerInfo: AssetInfo, askInfo: AssetInfo): SwapOperation[] => { - const pairExist = PAIRS.some((pair) => { - const assetInfos = pair.asset_infos; - return ( - (isEqual(assetInfos[0], offerInfo) && isEqual(assetInfos[1], askInfo)) || - (isEqual(assetInfos[1], offerInfo) && isEqual(assetInfos[0], askInfo)) - ); - }); - - if (pairExist) return generateSwapRoute(offerInfo, askInfo, []); - // TODO: hardcode NTMPI -> USDC -> ORAI -> X - if (isEqual(offerInfo, NEUTARO_INFO)) { - const swapRoute = isEqual(askInfo, ORAI_INFO) ? [USDC_INFO] : [USDC_INFO, ORAI_INFO]; - return generateSwapRoute(offerInfo, askInfo, swapRoute); - } - - // TODO: X -> ORAI -> USDC -> NTMPI - if (isEqual(askInfo, NEUTARO_INFO)) { - const swapRoute = isEqual(offerInfo, ORAI_INFO) ? [USDC_INFO] : [ORAI_INFO, USDC_INFO]; - return generateSwapRoute(offerInfo, askInfo, swapRoute); - } - - // Default case: ORAI_INFO - return generateSwapRoute(offerInfo, askInfo, [ORAI_INFO]); -}; + if (pairExist) return UniversalSwapHelper.generateSwapRoute(offerInfo, askInfo, []); + // TODO: hardcode NTMPI -> USDC -> ORAI -> X + if (isEqual(offerInfo, NEUTARO_INFO)) { + const swapRoute = isEqual(askInfo, ORAI_INFO) ? [USDC_INFO] : [USDC_INFO, ORAI_INFO]; + return UniversalSwapHelper.generateSwapRoute(offerInfo, askInfo, swapRoute); + } -// simulate swap functions -export const simulateSwap = async (query: { - fromInfo: TokenItemType; - toInfo: TokenItemType; - amount: string; - routerClient: OraiswapRouterReadOnlyInterface; -}) => { - const { amount, fromInfo, toInfo, routerClient } = query; - - // check for universal-swap 2 tokens that have same coingeckoId, should return simulate data with average ratio 1-1. - if (fromInfo.coinGeckoId === toInfo.coinGeckoId) { - return { - amount - }; - } - - // check if they have pairs. If not then we go through ORAI - const { info: offerInfo } = parseTokenInfo(fromInfo, amount); - const { info: askInfo } = parseTokenInfo(toInfo); - const operations = generateSwapOperationMsgs(offerInfo, askInfo); - console.log("operations: ", operations); - try { - let finalAmount = amount; - const data = await routerClient.simulateSwapOperations({ - offerAmount: finalAmount, - operations - }); - return data; - } catch (error) { - throw new Error(`Error when trying to simulate swap using router v2: ${JSON.stringify(error)}`); - } -}; + // TODO: X -> ORAI -> USDC -> NTMPI + if (isEqual(askInfo, NEUTARO_INFO)) { + const swapRoute = isEqual(offerInfo, ORAI_INFO) ? [USDC_INFO] : [ORAI_INFO, USDC_INFO]; + return UniversalSwapHelper.generateSwapRoute(offerInfo, askInfo, swapRoute); + } + + // Default case: ORAI_INFO + return UniversalSwapHelper.generateSwapRoute(offerInfo, askInfo, [ORAI_INFO]); + }; -export const simulateSwapEvm = async (query: { - fromInfo: TokenItemType; - toInfo: TokenItemType; - amount: string; -}): Promise => { - const { amount, fromInfo, toInfo } = query; - // check swap native and wrap native - const isCheckSwapNativeAndWrapNative = caseSwapNativeAndWrapNative(fromInfo.coinGeckoId, toInfo.coinGeckoId); - - // check for universal-swap 2 tokens that have same coingeckoId, should return simulate data with average ratio 1-1. - if (fromInfo.coinGeckoId === toInfo.coinGeckoId || isCheckSwapNativeAndWrapNative) { + // simulate swap functions + static simulateSwap = async (query: { + fromInfo: TokenItemType; + toInfo: TokenItemType; + amount: string; + routerClient: OraiswapRouterReadOnlyInterface; + }): Promise<{ amount: Uint128 }> => { + const { amount, fromInfo, toInfo, routerClient } = query; + + // check for universal-swap 2 tokens that have same coingeckoId, should return simulate data with average ratio 1-1. + if (fromInfo.coinGeckoId === toInfo.coinGeckoId) { + return { + amount + }; + } + + // check if they have pairs. If not then we go through ORAI + const { info: offerInfo } = parseTokenInfo(fromInfo, amount); + const { info: askInfo } = parseTokenInfo(toInfo); + const operations = UniversalSwapHelper.generateSwapOperationMsgs(offerInfo, askInfo); + console.log("operations: ", operations); + try { + let finalAmount = amount; + const data = await routerClient.simulateSwapOperations({ + offerAmount: finalAmount, + operations + }); + return data; + } catch (error) { + throw new Error(`Error when trying to simulate swap using router v2: ${JSON.stringify(error)}`); + } + }; + + static simulateSwapEvm = async (query: { + fromInfo: TokenItemType; + toInfo: TokenItemType; + amount: string; + }): Promise => { + const { amount, fromInfo, toInfo } = query; + // check swap native and wrap native + const isCheckSwapNativeAndWrapNative = caseSwapNativeAndWrapNative(fromInfo.coinGeckoId, toInfo.coinGeckoId); + + // check for universal-swap 2 tokens that have same coingeckoId, should return simulate data with average ratio 1-1. + if (fromInfo.coinGeckoId === toInfo.coinGeckoId || isCheckSwapNativeAndWrapNative) { + return { + // amount: toDisplay(amount, fromInfo.decimals, toInfo.decimals).toString(), + amount: new BigDecimal(amount) + .mul(10n ** BigInt(toInfo.decimals)) + .div(10n ** BigInt(fromInfo.decimals)) + .toString(), + displayAmount: toDisplay(amount, fromInfo.decimals) + }; + } + try { + // get proxy contract object so that we can query the corresponding router address + const provider = new ethers.providers.JsonRpcProvider(fromInfo.rpc); + const toTokenInfoOnSameChainId = getTokenOnSpecificChainId(toInfo.coinGeckoId, fromInfo.chainId); + const swapRouterV2 = IUniswapV2Router02__factory.connect( + proxyContractInfo[fromInfo.chainId].routerAddr, + provider + ); + const route = UniversalSwapHelper.getEvmSwapRoute( + fromInfo.chainId, + fromInfo.contractAddress, + toTokenInfoOnSameChainId.contractAddress + ); + const outs = await swapRouterV2.getAmountsOut(amount, route); + if (outs.length === 0) throw new Error("There is no output amounts after simulating evm swap"); + const simulateAmount = outs.slice(-1)[0].toString(); + return { + // to display to reset the simulate amount to correct display type (swap simulate from -> same chain id to, so we use same chain id toToken decimals) + // then toAmount with actual toInfo decimals so that it has the same decimals as other tokens displayed + amount: simulateAmount, + displayAmount: toDisplay(simulateAmount, toTokenInfoOnSameChainId.decimals) // get the final out amount, which is the token out amount we want + }; + } catch (ex) { + console.log("error simulating evm: ", ex); + } + }; + + static handleSimulateSwap = async (query: { + originalFromInfo: TokenItemType; + originalToInfo: TokenItemType; + originalAmount: number; + routerClient: OraiswapRouterReadOnlyInterface; + }): Promise => { + // if the from token info is on bsc or eth, then we simulate using uniswap / pancake router + // otherwise, simulate like normal + if ( + UniversalSwapHelper.isSupportedNoPoolSwapEvm(query.originalFromInfo.coinGeckoId) || + UniversalSwapHelper.isEvmSwappable({ + fromChainId: query.originalFromInfo.chainId, + toChainId: query.originalToInfo.chainId, + fromContractAddr: query.originalFromInfo.contractAddress, + toContractAddr: query.originalToInfo.contractAddress + }) + ) { + // reset previous amount calculation since now we need to deal with original from & to info, not oraichain token info + const { amount, displayAmount } = await UniversalSwapHelper.simulateSwapEvm({ + fromInfo: query.originalFromInfo, + toInfo: query.originalToInfo, + amount: toAmount(query.originalAmount, query.originalFromInfo.decimals).toString() + }); + console.log("amount, display amount: ", { amount, displayAmount }); + return { amount, displayAmount }; + } + const fromInfo = getTokenOnOraichain(query.originalFromInfo.coinGeckoId); + const toInfo = getTokenOnOraichain(query.originalToInfo.coinGeckoId); + if (!fromInfo || !toInfo) + throw new Error( + `Cannot find token on Oraichain for token ${query.originalFromInfo.coinGeckoId} and ${query.originalToInfo.coinGeckoId}` + ); + const { amount } = await UniversalSwapHelper.simulateSwap({ + fromInfo, + toInfo, + amount: toAmount(query.originalAmount, fromInfo.decimals).toString(), + routerClient: query.routerClient + }); return { - // amount: toDisplay(amount, fromInfo.decimals, toInfo.decimals).toString(), - amount: new BigDecimal(amount) - .mul(10n ** BigInt(toInfo.decimals)) - .div(10n ** BigInt(fromInfo.decimals)) - .toString(), - displayAmount: toDisplay(amount, fromInfo.decimals) + amount, + displayAmount: toDisplay(amount, getTokenOnOraichain(toInfo.coinGeckoId)?.decimals) }; - } - try { - // get proxy contract object so that we can query the corresponding router address - const provider = new ethers.providers.JsonRpcProvider(fromInfo.rpc); - const toTokenInfoOnSameChainId = getTokenOnSpecificChainId(toInfo.coinGeckoId, fromInfo.chainId); - const swapRouterV2 = IUniswapV2Router02__factory.connect(proxyContractInfo[fromInfo.chainId].routerAddr, provider); - const route = getEvmSwapRoute(fromInfo.chainId, fromInfo.contractAddress, toTokenInfoOnSameChainId.contractAddress); - const outs = await swapRouterV2.getAmountsOut(amount, route); - if (outs.length === 0) throw new Error("There is no output amounts after simulating evm swap"); - const simulateAmount = outs.slice(-1)[0].toString(); - return { - // to display to reset the simulate amount to correct display type (swap simulate from -> same chain id to, so we use same chain id toToken decimals) - // then toAmount with actual toInfo decimals so that it has the same decimals as other tokens displayed - amount: simulateAmount, - displayAmount: toDisplay(simulateAmount, toTokenInfoOnSameChainId.decimals) // get the final out amount, which is the token out amount we want + }; + + static checkFeeRelayer = async (query: { + originalFromToken: TokenItemType; + relayerFee: { + relayerAmount: string; + relayerDecimals: number; }; - } catch (ex) { - console.log("error simulating evm: ", ex); - } -}; + fromAmount: number; + routerClient: OraiswapRouterReadOnlyInterface; + }): Promise => { + const { originalFromToken, relayerFee, fromAmount, routerClient } = query; + if (!relayerFee || !parseInt(relayerFee.relayerAmount)) return true; + const relayerDisplay = toDisplay(relayerFee.relayerAmount, relayerFee.relayerDecimals); -export const handleSimulateSwap = async (query: { - originalFromInfo: TokenItemType; - originalToInfo: TokenItemType; - originalAmount: number; - routerClient: OraiswapRouterReadOnlyInterface; -}): Promise => { - // if the from token info is on bsc or eth, then we simulate using uniswap / pancake router - // otherwise, simulate like normal - if ( - isSupportedNoPoolSwapEvm(query.originalFromInfo.coinGeckoId) || - isEvmSwappable({ - fromChainId: query.originalFromInfo.chainId, - toChainId: query.originalToInfo.chainId, - fromContractAddr: query.originalFromInfo.contractAddress, - toContractAddr: query.originalToInfo.contractAddress - }) - ) { - // reset previous amount calculation since now we need to deal with original from & to info, not oraichain token info - const { amount, displayAmount } = await simulateSwapEvm({ - fromInfo: query.originalFromInfo, - toInfo: query.originalToInfo, - amount: toAmount(query.originalAmount, query.originalFromInfo.decimals).toString() + // From Token is orai + if (originalFromToken.coinGeckoId === "oraichain-token") { + if (relayerDisplay >= fromAmount) return false; + return true; + } + + return UniversalSwapHelper.checkFeeRelayerNotOrai({ + fromTokenInOrai: getTokenOnOraichain(originalFromToken.coinGeckoId), + fromAmount, + relayerAmount: relayerFee.relayerAmount, + routerClient }); - console.log("amount, display amount: ", { amount, displayAmount }); - return { amount, displayAmount }; - } - const fromInfo = getTokenOnOraichain(query.originalFromInfo.coinGeckoId); - const toInfo = getTokenOnOraichain(query.originalToInfo.coinGeckoId); - if (!fromInfo || !toInfo) - throw new Error( - `Cannot find token on Oraichain for token ${query.originalFromInfo.coinGeckoId} and ${query.originalToInfo.coinGeckoId}` - ); - const { amount } = await simulateSwap({ - fromInfo, - toInfo, - amount: toAmount(query.originalAmount, fromInfo.decimals).toString(), - routerClient: query.routerClient - }); - return { - amount, - displayAmount: toDisplay(amount, getTokenOnOraichain(toInfo.coinGeckoId)?.decimals) }; -}; -export const checkFeeRelayer = async (query: { - originalFromToken: TokenItemType; - relayerFee: { + static checkFeeRelayerNotOrai = async (query: { + fromTokenInOrai: TokenItemType; + fromAmount: number; relayerAmount: string; - relayerDecimals: number; - }; - fromAmount: number; - routerClient: OraiswapRouterReadOnlyInterface; -}): Promise => { - const { originalFromToken, relayerFee, fromAmount, routerClient } = query; - if (!relayerFee || !parseInt(relayerFee.relayerAmount)) return true; - const relayerDisplay = toDisplay(relayerFee.relayerAmount, relayerFee.relayerDecimals); - - // From Token is orai - if (originalFromToken.coinGeckoId === "oraichain-token") { - if (relayerDisplay >= fromAmount) return false; - return true; - } - - return checkFeeRelayerNotOrai({ - fromTokenInOrai: getTokenOnOraichain(originalFromToken.coinGeckoId), - fromAmount, - relayerAmount: relayerFee.relayerAmount, - routerClient - }); -}; - -export const checkFeeRelayerNotOrai = async (query: { - fromTokenInOrai: TokenItemType; - fromAmount: number; - relayerAmount: string; - routerClient: OraiswapRouterReadOnlyInterface; -}): Promise => { - const { fromTokenInOrai, fromAmount, routerClient, relayerAmount } = query; - if (!fromTokenInOrai) return true; - if (fromTokenInOrai.chainId !== "Oraichain") - throw generateError( - "From token on Oraichain is not on Oraichain. The developers have made a mistake. Please notify them!" - ); - // estimate exchange token when From Token not orai. Only need to swap & check if it is swappable with ORAI. Otherwise, we ignore the fees - if (isInPairList(fromTokenInOrai.denom) || isInPairList(fromTokenInOrai.contractAddress)) { - const oraiToken = getTokenOnOraichain("oraichain-token"); - const { amount } = await simulateSwap({ - fromInfo: fromTokenInOrai, - toInfo: oraiToken, - amount: toAmount(fromAmount, fromTokenInOrai.decimals).toString(), - routerClient: routerClient - }); - const amountDisplay = toDisplay(amount, fromTokenInOrai.decimals); - const relayerAmountDisplay = toDisplay(relayerAmount); - if (relayerAmountDisplay > amountDisplay) return false; + routerClient: OraiswapRouterReadOnlyInterface; + }): Promise => { + const { fromTokenInOrai, fromAmount, routerClient, relayerAmount } = query; + if (!fromTokenInOrai) return true; + if (fromTokenInOrai.chainId !== "Oraichain") + throw generateError( + "From token on Oraichain is not on Oraichain. The developers have made a mistake. Please notify them!" + ); + // estimate exchange token when From Token not orai. Only need to swap & check if it is swappable with ORAI. Otherwise, we ignore the fees + if (isInPairList(fromTokenInOrai.denom) || isInPairList(fromTokenInOrai.contractAddress)) { + const oraiToken = getTokenOnOraichain("oraichain-token"); + const { amount } = await UniversalSwapHelper.simulateSwap({ + fromInfo: fromTokenInOrai, + toInfo: oraiToken, + amount: toAmount(fromAmount, fromTokenInOrai.decimals).toString(), + routerClient: routerClient + }); + const amountDisplay = toDisplay(amount, fromTokenInOrai.decimals); + const relayerAmountDisplay = toDisplay(relayerAmount); + if (relayerAmountDisplay > amountDisplay) return false; + return true; + } return true; - } - return true; -}; + }; -// verify balance -export const checkBalanceChannelIbc = async ( - ibcInfo: IBCInfo, - fromToken: TokenItemType, - toToken: TokenItemType, - toSimulateAmount: string, - client: CosmWasmClient, - ibcWasmContract: string -) => { - try { - let pairKey = buildIbcWasmPairKey(ibcInfo.source, ibcInfo.channel, toToken.denom); - if (toToken.prefix && toToken.contractAddress) { - pairKey = buildIbcWasmPairKey(ibcInfo.source, ibcInfo.channel, `${toToken.prefix}${toToken.contractAddress}`); - } - const ics20Client = new CwIcs20LatestQueryClient(client, ibcWasmContract); - let balance: Amount; + // verify balance + static checkBalanceChannelIbc = async ( + ibcInfo: IBCInfo, + fromToken: TokenItemType, + toToken: TokenItemType, + toSimulateAmount: string, + client: CosmWasmClient, + ibcWasmContract: string + ) => { try { - const { balance: channelBalance } = await ics20Client.channelWithKey({ - channelId: ibcInfo.channel, - denom: pairKey - }); - balance = channelBalance; + let pairKey = UniversalSwapHelper.buildIbcWasmPairKey(ibcInfo.source, ibcInfo.channel, toToken.denom); + if (toToken.prefix && toToken.contractAddress) { + pairKey = UniversalSwapHelper.buildIbcWasmPairKey( + ibcInfo.source, + ibcInfo.channel, + `${toToken.prefix}${toToken.contractAddress}` + ); + } + const ics20Client = new CwIcs20LatestQueryClient(client, ibcWasmContract); + let balance: Amount; + try { + const { balance: channelBalance } = await ics20Client.channelWithKey({ + channelId: ibcInfo.channel, + denom: pairKey + }); + balance = channelBalance; + } catch (error) { + // do nothing because the given channel and key doesnt exist + // console.log("error querying channel with key: ", error); + return; + } + + if ("native" in balance) { + const pairMapping = await ics20Client.pairMapping({ key: pairKey }); + const trueBalance = toDisplay(balance.native.amount, pairMapping.pair_mapping.remote_decimals); + let _toAmount = toDisplay(toSimulateAmount, toToken.decimals); + if (fromToken.coinGeckoId !== toToken.coinGeckoId) { + const fromTokenInfo = getTokenOnOraichain(fromToken.coinGeckoId); + const toTokenInfo = getTokenOnOraichain(toToken.coinGeckoId); + const routerClient = new OraiswapRouterQueryClient(client, network.router); + if (!fromTokenInfo || !toTokenInfo) + throw generateError( + `Error in checking balance channel ibc: cannot simulate from: ${fromToken.coinGeckoId} to: ${toToken.coinGeckoId}` + ); + const { amount } = await UniversalSwapHelper.simulateSwap({ + fromInfo: fromTokenInfo, + toInfo: toTokenInfo, + amount: toAmount(_toAmount, fromTokenInfo.decimals).toString(), + routerClient + }); + _toAmount = toDisplay(amount, fromTokenInfo.decimals); + } + if (trueBalance < _toAmount) throw generateError(`pair key is not enough balance!`); + } } catch (error) { - // do nothing because the given channel and key doesnt exist - // console.log("error querying channel with key: ", error); - return; + throw generateError(`Error in checking balance channel ibc: ${JSON.stringify(error)}`); } + }; - if ("native" in balance) { - const pairMapping = await ics20Client.pairMapping({ key: pairKey }); - const trueBalance = toDisplay(balance.native.amount, pairMapping.pair_mapping.remote_decimals); - let _toAmount = toDisplay(toSimulateAmount, toToken.decimals); - if (fromToken.coinGeckoId !== toToken.coinGeckoId) { - const fromTokenInfo = getTokenOnOraichain(fromToken.coinGeckoId); - const toTokenInfo = getTokenOnOraichain(toToken.coinGeckoId); - const routerClient = new OraiswapRouterQueryClient(client, network.router); - if (!fromTokenInfo || !toTokenInfo) - throw generateError( - `Error in checking balance channel ibc: cannot simulate from: ${fromToken.coinGeckoId} to: ${toToken.coinGeckoId}` - ); - const { amount } = await simulateSwap({ - fromInfo: fromTokenInfo, - toInfo: toTokenInfo, - amount: toAmount(_toAmount, fromTokenInfo.decimals).toString(), - routerClient - }); - _toAmount = toDisplay(amount, fromTokenInfo.decimals); - } - if (trueBalance < _toAmount) throw generateError(`pair key is not enough balance!`); + static getBalanceIBCOraichain = async (token: TokenItemType, client: CosmWasmClient, ibcWasmContract: string) => { + if (!token) return { balance: 0 }; + if (token.contractAddress) { + const cw20Token = new OraiswapTokenQueryClient(client, token.contractAddress); + const { balance } = await cw20Token.balance({ address: ibcWasmContract }); + return { balance: toDisplay(balance, token.decimals) }; } - } catch (error) { - throw generateError(`Error in checking balance channel ibc: ${JSON.stringify(error)}`); - } -}; + const { amount } = await client.getBalance(ibcWasmContract, token.denom); + return { balance: toDisplay(amount, token.decimals) }; + }; -export const getBalanceIBCOraichain = async (token: TokenItemType, client: CosmWasmClient, ibcWasmContract: string) => { - if (!token) return { balance: 0 }; - if (token.contractAddress) { - const cw20Token = new OraiswapTokenQueryClient(client, token.contractAddress); - const { balance } = await cw20Token.balance({ address: ibcWasmContract }); - return { balance: toDisplay(balance, token.decimals) }; - } - const { amount } = await client.getBalance(ibcWasmContract, token.denom); - return { balance: toDisplay(amount, token.decimals) }; -}; + // ORAI ( ETH ) -> check ORAI (ORAICHAIN - compare from amount with cw20 / native amount) (fromAmount) -> check AIRI - compare to amount with channel balance (ORAICHAIN) (toAmount) -> AIRI (BSC) + // ORAI ( ETH ) -> check ORAI (ORAICHAIN) - compare from amount with cw20 / native amount) (fromAmount) -> check wTRX - compare to amount with channel balance (ORAICHAIN) (toAmount) -> wTRX (TRON) + static checkBalanceIBCOraichain = async ( + to: TokenItemType, + from: TokenItemType, + fromAmount: number, + toSimulateAmount: string, + client: CosmWasmClient, + ibcWasmContract: string + ) => { + // ORAI ( ETH ) -> check ORAI (ORAICHAIN) -> ORAI (BSC) + // no need to check this case because users will swap directly. This case should be impossible because it is only called when transferring from evm to other networks + if (from.chainId === "Oraichain" && to.chainId === from.chainId) return; + // always check from token in ibc wasm should have enough tokens to swap / send to destination + const token = getTokenOnOraichain(from.coinGeckoId); + if (!token) return; + let ibcWasmContractAddr = ibcWasmContract; + // TODO: check balance with kawaii token and milky token + if (["kawaii-islands", "milky-token"].includes(from.coinGeckoId) && ["0x38"].includes(from.chainId)) { + ibcWasmContractAddr = network.converter; + } + const { balance } = await UniversalSwapHelper.getBalanceIBCOraichain(token, client, ibcWasmContractAddr); + if (balance < fromAmount) { + throw generateError( + `The bridge contract does not have enough balance to process this bridge transaction. Wanted ${fromAmount}, have ${balance}` + ); + } + // if to token is evm, then we need to evaluate channel state balance of ibc wasm + if (to.chainId === "0x01" || to.chainId === "0x38" || to.chainId === "0x2b6653dc") { + const ibcInfo: IBCInfo | undefined = UniversalSwapHelper.getIbcInfo("Oraichain", to.chainId); + if (!ibcInfo) throw generateError("IBC Info error when checking ibc balance"); + await UniversalSwapHelper.checkBalanceChannelIbc(ibcInfo, from, to, toSimulateAmount, client, ibcWasmContract); + } + }; -// ORAI ( ETH ) -> check ORAI (ORAICHAIN - compare from amount with cw20 / native amount) (fromAmount) -> check AIRI - compare to amount with channel balance (ORAICHAIN) (toAmount) -> AIRI (BSC) -// ORAI ( ETH ) -> check ORAI (ORAICHAIN) - compare from amount with cw20 / native amount) (fromAmount) -> check wTRX - compare to amount with channel balance (ORAICHAIN) (toAmount) -> wTRX (TRON) -export const checkBalanceIBCOraichain = async ( - to: TokenItemType, - from: TokenItemType, - fromAmount: number, - toSimulateAmount: string, - client: CosmWasmClient, - ibcWasmContract: string -) => { - // ORAI ( ETH ) -> check ORAI (ORAICHAIN) -> ORAI (BSC) - // no need to check this case because users will swap directly. This case should be impossible because it is only called when transferring from evm to other networks - if (from.chainId === "Oraichain" && to.chainId === from.chainId) return; - // always check from token in ibc wasm should have enough tokens to swap / send to destination - const token = getTokenOnOraichain(from.coinGeckoId); - if (!token) return; - let ibcWasmContractAddr = ibcWasmContract; - // TODO: check balance with kawaii token and milky token - if (["kawaii-islands", "milky-token"].includes(from.coinGeckoId) && ["0x38"].includes(from.chainId)) { - ibcWasmContractAddr = network.converter; - } - const { balance } = await getBalanceIBCOraichain(token, client, ibcWasmContractAddr); - if (balance < fromAmount) { - throw generateError( - `The bridge contract does not have enough balance to process this bridge transaction. Wanted ${fromAmount}, have ${balance}` + static filterNonPoolEvmTokens = ( + chainId: string, + coingeckoId: CoinGeckoId, + denom: string, + searchTokenName: string, + direction: SwapDirection // direction = to means we are filtering to tokens + ) => { + // basic filter. Dont include itself & only collect tokens with searched letters + const listTokens = direction === SwapDirection.From ? swapFromTokens : swapToTokens; + let filteredToTokens = listTokens.filter( + (token) => token.denom !== denom && token.name.toLowerCase().includes(searchTokenName.toLowerCase()) ); - } - // if to token is evm, then we need to evaluate channel state balance of ibc wasm - if (to.chainId === "0x01" || to.chainId === "0x38" || to.chainId === "0x2b6653dc") { - const ibcInfo: IBCInfo | undefined = getIbcInfo("Oraichain", to.chainId); - if (!ibcInfo) throw generateError("IBC Info error when checking ibc balance"); - await checkBalanceChannelIbc(ibcInfo, from, to, toSimulateAmount, client, ibcWasmContract); - } -}; + // special case for tokens not having a pool on Oraichain + if (UniversalSwapHelper.isSupportedNoPoolSwapEvm(coingeckoId)) { + const swappableTokens = Object.keys(UniversalSwapHelper.swapEvmRoutes[chainId]).map((key) => key.split("-")[1]); + const filteredTokens = filteredToTokens.filter((token) => swappableTokens.includes(token.contractAddress)); -export function filterNonPoolEvmTokens( - chainId: string, - coingeckoId: CoinGeckoId, - denom: string, - searchTokenName: string, - direction: SwapDirection // direction = to means we are filtering to tokens -) { - // basic filter. Dont include itself & only collect tokens with searched letters - const listTokens = direction === SwapDirection.From ? swapFromTokens : swapToTokens; - let filteredToTokens = listTokens.filter( - (token) => token.denom !== denom && token.name.toLowerCase().includes(searchTokenName.toLowerCase()) - ); - // special case for tokens not having a pool on Oraichain - if (isSupportedNoPoolSwapEvm(coingeckoId)) { - const swappableTokens = Object.keys(swapEvmRoutes[chainId]).map((key) => key.split("-")[1]); - const filteredTokens = filteredToTokens.filter((token) => swappableTokens.includes(token.contractAddress)); - - // tokens that dont have a pool on Oraichain like WETH or WBNB cannot be swapped from a token on Oraichain - if (direction === SwapDirection.To) - return [...new Set(filteredTokens.concat(filteredTokens.map((token) => getTokenOnOraichain(token.coinGeckoId))))]; - filteredToTokens = filteredTokens; - } - // special case filter. Tokens on networks other than supported evm cannot swap to tokens, so we need to remove them - if (!isEvmNetworkNativeSwapSupported(chainId as NetworkChainId)) - return filteredToTokens.filter((t) => { - // one-directional swap. non-pool tokens of evm network can swap be swapped with tokens on Oraichain, but not vice versa - const isSupported = isSupportedNoPoolSwapEvm(t.coinGeckoId); - if (direction === SwapDirection.To) return !isSupported; - if (isSupported) { - // if we cannot find any matched token then we dont include it in the list since it cannot be swapped - const sameChainId = getTokenOnSpecificChainId(coingeckoId, t.chainId as NetworkChainId); - if (!sameChainId) return false; + // tokens that dont have a pool on Oraichain like WETH or WBNB cannot be swapped from a token on Oraichain + if (direction === SwapDirection.To) + return [ + ...new Set(filteredTokens.concat(filteredTokens.map((token) => getTokenOnOraichain(token.coinGeckoId)))) + ]; + filteredToTokens = filteredTokens; + } + // special case filter. Tokens on networks other than supported evm cannot swap to tokens, so we need to remove them + if (!UniversalSwapHelper.isEvmNetworkNativeSwapSupported(chainId as NetworkChainId)) + return filteredToTokens.filter((t) => { + // one-directional swap. non-pool tokens of evm network can swap be swapped with tokens on Oraichain, but not vice versa + const isSupported = UniversalSwapHelper.isSupportedNoPoolSwapEvm(t.coinGeckoId); + if (direction === SwapDirection.To) return !isSupported; + if (isSupported) { + // if we cannot find any matched token then we dont include it in the list since it cannot be swapped + const sameChainId = getTokenOnSpecificChainId(coingeckoId, t.chainId as NetworkChainId); + if (!sameChainId) return false; + return true; + } return true; - } + }); + return filteredToTokens.filter((t) => { + // filter out to tokens that are on a different network & with no pool because we are not ready to support them yet. TODO: support + if (UniversalSwapHelper.isSupportedNoPoolSwapEvm(t.coinGeckoId)) return t.chainId === chainId; return true; }); - return filteredToTokens.filter((t) => { - // filter out to tokens that are on a different network & with no pool because we are not ready to support them yet. TODO: support - if (isSupportedNoPoolSwapEvm(t.coinGeckoId)) return t.chainId === chainId; - return true; - }); -} + }; -export function generateConvertErc20Cw20Message( - amounts: AmountDetails, - tokenInfo: TokenItemType, - sender?: string -): ExecuteInstruction[] { - if (!tokenInfo.evmDenoms) return []; - const subAmounts = getSubAmountDetails(amounts, tokenInfo); - // we convert all mapped tokens to cw20 to unify the token - for (const denom in subAmounts) { - const balance = BigInt(subAmounts[denom] ?? "0"); - // reset so we convert using native first - const erc20TokenInfo = tokenMap[denom]; - if (balance > 0) { - const msgConvert: ExecuteInstruction = generateConvertMsgs({ - type: Type.CONVERT_TOKEN, - sender, - inputAmount: balance.toString(), - inputToken: erc20TokenInfo - }); - return [msgConvert]; + static generateConvertErc20Cw20Message = ( + amounts: AmountDetails, + tokenInfo: TokenItemType, + sender?: string + ): ExecuteInstruction[] => { + if (!tokenInfo.evmDenoms) return []; + const subAmounts = getSubAmountDetails(amounts, tokenInfo); + // we convert all mapped tokens to cw20 to unify the token + for (const denom in subAmounts) { + const balance = BigInt(subAmounts[denom] ?? "0"); + // reset so we convert using native first + const erc20TokenInfo = tokenMap[denom]; + if (balance > 0) { + const msgConvert: ExecuteInstruction = UniversalSwapHelper.generateConvertMsgs({ + type: Type.CONVERT_TOKEN, + sender, + inputAmount: balance.toString(), + inputToken: erc20TokenInfo + }); + return [msgConvert]; + } } - } - return []; -} + return []; + }; -export function generateConvertCw20Erc20Message( - amounts: AmountDetails, - tokenInfo: TokenItemType, - sender: string, - sendCoin: Coin -): ExecuteInstruction[] { - if (!tokenInfo.evmDenoms) return []; - // we convert all mapped tokens to cw20 to unify the token - for (const denom of tokenInfo.evmDenoms) { - // optimize. Only convert if not enough balance & match denom - if (denom !== sendCoin.denom) continue; - - // if this wallet already has enough native ibc bridge balance => no need to convert reverse - if (+amounts[sendCoin.denom] >= +sendCoin.amount) break; - - const balance = amounts[tokenInfo.denom]; - const evmToken = tokenMap[denom]; - - if (balance) { - const outputToken: TokenItemType = { - ...tokenInfo, - denom: evmToken.denom, - contractAddress: undefined, - decimals: evmToken.decimals - }; - const msgConvert = generateConvertMsgs({ - type: Type.CONVERT_TOKEN_REVERSE, - sender, - inputAmount: balance, - inputToken: tokenInfo, - outputToken - }); - return [msgConvert]; - } - } - return []; -} + static generateConvertCw20Erc20Message = ( + amounts: AmountDetails, + tokenInfo: TokenItemType, + sender: string, + sendCoin: Coin + ): ExecuteInstruction[] => { + if (!tokenInfo.evmDenoms) return []; + // we convert all mapped tokens to cw20 to unify the token + for (const denom of tokenInfo.evmDenoms) { + // optimize. Only convert if not enough balance & match denom + if (denom !== sendCoin.denom) continue; -export function generateConvertMsgs(data: ConvertType): ExecuteInstruction { - const { type, sender, inputToken, inputAmount } = data; - let funds: Coin[] | null; - // for withdraw & provide liquidity methods, we need to interact with the oraiswap pair contract - let contractAddr = network.converter; - let input: any; - switch (type) { - case Type.CONVERT_TOKEN: { - // currently only support cw20 token pool - const { info: assetInfo, fund } = parseTokenInfo(inputToken, inputAmount); - // native case - if ("native_token" in assetInfo) { - input = { - convert: {} - }; - funds = handleSentFunds(fund); - } else { - // cw20 case - input = { - send: { - contract: network.converter, - amount: inputAmount, - msg: toBinary({ - convert: {} - }) - } + // if this wallet already has enough native ibc bridge balance => no need to convert reverse + if (+amounts[sendCoin.denom] >= +sendCoin.amount) break; + + const balance = amounts[tokenInfo.denom]; + const evmToken = tokenMap[denom]; + + if (balance) { + const outputToken: TokenItemType = { + ...tokenInfo, + denom: evmToken.denom, + contractAddress: undefined, + decimals: evmToken.decimals }; - contractAddr = assetInfo.token.contract_addr; + const msgConvert = UniversalSwapHelper.generateConvertMsgs({ + type: Type.CONVERT_TOKEN_REVERSE, + sender, + inputAmount: balance, + inputToken: tokenInfo, + outputToken + }); + return [msgConvert]; } - break; } - case Type.CONVERT_TOKEN_REVERSE: { - const { outputToken } = data as ConvertReverse; - - // currently only support cw20 token pool - const { info: assetInfo, fund } = parseTokenInfo(inputToken, inputAmount); - const { info: outputAssetInfo } = parseTokenInfo(outputToken, "0"); - // native case - if ("native_token" in assetInfo) { - input = { - convert_reverse: { - from_asset: outputAssetInfo - } - }; - funds = handleSentFunds(fund); - } else { - // cw20 case - input = { - send: { - contract: network.converter, - amount: inputAmount, - msg: toBinary({ - convert_reverse: { - from: outputAssetInfo - } - }) - } - }; - contractAddr = assetInfo.token.contract_addr; + return []; + }; + + static generateConvertMsgs = (data: ConvertType): ExecuteInstruction => { + const { type, sender, inputToken, inputAmount } = data; + let funds: Coin[] | null; + // for withdraw & provide liquidity methods, we need to interact with the oraiswap pair contract + let contractAddr = network.converter; + let input: any; + switch (type) { + case Type.CONVERT_TOKEN: { + // currently only support cw20 token pool + const { info: assetInfo, fund } = parseTokenInfo(inputToken, inputAmount); + // native case + if ("native_token" in assetInfo) { + input = { + convert: {} + }; + funds = handleSentFunds(fund); + } else { + // cw20 case + input = { + send: { + contract: network.converter, + amount: inputAmount, + msg: toBinary({ + convert: {} + }) + } + }; + contractAddr = assetInfo.token.contract_addr; + } + break; } - break; + case Type.CONVERT_TOKEN_REVERSE: { + const { outputToken } = data as ConvertReverse; + + // currently only support cw20 token pool + const { info: assetInfo, fund } = parseTokenInfo(inputToken, inputAmount); + const { info: outputAssetInfo } = parseTokenInfo(outputToken, "0"); + // native case + if ("native_token" in assetInfo) { + input = { + convert_reverse: { + from_asset: outputAssetInfo + } + }; + funds = handleSentFunds(fund); + } else { + // cw20 case + input = { + send: { + contract: network.converter, + amount: inputAmount, + msg: toBinary({ + convert_reverse: { + from: outputAssetInfo + } + }) + } + }; + contractAddr = assetInfo.token.contract_addr; + } + break; + } + default: + break; } - default: - break; - } - - const msg: ExecuteInstruction = { - contractAddress: contractAddr, - msg: input, - funds - }; - return msg; + const msg: ExecuteInstruction = { + contractAddress: contractAddr, + msg: input, + funds + }; + + return msg; + }; } + +// evm swap helpers +/** + * @deprecated. Use UniversalSwapHelper.isSupportedNoPoolSwapEvm + */ +export const isSupportedNoPoolSwapEvm = UniversalSwapHelper.isSupportedNoPoolSwapEvm; + +/** + * @deprecated. + */ +export const isEvmNetworkNativeSwapSupported = UniversalSwapHelper.isEvmNetworkNativeSwapSupported; + +/** + * @deprecated. + */ +export const swapEvmRoutes: { + [network: string]: { + [pair: string]: string[]; + }; +} = UniversalSwapHelper.swapEvmRoutes; + +/** + * @deprecated. + */ +export const buildSwapRouterKey = UniversalSwapHelper.buildSwapRouterKey; + +/** + * @deprecated. + */ +export const getEvmSwapRoute = UniversalSwapHelper.getEvmSwapRoute; + +// static functions +/** + * @deprecated. + */ +export const isEvmSwappable = UniversalSwapHelper.isEvmSwappable; + +// ibc helpers +/** + * @deprecated. + */ +export const getIbcInfo = UniversalSwapHelper.getIbcInfo; + +/** + * @deprecated. + */ +export const buildIbcWasmPairKey = UniversalSwapHelper.buildIbcWasmPairKey; + +/** + * This function converts the destination address (from BSC / ETH -> Oraichain) to an appropriate format based on the BSC / ETH token contract address + * @param oraiAddress - receiver address on Oraichain + * @param contractAddress - BSC / ETH token contract address + * @returns converted receiver address + * @deprecated use UniversalSwapHelper.getSourceReceiver instead + */ +export const getSourceReceiver = UniversalSwapHelper.getSourceReceiver; + +/** + * This function receives fromToken and toToken as parameters to generate the destination memo for the receiver address + * @param from - from token + * @param to - to token + * @param destReceiver - destination destReceiver + * @returns destination in the format /: + * @deprecated use UniversalSwapHelper.getRoute instead + */ +export const getRoute = UniversalSwapHelper.getRoute; + +/** + * @deprecated + */ +export const addOraiBridgeRoute = UniversalSwapHelper.addOraiBridgeRoute; + +/** + * cases: + * /: + * : + * + * :/: + * : + * :: + * @deprecated + * */ +export const unmarshalOraiBridgeRoute = UniversalSwapHelper.unmarshalOraiBridgeRoute; + +/** + * @deprecated + */ +export const generateSwapRoute = UniversalSwapHelper.generateSwapRoute; + +// generate messages +/** + * @deprecated + */ +export const generateSwapOperationMsgs = UniversalSwapHelper.generateSwapOperationMsgs; + +// simulate swap functions +/** + * @deprecated + */ +export const simulateSwap = UniversalSwapHelper.simulateSwap; + +/** + * @deprecated + */ +export const simulateSwapEvm = UniversalSwapHelper.simulateSwapEvm; + +/** + * @deprecated + */ +export const handleSimulateSwap = UniversalSwapHelper.handleSimulateSwap; + +/** + * @deprecated + */ +export const checkFeeRelayer = UniversalSwapHelper.checkFeeRelayer; + +/** + * @deprecated + */ +export const checkFeeRelayerNotOrai = UniversalSwapHelper.checkFeeRelayerNotOrai; + +// verify balance +/** + * @deprecated + */ +export const checkBalanceChannelIbc = UniversalSwapHelper.checkBalanceChannelIbc; + +/** + * @deprecated + */ +export const getBalanceIBCOraichain = UniversalSwapHelper.getBalanceIBCOraichain; + +// ORAI ( ETH ) -> check ORAI (ORAICHAIN - compare from amount with cw20 / native amount) (fromAmount) -> check AIRI - compare to amount with channel balance (ORAICHAIN) (toAmount) -> AIRI (BSC) +// ORAI ( ETH ) -> check ORAI (ORAICHAIN) - compare from amount with cw20 / native amount) (fromAmount) -> check wTRX - compare to amount with channel balance (ORAICHAIN) (toAmount) -> wTRX (TRON) +/** + * @deprecated + */ +export const checkBalanceIBCOraichain = UniversalSwapHelper.checkBalanceIBCOraichain; + +/** + * @deprecated + */ +export const filterNonPoolEvmTokens = UniversalSwapHelper.filterNonPoolEvmTokens; + +/** + * @deprecated + */ +export const generateConvertErc20Cw20Message = UniversalSwapHelper.generateConvertErc20Cw20Message; + +/** + * @deprecated + */ +export const generateConvertCw20Erc20Message = UniversalSwapHelper.generateConvertCw20Erc20Message; + +/** + * @deprecated + */ +export const generateConvertMsgs = UniversalSwapHelper.generateConvertMsgs; diff --git a/packages/universal-swap/src/types.ts b/packages/universal-swap/src/types.ts index 5b8d6e6c..a79f2290 100644 --- a/packages/universal-swap/src/types.ts +++ b/packages/universal-swap/src/types.ts @@ -1,6 +1,5 @@ -import { CwIcs20LatestClient, CwIcs20LatestReadOnlyInterface } from "@oraichain/common-contracts-sdk"; import { AmountDetails, CosmosWallet, EvmWallet, TokenItemType } from "@oraichain/oraidex-common"; -import { OraiswapRouterInterface, OraiswapRouterReadOnlyInterface, Uint128 } from "@oraichain/oraidex-contracts-sdk"; +import { Uint128 } from "@oraichain/oraidex-contracts-sdk"; export type UniversalSwapType = | "other-networks-to-oraichain" @@ -36,6 +35,7 @@ export interface RelayerFeeData { } /** + * @property recipientAddress - recipient address from client, if user want to send to another address * @property simulatePrice - price of the token calculated with the quote. Eg: swapping ORAI to USDT => 1 ORAI = 2 USDT, then simulate price = 2. TODO: Auto simulate price if not passed */ export interface UniversalSwapData { @@ -49,6 +49,7 @@ export interface UniversalSwapData { readonly relayerFee?: RelayerFeeData; readonly amounts?: AmountDetails; readonly isSourceReceiverTest?: boolean; + readonly recipientAddress?: string; // recipient address from client, if user want to send to another address } /** diff --git a/packages/universal-swap/tests/helper.spec.ts b/packages/universal-swap/tests/helper.spec.ts index f08eb267..07aa684d 100644 --- a/packages/universal-swap/tests/helper.spec.ts +++ b/packages/universal-swap/tests/helper.spec.ts @@ -13,6 +13,8 @@ import { NEUTARO_INFO, NetworkChainId, ORAIX_INFO, + ORAI_BRIDGE_EVM_DENOM_PREFIX, + ORAI_BRIDGE_EVM_ETH_DENOM_PREFIX, ORAI_BRIDGE_EVM_TRON_DENOM_PREFIX, ORAI_BSC_CONTRACT, ORAI_ETH_CONTRACT, @@ -56,7 +58,7 @@ import { } from "../src/helper"; import { SwapRoute, UniversalSwapType } from "../src/types"; import { AssetInfo } from "@oraichain/oraidex-contracts-sdk"; -import { SwapOperation } from "@oraichain/oraidex-contracts-sdk/build/OraiswapRouter.types"; +import { SwapOperation } from "@oraichain/oraidex-contracts-sdk"; import { parseToIbcHookMemo, parseToIbcWasmMemo } from "../src/proto/proto-gen"; import { Coin, coin } from "@cosmjs/proto-signing"; @@ -256,10 +258,13 @@ describe("test helper functions", () => { "0x38", "oraichain-token", "0x01", - "orai1234", + "0x09beeedf51aa45718f46837c94712d89b157a9d3", { - // swapRoute: `${oraichain2oraib}/orai1234:${ORAI_ETH_CONTRACT}`, - swapRoute: parseToIbcWasmMemo("orai1234", oraichain2oraib, ORAI_ETH_CONTRACT), + swapRoute: parseToIbcWasmMemo( + `${ORAI_BRIDGE_EVM_ETH_DENOM_PREFIX}0x09beeedf51aa45718f46837c94712d89b157a9d3`, + oraichain2oraib, + ORAI_BRIDGE_EVM_ETH_DENOM_PREFIX + ORAI_ETH_CONTRACT + ), universalSwapType: "other-networks-to-oraichain" }, false @@ -269,10 +274,14 @@ describe("test helper functions", () => { "0x01", "tether", "0x38", - "orai1234", + "0x09beeedf51aa45718f46837c94712d89b157a9d3", { // swapRoute: `${oraichain2oraib}/orai1234:${USDT_BSC_CONTRACT}`, - swapRoute: parseToIbcWasmMemo("orai1234", oraichain2oraib, USDT_BSC_CONTRACT), + swapRoute: parseToIbcWasmMemo( + `${ORAI_BRIDGE_EVM_DENOM_PREFIX}0x09beeedf51aa45718f46837c94712d89b157a9d3`, + oraichain2oraib, + ORAI_BRIDGE_EVM_DENOM_PREFIX + USDT_BSC_CONTRACT + ), universalSwapType: "other-networks-to-oraichain" }, false @@ -282,10 +291,14 @@ describe("test helper functions", () => { "0x01", "tether", "0x2b6653dc", - "orai1234", + "0x09beeedf51aa45718f46837c94712d89b157a9d3", { // swapRoute: `${oraichain2oraib}/orai1234:${USDT_TRON_CONTRACT}`, - swapRoute: parseToIbcWasmMemo("orai1234", oraichain2oraib, USDT_TRON_CONTRACT), + swapRoute: parseToIbcWasmMemo( + `${ORAI_BRIDGE_EVM_TRON_DENOM_PREFIX}0x09beeedf51aa45718f46837c94712d89b157a9d3`, + oraichain2oraib, + ORAI_BRIDGE_EVM_TRON_DENOM_PREFIX + USDT_TRON_CONTRACT + ), universalSwapType: "other-networks-to-oraichain" }, false @@ -301,7 +314,7 @@ describe("test helper functions", () => { swapRoute: parseToIbcWasmMemo( `${ORAI_BRIDGE_EVM_TRON_DENOM_PREFIX}0x1234`, oraichain2oraib, - USDT_TRON_CONTRACT + ORAI_BRIDGE_EVM_TRON_DENOM_PREFIX + USDT_TRON_CONTRACT ), universalSwapType: "other-networks-to-oraichain" }, @@ -318,6 +331,51 @@ describe("test helper functions", () => { universalSwapType: "other-networks-to-oraichain" }, false + ], + [ + "oraichain-token", + "Oraichain", + "usd-coin", + "0x01", + "0x1234", + { swapRoute: "", universalSwapType: "oraichain-to-evm" }, + false + ], + [ + "kawaii-islands", + "0x1ae6", + "oraichain-token", + "Oraichain", + "orai1234", + { swapRoute: "", universalSwapType: "other-networks-to-oraichain" }, + true + ], + [ + "kawaii-islands", + "kawaii_6886-1", + "oraichain-token", + "Oraichain", + "orai1234", + { swapRoute: "", universalSwapType: "other-networks-to-oraichain" }, + true + ], + [ + "kawaii-islands", + "0x38", + "oraichain-token", + "Oraichain", + "orai1234", + { swapRoute: "", universalSwapType: "other-networks-to-oraichain" }, + false + ], + [ + "milky-token", + "0x38", + "oraichain-token", + "Oraichain", + "orai1234", + { swapRoute: "", universalSwapType: "other-networks-to-oraichain" }, + false ] ])( "test-getRoute-given %s coingecko id, chain id %s, send-to %s, chain id %s with receiver %s should have swapRoute %s", @@ -388,6 +446,66 @@ describe("test helper functions", () => { }, false ], + [ + "cosmos", + "cosmoshub-4", + "tether", + "0x01", + "orai1ek2243955krr3enky8jq8y8vhh3p63y5wjzs4j", + "0x09beeedf51aa45718f46837c94712d89b157a9d3", + { + swapRoute: parseToIbcHookMemo( + "orai1ek2243955krr3enky8jq8y8vhh3p63y5wjzs4j", + `${ORAI_BRIDGE_EVM_ETH_DENOM_PREFIX}0x09beeedf51aa45718f46837c94712d89b157a9d3`, + ibcInfos["Oraichain"]["0x01"]?.channel ?? "", + `${ORAI_BRIDGE_EVM_ETH_DENOM_PREFIX}${parseTokenInfoRawDenom( + getTokenOnSpecificChainId("tether", "0x01") as TokenItemType + )}` + ), + universalSwapType: "cosmos-to-others" + }, + false + ], + [ + "cosmos", + "cosmoshub-4", + "tether", + "0x38", + "orai1ek2243955krr3enky8jq8y8vhh3p63y5wjzs4j", + "0x09beeedf51aa45718f46837c94712d89b157a9d3", + { + swapRoute: parseToIbcHookMemo( + "orai1ek2243955krr3enky8jq8y8vhh3p63y5wjzs4j", + `${ORAI_BRIDGE_EVM_DENOM_PREFIX}0x09beeedf51aa45718f46837c94712d89b157a9d3`, + ibcInfos["Oraichain"]["0x38"]?.channel ?? "", + `${ORAI_BRIDGE_EVM_DENOM_PREFIX}${parseTokenInfoRawDenom( + getTokenOnSpecificChainId("tether", "0x38") as TokenItemType + )}` + ), + universalSwapType: "cosmos-to-others" + }, + false + ], + [ + "cosmos", + "cosmoshub-4", + "tether", + "0x2b6653dc", + "orai1ek2243955krr3enky8jq8y8vhh3p63y5wjzs4j", + "0x09beeedf51aa45718f46837c94712d89b157a9d3", + { + swapRoute: parseToIbcHookMemo( + "orai1ek2243955krr3enky8jq8y8vhh3p63y5wjzs4j", + `${ORAI_BRIDGE_EVM_TRON_DENOM_PREFIX}0x09beeedf51aa45718f46837c94712d89b157a9d3`, + ibcInfos["Oraichain"]["0x2b6653dc"]?.channel ?? "", + `${ORAI_BRIDGE_EVM_TRON_DENOM_PREFIX}${parseTokenInfoRawDenom( + getTokenOnSpecificChainId("tether", "0x2b6653dc") as TokenItemType + )}` + ), + universalSwapType: "cosmos-to-others" + }, + false + ], [ "osmosis", "osmosis-1", @@ -455,6 +573,7 @@ describe("test helper functions", () => { jest .spyOn(dexCommonHelper, "isEthAddress") .mockImplementation((address) => (address.includes("0x") ? true : false)); + const fromToken = flattenTokens.find( (item) => item.coinGeckoId === fromCoingeckoId && item.chainId === fromChainId )!; @@ -474,6 +593,9 @@ describe("test helper functions", () => { const result = addOraiBridgeRoute("receiver", "any" as any, "any" as any); expect(result.swapRoute).toEqual(`${oraib2oraichain}/receiver`); }); + it("test-addOraiBridgeRoute-empty-sourceReceiver", () => { + expect(() => addOraiBridgeRoute("", "" as any, "" as any)).toThrow(); + }); it("test-addOraiBridgeRoute-non-empty-swapRoute", () => { const result = addOraiBridgeRoute( "receiver", diff --git a/packages/universal-swap/tests/index.spec.ts b/packages/universal-swap/tests/index.spec.ts index 725ac865..ea5ab2dd 100644 --- a/packages/universal-swap/tests/index.spec.ts +++ b/packages/universal-swap/tests/index.spec.ts @@ -23,16 +23,13 @@ import { USDC_CONTRACT } from "@oraichain/oraidex-common"; import * as dexCommonHelper from "@oraichain/oraidex-common/build/helper"; // import like this to enable jest.spyOn & avoid redefine property error -import * as dexCommonNetwork from "@oraichain/oraidex-common/build/network"; // import like this to enable jest.spyOn & avoid redefine property error -import * as universalHelper from "../src/helper"; -import { UniversalSwapHandler } from "../src/index"; -import { AccountData, DirectSecp256k1HdWallet, EncodeObject, OfflineSigner } from "@cosmjs/proto-signing"; +import { DirectSecp256k1HdWallet, EncodeObject, OfflineSigner } from "@cosmjs/proto-signing"; import { JsonRpcProvider, JsonRpcSigner } from "@ethersproject/providers"; import TronWeb from "tronweb"; import Long from "long"; import { TronWeb as _TronWeb } from "@oraichain/oraidex-common/build/tronweb"; import { fromUtf8, toUtf8 } from "@cosmjs/encoding"; -import { SigningCosmWasmClient, SigningCosmWasmClientOptions, toBinary } from "@cosmjs/cosmwasm-stargate"; +import { toBinary } from "@cosmjs/cosmwasm-stargate"; import { ibcInfos, oraichain2oraib } from "@oraichain/oraidex-common/build/ibc-info"; import { OraiswapFactoryClient, @@ -45,20 +42,21 @@ import { CWSimulateApp, GenericError, IbcOrder, IbcPacket, SimulateCosmWasmClien import { CwIcs20LatestClient } from "@oraichain/common-contracts-sdk"; import bech32 from "bech32"; import { UniversalSwapConfig, UniversalSwapData, UniversalSwapType } from "../src/types"; +import { deployIcs20Token, deployToken, testSenderAddress } from "./test-common"; +import * as oraidexArtifacts from "@oraichain/oraidex-contracts-build"; +import { readFileSync } from "fs"; +import { UniversalSwapHandler } from "../src/handler"; import { + UniversalSwapHelper, checkBalanceChannelIbc, checkBalanceIBCOraichain, + checkFeeRelayer, + checkFeeRelayerNotOrai, getBalanceIBCOraichain, getIbcInfo, handleSimulateSwap, - simulateSwap, - checkFeeRelayer, - checkFeeRelayerNotOrai + simulateSwap } from "../src/helper"; -import { deployIcs20Token, deployToken, testSenderAddress } from "./test-common"; -import * as oraidexArtifacts from "@oraichain/oraidex-contracts-build"; -import { readFileSync } from "fs"; -import { SigningStargateClientOptions } from "@cosmjs/stargate"; describe("test universal swap handler functions", () => { const client = new SimulateCosmWasmClient({ @@ -440,7 +438,7 @@ describe("test universal swap handler functions", () => { (item) => item.coinGeckoId === fromDenom && item.chainId === fromChainId ); // TODO: run tests without mocking to simulate actual swap logic - jest.spyOn(universalHelper, "simulateSwap").mockResolvedValue({ amount: relayerFeeAmount }); + jest.spyOn(UniversalSwapHelper, "simulateSwap").mockResolvedValue({ amount: relayerFeeAmount }); const result = await checkFeeRelayer({ originalFromToken: originalFromToken as TokenItemType, fromAmount: 1, @@ -462,7 +460,7 @@ describe("test universal swap handler functions", () => { async (fromDenom, mockSimulateAmount, mockRelayerFee, isSufficient) => { const originalFromToken = oraichainTokens.find((item) => item.coinGeckoId === fromDenom); // TODO: run tests without mocking to simulate actual swap - jest.spyOn(universalHelper, "simulateSwap").mockResolvedValue({ amount: mockSimulateAmount }); + jest.spyOn(UniversalSwapHelper, "simulateSwap").mockResolvedValue({ amount: mockSimulateAmount }); const result = await checkFeeRelayerNotOrai({ fromTokenInOrai: originalFromToken as TokenItemType, fromAmount: 1, @@ -528,20 +526,39 @@ describe("test universal swap handler functions", () => { ); it.each([ - ["0x1234", flattenTokens.find((t) => t.chainId === "oraibridge-subnet-2")!, "oraib0x1234"], - ["0x1234", flattenTokens.find((t) => t.chainId !== "oraibridge-subnet-2")!, ""] + ["0x1234", flattenTokens.find((t) => t.chainId !== "oraibridge-subnet-2")!, "0x38", "", ""], + ["0x1234", flattenTokens.find((t) => t.chainId === "oraibridge-subnet-2")!, "0x38", "0x12345", "oraib0x12345"], + ["0x1234", flattenTokens.find((t) => t.chainId === "oraibridge-subnet-2")!, "0x38", "", "oraib0x1234"] ])( "test getIbcMemo should return ibc memo correctly", - (transferAddress: string, toToken: TokenItemType, expectedIbcMemo: string) => { + ( + transferAddress: string, + toToken: TokenItemType, + originalChainId: string, + recipientAddress: string, + expectedIbcMemo: string + ) => { const universalSwap = new FakeUniversalSwapHandler({ ...universalSwapData, originalToToken: toToken }); jest.spyOn(universalSwap, "getTranferAddress").mockReturnValue(transferAddress); - const ibcMemo = universalSwap.getIbcMemo("john doe", "john doe", "john doe", { - chainId: toToken.chainId, - prefix: toToken.prefix! + jest.spyOn(dexCommonHelper, "checkValidateAddressWithNetwork").mockReturnValue({ + isValid: true, + network: originalChainId }); + + const ibcMemo = universalSwap.getIbcMemo( + "john doe", + "john doe", + "john doe", + { + chainId: toToken.chainId, + prefix: toToken.prefix!, + originalChainId: originalChainId as NetworkChainId + }, + recipientAddress + ); expect(ibcMemo).toEqual(expectedIbcMemo); } ); @@ -620,7 +637,7 @@ describe("test universal swap handler functions", () => { async (from: TokenItemType, to: TokenItemType, fromAmount: number, toAmount: string, willThrow: boolean) => { try { jest - .spyOn(universalHelper, "getBalanceIBCOraichain") + .spyOn(UniversalSwapHelper, "getBalanceIBCOraichain") .mockReturnValue(new Promise((resolve) => resolve({ balance: +toAmount }))); checkBalanceIBCOraichain( from, @@ -645,6 +662,8 @@ describe("test universal swap handler functions", () => { ])("test-processUniversalSwap", async (universalSwapType, expectedFunction) => { const fromToken = flattenTokens.find((item) => item.coinGeckoId === "airight" && item.chainId === "0x38")!; const toToken = flattenTokens.find((item) => item.coinGeckoId === "tether" && item.chainId === "0x2b6653dc")!; + const spy = jest.spyOn(UniversalSwapHelper, "addOraiBridgeRoute"); + spy.mockReturnValue({ swapRoute: "", universalSwapType }); const universalSwap = new FakeUniversalSwapHandler({ ...universalSwapData, originalFromToken: fromToken, @@ -656,8 +675,8 @@ describe("test universal swap handler functions", () => { .mockResolvedValue("swapAndTransferToOtherNetworks" as any); jest.spyOn(universalSwap, "swapCosmosToOtherNetwork").mockResolvedValue("swapCosmosToOtherNetwork" as any); jest.spyOn(universalSwap, "transferAndSwap").mockResolvedValue("transferAndSwap" as any); - jest.spyOn(universalHelper, "addOraiBridgeRoute").mockReturnValue({ swapRoute: "", universalSwapType }); const result = await universalSwap.processUniversalSwap(); + expect(spy).toHaveBeenCalled(); expect(result).toEqual(expectedFunction); }); @@ -940,7 +959,7 @@ describe("test universal swap handler functions", () => { try { await universalSwap.combineMsgEvm("0x1234", "T1234"); } catch (error) { - expect(error?.ex?.message).toEqual("Please login keplr!"); + expect(error?.ex?.message).toEqual("Please login cosmos wallet!"); } }); @@ -967,12 +986,12 @@ describe("test universal swap handler functions", () => { [true, false, "2"], [true, true, "2"] ])("test handleSimulateSwap", async (isSupportedNoPoolSwapEvmRes, isEvmSwappableRes, expectedSimulateAmount) => { - const simulateSwapSpy = jest.spyOn(universalHelper, "simulateSwap"); - const simulateSwapEvmSpy = jest.spyOn(universalHelper, "simulateSwapEvm"); + const simulateSwapSpy = jest.spyOn(UniversalSwapHelper, "simulateSwap"); + const simulateSwapEvmSpy = jest.spyOn(UniversalSwapHelper, "simulateSwapEvm"); simulateSwapSpy.mockResolvedValue({ amount: "1" }); simulateSwapEvmSpy.mockResolvedValue({ amount: "2", displayAmount: 2 }); - const isSupportedNoPoolSwapEvmSpy = jest.spyOn(universalHelper, "isSupportedNoPoolSwapEvm"); - const isEvmSwappableSpy = jest.spyOn(universalHelper, "isEvmSwappable"); + const isSupportedNoPoolSwapEvmSpy = jest.spyOn(UniversalSwapHelper, "isSupportedNoPoolSwapEvm"); + const isEvmSwappableSpy = jest.spyOn(UniversalSwapHelper, "isEvmSwappable"); isSupportedNoPoolSwapEvmSpy.mockReturnValue(isSupportedNoPoolSwapEvmRes); isEvmSwappableSpy.mockReturnValue(isEvmSwappableRes); const simulateData = await handleSimulateSwap({ diff --git a/yarn.lock b/yarn.lock index b8dd4397..054b84f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -68,6 +68,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.1.tgz#31c1f66435f2a9c329bb5716a6d6186c516c3742" integrity sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA== +"@babel/compat-data@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a" + integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== + "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.22.0", "@babel/core@^7.22.9", "@babel/core@^7.23.9", "@babel/core@^7.7.5": version "7.24.3" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.3.tgz#568864247ea10fbd4eff04dda1e05f9e2ea985c3" @@ -139,6 +144,21 @@ "@babel/helper-split-export-declaration" "^7.22.6" semver "^6.3.1" +"@babel/helper-create-class-features-plugin@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz#c806f73788a6800a5cfbbc04d2df7ee4d927cce3" + integrity sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.24.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.15", "@babel/helper-create-regexp-features-plugin@^7.22.5": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" @@ -303,6 +323,14 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.1.tgz#1e416d3627393fab1cb5b0f2f1796a100ae9133a" integrity sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg== +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz#6125f0158543fb4edf1c22f322f3db67f21cb3e1" + integrity sha512-qpl6vOOEEzTLLcsuqYYo8yDtrTocmu2xkGvgNebvPjT9DTtfFYGmgDqY+rBYXNlqL4s9qLDn6xkrJv4RxAPiTA== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.1": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz#b645d9ba8c2bc5b7af50f0fe949f9edbeb07c8cf" @@ -552,6 +580,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.0" +"@babel/plugin-transform-block-scoping@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.4.tgz#28f5c010b66fbb8ccdeef853bef1935c434d7012" + integrity sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-class-properties@^7.24.1": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz#bcbf1aef6ba6085cfddec9fc8d58871cf011fc29" @@ -569,6 +604,15 @@ "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-class-static-block" "^7.14.5" +"@babel/plugin-transform-class-static-block@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz#1a4653c0cf8ac46441ec406dece6e9bc590356a4" + integrity sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.4" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-transform-classes@^7.24.1": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz#5bc8fc160ed96378184bc10042af47f50884dcb1" @@ -1036,6 +1080,93 @@ core-js-compat "^3.31.0" semver "^6.3.1" +"@babel/preset-env@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.4.tgz#46dbbcd608771373b88f956ffb67d471dce0d23b" + integrity sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A== + dependencies: + "@babel/compat-data" "^7.24.4" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.4" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.1" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.1" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.1" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.24.1" + "@babel/plugin-syntax-import-attributes" "^7.24.1" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.24.1" + "@babel/plugin-transform-async-generator-functions" "^7.24.3" + "@babel/plugin-transform-async-to-generator" "^7.24.1" + "@babel/plugin-transform-block-scoped-functions" "^7.24.1" + "@babel/plugin-transform-block-scoping" "^7.24.4" + "@babel/plugin-transform-class-properties" "^7.24.1" + "@babel/plugin-transform-class-static-block" "^7.24.4" + "@babel/plugin-transform-classes" "^7.24.1" + "@babel/plugin-transform-computed-properties" "^7.24.1" + "@babel/plugin-transform-destructuring" "^7.24.1" + "@babel/plugin-transform-dotall-regex" "^7.24.1" + "@babel/plugin-transform-duplicate-keys" "^7.24.1" + "@babel/plugin-transform-dynamic-import" "^7.24.1" + "@babel/plugin-transform-exponentiation-operator" "^7.24.1" + "@babel/plugin-transform-export-namespace-from" "^7.24.1" + "@babel/plugin-transform-for-of" "^7.24.1" + "@babel/plugin-transform-function-name" "^7.24.1" + "@babel/plugin-transform-json-strings" "^7.24.1" + "@babel/plugin-transform-literals" "^7.24.1" + "@babel/plugin-transform-logical-assignment-operators" "^7.24.1" + "@babel/plugin-transform-member-expression-literals" "^7.24.1" + "@babel/plugin-transform-modules-amd" "^7.24.1" + "@babel/plugin-transform-modules-commonjs" "^7.24.1" + "@babel/plugin-transform-modules-systemjs" "^7.24.1" + "@babel/plugin-transform-modules-umd" "^7.24.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" + "@babel/plugin-transform-new-target" "^7.24.1" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.1" + "@babel/plugin-transform-numeric-separator" "^7.24.1" + "@babel/plugin-transform-object-rest-spread" "^7.24.1" + "@babel/plugin-transform-object-super" "^7.24.1" + "@babel/plugin-transform-optional-catch-binding" "^7.24.1" + "@babel/plugin-transform-optional-chaining" "^7.24.1" + "@babel/plugin-transform-parameters" "^7.24.1" + "@babel/plugin-transform-private-methods" "^7.24.1" + "@babel/plugin-transform-private-property-in-object" "^7.24.1" + "@babel/plugin-transform-property-literals" "^7.24.1" + "@babel/plugin-transform-regenerator" "^7.24.1" + "@babel/plugin-transform-reserved-words" "^7.24.1" + "@babel/plugin-transform-shorthand-properties" "^7.24.1" + "@babel/plugin-transform-spread" "^7.24.1" + "@babel/plugin-transform-sticky-regex" "^7.24.1" + "@babel/plugin-transform-template-literals" "^7.24.1" + "@babel/plugin-transform-typeof-symbol" "^7.24.1" + "@babel/plugin-transform-unicode-escapes" "^7.24.1" + "@babel/plugin-transform-unicode-property-regex" "^7.24.1" + "@babel/plugin-transform-unicode-regex" "^7.24.1" + "@babel/plugin-transform-unicode-sets-regex" "^7.24.1" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.10.4" + babel-plugin-polyfill-regenerator "^0.6.1" + core-js-compat "^3.31.0" + semver "^6.3.1" + "@babel/preset-flow@^7.13.13", "@babel/preset-flow@^7.22.5": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.24.1.tgz#da7196c20c2d7dd4e98cfd8b192fe53b5eb6f0bb" @@ -1054,7 +1185,7 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-react@^7.22.5": +"@babel/preset-react@^7.22.5", "@babel/preset-react@^7.24.1": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.24.1.tgz#2450c2ac5cc498ef6101a6ca5474de251e33aa95" integrity sha512-eFa8up2/8cZXLIpkafhaADTXSnl7IsUFCYenRWrARBz0/qZwcT0RBXpys0LJU4+WfPoF2ZG6ew6s2V6izMCwRA== @@ -1066,7 +1197,7 @@ "@babel/plugin-transform-react-jsx-development" "^7.22.5" "@babel/plugin-transform-react-pure-annotations" "^7.24.1" -"@babel/preset-typescript@^7.13.0": +"@babel/preset-typescript@^7.13.0", "@babel/preset-typescript@^7.24.1": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz#89bdf13a3149a17b3b2a2c9c62547f06db8845ec" integrity sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ== @@ -2369,6 +2500,13 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/create-cache-key-function@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz#793be38148fab78e65f40ae30c36785f4ad859f0" + integrity sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA== + dependencies: + "@jest/types" "^29.6.3" + "@jest/environment@^29.7.0": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" @@ -2566,7 +2704,7 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.21", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== @@ -4807,7 +4945,7 @@ resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.11.tgz#92fd6d4e2d70bbd4fda438f02310d998db8c7b7c" integrity sha512-0xRFW6K9UZQH2NVC/0pVB0GJXS45lY24f+6XaPBF1YnMHd8A8GoHl7ugyM5yNUTe2AKhSgk5fJV00EJt/XBtdQ== -"@swc/core@^1.3.82": +"@swc/core@^1.3.82", "@swc/core@^1.4.11": version "1.4.11" resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.4.11.tgz#e91f488df9242584cc6f1b034419f8302aeb0c85" integrity sha512-WKEakMZxkVwRdgMN4AMJ9K5nysY8g8npgQPczmjBeNK5In7QEAZAJwnyccrWwJZU0XjVeHn2uj+XbOKdDW17rg== @@ -4831,6 +4969,15 @@ resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== +"@swc/jest@^0.2.36": + version "0.2.36" + resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.36.tgz#2797450a30d28b471997a17e901ccad946fe693e" + integrity sha512-8X80dp81ugxs4a11z1ka43FPhP+/e+mJNXJSxiNYk8gIX/jPBtY4gQTrKu/KIoco8bzKuPI5lUxjfLiGsfvnlw== + dependencies: + "@jest/create-cache-key-function" "^29.7.0" + "@swc/counter" "^0.1.3" + jsonc-parser "^3.2.0" + "@swc/types@^0.1.5": version "0.1.6" resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.6.tgz#2f13f748995b247d146de2784d3eb7195410faba" @@ -6549,7 +6696,7 @@ browserify-zlib@^0.1.4: dependencies: pako "~0.2.0" -browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.21.10, browserslist@^4.21.4, browserslist@^4.22.2, browserslist@^4.23.0: +browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.21.10, browserslist@^4.22.2, browserslist@^4.23.0: version "4.23.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== @@ -7051,7 +7198,7 @@ color-support@^1.1.2, color-support@^1.1.3: resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -colord@^2.9.1: +colord@^2.9.3: version "2.9.3" resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== @@ -7525,10 +7672,10 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -css-declaration-sorter@^6.3.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz#28beac7c20bad7f1775be3a7129d7eae409a3a71" - integrity sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g== +css-declaration-sorter@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz#6dec1c9523bc4a643e088aab8f09e67a54961024" + integrity sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow== css-in-js-utils@^3.1.0: version "3.1.0" @@ -7551,17 +7698,17 @@ css-loader@^6.7.1: postcss-value-parser "^4.2.0" semver "^7.5.4" -css-minimizer-webpack-plugin@^4.1.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-4.2.2.tgz#79f6199eb5adf1ff7ba57f105e3752d15211eb35" - integrity sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA== +css-minimizer-webpack-plugin@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-6.0.0.tgz#eb79947af785467739375faf7fcb8c2dbf4f06dc" + integrity sha512-BLpR9CCDkKvhO3i0oZQgad6v9pCxUuhSc5RT6iUEy9M8hBXi4TJb5vqF2GQ2deqYHmRi3O6IR9hgAZQWg0EBwA== dependencies: - cssnano "^5.1.8" - jest-worker "^29.1.2" - postcss "^8.4.17" - schema-utils "^4.0.0" - serialize-javascript "^6.0.0" - source-map "^0.6.1" + "@jridgewell/trace-mapping" "^0.3.21" + cssnano "^6.0.3" + jest-worker "^29.7.0" + postcss "^8.4.33" + schema-utils "^4.2.0" + serialize-javascript "^6.0.2" css-select@^4.1.3: version "4.3.0" @@ -7574,7 +7721,18 @@ css-select@^4.1.3: domutils "^2.8.0" nth-check "^2.0.1" -css-tree@^1.1.2, css-tree@^1.1.3: +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-tree@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== @@ -7582,7 +7740,23 @@ css-tree@^1.1.2, css-tree@^1.1.3: mdn-data "2.0.14" source-map "^0.6.1" -css-what@^6.0.1: +css-tree@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + +css-tree@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032" + integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA== + dependencies: + mdn-data "2.0.28" + source-map-js "^1.0.1" + +css-what@^6.0.1, css-what@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== @@ -7592,61 +7766,61 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssnano-preset-default@^5.2.14: - version "5.2.14" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz#309def4f7b7e16d71ab2438052093330d9ab45d8" - integrity sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A== - dependencies: - css-declaration-sorter "^6.3.1" - cssnano-utils "^3.1.0" - postcss-calc "^8.2.3" - postcss-colormin "^5.3.1" - postcss-convert-values "^5.1.3" - postcss-discard-comments "^5.1.2" - postcss-discard-duplicates "^5.1.0" - postcss-discard-empty "^5.1.1" - postcss-discard-overridden "^5.1.0" - postcss-merge-longhand "^5.1.7" - postcss-merge-rules "^5.1.4" - postcss-minify-font-values "^5.1.0" - postcss-minify-gradients "^5.1.1" - postcss-minify-params "^5.1.4" - postcss-minify-selectors "^5.2.1" - postcss-normalize-charset "^5.1.0" - postcss-normalize-display-values "^5.1.0" - postcss-normalize-positions "^5.1.1" - postcss-normalize-repeat-style "^5.1.1" - postcss-normalize-string "^5.1.0" - postcss-normalize-timing-functions "^5.1.0" - postcss-normalize-unicode "^5.1.1" - postcss-normalize-url "^5.1.0" - postcss-normalize-whitespace "^5.1.1" - postcss-ordered-values "^5.1.3" - postcss-reduce-initial "^5.1.2" - postcss-reduce-transforms "^5.1.0" - postcss-svgo "^5.1.0" - postcss-unique-selectors "^5.1.1" - -cssnano-utils@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" - integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== +cssnano-preset-default@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz#adf4b89b975aa775f2750c89dbaf199bbd9da35e" + integrity sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg== + dependencies: + browserslist "^4.23.0" + css-declaration-sorter "^7.2.0" + cssnano-utils "^4.0.2" + postcss-calc "^9.0.1" + postcss-colormin "^6.1.0" + postcss-convert-values "^6.1.0" + postcss-discard-comments "^6.0.2" + postcss-discard-duplicates "^6.0.3" + postcss-discard-empty "^6.0.3" + postcss-discard-overridden "^6.0.2" + postcss-merge-longhand "^6.0.5" + postcss-merge-rules "^6.1.1" + postcss-minify-font-values "^6.1.0" + postcss-minify-gradients "^6.0.3" + postcss-minify-params "^6.1.0" + postcss-minify-selectors "^6.0.4" + postcss-normalize-charset "^6.0.2" + postcss-normalize-display-values "^6.0.2" + postcss-normalize-positions "^6.0.2" + postcss-normalize-repeat-style "^6.0.2" + postcss-normalize-string "^6.0.2" + postcss-normalize-timing-functions "^6.0.2" + postcss-normalize-unicode "^6.1.0" + postcss-normalize-url "^6.0.2" + postcss-normalize-whitespace "^6.0.2" + postcss-ordered-values "^6.0.2" + postcss-reduce-initial "^6.1.0" + postcss-reduce-transforms "^6.0.2" + postcss-svgo "^6.0.3" + postcss-unique-selectors "^6.0.4" + +cssnano-utils@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-4.0.2.tgz#56f61c126cd0f11f2eef1596239d730d9fceff3c" + integrity sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ== -cssnano@^5.1.8: - version "5.1.15" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.15.tgz#ded66b5480d5127fcb44dac12ea5a983755136bf" - integrity sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw== +cssnano@^6.0.3: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-6.1.2.tgz#4bd19e505bd37ee7cf0dc902d3d869f6d79c66b8" + integrity sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA== dependencies: - cssnano-preset-default "^5.2.14" - lilconfig "^2.0.3" - yaml "^1.10.2" + cssnano-preset-default "^6.1.2" + lilconfig "^3.1.1" -csso@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== +csso@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6" + integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ== dependencies: - css-tree "^1.1.2" + css-tree "~2.2.0" csstype@^3.0.2, csstype@^3.1.2: version "3.1.3" @@ -7973,7 +8147,16 @@ dom-serializer@^1.0.1: domhandler "^4.2.0" entities "^2.0.0" -domelementtype@^2.0.1, domelementtype@^2.2.0: +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== @@ -7985,6 +8168,13 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + domutils@^2.5.2, domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" @@ -7994,6 +8184,15 @@ domutils@^2.5.2, domutils@^2.8.0: domelementtype "^2.2.0" domhandler "^4.2.0" +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + dot-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" @@ -8207,6 +8406,11 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +entities@^4.2.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + env-paths@^2.2.0, env-paths@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" @@ -11101,7 +11305,7 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^29.1.2, jest-worker@^29.7.0: +jest-worker@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== @@ -11513,10 +11717,10 @@ libsodium@^0.7.13: resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.13.tgz#230712ec0b7447c57b39489c48a4af01985fb393" integrity sha512-mK8ju0fnrKXXfleL53vtp9xiPq5hKM0zbDQtcxQIsSmxNgSxqCj6R7Hl9PkrNe2j29T4yoDaF7DJLK9/i5iWUw== -lilconfig@^2.0.3: - version "2.1.0" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" - integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== +lilconfig@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.1.tgz#9d8a246fa753106cfc205fd2d77042faca56e5e3" + integrity sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ== lines-and-columns@^1.1.6: version "1.2.4" @@ -11887,6 +12091,16 @@ mdn-data@2.0.14: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== +mdn-data@2.0.28: + version "2.0.28" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba" + integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g== + +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -12567,11 +12781,6 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - npm-bundled@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" @@ -13480,101 +13689,101 @@ possible-typed-array-names@^1.0.0: resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== -postcss-calc@^8.2.3: - version "8.2.4" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" - integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== +postcss-calc@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-9.0.1.tgz#a744fd592438a93d6de0f1434c572670361eb6c6" + integrity sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ== dependencies: - postcss-selector-parser "^6.0.9" + postcss-selector-parser "^6.0.11" postcss-value-parser "^4.2.0" -postcss-colormin@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.1.tgz#86c27c26ed6ba00d96c79e08f3ffb418d1d1988f" - integrity sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ== +postcss-colormin@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-6.1.0.tgz#076e8d3fb291fbff7b10e6b063be9da42ff6488d" + integrity sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw== dependencies: - browserslist "^4.21.4" + browserslist "^4.23.0" caniuse-api "^3.0.0" - colord "^2.9.1" + colord "^2.9.3" postcss-value-parser "^4.2.0" -postcss-convert-values@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz#04998bb9ba6b65aa31035d669a6af342c5f9d393" - integrity sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA== +postcss-convert-values@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz#3498387f8efedb817cbc63901d45bd1ceaa40f48" + integrity sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w== dependencies: - browserslist "^4.21.4" + browserslist "^4.23.0" postcss-value-parser "^4.2.0" -postcss-discard-comments@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696" - integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ== +postcss-discard-comments@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz#e768dcfdc33e0216380623652b0a4f69f4678b6c" + integrity sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw== -postcss-discard-duplicates@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" - integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== +postcss-discard-duplicates@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz#d121e893c38dc58a67277f75bb58ba43fce4c3eb" + integrity sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw== -postcss-discard-empty@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" - integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== +postcss-discard-empty@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz#ee39c327219bb70473a066f772621f81435a79d9" + integrity sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ== -postcss-discard-overridden@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" - integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== +postcss-discard-overridden@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz#4e9f9c62ecd2df46e8fdb44dc17e189776572e2d" + integrity sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ== -postcss-merge-longhand@^5.1.7: - version "5.1.7" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz#24a1bdf402d9ef0e70f568f39bdc0344d568fb16" - integrity sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ== +postcss-merge-longhand@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz#ba8a8d473617c34a36abbea8dda2b215750a065a" + integrity sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w== dependencies: postcss-value-parser "^4.2.0" - stylehacks "^5.1.1" + stylehacks "^6.1.1" -postcss-merge-rules@^5.1.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz#2f26fa5cacb75b1402e213789f6766ae5e40313c" - integrity sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g== +postcss-merge-rules@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz#7aa539dceddab56019469c0edd7d22b64c3dea9d" + integrity sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ== dependencies: - browserslist "^4.21.4" + browserslist "^4.23.0" caniuse-api "^3.0.0" - cssnano-utils "^3.1.0" - postcss-selector-parser "^6.0.5" + cssnano-utils "^4.0.2" + postcss-selector-parser "^6.0.16" -postcss-minify-font-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" - integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== +postcss-minify-font-values@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz#a0e574c02ee3f299be2846369211f3b957ea4c59" + integrity sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg== dependencies: postcss-value-parser "^4.2.0" -postcss-minify-gradients@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" - integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== +postcss-minify-gradients@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz#ca3eb55a7bdb48a1e187a55c6377be918743dbd6" + integrity sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q== dependencies: - colord "^2.9.1" - cssnano-utils "^3.1.0" + colord "^2.9.3" + cssnano-utils "^4.0.2" postcss-value-parser "^4.2.0" -postcss-minify-params@^5.1.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz#c06a6c787128b3208b38c9364cfc40c8aa5d7352" - integrity sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw== +postcss-minify-params@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz#54551dec77b9a45a29c3cb5953bf7325a399ba08" + integrity sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA== dependencies: - browserslist "^4.21.4" - cssnano-utils "^3.1.0" + browserslist "^4.23.0" + cssnano-utils "^4.0.2" postcss-value-parser "^4.2.0" -postcss-minify-selectors@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6" - integrity sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg== +postcss-minify-selectors@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz#197f7d72e6dd19eed47916d575d69dc38b396aff" + integrity sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ== dependencies: - postcss-selector-parser "^6.0.5" + postcss-selector-parser "^6.0.16" postcss-modules-extract-imports@^3.0.0: version "3.0.0" @@ -13604,93 +13813,92 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" -postcss-normalize-charset@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" - integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== +postcss-normalize-charset@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz#1ec25c435057a8001dac942942a95ffe66f721e1" + integrity sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ== -postcss-normalize-display-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" - integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== +postcss-normalize-display-values@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz#54f02764fed0b288d5363cbb140d6950dbbdd535" + integrity sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg== dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-positions@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz#ef97279d894087b59325b45c47f1e863daefbb92" - integrity sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg== +postcss-normalize-positions@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz#e982d284ec878b9b819796266f640852dbbb723a" + integrity sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q== dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-repeat-style@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz#e9eb96805204f4766df66fd09ed2e13545420fb2" - integrity sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g== +postcss-normalize-repeat-style@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz#f8006942fd0617c73f049dd8b6201c3a3040ecf3" + integrity sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ== dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-string@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" - integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== +postcss-normalize-string@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz#e3cc6ad5c95581acd1fc8774b309dd7c06e5e363" + integrity sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ== dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-timing-functions@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" - integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== +postcss-normalize-timing-functions@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz#40cb8726cef999de984527cbd9d1db1f3e9062c0" + integrity sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA== dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-unicode@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz#f67297fca3fea7f17e0d2caa40769afc487aa030" - integrity sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA== +postcss-normalize-unicode@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz#aaf8bbd34c306e230777e80f7f12a4b7d27ce06e" + integrity sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg== dependencies: - browserslist "^4.21.4" + browserslist "^4.23.0" postcss-value-parser "^4.2.0" -postcss-normalize-url@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" - integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== +postcss-normalize-url@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz#292792386be51a8de9a454cb7b5c58ae22db0f79" + integrity sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ== dependencies: - normalize-url "^6.0.1" postcss-value-parser "^4.2.0" -postcss-normalize-whitespace@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" - integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== +postcss-normalize-whitespace@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz#fbb009e6ebd312f8b2efb225c2fcc7cf32b400cd" + integrity sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q== dependencies: postcss-value-parser "^4.2.0" -postcss-ordered-values@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz#b6fd2bd10f937b23d86bc829c69e7732ce76ea38" - integrity sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ== +postcss-ordered-values@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz#366bb663919707093451ab70c3f99c05672aaae5" + integrity sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q== dependencies: - cssnano-utils "^3.1.0" + cssnano-utils "^4.0.2" postcss-value-parser "^4.2.0" -postcss-reduce-initial@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz#798cd77b3e033eae7105c18c9d371d989e1382d6" - integrity sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg== +postcss-reduce-initial@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz#4401297d8e35cb6e92c8e9586963e267105586ba" + integrity sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw== dependencies: - browserslist "^4.21.4" + browserslist "^4.23.0" caniuse-api "^3.0.0" -postcss-reduce-transforms@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" - integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== +postcss-reduce-transforms@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz#6fa2c586bdc091a7373caeee4be75a0f3e12965d" + integrity sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA== dependencies: postcss-value-parser "^4.2.0" -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: +postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.16, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: version "6.0.16" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz#3b88b9f5c5abd989ef4e2fc9ec8eedd34b20fb04" integrity sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw== @@ -13698,27 +13906,27 @@ postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector cssesc "^3.0.0" util-deprecate "^1.0.2" -postcss-svgo@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" - integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== +postcss-svgo@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-6.0.3.tgz#1d6e180d6df1fa8a3b30b729aaa9161e94f04eaa" + integrity sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g== dependencies: postcss-value-parser "^4.2.0" - svgo "^2.7.0" + svgo "^3.2.0" -postcss-unique-selectors@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" - integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== +postcss-unique-selectors@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz#983ab308896b4bf3f2baaf2336e14e52c11a2088" + integrity sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg== dependencies: - postcss-selector-parser "^6.0.5" + postcss-selector-parser "^6.0.16" postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.4.17, postcss@^8.4.33: +postcss@^8.4.33: version "8.4.38" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== @@ -14627,7 +14835,7 @@ schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0: ajv "^6.12.5" ajv-keywords "^3.5.2" -schema-utils@^4.0.0: +schema-utils@^4.0.0, schema-utils@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== @@ -14722,7 +14930,7 @@ serialize-javascript@6.0.0: dependencies: randombytes "^2.1.0" -serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: +serialize-javascript@^6.0.1, serialize-javascript@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== @@ -14979,7 +15187,7 @@ sort-keys@^2.0.0: dependencies: is-plain-obj "^1.0.0" -source-map-js@^1.2.0: +source-map-js@^1.0.1, source-map-js@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== @@ -15084,11 +15292,6 @@ ssri@^9.0.0, ssri@^9.0.1: dependencies: minipass "^3.1.1" -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - stack-generator@^2.0.5: version "2.0.10" resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.10.tgz#8ae171e985ed62287d4f1ed55a1633b3fb53bb4d" @@ -15309,13 +15512,13 @@ style-loader@^3.3.1: resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.4.tgz#f30f786c36db03a45cbd55b6a70d930c479090e7" integrity sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w== -stylehacks@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.1.tgz#7934a34eb59d7152149fa69d6e9e56f2fc34bcc9" - integrity sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw== +stylehacks@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-6.1.1.tgz#543f91c10d17d00a440430362d419f79c25545a6" + integrity sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg== dependencies: - browserslist "^4.21.4" - postcss-selector-parser "^6.0.4" + browserslist "^4.23.0" + postcss-selector-parser "^6.0.16" stylis@^4.3.0: version "4.3.1" @@ -15348,18 +15551,18 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -svgo@^2.7.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== +svgo@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.2.0.tgz#7a5dff2938d8c6096e00295c2390e8e652fa805d" + integrity sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ== dependencies: "@trysound/sax" "0.2.0" commander "^7.2.0" - css-select "^4.1.3" - css-tree "^1.1.3" - csso "^4.2.0" + css-select "^5.1.0" + css-tree "^2.3.1" + css-what "^6.1.0" + csso "^5.0.5" picocolors "^1.0.0" - stable "^0.1.8" swc-loader@^0.2.3: version "0.2.6" @@ -15497,18 +15700,7 @@ tempy@^1.0.1: type-fest "^0.16.0" unique-string "^2.0.0" -terser-webpack-plugin@5.3.6: - version "5.3.6" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz#5590aec31aa3c6f771ce1b1acca60639eab3195c" - integrity sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ== - dependencies: - "@jridgewell/trace-mapping" "^0.3.14" - jest-worker "^27.4.5" - schema-utils "^3.1.1" - serialize-javascript "^6.0.0" - terser "^5.14.1" - -terser-webpack-plugin@^5.3.1, terser-webpack-plugin@^5.3.10, terser-webpack-plugin@^5.3.7: +terser-webpack-plugin@5.3.10, terser-webpack-plugin@^5.3.1, terser-webpack-plugin@^5.3.10, terser-webpack-plugin@^5.3.7: version "5.3.10" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== @@ -15519,7 +15711,7 @@ terser-webpack-plugin@^5.3.1, terser-webpack-plugin@^5.3.10, terser-webpack-plug serialize-javascript "^6.0.1" terser "^5.26.0" -terser@^5.10.0, terser@^5.14.1, terser@^5.26.0: +terser@^5.10.0, terser@^5.26.0: version "5.30.0" resolved "https://registry.yarnpkg.com/terser/-/terser-5.30.0.tgz#64cb2af71e16ea3d32153f84d990f9be0cdc22bf" integrity sha512-Y/SblUl5kEyEFzhMAQdsxVHh+utAxd4IuRNJzKywY/4uzSogh3G219jqbDDxYu4MXO9CzY3tSEqmZvW6AoEDJw== @@ -16668,7 +16860,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.10.0, yaml@^1.10.2: +yaml@^1.10.0: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==