├── .editorconfig
├── .gitignore
├── .node-version
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── src
├── addresses.ts
├── api.ts
├── cctp.ts
├── evm
│ ├── ERC20Artifact.ts
│ ├── HCDepositInitiatorArtifact.ts
│ ├── MayanCircleArtifact.ts
│ ├── MayanFastMctpArtifact.ts
│ ├── MayanForwarderArtifact.ts
│ ├── MayanMonoChainArtifact.ts
│ ├── MayanSwapArtifact.ts
│ ├── MayanSwiftArtifact.ts
│ ├── ShuttleArtifact.ts
│ ├── evmFastMctp.ts
│ ├── evmHyperCore.ts
│ ├── evmMctp.ts
│ ├── evmMonoChain.ts
│ ├── evmShuttle.ts
│ ├── evmSwap.ts
│ ├── evmSwift.ts
│ └── index.ts
├── index.ts
├── solana
│ ├── index.ts
│ ├── jupiter.ts
│ ├── solanaHyperCore.ts
│ ├── solanaMctp.ts
│ ├── solanaMonoChain.ts
│ ├── solanaSwap.ts
│ ├── solanaSwift.ts
│ └── utils.ts
├── sui
│ ├── index.ts
│ ├── suiHyperCore.ts
│ ├── suiMctp.ts
│ ├── suiSwap.ts
│ └── utils.ts
├── types.ts
├── utils.ts
└── wormhole.ts
├── tsconfig.json
└── tsup.config.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = tab
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | # The JSON files contain newlines inconsistently
13 | [*.json]
14 | insert_final_newline = ignore
15 |
16 | [*.md]
17 | trim_trailing_whitespace = false
18 |
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 | .idea
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Optional REPL history
57 | .node_repl_history
58 |
59 | # Output of 'npm pack'
60 | *.tgz
61 |
62 | # Yarn Integrity file
63 | .yarn-integrity
64 |
65 | # dotenv environment variables file
66 | .env
67 | .env.test
68 |
69 | # parcel-bundler cache (https://parceljs.org/)
70 | .cache
71 |
72 | # next.js build output
73 | .next
74 |
75 | # nuxt.js build output
76 | .nuxt
77 |
78 | # vuepress build output
79 | .vuepress/dist
80 |
81 | # Serverless directories
82 | .serverless/
83 |
84 | # FuseBox cache
85 | .fusebox/
86 |
87 | # DynamoDB Local files
88 | .dynamodb/
89 | dist
90 | .rts2_cache_*
91 | schema.json
92 | _gen
93 | lib
94 |
--------------------------------------------------------------------------------
/.node-version:
--------------------------------------------------------------------------------
1 | 18
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2019-2020 Hugo Di Francesco
3 |
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
21 | OR OTHER DEALINGS IN THE SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Mayan Cross-Chain Swap SDK
3 | A minimal package for sending cross-chain swap transactions
4 |
5 | ## Installation:
6 |
7 | ```bash
8 | npm install --save @mayanfinance/swap-sdk
9 | ```
10 |
11 | ## Usage:
12 |
13 | Import the necessary functions and models:
14 |
15 | ```javascript
16 | import { fetchQuote, swapFromEvm, swapFromSolana, Quote, createSwapFromSuiMoveCalls } from '@mayanfinance/swap-sdk'
17 | ```
18 |
19 | Then you will need to get a quote:
20 |
21 | ### Getting Quote:
22 | ```javascript
23 | const quotes = await fetchQuote({
24 | amountIn64: "250000000", // if fromToken is USDC means 250 USDC
25 | fromToken: fromToken.contract,
26 | toToken: toToken.contract,
27 | fromChain: "avalanche",
28 | toChain: "solana",
29 | slippageBps: "auto",
30 | gasDrop: 0.04, // optional
31 | referrer: "YOUR SOLANA WALLET ADDRESS", // optional
32 | referrerBps: 5, // optional
33 | });
34 | ```
35 | > `slippageBps` can either be a specific basis point number or the string **"auto"**. When set to "auto", the system determines the safest slippage based on the input and output tokens.
36 | You can also provide slippageBps directly as a number in basis points; for example, 300 means 3%. Regardless of whether you pass "auto" or a basis point number, the `slippageBps` field in the quote response will always be returned as a **basis point number**.
37 |
38 | > see the list of supported chains [here](./src/types.ts#L13).
39 |
40 | You can get the list of supported tokens using [Tokens API](https://price-api.mayan.finance/swagger/)
41 |
42 | #### Gas on destination:
43 | To enable [Gas on destination](https://docs.mayan.finance/dapp/gas-on-destination) set the gasDrop param to the amount of native token (e.g. ETH, BNB..) you want to receive on the destination chain.
44 |
45 |
46 | ```
47 | Maximum supported amount of gasDrop for each destination chain:
48 |
49 | ethereum: 0.05 ETH
50 | bsc: 0.02 BNB
51 | polygon: 0.2 MATIC
52 | avalanche: 0.2 AVAX
53 | solana: 0.2 SOL
54 | arbitrum: 0.01 ETH
55 | optimism: 0.01 ETH
56 | unichain: 0.01 ETH
57 | base: 0.01 ETH
58 | ```
59 |
60 | #### Referrer fee:
61 | > If you want to receive [referrer fee](https://docs.mayan.finance/integration/referral), set the `referrer` param to your wallet address.
62 |
63 | #### Slippage:
64 | > Slippage is in bps (basis points), so 300 means "up to three percent slippage".
65 |
66 |
67 | After you get the quote, you can build and send the swap transaction:
68 |
69 | ### Bridge from Solana:
70 |
71 | ```javascript
72 | swapTrx = await swapFromSolana(quotes[0], originWalletAddress, destinationWalletAddress, referrerAddresses, signSolanaTransaction, solanaConnection)
73 | ```
74 |
75 |
76 | `referrerAddresses` is an optional object with two keys `evm` and `solana` that contains the referrer addresses for each network type.
77 |
78 | example:
79 |
80 | ```javascript
81 | {
82 | evm: "YOUR EVM WALLET",
83 | solana: "YOUR SOLANA WALLET",
84 | sui: "YOUR SUI WALLET"
85 | }
86 | ```
87 |
88 |
89 | If you need more control over the transaction and manually send the trx you can use `createSwapFromSolanaInstructions` function to build the solana instruction.
90 |
91 |
92 | ### Bridge from EVM:
93 |
94 | ```javascript
95 | swapTrx = await swapFromEvm(quotes[0], destinationWalletAddress, referrerAddress, provider, signer, permit?)
96 | ```
97 |
98 | #### ERC20 Allowance
99 |
100 | * If you want to initiate a swap using an ERC20 token as the input, ensure that you have already approved sufficient allowance for the Mayan Forwarder contract. The Forwarder's address can be accessed via `addresses.MAYAN_FORWARDER_CONTRACT`.
101 |
102 |
103 | * Alternatively, the user can sign a permit message ([EIP-2612](https://eips.ethereum.org/EIPS/eip-2612)). The permit parameter is optional; you can pass the permit object to the function if the input token supports the permit standard. The permit object should contain the following fields:
104 |
105 | ```javascript
106 | {
107 | value: bigint,
108 | deadline: number,
109 | v: number,
110 | r: string,
111 | s: string,
112 | }
113 | ```
114 |
115 |
116 | ### Bridge from Sui
117 | The `createSwapFromSuiMoveCalls` function returns a Transaction instance containing all the required Move calls. This transaction should then be signed by the user's wallet and broadcast to the Sui network.
118 |
119 | ```javascript
120 | const bridgeFromSuiMoveCalls = await createSwapFromSuiMoveCalls(
121 | quote, // Quote
122 | originWalletAddress, // string
123 | destinationWalletAddress, // string
124 | referrerAddresses, // Optional(ReferrerAddresses)
125 | customPayload, // Optional(Uint8Array | Buffer)
126 | suiClient, // SuiClient
127 | options, // Optional(ComposableSuiMoveCallsOptions)
128 | );
129 |
130 | await suiClient.signAndExecuteTransaction({
131 | signer: suiKeypair,
132 | transaction: bridgeFromSuiMoveCalls,
133 | });
134 | ```
135 |
136 | #### Composability on Move Calls and Input Coin
137 |
138 | The SDK offers composability for advanced use cases where you want to integrate bridge Move calls into an existing Sui transaction or use a specific coin as the input for bridging.
139 |
140 | - **Custom Move Calls**: To compose the bridge logic into an existing transaction, pass your transaction through the `builtTransaction` parameter. The bridge Move calls will be appended, allowing you to sign and send the combined transaction.
141 |
142 | - **Custom Input Coin**: If you'd like to use a specific coin (e.g., one returned from earlier Move calls) as the input for the bridge, provide it via the `inputCoin` parameter.
143 |
144 | ```javascript
145 | type ComposableSuiMoveCallsOptions = {
146 | builtTransaction?: SuiTransaction;
147 | inputCoin?: SuiFunctionParameter;
148 | }
149 | ```
150 |
151 |
152 | ### Depositing on HyperCore (Hyperliquid Core) as a Destination
153 |
154 |
155 | To deposit into HyperCore, start by fetching a quote as described earlier, just set the `toChain` parameter to `hypercore`. Then, depending on the source chain, use the appropriate transaction-building method, also covered above.
156 |
157 | The key difference when depositing **USDC on HyperCore** is that you must pass a `usdcPermitSignature` in the options object when building the transaction.
158 |
159 | You can generate this signature using the `getHyperCoreUSDCDepositPermitParams` helper function as shown below:
160 |
161 | ```javascript
162 | import { getHyperCoreUSDCDepositPermitParams } from '@mayanfinance/swap-sdk';
163 |
164 | const arbitrumProvider = new ethers.providers.JsonRpcProvider('ARBITRUM_RPC_URL');
165 | const arbDestUserWallet = new ethers.Wallet('USER_ARBITRUM_WALLET_PRIVATE_KEY', arbitrumProvider);
166 |
167 | const { value, domain, types } = await getHyperCoreUSDCDepositPermitParams(
168 | quote,
169 | userDestAddress,
170 | arbitrumProvider
171 | );
172 |
173 | const permitSignature = await arbDestUserWallet.signTypedData(domain, types, value);
174 | ```
175 |
176 |
177 | #### Gasless Transaction:
178 | > If the selected quote's `gasless` parameter is set to true (`quote.gasless == true`), the return value of the `swapFromEvm` function will be the order hash of the `string` type. This hash can be queried on the Mayan Explorer API, similar to a transaction hash.
179 |
180 |
181 |
182 |
183 | If you need to get the transaction payload and send it manually, you can use `getSwapFromEvmTxPayload` function to build the EVM transaction payload.
184 |
185 | #### Contract Level Integration:
186 | >If you aim to integrate the Mayan protocol at the contract level, you can use the `_forwarder` object returned from the `getSwapFromEvmTxPayload`. It contains the method name and parameters for a contract level method call.
187 |
188 | ### Tracking:
189 | To track the progress of swaps, you can use [Mayan Explorer API](https://explorer-api.mayan.finance/swagger/#/default/SwapDetailsController_getSwapByTrxHash) by passing the transaction hash of the swap transaction.
190 |
191 |
192 | The response contains a lot of info about the swap but the important field is `clientStatus` which can be one of the following values:
193 |
194 | - `INPROGRESS` - the swap is being processed
195 | - `COMPLETED` - the swap is completed
196 | - `REFUNDED` - the swap has refunded
197 |
198 |
199 |
200 | ## 📱 React Native Support (Solana Mobile SDK):
201 |
202 | You can also use this SDK in your react native app:
203 |
204 | ```javascript
205 | import { transact, Web3MobileWallet } from '@solana-mobile/mobile-wallet-adapter-protocol-web3js';
206 | ```
207 |
208 | For swaps from solana after importing the above functions from Solana Mobile SDK you have to pass a callback function that calls `transact` function as the `signSolanaTransaction` parameter of `swapFromSolana` function:
209 |
210 |
211 | ```javascript
212 | const signSolanaTransaction = useCallback(
213 | async (tx: Transaction) => {
214 | return await transact(async (wallet: Web3MobileWallet) => {
215 | authorizeSession(wallet);
216 | const signedTransactions = await wallet.signTransactions({
217 | transactions: [tx],
218 | });
219 |
220 | return signedTransactions[0];
221 | });
222 | },
223 | [authorizeSession],
224 | );
225 | ```
226 |
227 | For swaps from EVM you can use `useWalletConnectModal` hook from [WalletConnet](https://github.com/WalletConnect/modal-react-native) to get the provider and pass it to `swapFromEvm` function as the `signer`:
228 |
229 | ```javascript
230 | import {useWalletConnectModal} from '@walletconnect/modal-react-native';
231 | ...
232 | const { provider: evmWalletProvider} =
233 | useWalletConnectModal();
234 | ...
235 | const web3Provider = new ethers.providers.Web3Provider(
236 | evmWalletProvider,
237 | );
238 | const signer = web3Provider.getSigner(0);
239 | ```
240 |
241 | To learn more about how to use Mayan SDK in a react-native project, you can check [this scaffold](https://github.com/mayan-finance/react-native-scaffold).
242 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@mayanfinance/swap-sdk",
3 | "description": "A SDK to swap with Mayan",
4 | "version": "11.4.0",
5 | "source": "src/main.ts",
6 | "main": "dist/index.js",
7 | "module": "dist/index.esm.min.js",
8 | "unpkg": "dist/index.iife.min.js",
9 | "types": "dist/index.d.ts",
10 | "homepage": "https://github.com/mayan-finance/swap-sdk#readme",
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/mayan-finance/swap-sdk.git"
14 | },
15 | "keywords": [
16 | "mayan",
17 | "mayanfinance",
18 | "mayansdk",
19 | "swap-sdk"
20 | ],
21 | "exports": {
22 | ".": {
23 | "require": "./dist/index.js",
24 | "import": "./dist/index.mjs",
25 | "types": "./dist/index.d.ts"
26 | }
27 | },
28 | "files": [
29 | "dist/**/*",
30 | "LICENSE",
31 | "README.md"
32 | ],
33 | "scripts": {
34 | "build": "tsup --clean && tsup --minify",
35 | "watch": "tsup --watch",
36 | "lint": "prettier src -c",
37 | "format": "prettier src --write",
38 | "prepack": "npm run build",
39 | "release": "npm run prepack && npx np --no-tests"
40 | },
41 | "devDependencies": {
42 | "prettier": "^2.6.2",
43 | "tsup": "^6.7.0",
44 | "typescript": "^5.8.3"
45 | },
46 | "prettier": {
47 | "singleQuote": true
48 | },
49 | "license": "MIT",
50 | "publishConfig": {
51 | "registry": "https://registry.npmjs.org",
52 | "access": "public"
53 | },
54 | "author": "Jiri",
55 | "dependencies": {
56 | "@mysten/sui": "^1.34.0",
57 | "@noble/hashes": "1.8.0",
58 | "@solana/buffer-layout": "^4 || ^3",
59 | "@solana/web3.js": "^1.87.6",
60 | "bs58": "^6.0.0",
61 | "cross-fetch": "^3.1.5",
62 | "ethers": "^6",
63 | "js-sha3": "^0.8.0"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/addresses.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | "MAYAN_FORWARDER_CONTRACT": "0x337685fdaB40D39bd02028545a4FfA7D287cC3E2",
3 | "MAYAN_PROGRAM_ID": "FC4eXxkyrMPTjiYUpp4EAnkmwMbQyZ6NDCh1kfLn6vsf",
4 | "AUCTION_PROGRAM_ID": "8QJmxZcEzwuYmCPy6XqgN2sHcYCcFq6AEfBMJZZuLo5a",
5 | "MCTP_PROGRAM_ID": "dkpZqrxHFrhziEMQ931GLtfy11nFkCsfMftH9u6QwBU",
6 | "SWIFT_PROGRAM_ID": "BLZRi6frs4X4DNLw56V4EXai1b6QVESN1BhHBTYM9VcY",
7 | "FEE_MANAGER_PROGRAM_ID": "5VtQHnhs2pfVEr68qQsbTRwKh4JV5GTu9mBHgHFxpHeQ",
8 | "WORMHOLE_PROGRAM_ID": "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth",
9 | "WORMHOLE_SHIM_POST_MESSAGE_PROGRAM_ID": "EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX",
10 | "CCTP_CORE_PROGRAM_ID": "CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd",
11 | "CCTP_TOKEN_PROGRAM_ID": "CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3",
12 | "TOKEN_PROGRAM_ID": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
13 | "TOKEN_2022_PROGRAM_ID": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
14 | "ASSOCIATED_TOKEN_PROGRAM_ID": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
15 | "SPL_UTILS_PROGRAM_ID": "B96dV3Luxzo6SokJx3xt8i5y8Mb7HRR6Eec8hCjJDT69",
16 | "LOOKUP_TABLE": "Ff3yi1meWQQ19VPZMzGg6H8JQQeRudiV7QtVtyzJyoht",
17 | "SUI_MCTP_STATE": "0xb787fe0f7530b4fd2162fa0cc92f4f6c5a97c54b4c5c55eb04ab29f4b803ac9c",
18 | "SUI_MCTP_FEE_MANAGER_STATE": "0xa1b4a96ce93d36dd0bbce0adc39533a07d2f32928918c80cd6fe7868320978f2",
19 | "SUI_CCTP_CORE_PACKAGE_ID": "0x08d87d37ba49e785dde270a83f8e979605b03dc552b5548f26fdf2f49bf7ed1b",
20 | "SUI_CCTP_CORE_STATE": "0xf68268c3d9b1df3215f2439400c1c4ea08ac4ef4bb7d6f3ca6a2a239e17510af",
21 | "SUI_CCTP_TOKEN_PACKAGE_ID": "0x2aa6c5d56376c371f88a6cc42e852824994993cb9bab8d3e6450cbe3cb32b94e",
22 | "SUI_CCTP_TOKEN_STATE": "0x45993eecc0382f37419864992c12faee2238f5cfe22b98ad3bf455baf65c8a2f",
23 | "SUI_WORMHOLE_PACKAGE_ID": "0x5306f64e312b581766351c07af79c72fcb1cd25147157fdc2f8ad76de9a3fb6a",
24 | "SUI_WORMHOLE_STATE": "0xaeab97f96cf9877fee2883315d459552b2b921edc16d7ceac6eab944dd88919c",
25 | "SUI_LOGGER_PACKAGE_ID": "0x05680e9030c147b413a489f7891273acc221d49bd061c433e5771bc170fc37ac",
26 | "EXPLORER_URL": "https://explorer-api.mayan.finance/v3",
27 | "PRICE_URL": "https://price-api.mayan.finance/v3",
28 | "RELAYER_URL": "https://relayer-api.mayan.finance/v3",
29 | "GAS_ESTIMATE_URL": "https://gas-estimate.mayan.finance/v2",
30 | "HC_ARBITRUM_BRIDGE": "0x2df1c51e09aecf9cacb7bc98cb1742757f163df7",
31 | "HC_ARBITRUM_DEPOSIT_PROCESSOR": "0xdDd77e62C848C3334f1148d4F1457FC59ede4E4B",
32 | "ARBITRUM_USDC_CONTRACT": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
33 | "PAYLOAD_WRITER_PROGRAM_ID": "DwMLtdtJqJQkHzNcrdTBuWHJByJfgpKBnvFvzyKdy3cU",
34 | "CPI_PROXY_PROGRAM_ID": "D8C8iW6zmoKg5TRr8nQ7h14TMWqQX8FiBdj2ju5MF3wa",
35 | }
36 |
--------------------------------------------------------------------------------
/src/api.ts:
--------------------------------------------------------------------------------
1 | import fetch from 'cross-fetch';
2 | import {
3 | Token,
4 | ChainName,
5 | QuoteParams,
6 | Quote,
7 | QuoteOptions,
8 | QuoteError,
9 | SolanaClientSwap,
10 | GetSolanaSwapParams,
11 | TokenStandard,
12 | GetSuiSwapParams,
13 | SuiClientSwap,
14 | EstimateGasEvmParams
15 | } from './types';
16 | import addresses from './addresses';
17 | import { checkSdkVersionSupport, getSdkVersion } from './utils';
18 | import { SwiftEvmGasLessParams } from './evm/evmSwift';
19 |
20 | function toQueryString(params: Record): string {
21 | return Object.entries(params)
22 | .filter(([_, value]) => value !== undefined && value !== null && !Array.isArray(value))
23 | .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
24 | .join('&');
25 | }
26 |
27 | async function check5xxError(res: Response): Promise {
28 | if (res.status.toString().startsWith('5')) {
29 | let error: Error | QuoteError;
30 | try {
31 | const err = await res.json();
32 | if ((err?.code || err?.statusCode) && (err?.message || err?.msg)) {
33 | error = {
34 | code: err?.code || err?.statusCode,
35 | message: err?.message || err?.msg,
36 | } as QuoteError
37 | }
38 | } catch (err) {
39 | error = new Error('Internal server error');
40 | }
41 | throw error;
42 | }
43 | }
44 |
45 | export async function fetchAllTokenList(tokenStandards?: TokenStandard[]): Promise<{[index: string]: Token[]}> {
46 | const query = tokenStandards ? `?standard=${tokenStandards.join(',')}` : '';
47 | const res = await fetch(`${addresses.PRICE_URL}/tokens${query}`, {
48 | method: 'GET',
49 | redirect: 'follow',
50 | });
51 | await check5xxError(res);
52 | if (res.status === 200) {
53 | const result = await res.json();
54 | return result as { [index: string]: Token[] };
55 | }
56 | throw new Error('Cannot fetch Mayan tokens!');
57 | }
58 |
59 | export async function fetchTokenList(chain: ChainName, nonPortal: boolean = false, tokenStandards?: TokenStandard[]): Promise {
60 | const queryParams = {
61 | chain,
62 | nonPortal,
63 | standard: tokenStandards ? tokenStandards?.join(',') : undefined,
64 | };
65 | const res = await fetch(`${addresses.PRICE_URL}/tokens?${toQueryString(queryParams)}`);
66 | await check5xxError(res);
67 | if (res.status === 200) {
68 | const result = await res.json();
69 | return result[chain] as Token[];
70 | }
71 | throw new Error('Cannot fetch Mayan tokens!');
72 | }
73 |
74 | export function generateFetchQuoteUrl(params: QuoteParams, quoteOptions: QuoteOptions = {
75 | wormhole: true,
76 | swift: true,
77 | mctp: true,
78 | shuttle: true,
79 | gasless: false,
80 | onlyDirect: false,
81 | fastMctp: true,
82 | fullList: false,
83 | payload: undefined,
84 | monoChain: true,
85 | }): string {
86 | const { gasDrop, referrerBps } = params;
87 | let slippageBps = params.slippageBps;
88 | if (slippageBps !== 'auto' && !Number.isFinite(slippageBps)) {
89 | slippageBps = params.slippage * 100;
90 | }
91 | const _quoteOptions: QuoteOptions = {
92 | wormhole: quoteOptions.wormhole !== false, // default to true
93 | swift: quoteOptions.swift !== false, // default to true
94 | mctp: quoteOptions.mctp !== false, // default to true
95 | shuttle: quoteOptions.shuttle === true, // default to false
96 | fastMctp: quoteOptions.fastMctp !== false, // default to true
97 | gasless: quoteOptions.gasless === true, // default to false
98 | onlyDirect: quoteOptions.onlyDirect === true, // default to false
99 | fullList: quoteOptions.fullList === true, // default to false
100 | payload: typeof quoteOptions.payload === 'string' ? quoteOptions.payload : undefined,
101 | monoChain: quoteOptions.monoChain !== false, // default to true
102 | }
103 | const queryParams: Record = {
104 | ..._quoteOptions,
105 | solanaProgram: addresses.MAYAN_PROGRAM_ID,
106 | forwarderAddress: addresses.MAYAN_FORWARDER_CONTRACT,
107 | amountIn: !params.amountIn64 && Number.isFinite(params.amount) ? params.amount : undefined,
108 | amountIn64: params.amountIn64,
109 | fromToken: params.fromToken,
110 | fromChain: params.fromChain,
111 | toToken: params.toToken,
112 | toChain: params.toChain,
113 | slippageBps,
114 | referrer: params.referrer,
115 | referrerBps: Number.isFinite(referrerBps) ? referrerBps : undefined,
116 | gasDrop: Number.isFinite(gasDrop) ? gasDrop : undefined,
117 | sdkVersion: getSdkVersion(),
118 | };
119 | const baseUrl = `${addresses.PRICE_URL}/quote?`;
120 | const queryString = toQueryString(queryParams);
121 | return (baseUrl + queryString);
122 | }
123 | export async function fetchQuote(params: QuoteParams, quoteOptions: QuoteOptions = {
124 | swift: true,
125 | mctp: true,
126 | gasless: false,
127 | onlyDirect: false,
128 | }): Promise {
129 | const url = generateFetchQuoteUrl(params, quoteOptions);
130 | const res = await fetch(url, {
131 | method: 'GET',
132 | redirect: 'follow',
133 | });
134 | await check5xxError(res);
135 | const result = await res.json();
136 | if (res.status !== 200 && res.status !== 201) {
137 | throw {
138 | code: result?.code || 0,
139 | message: result?.msg || result?.message || 'Route not found',
140 | data: result?.data,
141 | } as QuoteError
142 | }
143 | if (!checkSdkVersionSupport(result.minimumSdkVersion)) {
144 | throw {
145 | code: 9999,
146 | message: 'Swap SDK is outdated!',
147 | } as QuoteError
148 | }
149 | return result.quotes as Quote[];
150 | }
151 |
152 | export async function getCurrentChainTime(chain: ChainName): Promise {
153 | const res = await fetch(`${addresses.PRICE_URL}/clock/${chain}`, {
154 | method: 'GET',
155 | redirect: 'follow',
156 | });
157 | await check5xxError(res);
158 | const result = await res.json();
159 | if (res.status !== 200 && res.status !== 201) {
160 | throw result;
161 | }
162 | return result.clock;
163 | }
164 |
165 | export async function getSuggestedRelayer(): Promise {
166 | const res = await fetch(`${addresses.RELAYER_URL}/active-relayers?solanaProgram=${addresses.MAYAN_PROGRAM_ID}`, {
167 | method: 'GET',
168 | redirect: 'follow',
169 | });
170 | await check5xxError(res);
171 | const result = await res.json();
172 | if (res.status !== 200 && res.status !== 201) {
173 | throw result;
174 | }
175 | return result.suggested;
176 | }
177 |
178 |
179 | export async function getSwapSolana(params : GetSolanaSwapParams): Promise {
180 | const query = toQueryString({
181 | ...params,
182 | sdkVersion: getSdkVersion(),
183 | });
184 | const res = await fetch(`${addresses.PRICE_URL}/get-swap/solana?${query}`, {
185 | method: 'GET',
186 | redirect: 'follow',
187 | });
188 | await check5xxError(res);
189 | const result = await res.json();
190 | if (res.status !== 200 && res.status !== 201) {
191 | throw result;
192 | }
193 | return result;
194 | }
195 |
196 | export async function getSwapSui(params : GetSuiSwapParams): Promise {
197 | const requestBody = JSON.stringify({
198 | ...params,
199 | sdkVersion: getSdkVersion(),
200 | });
201 | const requestUrl = `${addresses.PRICE_URL}/get-swap/sui`;
202 |
203 | const res = await fetch(requestUrl, {
204 | method: 'POST',
205 | redirect: 'follow',
206 | body: requestBody,
207 | headers: {
208 | 'Content-Type': 'application/json',
209 | },
210 | });
211 | await check5xxError(res);
212 | const result = await res.json();
213 | if (res.status !== 200 && res.status !== 201) {
214 | throw result;
215 | }
216 | return result;
217 | }
218 |
219 | export async function submitSwiftEvmSwap(params: SwiftEvmGasLessParams, signature: string): Promise {
220 | const res = await fetch(`${addresses.EXPLORER_URL}/submit/evm`, {
221 | method: 'POST',
222 | headers: {
223 | 'Content-Type': 'application/json',
224 | },
225 | body: JSON.stringify({
226 | ...params,
227 | signature,
228 | }, (_key, value) => {
229 | if (typeof value === 'bigint') {
230 | return value.toString();
231 | }
232 | return value;
233 | }),
234 | });
235 | await check5xxError(res);
236 | }
237 |
238 | export async function submitSwiftSolanaSwap(signedTx: string): Promise<{ orderHash: string }> {
239 | const res = await fetch(`${addresses.EXPLORER_URL}/submit/solana`, {
240 | method: 'POST',
241 | headers: {
242 | 'Content-Type': 'application/json',
243 | },
244 | body: JSON.stringify({
245 | signedTx,
246 | }),
247 | });
248 | await check5xxError(res);
249 | const result = await res.json();
250 | if (res.status !== 200 && res.status !== 201) {
251 | throw result;
252 | }
253 | return result;
254 | }
255 |
256 |
257 | export async function checkHyperCoreDeposit(destinationAddress: string, tokenAddress: string): Promise {
258 | const query = toQueryString({
259 | destWallet: destinationAddress,
260 | destToken: tokenAddress,
261 | sdkVersion: getSdkVersion(),
262 | });
263 | const res = await fetch(`${addresses.EXPLORER_URL}/hypercore/is-allowed?${query}`, {
264 | method: 'GET',
265 | redirect: 'follow',
266 | });
267 | await check5xxError(res);
268 | const result = await res.json();
269 | if (res.status !== 200 && res.status !== 201) {
270 | throw result;
271 | }
272 | return result.allowed === true;
273 | }
274 |
275 | export async function getEstimateGasEvm(
276 | params: EstimateGasEvmParams
277 | ): Promise<{
278 | estimatedGas: bigint;
279 | gasPrice: bigint;
280 | }> {
281 | const res = await fetch(`${addresses.GAS_ESTIMATE_URL}/evm`, {
282 | method: 'POST',
283 | headers: {
284 | 'Content-Type': 'application/json',
285 | },
286 | body: JSON.stringify({
287 | ...params,
288 | sdkVersion: getSdkVersion(),
289 | }),
290 | });
291 | await check5xxError(res);
292 | const result = await res.json();
293 | if (res.status !== 200 && res.status !== 201) {
294 | throw result;
295 | }
296 | return {
297 | estimatedGas: BigInt(result.estimatedGas),
298 | gasPrice: BigInt(result.gasPrice),
299 | };
300 | }
301 |
--------------------------------------------------------------------------------
/src/cctp.ts:
--------------------------------------------------------------------------------
1 | import { PublicKey } from '@solana/web3.js';
2 | import addresses from './addresses';
3 | import { Buffer } from 'buffer';
4 | import {ChainName} from "./types";
5 |
6 | export const CCTP_TOKEN_DECIMALS = 6;
7 | export function getCCTPDomain(chain: ChainName): number {
8 | switch (chain) {
9 | case 'ethereum':
10 | return 0;
11 | case 'avalanche':
12 | return 1;
13 | case 'optimism':
14 | return 2;
15 | case 'arbitrum':
16 | return 3;
17 | case 'solana':
18 | return 5;
19 | case 'base':
20 | return 6;
21 | case 'polygon':
22 | return 7;
23 | case 'sui':
24 | return 8;
25 | case 'unichain':
26 | return 10;
27 | case 'linea':
28 | return 11;
29 | case 'sonic':
30 | return 13;
31 | case 'hyperevm':
32 | return 19;
33 | default:
34 | throw new Error('unsupported chain for cctp');
35 | }
36 | }
37 |
38 | export function getCCTPBridgePDAs(mint: PublicKey, destinationChain: ChainName): {
39 | messageTransmitter: PublicKey,
40 | senderAuthority: PublicKey,
41 | localToken: PublicKey,
42 | tokenMessenger: PublicKey,
43 | tokenMinter: PublicKey,
44 | remoteTokenMessengerKey: PublicKey,
45 | eventAuthCore: PublicKey,
46 | eventAuthToken: PublicKey,
47 | } {
48 | const cctpCoreProgramId = new PublicKey(addresses.CCTP_CORE_PROGRAM_ID);
49 | const cctpTokenProgramId = new PublicKey(addresses.CCTP_TOKEN_PROGRAM_ID);
50 |
51 | const [messageTransmitter] = PublicKey.findProgramAddressSync(
52 | [Buffer.from('message_transmitter')],
53 | cctpCoreProgramId,
54 | );
55 |
56 | const [senderAuthority] = PublicKey.findProgramAddressSync(
57 | [Buffer.from('sender_authority')],
58 | cctpTokenProgramId,
59 | );
60 |
61 | const [localToken] = PublicKey.findProgramAddressSync(
62 | [Buffer.from('local_token'), mint.toBytes()],
63 | cctpTokenProgramId,
64 | );
65 |
66 | const [tokenMessenger] = PublicKey.findProgramAddressSync(
67 | [Buffer.from('token_messenger')],
68 | cctpTokenProgramId,
69 | );
70 | const [tokenMinter] = PublicKey.findProgramAddressSync(
71 | [Buffer.from('token_minter')],
72 | cctpTokenProgramId,
73 | );
74 |
75 | const destinationDomain = getCCTPDomain(destinationChain);
76 |
77 | const [remoteTokenMessengerKey] = PublicKey.findProgramAddressSync(
78 | [Buffer.from('remote_token_messenger'), Buffer.from(destinationDomain.toString())],
79 | cctpTokenProgramId,
80 | );
81 |
82 | const [eventAuthCore] = PublicKey.findProgramAddressSync(
83 | [Buffer.from('__event_authority')],
84 | cctpCoreProgramId,
85 | );
86 |
87 | const [eventAuthToken] = PublicKey.findProgramAddressSync(
88 | [Buffer.from('__event_authority')],
89 | cctpTokenProgramId,
90 | );
91 |
92 | return {
93 | messageTransmitter,
94 | senderAuthority,
95 | remoteTokenMessengerKey,
96 | tokenMessenger,
97 | tokenMinter,
98 | eventAuthToken,
99 | eventAuthCore,
100 | localToken,
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/evm/ERC20Artifact.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | "_format": "hh-sol-artifact-1",
3 | "contractName": "ERC20Permit",
4 | "sourceName": "contracts/token/ERC20/extensions/ERC20Permit.sol",
5 | "abi": [
6 | {
7 | "inputs": [],
8 | "name": "ECDSAInvalidSignature",
9 | "type": "error"
10 | },
11 | {
12 | "inputs": [
13 | {
14 | "internalType": "uint256",
15 | "name": "length",
16 | "type": "uint256"
17 | }
18 | ],
19 | "name": "ECDSAInvalidSignatureLength",
20 | "type": "error"
21 | },
22 | {
23 | "inputs": [
24 | {
25 | "internalType": "bytes32",
26 | "name": "s",
27 | "type": "bytes32"
28 | }
29 | ],
30 | "name": "ECDSAInvalidSignatureS",
31 | "type": "error"
32 | },
33 | {
34 | "inputs": [
35 | {
36 | "internalType": "address",
37 | "name": "spender",
38 | "type": "address"
39 | },
40 | {
41 | "internalType": "uint256",
42 | "name": "allowance",
43 | "type": "uint256"
44 | },
45 | {
46 | "internalType": "uint256",
47 | "name": "needed",
48 | "type": "uint256"
49 | }
50 | ],
51 | "name": "ERC20InsufficientAllowance",
52 | "type": "error"
53 | },
54 | {
55 | "inputs": [
56 | {
57 | "internalType": "address",
58 | "name": "sender",
59 | "type": "address"
60 | },
61 | {
62 | "internalType": "uint256",
63 | "name": "balance",
64 | "type": "uint256"
65 | },
66 | {
67 | "internalType": "uint256",
68 | "name": "needed",
69 | "type": "uint256"
70 | }
71 | ],
72 | "name": "ERC20InsufficientBalance",
73 | "type": "error"
74 | },
75 | {
76 | "inputs": [
77 | {
78 | "internalType": "address",
79 | "name": "approver",
80 | "type": "address"
81 | }
82 | ],
83 | "name": "ERC20InvalidApprover",
84 | "type": "error"
85 | },
86 | {
87 | "inputs": [
88 | {
89 | "internalType": "address",
90 | "name": "receiver",
91 | "type": "address"
92 | }
93 | ],
94 | "name": "ERC20InvalidReceiver",
95 | "type": "error"
96 | },
97 | {
98 | "inputs": [
99 | {
100 | "internalType": "address",
101 | "name": "sender",
102 | "type": "address"
103 | }
104 | ],
105 | "name": "ERC20InvalidSender",
106 | "type": "error"
107 | },
108 | {
109 | "inputs": [
110 | {
111 | "internalType": "address",
112 | "name": "spender",
113 | "type": "address"
114 | }
115 | ],
116 | "name": "ERC20InvalidSpender",
117 | "type": "error"
118 | },
119 | {
120 | "inputs": [
121 | {
122 | "internalType": "uint256",
123 | "name": "deadline",
124 | "type": "uint256"
125 | }
126 | ],
127 | "name": "ERC2612ExpiredSignature",
128 | "type": "error"
129 | },
130 | {
131 | "inputs": [
132 | {
133 | "internalType": "address",
134 | "name": "signer",
135 | "type": "address"
136 | },
137 | {
138 | "internalType": "address",
139 | "name": "owner",
140 | "type": "address"
141 | }
142 | ],
143 | "name": "ERC2612InvalidSigner",
144 | "type": "error"
145 | },
146 | {
147 | "inputs": [
148 | {
149 | "internalType": "address",
150 | "name": "account",
151 | "type": "address"
152 | },
153 | {
154 | "internalType": "uint256",
155 | "name": "currentNonce",
156 | "type": "uint256"
157 | }
158 | ],
159 | "name": "InvalidAccountNonce",
160 | "type": "error"
161 | },
162 | {
163 | "inputs": [],
164 | "name": "InvalidShortString",
165 | "type": "error"
166 | },
167 | {
168 | "inputs": [
169 | {
170 | "internalType": "string",
171 | "name": "str",
172 | "type": "string"
173 | }
174 | ],
175 | "name": "StringTooLong",
176 | "type": "error"
177 | },
178 | {
179 | "anonymous": false,
180 | "inputs": [
181 | {
182 | "indexed": true,
183 | "internalType": "address",
184 | "name": "owner",
185 | "type": "address"
186 | },
187 | {
188 | "indexed": true,
189 | "internalType": "address",
190 | "name": "spender",
191 | "type": "address"
192 | },
193 | {
194 | "indexed": false,
195 | "internalType": "uint256",
196 | "name": "value",
197 | "type": "uint256"
198 | }
199 | ],
200 | "name": "Approval",
201 | "type": "event"
202 | },
203 | {
204 | "anonymous": false,
205 | "inputs": [],
206 | "name": "EIP712DomainChanged",
207 | "type": "event"
208 | },
209 | {
210 | "anonymous": false,
211 | "inputs": [
212 | {
213 | "indexed": true,
214 | "internalType": "address",
215 | "name": "from",
216 | "type": "address"
217 | },
218 | {
219 | "indexed": true,
220 | "internalType": "address",
221 | "name": "to",
222 | "type": "address"
223 | },
224 | {
225 | "indexed": false,
226 | "internalType": "uint256",
227 | "name": "value",
228 | "type": "uint256"
229 | }
230 | ],
231 | "name": "Transfer",
232 | "type": "event"
233 | },
234 | {
235 | "inputs": [],
236 | "name": "DOMAIN_SEPARATOR",
237 | "outputs": [
238 | {
239 | "internalType": "bytes32",
240 | "name": "",
241 | "type": "bytes32"
242 | }
243 | ],
244 | "stateMutability": "view",
245 | "type": "function"
246 | },
247 | {
248 | "inputs": [
249 | {
250 | "internalType": "address",
251 | "name": "owner",
252 | "type": "address"
253 | },
254 | {
255 | "internalType": "address",
256 | "name": "spender",
257 | "type": "address"
258 | }
259 | ],
260 | "name": "allowance",
261 | "outputs": [
262 | {
263 | "internalType": "uint256",
264 | "name": "",
265 | "type": "uint256"
266 | }
267 | ],
268 | "stateMutability": "view",
269 | "type": "function"
270 | },
271 | {
272 | "inputs": [
273 | {
274 | "internalType": "address",
275 | "name": "spender",
276 | "type": "address"
277 | },
278 | {
279 | "internalType": "uint256",
280 | "name": "value",
281 | "type": "uint256"
282 | }
283 | ],
284 | "name": "approve",
285 | "outputs": [
286 | {
287 | "internalType": "bool",
288 | "name": "",
289 | "type": "bool"
290 | }
291 | ],
292 | "stateMutability": "nonpayable",
293 | "type": "function"
294 | },
295 | {
296 | "inputs": [
297 | {
298 | "internalType": "address",
299 | "name": "account",
300 | "type": "address"
301 | }
302 | ],
303 | "name": "balanceOf",
304 | "outputs": [
305 | {
306 | "internalType": "uint256",
307 | "name": "",
308 | "type": "uint256"
309 | }
310 | ],
311 | "stateMutability": "view",
312 | "type": "function"
313 | },
314 | {
315 | "inputs": [],
316 | "name": "decimals",
317 | "outputs": [
318 | {
319 | "internalType": "uint8",
320 | "name": "",
321 | "type": "uint8"
322 | }
323 | ],
324 | "stateMutability": "view",
325 | "type": "function"
326 | },
327 | {
328 | "inputs": [],
329 | "name": "eip712Domain",
330 | "outputs": [
331 | {
332 | "internalType": "bytes1",
333 | "name": "fields",
334 | "type": "bytes1"
335 | },
336 | {
337 | "internalType": "string",
338 | "name": "name",
339 | "type": "string"
340 | },
341 | {
342 | "internalType": "string",
343 | "name": "version",
344 | "type": "string"
345 | },
346 | {
347 | "internalType": "uint256",
348 | "name": "chainId",
349 | "type": "uint256"
350 | },
351 | {
352 | "internalType": "address",
353 | "name": "verifyingContract",
354 | "type": "address"
355 | },
356 | {
357 | "internalType": "bytes32",
358 | "name": "salt",
359 | "type": "bytes32"
360 | },
361 | {
362 | "internalType": "uint256[]",
363 | "name": "extensions",
364 | "type": "uint256[]"
365 | }
366 | ],
367 | "stateMutability": "view",
368 | "type": "function"
369 | },
370 | {
371 | "inputs": [],
372 | "name": "name",
373 | "outputs": [
374 | {
375 | "internalType": "string",
376 | "name": "",
377 | "type": "string"
378 | }
379 | ],
380 | "stateMutability": "view",
381 | "type": "function"
382 | },
383 | {
384 | "inputs": [
385 | {
386 | "internalType": "address",
387 | "name": "owner",
388 | "type": "address"
389 | }
390 | ],
391 | "name": "nonces",
392 | "outputs": [
393 | {
394 | "internalType": "uint256",
395 | "name": "",
396 | "type": "uint256"
397 | }
398 | ],
399 | "stateMutability": "view",
400 | "type": "function"
401 | },
402 | {
403 | "inputs": [
404 | {
405 | "internalType": "address",
406 | "name": "owner",
407 | "type": "address"
408 | },
409 | {
410 | "internalType": "address",
411 | "name": "spender",
412 | "type": "address"
413 | },
414 | {
415 | "internalType": "uint256",
416 | "name": "value",
417 | "type": "uint256"
418 | },
419 | {
420 | "internalType": "uint256",
421 | "name": "deadline",
422 | "type": "uint256"
423 | },
424 | {
425 | "internalType": "uint8",
426 | "name": "v",
427 | "type": "uint8"
428 | },
429 | {
430 | "internalType": "bytes32",
431 | "name": "r",
432 | "type": "bytes32"
433 | },
434 | {
435 | "internalType": "bytes32",
436 | "name": "s",
437 | "type": "bytes32"
438 | }
439 | ],
440 | "name": "permit",
441 | "outputs": [],
442 | "stateMutability": "nonpayable",
443 | "type": "function"
444 | },
445 | {
446 | "inputs": [],
447 | "name": "symbol",
448 | "outputs": [
449 | {
450 | "internalType": "string",
451 | "name": "",
452 | "type": "string"
453 | }
454 | ],
455 | "stateMutability": "view",
456 | "type": "function"
457 | },
458 | {
459 | "inputs": [],
460 | "name": "totalSupply",
461 | "outputs": [
462 | {
463 | "internalType": "uint256",
464 | "name": "",
465 | "type": "uint256"
466 | }
467 | ],
468 | "stateMutability": "view",
469 | "type": "function"
470 | },
471 | {
472 | "inputs": [
473 | {
474 | "internalType": "address",
475 | "name": "to",
476 | "type": "address"
477 | },
478 | {
479 | "internalType": "uint256",
480 | "name": "value",
481 | "type": "uint256"
482 | }
483 | ],
484 | "name": "transfer",
485 | "outputs": [
486 | {
487 | "internalType": "bool",
488 | "name": "",
489 | "type": "bool"
490 | }
491 | ],
492 | "stateMutability": "nonpayable",
493 | "type": "function"
494 | },
495 | {
496 | "inputs": [
497 | {
498 | "internalType": "address",
499 | "name": "from",
500 | "type": "address"
501 | },
502 | {
503 | "internalType": "address",
504 | "name": "to",
505 | "type": "address"
506 | },
507 | {
508 | "internalType": "uint256",
509 | "name": "value",
510 | "type": "uint256"
511 | }
512 | ],
513 | "name": "transferFrom",
514 | "outputs": [
515 | {
516 | "internalType": "bool",
517 | "name": "",
518 | "type": "bool"
519 | }
520 | ],
521 | "stateMutability": "nonpayable",
522 | "type": "function"
523 | }
524 | ],
525 | "bytecode": "0x",
526 | "deployedBytecode": "0x",
527 | "linkReferences": {},
528 | "deployedLinkReferences": {}
529 | }
530 |
--------------------------------------------------------------------------------
/src/evm/HCDepositInitiatorArtifact.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | abi: [
3 | {
4 | type: 'constructor',
5 | inputs: [
6 | { name: '_hcProcessor', type: 'address', internalType: 'address' },
7 | {
8 | name: '_usdc',
9 | type: 'address',
10 | internalType: 'address',
11 | },
12 | ],
13 | stateMutability: 'nonpayable',
14 | },
15 | {
16 | type: 'function',
17 | name: 'changeGuardian',
18 | inputs: [
19 | { name: 'newGuardian', type: 'address', internalType: 'address' },
20 | ],
21 | outputs: [],
22 | stateMutability: 'nonpayable',
23 | },
24 | {
25 | type: 'function',
26 | name: 'claimGuardian',
27 | inputs: [],
28 | outputs: [],
29 | stateMutability: 'nonpayable',
30 | },
31 | {
32 | type: 'function',
33 | name: 'deposit',
34 | inputs: [
35 | { name: 'tokenIn', type: 'address', internalType: 'address' },
36 | {
37 | name: 'amountIn',
38 | type: 'uint256',
39 | internalType: 'uint256',
40 | },
41 | { name: 'trader', type: 'address', internalType: 'address' },
42 | {
43 | name: 'gasDrop',
44 | type: 'uint64',
45 | internalType: 'uint64',
46 | },
47 | { name: 'bridgeAmount', type: 'uint256', internalType: 'uint256' },
48 | {
49 | name: 'depositPayload',
50 | type: 'tuple',
51 | internalType: 'struct HCDepositInitiator.DepositPayload',
52 | components: [
53 | { name: 'relayerFee', type: 'uint64', internalType: 'uint64' },
54 | {
55 | name: 'permit',
56 | type: 'tuple',
57 | internalType: 'struct IHCBridge.DepositWithPermit',
58 | components: [
59 | { name: 'user', type: 'address', internalType: 'address' },
60 | {
61 | name: 'usd',
62 | type: 'uint64',
63 | internalType: 'uint64',
64 | },
65 | { name: 'deadline', type: 'uint64', internalType: 'uint64' },
66 | {
67 | name: 'signature',
68 | type: 'tuple',
69 | internalType: 'struct IHCBridge.Signature',
70 | components: [
71 | { name: 'r', type: 'uint256', internalType: 'uint256' },
72 | {
73 | name: 's',
74 | type: 'uint256',
75 | internalType: 'uint256',
76 | },
77 | { name: 'v', type: 'uint8', internalType: 'uint8' },
78 | ],
79 | },
80 | ],
81 | },
82 | ],
83 | },
84 | ],
85 | outputs: [],
86 | stateMutability: 'nonpayable',
87 | },
88 | {
89 | type: 'function',
90 | name: 'fastDeposit',
91 | inputs: [
92 | { name: 'tokenIn', type: 'address', internalType: 'address' },
93 | {
94 | name: 'amountIn',
95 | type: 'uint256',
96 | internalType: 'uint256',
97 | },
98 | { name: 'trader', type: 'address', internalType: 'address' },
99 | {
100 | name: 'circleMaxFee',
101 | type: 'uint256',
102 | internalType: 'uint256',
103 | },
104 | { name: 'gasDrop', type: 'uint64', internalType: 'uint64' },
105 | {
106 | name: 'referrerAddress',
107 | type: 'bytes32',
108 | internalType: 'bytes32',
109 | },
110 | { name: 'referrerBps', type: 'uint8', internalType: 'uint8' },
111 | {
112 | name: 'minFinalityThreshold',
113 | type: 'uint32',
114 | internalType: 'uint32',
115 | },
116 | { name: 'bridgeAmount', type: 'uint256', internalType: 'uint256' },
117 | {
118 | name: 'depositPayload',
119 | type: 'tuple',
120 | internalType: 'struct HCDepositInitiator.DepositPayload',
121 | components: [
122 | { name: 'relayerFee', type: 'uint64', internalType: 'uint64' },
123 | {
124 | name: 'permit',
125 | type: 'tuple',
126 | internalType: 'struct IHCBridge.DepositWithPermit',
127 | components: [
128 | { name: 'user', type: 'address', internalType: 'address' },
129 | {
130 | name: 'usd',
131 | type: 'uint64',
132 | internalType: 'uint64',
133 | },
134 | { name: 'deadline', type: 'uint64', internalType: 'uint64' },
135 | {
136 | name: 'signature',
137 | type: 'tuple',
138 | internalType: 'struct IHCBridge.Signature',
139 | components: [
140 | { name: 'r', type: 'uint256', internalType: 'uint256' },
141 | {
142 | name: 's',
143 | type: 'uint256',
144 | internalType: 'uint256',
145 | },
146 | { name: 'v', type: 'uint8', internalType: 'uint8' },
147 | ],
148 | },
149 | ],
150 | },
151 | ],
152 | },
153 | ],
154 | outputs: [],
155 | stateMutability: 'nonpayable',
156 | },
157 | {
158 | type: 'function',
159 | name: 'fastMCTP',
160 | inputs: [],
161 | outputs: [{ name: '', type: 'address', internalType: 'address' }],
162 | stateMutability: 'view',
163 | },
164 | {
165 | type: 'function',
166 | name: 'guardian',
167 | inputs: [],
168 | outputs: [{ name: '', type: 'address', internalType: 'address' }],
169 | stateMutability: 'view',
170 | },
171 | {
172 | type: 'function',
173 | name: 'mayanCircle',
174 | inputs: [],
175 | outputs: [{ name: '', type: 'address', internalType: 'address' }],
176 | stateMutability: 'view',
177 | },
178 | {
179 | type: 'function',
180 | name: 'nextGuardian',
181 | inputs: [],
182 | outputs: [{ name: '', type: 'address', internalType: 'address' }],
183 | stateMutability: 'view',
184 | },
185 | {
186 | type: 'function',
187 | name: 'setFastMCTP',
188 | inputs: [{ name: '_fastMCTP', type: 'address', internalType: 'address' }],
189 | outputs: [],
190 | stateMutability: 'nonpayable',
191 | },
192 | {
193 | type: 'function',
194 | name: 'setMayanCircle',
195 | inputs: [
196 | { name: '_mayanCircle', type: 'address', internalType: 'address' },
197 | ],
198 | outputs: [],
199 | stateMutability: 'nonpayable',
200 | },
201 | { type: 'error', name: 'AlreadySet', inputs: [] },
202 | {
203 | type: 'error',
204 | name: 'InsufficientAmount',
205 | inputs: [],
206 | },
207 | { type: 'error', name: 'Unauthorized', inputs: [] },
208 | ],
209 | };
210 |
--------------------------------------------------------------------------------
/src/evm/MayanForwarderArtifact.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | "_format": "hh-sol-artifact-1",
3 | "contractName": "MayanForwarder",
4 | "sourceName": "src/MayanForwarder.sol",
5 | "abi": [
6 | {
7 | "inputs": [
8 | {
9 | "internalType": "address",
10 | "name": "_guardian",
11 | "type": "address"
12 | },
13 | {
14 | "internalType": "address[]",
15 | "name": "_swapProtocols",
16 | "type": "address[]"
17 | },
18 | {
19 | "internalType": "address[]",
20 | "name": "_mayanProtocols",
21 | "type": "address[]"
22 | }
23 | ],
24 | "stateMutability": "nonpayable",
25 | "type": "constructor"
26 | },
27 | {
28 | "inputs": [],
29 | "name": "UnsupportedProtocol",
30 | "type": "error"
31 | },
32 | {
33 | "anonymous": false,
34 | "inputs": [
35 | {
36 | "indexed": false,
37 | "internalType": "address",
38 | "name": "token",
39 | "type": "address"
40 | },
41 | {
42 | "indexed": false,
43 | "internalType": "uint256",
44 | "name": "amount",
45 | "type": "uint256"
46 | },
47 | {
48 | "indexed": false,
49 | "internalType": "address",
50 | "name": "mayanProtocol",
51 | "type": "address"
52 | },
53 | {
54 | "indexed": false,
55 | "internalType": "bytes",
56 | "name": "protocolData",
57 | "type": "bytes"
58 | }
59 | ],
60 | "name": "ForwardedERC20",
61 | "type": "event"
62 | },
63 | {
64 | "anonymous": false,
65 | "inputs": [
66 | {
67 | "indexed": false,
68 | "internalType": "address",
69 | "name": "mayanProtocol",
70 | "type": "address"
71 | },
72 | {
73 | "indexed": false,
74 | "internalType": "bytes",
75 | "name": "protocolData",
76 | "type": "bytes"
77 | }
78 | ],
79 | "name": "ForwardedEth",
80 | "type": "event"
81 | },
82 | {
83 | "anonymous": false,
84 | "inputs": [
85 | {
86 | "indexed": false,
87 | "internalType": "uint256",
88 | "name": "amount",
89 | "type": "uint256"
90 | }
91 | ],
92 | "name": "SwapAndForwarded",
93 | "type": "event"
94 | },
95 | {
96 | "anonymous": false,
97 | "inputs": [
98 | {
99 | "indexed": false,
100 | "internalType": "address",
101 | "name": "tokenIn",
102 | "type": "address"
103 | },
104 | {
105 | "indexed": false,
106 | "internalType": "uint256",
107 | "name": "amountIn",
108 | "type": "uint256"
109 | },
110 | {
111 | "indexed": false,
112 | "internalType": "address",
113 | "name": "swapProtocol",
114 | "type": "address"
115 | },
116 | {
117 | "indexed": false,
118 | "internalType": "address",
119 | "name": "middleToken",
120 | "type": "address"
121 | },
122 | {
123 | "indexed": false,
124 | "internalType": "uint256",
125 | "name": "middleAmount",
126 | "type": "uint256"
127 | },
128 | {
129 | "indexed": false,
130 | "internalType": "address",
131 | "name": "mayanProtocol",
132 | "type": "address"
133 | },
134 | {
135 | "indexed": false,
136 | "internalType": "bytes",
137 | "name": "mayanData",
138 | "type": "bytes"
139 | }
140 | ],
141 | "name": "SwapAndForwardedERC20",
142 | "type": "event"
143 | },
144 | {
145 | "anonymous": false,
146 | "inputs": [
147 | {
148 | "indexed": false,
149 | "internalType": "uint256",
150 | "name": "amountIn",
151 | "type": "uint256"
152 | },
153 | {
154 | "indexed": false,
155 | "internalType": "address",
156 | "name": "swapProtocol",
157 | "type": "address"
158 | },
159 | {
160 | "indexed": false,
161 | "internalType": "address",
162 | "name": "middleToken",
163 | "type": "address"
164 | },
165 | {
166 | "indexed": false,
167 | "internalType": "uint256",
168 | "name": "middleAmount",
169 | "type": "uint256"
170 | },
171 | {
172 | "indexed": false,
173 | "internalType": "address",
174 | "name": "mayanProtocol",
175 | "type": "address"
176 | },
177 | {
178 | "indexed": false,
179 | "internalType": "bytes",
180 | "name": "mayanData",
181 | "type": "bytes"
182 | }
183 | ],
184 | "name": "SwapAndForwardedEth",
185 | "type": "event"
186 | },
187 | {
188 | "inputs": [
189 | {
190 | "internalType": "address",
191 | "name": "newGuardian",
192 | "type": "address"
193 | }
194 | ],
195 | "name": "changeGuardian",
196 | "outputs": [],
197 | "stateMutability": "nonpayable",
198 | "type": "function"
199 | },
200 | {
201 | "inputs": [],
202 | "name": "claimGuardian",
203 | "outputs": [],
204 | "stateMutability": "nonpayable",
205 | "type": "function"
206 | },
207 | {
208 | "inputs": [
209 | {
210 | "internalType": "address",
211 | "name": "tokenIn",
212 | "type": "address"
213 | },
214 | {
215 | "internalType": "uint256",
216 | "name": "amountIn",
217 | "type": "uint256"
218 | },
219 | {
220 | "components": [
221 | {
222 | "internalType": "uint256",
223 | "name": "value",
224 | "type": "uint256"
225 | },
226 | {
227 | "internalType": "uint256",
228 | "name": "deadline",
229 | "type": "uint256"
230 | },
231 | {
232 | "internalType": "uint8",
233 | "name": "v",
234 | "type": "uint8"
235 | },
236 | {
237 | "internalType": "bytes32",
238 | "name": "r",
239 | "type": "bytes32"
240 | },
241 | {
242 | "internalType": "bytes32",
243 | "name": "s",
244 | "type": "bytes32"
245 | }
246 | ],
247 | "internalType": "struct MayanForwarder.PermitParams",
248 | "name": "permitParams",
249 | "type": "tuple"
250 | },
251 | {
252 | "internalType": "address",
253 | "name": "mayanProtocol",
254 | "type": "address"
255 | },
256 | {
257 | "internalType": "bytes",
258 | "name": "protocolData",
259 | "type": "bytes"
260 | }
261 | ],
262 | "name": "forwardERC20",
263 | "outputs": [],
264 | "stateMutability": "payable",
265 | "type": "function"
266 | },
267 | {
268 | "inputs": [
269 | {
270 | "internalType": "address",
271 | "name": "mayanProtocol",
272 | "type": "address"
273 | },
274 | {
275 | "internalType": "bytes",
276 | "name": "protocolData",
277 | "type": "bytes"
278 | }
279 | ],
280 | "name": "forwardEth",
281 | "outputs": [],
282 | "stateMutability": "payable",
283 | "type": "function"
284 | },
285 | {
286 | "inputs": [],
287 | "name": "guardian",
288 | "outputs": [
289 | {
290 | "internalType": "address",
291 | "name": "",
292 | "type": "address"
293 | }
294 | ],
295 | "stateMutability": "view",
296 | "type": "function"
297 | },
298 | {
299 | "inputs": [
300 | {
301 | "internalType": "address",
302 | "name": "",
303 | "type": "address"
304 | }
305 | ],
306 | "name": "mayanProtocols",
307 | "outputs": [
308 | {
309 | "internalType": "bool",
310 | "name": "",
311 | "type": "bool"
312 | }
313 | ],
314 | "stateMutability": "view",
315 | "type": "function"
316 | },
317 | {
318 | "inputs": [],
319 | "name": "nextGuardian",
320 | "outputs": [
321 | {
322 | "internalType": "address",
323 | "name": "",
324 | "type": "address"
325 | }
326 | ],
327 | "stateMutability": "view",
328 | "type": "function"
329 | },
330 | {
331 | "inputs": [
332 | {
333 | "internalType": "uint256",
334 | "name": "amount",
335 | "type": "uint256"
336 | },
337 | {
338 | "internalType": "address payable",
339 | "name": "to",
340 | "type": "address"
341 | }
342 | ],
343 | "name": "rescueEth",
344 | "outputs": [],
345 | "stateMutability": "nonpayable",
346 | "type": "function"
347 | },
348 | {
349 | "inputs": [
350 | {
351 | "internalType": "address",
352 | "name": "token",
353 | "type": "address"
354 | },
355 | {
356 | "internalType": "uint256",
357 | "name": "amount",
358 | "type": "uint256"
359 | },
360 | {
361 | "internalType": "address",
362 | "name": "to",
363 | "type": "address"
364 | }
365 | ],
366 | "name": "rescueToken",
367 | "outputs": [],
368 | "stateMutability": "nonpayable",
369 | "type": "function"
370 | },
371 | {
372 | "inputs": [
373 | {
374 | "internalType": "address",
375 | "name": "mayanProtocol",
376 | "type": "address"
377 | },
378 | {
379 | "internalType": "bool",
380 | "name": "enabled",
381 | "type": "bool"
382 | }
383 | ],
384 | "name": "setMayanProtocol",
385 | "outputs": [],
386 | "stateMutability": "nonpayable",
387 | "type": "function"
388 | },
389 | {
390 | "inputs": [
391 | {
392 | "internalType": "address",
393 | "name": "swapProtocol",
394 | "type": "address"
395 | },
396 | {
397 | "internalType": "bool",
398 | "name": "enabled",
399 | "type": "bool"
400 | }
401 | ],
402 | "name": "setSwapProtocol",
403 | "outputs": [],
404 | "stateMutability": "nonpayable",
405 | "type": "function"
406 | },
407 | {
408 | "inputs": [
409 | {
410 | "internalType": "address",
411 | "name": "tokenIn",
412 | "type": "address"
413 | },
414 | {
415 | "internalType": "uint256",
416 | "name": "amountIn",
417 | "type": "uint256"
418 | },
419 | {
420 | "components": [
421 | {
422 | "internalType": "uint256",
423 | "name": "value",
424 | "type": "uint256"
425 | },
426 | {
427 | "internalType": "uint256",
428 | "name": "deadline",
429 | "type": "uint256"
430 | },
431 | {
432 | "internalType": "uint8",
433 | "name": "v",
434 | "type": "uint8"
435 | },
436 | {
437 | "internalType": "bytes32",
438 | "name": "r",
439 | "type": "bytes32"
440 | },
441 | {
442 | "internalType": "bytes32",
443 | "name": "s",
444 | "type": "bytes32"
445 | }
446 | ],
447 | "internalType": "struct MayanForwarder.PermitParams",
448 | "name": "permitParams",
449 | "type": "tuple"
450 | },
451 | {
452 | "internalType": "address",
453 | "name": "swapProtocol",
454 | "type": "address"
455 | },
456 | {
457 | "internalType": "bytes",
458 | "name": "swapData",
459 | "type": "bytes"
460 | },
461 | {
462 | "internalType": "address",
463 | "name": "middleToken",
464 | "type": "address"
465 | },
466 | {
467 | "internalType": "uint256",
468 | "name": "minMiddleAmount",
469 | "type": "uint256"
470 | },
471 | {
472 | "internalType": "address",
473 | "name": "mayanProtocol",
474 | "type": "address"
475 | },
476 | {
477 | "internalType": "bytes",
478 | "name": "mayanData",
479 | "type": "bytes"
480 | }
481 | ],
482 | "name": "swapAndForwardERC20",
483 | "outputs": [],
484 | "stateMutability": "payable",
485 | "type": "function"
486 | },
487 | {
488 | "inputs": [
489 | {
490 | "internalType": "uint256",
491 | "name": "amountIn",
492 | "type": "uint256"
493 | },
494 | {
495 | "internalType": "address",
496 | "name": "swapProtocol",
497 | "type": "address"
498 | },
499 | {
500 | "internalType": "bytes",
501 | "name": "swapData",
502 | "type": "bytes"
503 | },
504 | {
505 | "internalType": "address",
506 | "name": "middleToken",
507 | "type": "address"
508 | },
509 | {
510 | "internalType": "uint256",
511 | "name": "minMiddleAmount",
512 | "type": "uint256"
513 | },
514 | {
515 | "internalType": "address",
516 | "name": "mayanProtocol",
517 | "type": "address"
518 | },
519 | {
520 | "internalType": "bytes",
521 | "name": "mayanData",
522 | "type": "bytes"
523 | }
524 | ],
525 | "name": "swapAndForwardEth",
526 | "outputs": [],
527 | "stateMutability": "payable",
528 | "type": "function"
529 | },
530 | {
531 | "inputs": [
532 | {
533 | "internalType": "address",
534 | "name": "",
535 | "type": "address"
536 | }
537 | ],
538 | "name": "swapProtocols",
539 | "outputs": [
540 | {
541 | "internalType": "bool",
542 | "name": "",
543 | "type": "bool"
544 | }
545 | ],
546 | "stateMutability": "view",
547 | "type": "function"
548 | }
549 | ],
550 | "linkReferences": {},
551 | "deployedLinkReferences": {}
552 | }
553 |
--------------------------------------------------------------------------------
/src/evm/MayanMonoChainArtifact.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | abi: [
3 | {
4 | type: 'constructor',
5 | inputs: [
6 | { name: '_forwarderAddress', type: 'address', internalType: 'address' },
7 | ],
8 | stateMutability: 'nonpayable',
9 | },
10 | { type: 'receive', stateMutability: 'payable' },
11 | {
12 | type: 'function',
13 | name: 'ForwarderAddress',
14 | inputs: [],
15 | outputs: [{ name: '', type: 'address', internalType: 'address' }],
16 | stateMutability: 'view',
17 | },
18 | {
19 | type: 'function',
20 | name: 'MAX_REFERRER_BPS',
21 | inputs: [],
22 | outputs: [{ name: '', type: 'uint8', internalType: 'uint8' }],
23 | stateMutability: 'view',
24 | },
25 | {
26 | type: 'function',
27 | name: 'changeGuardian',
28 | inputs: [
29 | { name: 'newGuardian', type: 'address', internalType: 'address' },
30 | ],
31 | outputs: [],
32 | stateMutability: 'nonpayable',
33 | },
34 | {
35 | type: 'function',
36 | name: 'claimGuardian',
37 | inputs: [],
38 | outputs: [],
39 | stateMutability: 'nonpayable',
40 | },
41 | {
42 | type: 'function',
43 | name: 'guardian',
44 | inputs: [],
45 | outputs: [{ name: '', type: 'address', internalType: 'address' }],
46 | stateMutability: 'view',
47 | },
48 | {
49 | type: 'function',
50 | name: 'nextGuardian',
51 | inputs: [],
52 | outputs: [{ name: '', type: 'address', internalType: 'address' }],
53 | stateMutability: 'view',
54 | },
55 | {
56 | type: 'function',
57 | name: 'rescueETH',
58 | inputs: [
59 | { name: 'to', type: 'address', internalType: 'address' },
60 | {
61 | name: 'amount',
62 | type: 'uint256',
63 | internalType: 'uint256',
64 | },
65 | ],
66 | outputs: [],
67 | stateMutability: 'nonpayable',
68 | },
69 | {
70 | type: 'function',
71 | name: 'rescueToken',
72 | inputs: [
73 | { name: 'token', type: 'address', internalType: 'address' },
74 | {
75 | name: 'to',
76 | type: 'address',
77 | internalType: 'address',
78 | },
79 | { name: 'amount', type: 'uint256', internalType: 'uint256' },
80 | ],
81 | outputs: [],
82 | stateMutability: 'nonpayable',
83 | },
84 | {
85 | type: 'function',
86 | name: 'transferEth',
87 | inputs: [
88 | { name: 'to', type: 'address', internalType: 'address' },
89 | {
90 | name: 'referrerAddr',
91 | type: 'address',
92 | internalType: 'address',
93 | },
94 | { name: 'referrerBps', type: 'uint8', internalType: 'uint8' },
95 | ],
96 | outputs: [],
97 | stateMutability: 'payable',
98 | },
99 | {
100 | type: 'function',
101 | name: 'transferToken',
102 | inputs: [
103 | { name: 'token', type: 'address', internalType: 'address' },
104 | {
105 | name: 'amount',
106 | type: 'uint256',
107 | internalType: 'uint256',
108 | },
109 | { name: 'to', type: 'address', internalType: 'address' },
110 | {
111 | name: 'referrerAddr',
112 | type: 'address',
113 | internalType: 'address',
114 | },
115 | { name: 'referrerBps', type: 'uint8', internalType: 'uint8' },
116 | ],
117 | outputs: [],
118 | stateMutability: 'nonpayable',
119 | },
120 | {
121 | type: 'event',
122 | name: 'EthTransferred',
123 | inputs: [
124 | {
125 | name: 'to',
126 | type: 'address',
127 | indexed: false,
128 | internalType: 'address',
129 | },
130 | {
131 | name: 'amount',
132 | type: 'uint256',
133 | indexed: false,
134 | internalType: 'uint256',
135 | },
136 | ],
137 | anonymous: false,
138 | },
139 | {
140 | type: 'event',
141 | name: 'TokenTransferred',
142 | inputs: [
143 | {
144 | name: 'token',
145 | type: 'address',
146 | indexed: false,
147 | internalType: 'address',
148 | },
149 | {
150 | name: 'to',
151 | type: 'address',
152 | indexed: false,
153 | internalType: 'address',
154 | },
155 | {
156 | name: 'amount',
157 | type: 'uint256',
158 | indexed: false,
159 | internalType: 'uint256',
160 | },
161 | ],
162 | anonymous: false,
163 | },
164 | { type: 'error', name: 'InvalidReferrerBps', inputs: [] },
165 | {
166 | type: 'error',
167 | name: 'Unauthorized',
168 | inputs: [],
169 | },
170 | ],
171 | };
172 |
--------------------------------------------------------------------------------
/src/evm/MayanSwapArtifact.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | "_format": "hh-sol-artifact-1",
3 | "contractName": "MayanSwap",
4 | "sourceName": "src/MayanSwap.sol",
5 | "abi": [
6 | {
7 | "inputs": [
8 | {
9 | "internalType": "address",
10 | "name": "_tokenBridge",
11 | "type": "address"
12 | },
13 | {
14 | "internalType": "address",
15 | "name": "_weth",
16 | "type": "address"
17 | }
18 | ],
19 | "stateMutability": "nonpayable",
20 | "type": "constructor"
21 | },
22 | {
23 | "anonymous": false,
24 | "inputs": [
25 | {
26 | "indexed": true,
27 | "internalType": "uint16",
28 | "name": "emitterChainId",
29 | "type": "uint16"
30 | },
31 | {
32 | "indexed": true,
33 | "internalType": "bytes32",
34 | "name": "emitterAddress",
35 | "type": "bytes32"
36 | },
37 | {
38 | "indexed": true,
39 | "internalType": "uint64",
40 | "name": "sequence",
41 | "type": "uint64"
42 | }
43 | ],
44 | "name": "Redeemed",
45 | "type": "event"
46 | },
47 | {
48 | "inputs": [
49 | {
50 | "internalType": "address",
51 | "name": "newGuardian",
52 | "type": "address"
53 | }
54 | ],
55 | "name": "changeGuardian",
56 | "outputs": [],
57 | "stateMutability": "nonpayable",
58 | "type": "function"
59 | },
60 | {
61 | "inputs": [],
62 | "name": "claimGuardian",
63 | "outputs": [],
64 | "stateMutability": "nonpayable",
65 | "type": "function"
66 | },
67 | {
68 | "inputs": [
69 | {
70 | "components": [
71 | {
72 | "internalType": "uint8",
73 | "name": "payloadId",
74 | "type": "uint8"
75 | },
76 | {
77 | "internalType": "bytes32",
78 | "name": "tokenAddr",
79 | "type": "bytes32"
80 | },
81 | {
82 | "internalType": "uint16",
83 | "name": "tokenChainId",
84 | "type": "uint16"
85 | },
86 | {
87 | "internalType": "bytes32",
88 | "name": "destAddr",
89 | "type": "bytes32"
90 | },
91 | {
92 | "internalType": "uint16",
93 | "name": "destChainId",
94 | "type": "uint16"
95 | },
96 | {
97 | "internalType": "bytes32",
98 | "name": "sourceAddr",
99 | "type": "bytes32"
100 | },
101 | {
102 | "internalType": "uint16",
103 | "name": "sourceChainId",
104 | "type": "uint16"
105 | },
106 | {
107 | "internalType": "uint64",
108 | "name": "sequence",
109 | "type": "uint64"
110 | },
111 | {
112 | "internalType": "uint64",
113 | "name": "amountOutMin",
114 | "type": "uint64"
115 | },
116 | {
117 | "internalType": "uint64",
118 | "name": "deadline",
119 | "type": "uint64"
120 | },
121 | {
122 | "internalType": "uint64",
123 | "name": "swapFee",
124 | "type": "uint64"
125 | },
126 | {
127 | "internalType": "uint64",
128 | "name": "redeemFee",
129 | "type": "uint64"
130 | },
131 | {
132 | "internalType": "uint64",
133 | "name": "refundFee",
134 | "type": "uint64"
135 | },
136 | {
137 | "internalType": "bytes32",
138 | "name": "auctionAddr",
139 | "type": "bytes32"
140 | },
141 | {
142 | "internalType": "bool",
143 | "name": "unwrapRedeem",
144 | "type": "bool"
145 | },
146 | {
147 | "internalType": "bool",
148 | "name": "unwrapRefund",
149 | "type": "bool"
150 | }
151 | ],
152 | "internalType": "struct MayanStructs.Swap",
153 | "name": "s",
154 | "type": "tuple"
155 | }
156 | ],
157 | "name": "encodeSwap",
158 | "outputs": [
159 | {
160 | "internalType": "bytes",
161 | "name": "encoded",
162 | "type": "bytes"
163 | }
164 | ],
165 | "stateMutability": "pure",
166 | "type": "function"
167 | },
168 | {
169 | "inputs": [],
170 | "name": "getWeth",
171 | "outputs": [
172 | {
173 | "internalType": "address",
174 | "name": "",
175 | "type": "address"
176 | }
177 | ],
178 | "stateMutability": "view",
179 | "type": "function"
180 | },
181 | {
182 | "inputs": [],
183 | "name": "isPaused",
184 | "outputs": [
185 | {
186 | "internalType": "bool",
187 | "name": "",
188 | "type": "bool"
189 | }
190 | ],
191 | "stateMutability": "view",
192 | "type": "function"
193 | },
194 | {
195 | "inputs": [
196 | {
197 | "internalType": "bytes",
198 | "name": "encoded",
199 | "type": "bytes"
200 | }
201 | ],
202 | "name": "parseRedeemPayload",
203 | "outputs": [
204 | {
205 | "components": [
206 | {
207 | "internalType": "uint8",
208 | "name": "payloadId",
209 | "type": "uint8"
210 | },
211 | {
212 | "internalType": "bytes32",
213 | "name": "recipient",
214 | "type": "bytes32"
215 | },
216 | {
217 | "internalType": "uint64",
218 | "name": "relayerFee",
219 | "type": "uint64"
220 | },
221 | {
222 | "internalType": "bool",
223 | "name": "unwrap",
224 | "type": "bool"
225 | },
226 | {
227 | "internalType": "uint64",
228 | "name": "gasDrop",
229 | "type": "uint64"
230 | },
231 | {
232 | "internalType": "bytes",
233 | "name": "customPayload",
234 | "type": "bytes"
235 | }
236 | ],
237 | "internalType": "struct MayanStructs.Redeem",
238 | "name": "r",
239 | "type": "tuple"
240 | }
241 | ],
242 | "stateMutability": "pure",
243 | "type": "function"
244 | },
245 | {
246 | "inputs": [
247 | {
248 | "internalType": "bytes",
249 | "name": "encodedVm",
250 | "type": "bytes"
251 | }
252 | ],
253 | "name": "redeem",
254 | "outputs": [],
255 | "stateMutability": "payable",
256 | "type": "function"
257 | },
258 | {
259 | "inputs": [
260 | {
261 | "internalType": "bytes",
262 | "name": "encodedVm",
263 | "type": "bytes"
264 | }
265 | ],
266 | "name": "redeemAndUnwrap",
267 | "outputs": [],
268 | "stateMutability": "nonpayable",
269 | "type": "function"
270 | },
271 | {
272 | "inputs": [
273 | {
274 | "internalType": "bool",
275 | "name": "_pause",
276 | "type": "bool"
277 | }
278 | ],
279 | "name": "setPause",
280 | "outputs": [],
281 | "stateMutability": "nonpayable",
282 | "type": "function"
283 | },
284 | {
285 | "inputs": [
286 | {
287 | "components": [
288 | {
289 | "internalType": "uint64",
290 | "name": "swapFee",
291 | "type": "uint64"
292 | },
293 | {
294 | "internalType": "uint64",
295 | "name": "redeemFee",
296 | "type": "uint64"
297 | },
298 | {
299 | "internalType": "uint64",
300 | "name": "refundFee",
301 | "type": "uint64"
302 | }
303 | ],
304 | "internalType": "struct MayanSwap.RelayerFees",
305 | "name": "relayerFees",
306 | "type": "tuple"
307 | },
308 | {
309 | "components": [
310 | {
311 | "internalType": "bytes32",
312 | "name": "mayanAddr",
313 | "type": "bytes32"
314 | },
315 | {
316 | "internalType": "uint16",
317 | "name": "mayanChainId",
318 | "type": "uint16"
319 | },
320 | {
321 | "internalType": "bytes32",
322 | "name": "auctionAddr",
323 | "type": "bytes32"
324 | },
325 | {
326 | "internalType": "bytes32",
327 | "name": "destAddr",
328 | "type": "bytes32"
329 | },
330 | {
331 | "internalType": "uint16",
332 | "name": "destChainId",
333 | "type": "uint16"
334 | },
335 | {
336 | "internalType": "bytes32",
337 | "name": "referrer",
338 | "type": "bytes32"
339 | },
340 | {
341 | "internalType": "bytes32",
342 | "name": "refundAddr",
343 | "type": "bytes32"
344 | }
345 | ],
346 | "internalType": "struct MayanSwap.Recepient",
347 | "name": "recipient",
348 | "type": "tuple"
349 | },
350 | {
351 | "internalType": "bytes32",
352 | "name": "tokenOutAddr",
353 | "type": "bytes32"
354 | },
355 | {
356 | "internalType": "uint16",
357 | "name": "tokenOutChainId",
358 | "type": "uint16"
359 | },
360 | {
361 | "components": [
362 | {
363 | "internalType": "uint256",
364 | "name": "transferDeadline",
365 | "type": "uint256"
366 | },
367 | {
368 | "internalType": "uint64",
369 | "name": "swapDeadline",
370 | "type": "uint64"
371 | },
372 | {
373 | "internalType": "uint64",
374 | "name": "amountOutMin",
375 | "type": "uint64"
376 | },
377 | {
378 | "internalType": "bool",
379 | "name": "unwrap",
380 | "type": "bool"
381 | },
382 | {
383 | "internalType": "uint64",
384 | "name": "gasDrop",
385 | "type": "uint64"
386 | },
387 | {
388 | "internalType": "bytes",
389 | "name": "customPayload",
390 | "type": "bytes"
391 | }
392 | ],
393 | "internalType": "struct MayanSwap.Criteria",
394 | "name": "criteria",
395 | "type": "tuple"
396 | },
397 | {
398 | "internalType": "address",
399 | "name": "tokenIn",
400 | "type": "address"
401 | },
402 | {
403 | "internalType": "uint256",
404 | "name": "amountIn",
405 | "type": "uint256"
406 | }
407 | ],
408 | "name": "swap",
409 | "outputs": [
410 | {
411 | "internalType": "uint64",
412 | "name": "sequence",
413 | "type": "uint64"
414 | }
415 | ],
416 | "stateMutability": "payable",
417 | "type": "function"
418 | },
419 | {
420 | "inputs": [
421 | {
422 | "internalType": "uint256",
423 | "name": "amount",
424 | "type": "uint256"
425 | },
426 | {
427 | "internalType": "address payable",
428 | "name": "to",
429 | "type": "address"
430 | }
431 | ],
432 | "name": "sweepEth",
433 | "outputs": [],
434 | "stateMutability": "nonpayable",
435 | "type": "function"
436 | },
437 | {
438 | "inputs": [
439 | {
440 | "internalType": "address",
441 | "name": "token",
442 | "type": "address"
443 | },
444 | {
445 | "internalType": "uint256",
446 | "name": "amount",
447 | "type": "uint256"
448 | },
449 | {
450 | "internalType": "address",
451 | "name": "to",
452 | "type": "address"
453 | }
454 | ],
455 | "name": "sweepToken",
456 | "outputs": [],
457 | "stateMutability": "nonpayable",
458 | "type": "function"
459 | },
460 | {
461 | "inputs": [
462 | {
463 | "components": [
464 | {
465 | "internalType": "uint64",
466 | "name": "swapFee",
467 | "type": "uint64"
468 | },
469 | {
470 | "internalType": "uint64",
471 | "name": "redeemFee",
472 | "type": "uint64"
473 | },
474 | {
475 | "internalType": "uint64",
476 | "name": "refundFee",
477 | "type": "uint64"
478 | }
479 | ],
480 | "internalType": "struct MayanSwap.RelayerFees",
481 | "name": "relayerFees",
482 | "type": "tuple"
483 | },
484 | {
485 | "components": [
486 | {
487 | "internalType": "bytes32",
488 | "name": "mayanAddr",
489 | "type": "bytes32"
490 | },
491 | {
492 | "internalType": "uint16",
493 | "name": "mayanChainId",
494 | "type": "uint16"
495 | },
496 | {
497 | "internalType": "bytes32",
498 | "name": "auctionAddr",
499 | "type": "bytes32"
500 | },
501 | {
502 | "internalType": "bytes32",
503 | "name": "destAddr",
504 | "type": "bytes32"
505 | },
506 | {
507 | "internalType": "uint16",
508 | "name": "destChainId",
509 | "type": "uint16"
510 | },
511 | {
512 | "internalType": "bytes32",
513 | "name": "referrer",
514 | "type": "bytes32"
515 | },
516 | {
517 | "internalType": "bytes32",
518 | "name": "refundAddr",
519 | "type": "bytes32"
520 | }
521 | ],
522 | "internalType": "struct MayanSwap.Recepient",
523 | "name": "recipient",
524 | "type": "tuple"
525 | },
526 | {
527 | "internalType": "bytes32",
528 | "name": "tokenOutAddr",
529 | "type": "bytes32"
530 | },
531 | {
532 | "internalType": "uint16",
533 | "name": "tokenOutChainId",
534 | "type": "uint16"
535 | },
536 | {
537 | "components": [
538 | {
539 | "internalType": "uint256",
540 | "name": "transferDeadline",
541 | "type": "uint256"
542 | },
543 | {
544 | "internalType": "uint64",
545 | "name": "swapDeadline",
546 | "type": "uint64"
547 | },
548 | {
549 | "internalType": "uint64",
550 | "name": "amountOutMin",
551 | "type": "uint64"
552 | },
553 | {
554 | "internalType": "bool",
555 | "name": "unwrap",
556 | "type": "bool"
557 | },
558 | {
559 | "internalType": "uint64",
560 | "name": "gasDrop",
561 | "type": "uint64"
562 | },
563 | {
564 | "internalType": "bytes",
565 | "name": "customPayload",
566 | "type": "bytes"
567 | }
568 | ],
569 | "internalType": "struct MayanSwap.Criteria",
570 | "name": "criteria",
571 | "type": "tuple"
572 | }
573 | ],
574 | "name": "wrapAndSwapETH",
575 | "outputs": [
576 | {
577 | "internalType": "uint64",
578 | "name": "sequence",
579 | "type": "uint64"
580 | }
581 | ],
582 | "stateMutability": "payable",
583 | "type": "function"
584 | },
585 | {
586 | "stateMutability": "payable",
587 | "type": "receive"
588 | }
589 | ]
590 | }
591 |
--------------------------------------------------------------------------------
/src/evm/ShuttleArtifact.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | abi: [
3 | {
4 | type: 'function',
5 | name: 'batchMaxApprove',
6 | inputs: [{ name: 'approvals', type: 'bytes', internalType: 'bytes' }],
7 | outputs: [],
8 | stateMutability: 'nonpayable',
9 | },
10 | {
11 | type: 'function',
12 | name: 'initiate',
13 | inputs: [
14 | { name: 'recipient', type: 'bytes32', internalType: 'bytes32' },
15 | {
16 | name: 'overrideAmountIn',
17 | type: 'uint256',
18 | internalType: 'uint256',
19 | },
20 | { name: 'targetChain', type: 'uint16', internalType: 'uint16' },
21 | {
22 | name: 'params',
23 | type: 'bytes',
24 | internalType: 'bytes',
25 | },
26 | ],
27 | outputs: [{ name: '', type: 'bytes', internalType: 'bytes' }],
28 | stateMutability: 'payable',
29 | },
30 | {
31 | type: 'error',
32 | name: 'ChainNotSupported',
33 | inputs: [{ name: 'chain', type: 'uint16', internalType: 'uint16' }],
34 | },
35 | {
36 | type: 'error',
37 | name: 'DeadlineExpired',
38 | inputs: [
39 | { name: 'blocktime', type: 'uint256', internalType: 'uint256' },
40 | {
41 | name: 'deadline',
42 | type: 'uint256',
43 | internalType: 'uint256',
44 | },
45 | ],
46 | },
47 | { type: 'error', name: 'EthTransferFailed', inputs: [] },
48 | {
49 | type: 'error',
50 | name: 'ExceedsMaxGasDropoff',
51 | inputs: [
52 | { name: 'requested', type: 'uint256', internalType: 'uint256' },
53 | {
54 | name: 'maximum',
55 | type: 'uint256',
56 | internalType: 'uint256',
57 | },
58 | ],
59 | },
60 | {
61 | type: 'error',
62 | name: 'ExceedsMaxRelayingFee',
63 | inputs: [
64 | { name: 'fee', type: 'uint256', internalType: 'uint256' },
65 | {
66 | name: 'maximum',
67 | type: 'uint256',
68 | internalType: 'uint256',
69 | },
70 | ],
71 | },
72 | {
73 | type: 'error',
74 | name: 'InsufficientInputAmount',
75 | inputs: [
76 | { name: 'input', type: 'uint256', internalType: 'uint256' },
77 | {
78 | name: 'minimum',
79 | type: 'uint256',
80 | internalType: 'uint256',
81 | },
82 | ],
83 | },
84 | {
85 | type: 'error',
86 | name: 'InvalidAddress',
87 | inputs: [{ name: 'addr', type: 'bytes32', internalType: 'bytes32' }],
88 | },
89 | {
90 | type: 'error',
91 | name: 'InvalidBoolVal',
92 | inputs: [{ name: 'val', type: 'uint8', internalType: 'uint8' }],
93 | },
94 | {
95 | type: 'error',
96 | name: 'InvalidOverrideAmount',
97 | inputs: [
98 | { name: 'received', type: 'uint256', internalType: 'uint256' },
99 | {
100 | name: 'maximum',
101 | type: 'uint256',
102 | internalType: 'uint256',
103 | },
104 | ],
105 | },
106 | {
107 | type: 'error',
108 | name: 'InvalidSwapType',
109 | inputs: [{ name: 'swapType', type: 'uint256', internalType: 'uint256' }],
110 | },
111 | {
112 | type: 'error',
113 | name: 'InvalidSwapTypeForChain',
114 | inputs: [
115 | { name: 'chain', type: 'uint16', internalType: 'uint16' },
116 | {
117 | name: 'swapType',
118 | type: 'uint256',
119 | internalType: 'uint256',
120 | },
121 | ],
122 | },
123 | {
124 | type: 'error',
125 | name: 'LengthMismatch',
126 | inputs: [
127 | { name: 'encodedLength', type: 'uint256', internalType: 'uint256' },
128 | {
129 | name: 'expectedLength',
130 | type: 'uint256',
131 | internalType: 'uint256',
132 | },
133 | ],
134 | },
135 | { type: 'error', name: 'RelayingDisabledForChain', inputs: [] },
136 | ],
137 | };
138 |
139 |
--------------------------------------------------------------------------------
/src/evm/evmFastMctp.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Contract,
3 | toBeHex,
4 | ZeroAddress,
5 | TransactionRequest,
6 | } from 'ethers';
7 | import { SystemProgram } from '@solana/web3.js';
8 | import type { EvmForwarderParams, Quote } from '../types';
9 | import {
10 | nativeAddressToHexString,
11 | getAmountOfFractionalAmount,
12 | getWormholeChainIdByName,
13 | getWormholeChainIdById,
14 | getGasDecimal,
15 | ZeroPermit,
16 | FAST_MCTP_PAYLOAD_TYPE_CUSTOM_PAYLOAD, FAST_MCTP_PAYLOAD_TYPE_DEFAULT, FAST_MCTP_PAYLOAD_TYPE_ORDER
17 | } from '../utils';
18 |
19 | import MayanFastMctpArtifact from './MayanFastMctpArtifact';
20 | import MayanForwarderArtifact from './MayanForwarderArtifact';
21 | import addresses from '../addresses';
22 | import { Buffer } from 'buffer';
23 | import { getCCTPDomain, CCTP_TOKEN_DECIMALS } from '../cctp';
24 | import { Erc20Permit } from '../types';
25 |
26 | type EvmFastMctpBridgeParams = {
27 | tokenIn: string,
28 | amountIn: bigint,
29 | redeemFee: bigint,
30 | gasDrop: bigint,
31 | destAddr: string,
32 | referrerAddr: string,
33 | referrerBps: number,
34 | customPayload: string,
35 | payloadType: number,
36 | destDomain: number,
37 | contractAddress: string,
38 | circleMaxFee: bigint,
39 | minFinalityThreshold: number,
40 | }
41 | function getEvmFastMctpBridgeParams(
42 | quote: Quote, destinationAddress: string,
43 | referrerAddress: string | null | undefined, signerChainId: number | string,
44 | customPayload?: Uint8Array | Buffer | null
45 | ): EvmFastMctpBridgeParams {
46 | const signerWormholeChainId = getWormholeChainIdById(Number(signerChainId));
47 | const sourceChainId = getWormholeChainIdByName(quote.fromChain);
48 | const destChainId = getWormholeChainIdByName(quote.toChain);
49 | if (sourceChainId !== signerWormholeChainId) {
50 | throw new Error(`Signer chain id(${Number(signerChainId)}) and quote from chain are not same! ${sourceChainId} !== ${signerWormholeChainId}`);
51 | }
52 | const destinationAddressHex = nativeAddressToHexString(destinationAddress, destChainId);
53 | const redeemFee = getAmountOfFractionalAmount(quote.redeemRelayerFee, CCTP_TOKEN_DECIMALS);
54 | const gasDrop = getAmountOfFractionalAmount(quote.gasDrop, Math.min(getGasDecimal(quote.toChain), 8));
55 | const circleMaxFee = BigInt(quote.circleMaxFee64);
56 | const amountIn = BigInt(quote.effectiveAmountIn64);
57 | const destDomain = getCCTPDomain(quote.toChain);
58 | let referrerHex: string;
59 | if (referrerAddress) {
60 | referrerHex = nativeAddressToHexString(
61 | referrerAddress, destChainId
62 | );
63 | } else {
64 | referrerHex = nativeAddressToHexString(
65 | SystemProgram.programId.toString(), getWormholeChainIdByName('solana')
66 | );
67 | }
68 |
69 | if (!quote.fastMctpMayanContract) {
70 | throw new Error('FastMctp contract address is missing');
71 | }
72 | const contractAddress = quote.fastMctpMayanContract;
73 |
74 | return {
75 | tokenIn: quote.fastMctpInputContract,
76 | amountIn,
77 | redeemFee,
78 | gasDrop,
79 | destAddr: destinationAddressHex,
80 | destDomain,
81 | referrerAddr: referrerHex,
82 | referrerBps: quote.referrerBps,
83 | payloadType: customPayload ? FAST_MCTP_PAYLOAD_TYPE_CUSTOM_PAYLOAD : FAST_MCTP_PAYLOAD_TYPE_DEFAULT,
84 | customPayload: customPayload ? `0x${Buffer.from(customPayload).toString('hex')}` : '0x',
85 | minFinalityThreshold: Number(quote.fastMctpMinFinality),
86 | circleMaxFee,
87 | contractAddress,
88 | };
89 | }
90 |
91 | function getEvmFastMctpBridgeTxPayload(
92 | quote: Quote, destinationAddress: string,
93 | referrerAddress: string | null | undefined, signerChainId: number | string,
94 | payload: Uint8Array | Buffer | null | undefined
95 | ): TransactionRequest & { _params: EvmFastMctpBridgeParams } {
96 | const params = getEvmFastMctpBridgeParams(
97 | quote, destinationAddress, referrerAddress, signerChainId, payload
98 | );
99 | const {
100 | contractAddress, tokenIn, amountIn, destAddr,
101 | redeemFee, gasDrop, circleMaxFee, referrerAddr, referrerBps,
102 | destDomain, customPayload, minFinalityThreshold, payloadType,
103 | } = params;
104 |
105 | const fastMctpContract = new Contract(contractAddress, MayanFastMctpArtifact.abi);
106 | let data: string;
107 | let value: string | null;
108 | data = fastMctpContract.interface.encodeFunctionData(
109 | 'bridge',
110 | [
111 | tokenIn, amountIn, redeemFee, circleMaxFee, gasDrop, destAddr,
112 | destDomain, referrerAddr, referrerBps, payloadType, minFinalityThreshold, customPayload
113 | ]
114 | );
115 | value = toBeHex(0);
116 |
117 | return {
118 | to: contractAddress,
119 | data,
120 | value,
121 | _params: params
122 | };
123 | }
124 |
125 |
126 | type EvmFastMctpCreateOrderParams = {
127 | tokenIn: string,
128 | amountIn: bigint,
129 | circleMaxFee: bigint,
130 | destDomain: number,
131 | minFinalityThreshold: number,
132 | orderPayload: {
133 | payloadType: number,
134 | destAddr: string,
135 | tokenOut: string,
136 | amountOutMin: bigint,
137 | gasDrop: bigint,
138 | redeemFee: bigint,
139 | refundFee: bigint,
140 | deadline: bigint,
141 | referrerAddr: string,
142 | referrerBps: number,
143 | },
144 | contractAddress: string,
145 | }
146 |
147 | function getEvmFastMctpCreateOrderParams(
148 | quote: Quote, destinationAddress: string,
149 | referrerAddress: string | null | undefined, signerChainId: string | number
150 | ): EvmFastMctpCreateOrderParams {
151 | const signerWormholeChainId = getWormholeChainIdById(Number(signerChainId));
152 | const sourceChainId = getWormholeChainIdByName(quote.fromChain);
153 | const destChainId = getWormholeChainIdByName(quote.toChain);
154 | if (sourceChainId !== signerWormholeChainId) {
155 | throw new Error(`Signer chain id(${Number(signerChainId)}) and quote from chain are not same! ${sourceChainId} !== ${signerWormholeChainId}`);
156 | }
157 | if (!quote.fastMctpMayanContract) {
158 | throw new Error('MCTP contract address is missing');
159 | }
160 | const contractAddress = quote.fastMctpMayanContract;
161 |
162 | const destinationAddressHex = nativeAddressToHexString(destinationAddress, destChainId);
163 | let referrerHex: string;
164 | if (referrerAddress) {
165 | referrerHex = nativeAddressToHexString(
166 | referrerAddress, destChainId
167 | );
168 | } else {
169 | referrerHex = nativeAddressToHexString(
170 | SystemProgram.programId.toString(), getWormholeChainIdByName('solana')
171 | );
172 | }
173 |
174 | const redeemFee = getAmountOfFractionalAmount(quote.redeemRelayerFee, CCTP_TOKEN_DECIMALS);
175 | const refundFee = BigInt(quote.refundRelayerFee64);
176 | const circleMaxFee = BigInt(quote.circleMaxFee64);
177 | const gasDrop = getAmountOfFractionalAmount(quote.gasDrop, Math.min(getGasDecimal(quote.toChain), 8));
178 | const destDomain = getCCTPDomain(quote.toChain);
179 |
180 | const amountIn = BigInt(quote.effectiveAmountIn64);
181 | const amountOutMin = getAmountOfFractionalAmount(
182 | quote.minAmountOut, Math.min(8, quote.toToken.decimals)
183 | );
184 |
185 | const deadline = BigInt(quote.deadline64);
186 |
187 | const tokenOut =
188 | quote.toToken.contract === ZeroAddress ?
189 | nativeAddressToHexString(SystemProgram.programId.toString(), getWormholeChainIdByName('solana')) :
190 | nativeAddressToHexString(
191 | quote.toChain === 'sui' ? quote.toToken.verifiedAddress : quote.toToken.contract,
192 | quote.toToken.wChainId,
193 | );
194 |
195 | return {
196 | tokenIn: quote.fastMctpInputContract,
197 | amountIn,
198 | circleMaxFee,
199 | destDomain,
200 | minFinalityThreshold: Number(quote.fastMctpMinFinality),
201 | orderPayload: {
202 | payloadType: FAST_MCTP_PAYLOAD_TYPE_ORDER,
203 | destAddr: destinationAddressHex,
204 | tokenOut,
205 | amountOutMin,
206 | gasDrop,
207 | redeemFee,
208 | refundFee,
209 | deadline,
210 | referrerAddr: referrerHex,
211 | referrerBps: quote.referrerBps || 0,
212 | },
213 | contractAddress,
214 | };
215 | }
216 |
217 | function getEvmFastMctpCreateOrderTxPayload(
218 | quote: Quote, destinationAddress: string,
219 | referrerAddress: string | null | undefined, signerChainId: string | number
220 | ): TransactionRequest & { _params: EvmFastMctpCreateOrderParams } {
221 | const orderParams = getEvmFastMctpCreateOrderParams(
222 | quote, destinationAddress, referrerAddress, signerChainId
223 | );
224 | const {
225 | contractAddress, orderPayload, tokenIn, amountIn, circleMaxFee, destDomain, minFinalityThreshold
226 | } = orderParams;
227 | const fastMctpContract = new Contract(contractAddress, MayanFastMctpArtifact.abi);
228 | const data = fastMctpContract.interface.encodeFunctionData(
229 | 'createOrder',
230 | [tokenIn, amountIn, circleMaxFee, destDomain, minFinalityThreshold, orderPayload]
231 | );
232 | const value = toBeHex(0);
233 |
234 | return {
235 | to: contractAddress,
236 | data,
237 | value,
238 | _params: orderParams,
239 | };
240 | }
241 |
242 | export function getFastMctpFromEvmTxPayload(
243 | quote: Quote, destinationAddress: string, referrerAddress: string | null | undefined,
244 | signerChainId: number | string, permit: Erc20Permit | null, payload: Uint8Array | Buffer | null | undefined,
245 | ): TransactionRequest & { _forwarder: EvmForwarderParams } {
246 |
247 | if (quote.type !== 'FAST_MCTP') {
248 | throw new Error('Quote type is not FAST_MCTP');
249 | }
250 |
251 | if (!Number.isFinite(Number(signerChainId))) {
252 | throw new Error('Invalid signer chain id');
253 | }
254 |
255 | signerChainId = Number(signerChainId);
256 |
257 | const _permit = permit || ZeroPermit;
258 | const forwarder = new Contract(addresses.MAYAN_FORWARDER_CONTRACT, MayanForwarderArtifact.abi);
259 |
260 | if (quote.fromToken.contract === quote.fastMctpInputContract) {
261 | if (quote.hasAuction) {
262 | if (!Number(quote.deadline64)) {
263 | throw new Error('Fast Mctp order requires timeout');
264 | }
265 | const fastMctpPayloadIx = getEvmFastMctpCreateOrderTxPayload(
266 | quote, destinationAddress, referrerAddress, signerChainId
267 | );
268 |
269 | const forwarderMethod = 'forwardERC20';
270 | const forwarderParams = [
271 | quote.fromToken.contract,
272 | fastMctpPayloadIx._params.amountIn,
273 | _permit,
274 | fastMctpPayloadIx._params.contractAddress,
275 | fastMctpPayloadIx.data,
276 | ];
277 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
278 | return {
279 | data,
280 | to: addresses.MAYAN_FORWARDER_CONTRACT,
281 | value: toBeHex(0),
282 | chainId: signerChainId,
283 | _forwarder: {
284 | method: forwarderMethod,
285 | params: forwarderParams,
286 | }
287 | }
288 | } else {
289 | const fastMctpPayloadIx = getEvmFastMctpBridgeTxPayload(
290 | quote, destinationAddress, referrerAddress, signerChainId, payload
291 | );
292 | const forwarderMethod = 'forwardERC20';
293 | const forwarderParams = [
294 | quote.fromToken.contract,
295 | fastMctpPayloadIx._params.amountIn,
296 | _permit,
297 | fastMctpPayloadIx._params.contractAddress,
298 | fastMctpPayloadIx.data,
299 | ];
300 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
301 | return {
302 | data,
303 | to: addresses.MAYAN_FORWARDER_CONTRACT,
304 | value: toBeHex(0),
305 | chainId: signerChainId,
306 | _forwarder: {
307 | method: forwarderMethod,
308 | params: forwarderParams,
309 | }
310 | }
311 | }
312 | } else {
313 | const { minMiddleAmount, evmSwapRouterAddress, evmSwapRouterCalldata } = quote;
314 | if (!minMiddleAmount || !evmSwapRouterAddress || !evmSwapRouterCalldata) {
315 | throw new Error('Fast Mctp swap requires middle amount, router address and calldata');
316 | }
317 | if (quote.hasAuction) {
318 | if (!Number(quote.deadline64)) {
319 | throw new Error('Fast Mctp order requires timeout');
320 | }
321 | const fastMctpPayloadIx = getEvmFastMctpCreateOrderTxPayload(
322 | quote, destinationAddress, referrerAddress, signerChainId
323 | );
324 | const minMiddleAmount = getAmountOfFractionalAmount(quote.minMiddleAmount, CCTP_TOKEN_DECIMALS);
325 |
326 | if (quote.fromToken.contract === ZeroAddress) {
327 | const forwarderMethod = 'swapAndForwardEth';
328 | const forwarderParams = [
329 | fastMctpPayloadIx._params.amountIn,
330 | evmSwapRouterAddress,
331 | evmSwapRouterCalldata,
332 | quote.fastMctpInputContract,
333 | minMiddleAmount,
334 | fastMctpPayloadIx._params.contractAddress,
335 | fastMctpPayloadIx.data,
336 | ];
337 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
338 | return {
339 | data,
340 | to: addresses.MAYAN_FORWARDER_CONTRACT,
341 | value: toBeHex(fastMctpPayloadIx._params.amountIn),
342 | chainId: signerChainId,
343 | _forwarder: {
344 | method: forwarderMethod,
345 | params: forwarderParams,
346 | }
347 | }
348 | } else {
349 | const forwarderMethod = 'swapAndForwardERC20';
350 | const forwarderParams = [
351 | quote.fromToken.contract,
352 | fastMctpPayloadIx._params.amountIn,
353 | _permit,
354 | evmSwapRouterAddress,
355 | evmSwapRouterCalldata,
356 | quote.fastMctpInputContract,
357 | minMiddleAmount,
358 | fastMctpPayloadIx._params.contractAddress,
359 | fastMctpPayloadIx.data,
360 | ];
361 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
362 | return {
363 | data,
364 | to: addresses.MAYAN_FORWARDER_CONTRACT,
365 | value: toBeHex(0),
366 | chainId: signerChainId,
367 | _forwarder: {
368 | method: forwarderMethod,
369 | params: forwarderParams,
370 | }
371 | }
372 | }
373 | } else {
374 | const fastMctpPayloadIx = getEvmFastMctpBridgeTxPayload(
375 | quote, destinationAddress, referrerAddress, signerChainId, payload
376 | );
377 | const minMiddleAmount = getAmountOfFractionalAmount(quote.minMiddleAmount, CCTP_TOKEN_DECIMALS);
378 |
379 | if (quote.fromToken.contract === ZeroAddress) {
380 | const forwarderMethod = 'swapAndForwardEth';
381 | const forwarderParams = [
382 | fastMctpPayloadIx._params.amountIn,
383 | evmSwapRouterAddress,
384 | evmSwapRouterCalldata,
385 | quote.fastMctpInputContract,
386 | minMiddleAmount,
387 | fastMctpPayloadIx._params.contractAddress,
388 | fastMctpPayloadIx.data,
389 | ];
390 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
391 | return {
392 | data,
393 | to: addresses.MAYAN_FORWARDER_CONTRACT,
394 | value: toBeHex(fastMctpPayloadIx._params.amountIn),
395 | chainId: signerChainId,
396 | _forwarder: {
397 | method: forwarderMethod,
398 | params: forwarderParams,
399 | }
400 | }
401 | } else {
402 | const forwarderMethod = 'swapAndForwardERC20';
403 | const forwarderParams = [
404 | quote.fromToken.contract,
405 | fastMctpPayloadIx._params.amountIn,
406 | _permit,
407 | evmSwapRouterAddress,
408 | evmSwapRouterCalldata,
409 | quote.fastMctpInputContract,
410 | minMiddleAmount,
411 | fastMctpPayloadIx._params.contractAddress,
412 | fastMctpPayloadIx.data,
413 | ]
414 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
415 | return {
416 | data,
417 | to: addresses.MAYAN_FORWARDER_CONTRACT,
418 | value: toBeHex(0),
419 | chainId: signerChainId,
420 | _forwarder: {
421 | method: forwarderMethod,
422 | params: forwarderParams,
423 | }
424 | }
425 | }
426 | }
427 | }
428 | }
429 |
--------------------------------------------------------------------------------
/src/evm/evmHyperCore.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Contract,
3 | toBeHex,
4 | ZeroAddress,
5 | TransactionRequest,
6 | } from 'ethers';
7 | import { SystemProgram } from '@solana/web3.js';
8 | import type { EvmForwarderParams, Quote } from '../types';
9 | import {
10 | nativeAddressToHexString,
11 | getAmountOfFractionalAmount,
12 | getWormholeChainIdByName,
13 | getWormholeChainIdById,
14 | getGasDecimal,
15 | ZeroPermit,
16 | hexToUint8Array
17 | } from '../utils';
18 |
19 | import HCDepositInitiatorArtifact from './HCDepositInitiatorArtifact';
20 | import MayanForwarderArtifact from './MayanForwarderArtifact';
21 | import addresses from '../addresses';
22 | import { Buffer } from 'buffer';
23 | import { CCTP_TOKEN_DECIMALS } from '../cctp';
24 | import { Erc20Permit } from '../types';
25 |
26 | function getUsdcDepositInitiatorMctpTxPayload(
27 | quote: Quote,
28 | swapperAddress: string,
29 | destinationAddress: string,
30 | usdcPermitSignature: string,
31 | ): TransactionRequest & { _params: { amountIn: bigint, contractAddress: string } } {
32 | if (!quote.hyperCoreParams.initiateContractAddress) {
33 | throw new Error('HyperCore initiate contract address is missing');
34 | }
35 | if (quote.type !== 'MCTP') {
36 | throw new Error('Unsupported quote type for HyperCore deposit: ' + quote.type);
37 | }
38 |
39 | const initiatorContract = new Contract(
40 | quote.hyperCoreParams.initiateContractAddress,
41 | HCDepositInitiatorArtifact.abi
42 | );
43 |
44 | const signatureBuf = Buffer.from(hexToUint8Array(usdcPermitSignature));
45 | if (signatureBuf.length !== 65) {
46 | throw new Error('Invalid USDC permit signature length');
47 | }
48 | const r = '0x' + signatureBuf.subarray(0, 32).toString('hex');
49 | const s = '0x' + signatureBuf.subarray(32, 64).toString('hex');
50 | const v = signatureBuf[64];
51 |
52 | let data: string;
53 | let value: string | null;
54 | data = initiatorContract.interface.encodeFunctionData('deposit', [
55 | quote.hyperCoreParams.initiateTokenContract,
56 | BigInt(quote.hyperCoreParams.bridgeAmountUSDC64),
57 | swapperAddress,
58 | getAmountOfFractionalAmount(
59 | quote.hyperCoreParams.failureGasDrop,
60 | Math.min(getGasDecimal('arbitrum'), 8)
61 | ),
62 | BigInt(quote.hyperCoreParams.bridgeAmountUSDC64),
63 | {
64 | relayerFee: getAmountOfFractionalAmount(quote.redeemRelayerFee, CCTP_TOKEN_DECIMALS),
65 | permit: {
66 | user: destinationAddress,
67 | usd: BigInt(quote.hyperCoreParams.depositAmountUSDC64),
68 | deadline: BigInt(quote.deadline64),
69 | signature: {
70 | r,
71 | s,
72 | v,
73 | },
74 | }
75 | },
76 | ]);
77 | value = toBeHex(0);
78 |
79 | return {
80 | to: quote.hyperCoreParams.initiateContractAddress,
81 | data,
82 | value,
83 | _params: {
84 | amountIn: BigInt(quote.hyperCoreParams.bridgeAmountUSDC64),
85 | contractAddress: quote.hyperCoreParams.initiateContractAddress,
86 | },
87 | };
88 | }
89 |
90 | function getUsdcDepositInitiatorFastMctpTxPayload(
91 | quote: Quote,
92 | swapperAddress: string,
93 | destinationAddress: string,
94 | referrerAddress: string | null | undefined,
95 | usdcPermitSignature: string,
96 | ): TransactionRequest & { _params: { amountIn: bigint, contractAddress: string } } {
97 | const destChainId = getWormholeChainIdByName('arbitrum');
98 | if (!quote.hyperCoreParams.initiateContractAddress) {
99 | throw new Error('HyperCore initiate contract address is missing');
100 | }
101 | if (quote.type !== 'FAST_MCTP') {
102 | throw new Error('Unsupported quote type for HyperCore deposit: ' + quote.type);
103 | }
104 |
105 | const initiatorContract = new Contract(
106 | quote.hyperCoreParams.initiateContractAddress,
107 | HCDepositInitiatorArtifact.abi
108 | );
109 |
110 | const signatureBuf = Buffer.from(hexToUint8Array(usdcPermitSignature));
111 | if (signatureBuf.length !== 65) {
112 | throw new Error('Invalid USDC permit signature length');
113 | }
114 | const r = '0x' + signatureBuf.subarray(0, 32).toString('hex');
115 | const s = '0x' + signatureBuf.subarray(32, 64).toString('hex');
116 | const v = signatureBuf[64];
117 |
118 | let referrerHex: string;
119 | if (referrerAddress) {
120 | referrerHex = nativeAddressToHexString(
121 | referrerAddress, destChainId
122 | );
123 | } else {
124 | referrerHex = nativeAddressToHexString(
125 | SystemProgram.programId.toString(), getWormholeChainIdByName('solana')
126 | );
127 | }
128 |
129 | let data: string;
130 | let value: string | null;
131 | data = initiatorContract.interface.encodeFunctionData('fastDeposit', [
132 | quote.hyperCoreParams.initiateTokenContract,
133 | BigInt(quote.hyperCoreParams.bridgeAmountUSDC64),
134 | swapperAddress,
135 | BigInt(quote.circleMaxFee64),
136 | getAmountOfFractionalAmount(
137 | quote.hyperCoreParams.failureGasDrop,
138 | Math.min(getGasDecimal('arbitrum'), 8)
139 | ),
140 | referrerHex,
141 | quote.referrerBps,
142 | Number(quote.fastMctpMinFinality),
143 | BigInt(quote.hyperCoreParams.bridgeAmountUSDC64),
144 | {
145 | relayerFee: getAmountOfFractionalAmount(quote.redeemRelayerFee, CCTP_TOKEN_DECIMALS),
146 | permit: {
147 | user: destinationAddress,
148 | usd: BigInt(quote.hyperCoreParams.depositAmountUSDC64),
149 | deadline: BigInt(quote.deadline64),
150 | signature: {
151 | r,
152 | s,
153 | v,
154 | },
155 | }
156 | },
157 | ]);
158 | value = toBeHex(0);
159 |
160 | return {
161 | to: quote.hyperCoreParams.initiateContractAddress,
162 | data,
163 | value,
164 | _params: {
165 | amountIn: BigInt(quote.hyperCoreParams.bridgeAmountUSDC64),
166 | contractAddress: quote.hyperCoreParams.initiateContractAddress,
167 | },
168 | };
169 | }
170 |
171 | export function getHyperCoreDepositFromEvmTxPayload(
172 | quote: Quote, swapperAddress: string, destinationAddress: string, referrerAddress: string | null | undefined,
173 | signerChainId: number | string, permit: Erc20Permit | null, payload: Uint8Array | Buffer | null | undefined,
174 | options: {
175 | usdcPermitSignature?: string;
176 | } = {}
177 | ): TransactionRequest & { _forwarder: EvmForwarderParams } {
178 |
179 | if (
180 | quote.toToken.contract.toLowerCase() !== addresses.ARBITRUM_USDC_CONTRACT.toLowerCase() ||
181 | (quote.type !== 'MCTP' && quote.type !== 'FAST_MCTP')
182 | ) {
183 | throw new Error('Unsupported quote type for USDC deposit: ' + quote.type);
184 | }
185 | if (!options?.usdcPermitSignature) {
186 | throw new Error('USDC permit signature is required for this quote');
187 | }
188 | if (!quote.hyperCoreParams) {
189 | throw new Error('HyperCore parameters are required for this quote');
190 | }
191 | if (payload) {
192 | throw new Error('HyperCore deposit does not support payload');
193 | }
194 |
195 | if (!Number.isFinite(Number(signerChainId))) {
196 | throw new Error('Invalid signer chain id');
197 | }
198 |
199 | const signerWormholeChainId = getWormholeChainIdById(Number(signerChainId));
200 | const sourceChainId = getWormholeChainIdByName(quote.fromChain);
201 | if (sourceChainId !== signerWormholeChainId) {
202 | throw new Error(`Signer chain id(${Number(signerChainId)}) and quote from chain are not same! ${sourceChainId} !== ${signerWormholeChainId}`);
203 | }
204 | const _permit = permit || ZeroPermit;
205 | const forwarder = new Contract(addresses.MAYAN_FORWARDER_CONTRACT, MayanForwarderArtifact.abi);
206 |
207 | let initiatorPayloadIx: TransactionRequest & { _params: { amountIn: bigint, contractAddress: string } };
208 | if (!Number(quote.deadline64)) {
209 | throw new Error('HyperCore deposit requires timeout');
210 | }
211 | if (quote.type === 'MCTP') {
212 | initiatorPayloadIx = getUsdcDepositInitiatorMctpTxPayload(
213 | quote, swapperAddress, destinationAddress, options.usdcPermitSignature
214 | );
215 | } else if (quote.type === 'FAST_MCTP') {
216 | initiatorPayloadIx = getUsdcDepositInitiatorFastMctpTxPayload(
217 | quote, swapperAddress, destinationAddress, referrerAddress, options.usdcPermitSignature
218 | );
219 | } else {
220 | throw new Error('Unsupported quote type for HyperCore deposit: ' + quote.type);
221 | }
222 |
223 | if (quote.fromToken.contract.toLowerCase() === quote.hyperCoreParams.initiateTokenContract.toLowerCase()) {
224 | const forwarderMethod = 'forwardERC20';
225 | const forwarderParams = [
226 | quote.fromToken.contract,
227 | BigInt(quote.effectiveAmountIn64),
228 | _permit,
229 | initiatorPayloadIx._params.contractAddress,
230 | initiatorPayloadIx.data,
231 | ];
232 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
233 | return {
234 | data,
235 | to: addresses.MAYAN_FORWARDER_CONTRACT,
236 | value: toBeHex(0),
237 | chainId: signerChainId,
238 | _forwarder: {
239 | method: forwarderMethod,
240 | params: forwarderParams,
241 | }
242 |
243 | }
244 | } else {
245 | const { evmSwapRouterAddress, evmSwapRouterCalldata } = quote;
246 | if (!quote.minMiddleAmount || !evmSwapRouterAddress || !evmSwapRouterCalldata) {
247 | throw new Error('Fast Mctp swap requires middle amount, router address and calldata');
248 | }
249 | const minMiddleAmount = getAmountOfFractionalAmount(quote.minMiddleAmount, CCTP_TOKEN_DECIMALS);
250 |
251 | if (quote.fromToken.contract === ZeroAddress) {
252 | const forwarderMethod = 'swapAndForwardEth';
253 | const forwarderParams = [
254 | BigInt(quote.effectiveAmountIn64),
255 | evmSwapRouterAddress,
256 | evmSwapRouterCalldata,
257 | quote.hyperCoreParams.initiateTokenContract,
258 | minMiddleAmount,
259 | initiatorPayloadIx._params.contractAddress,
260 | initiatorPayloadIx.data,
261 | ];
262 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
263 | return {
264 | data,
265 | to: addresses.MAYAN_FORWARDER_CONTRACT,
266 | value: toBeHex(BigInt(quote.effectiveAmountIn64)),
267 | chainId: signerChainId,
268 | _forwarder: {
269 | method: forwarderMethod,
270 | params: forwarderParams,
271 | }
272 | }
273 | } else {
274 | const forwarderMethod = 'swapAndForwardERC20';
275 | const forwarderParams = [
276 | quote.fromToken.contract,
277 | BigInt(quote.effectiveAmountIn64),
278 | _permit,
279 | evmSwapRouterAddress,
280 | evmSwapRouterCalldata,
281 | quote.hyperCoreParams.initiateTokenContract,
282 | minMiddleAmount,
283 | initiatorPayloadIx._params.contractAddress,
284 | initiatorPayloadIx.data,
285 | ];
286 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
287 | return {
288 | data,
289 | to: addresses.MAYAN_FORWARDER_CONTRACT,
290 | value: toBeHex(0),
291 | chainId: signerChainId,
292 | _forwarder: {
293 | method: forwarderMethod,
294 | params: forwarderParams,
295 | }
296 | }
297 | }
298 | }
299 | }
300 |
--------------------------------------------------------------------------------
/src/evm/evmMctp.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Contract,
3 | toBeHex,
4 | ZeroAddress,
5 | TransactionRequest,
6 | } from 'ethers';
7 | import { SystemProgram } from '@solana/web3.js';
8 | import type { EvmForwarderParams, Quote } from '../types';
9 | import {
10 | nativeAddressToHexString,
11 | getAmountOfFractionalAmount, getWormholeChainIdByName,
12 | getWormholeChainIdById, getGasDecimal, ZeroPermit, MCTP_PAYLOAD_TYPE_CUSTOM_PAYLOAD, MCTP_PAYLOAD_TYPE_DEFAULT
13 | } from '../utils';
14 |
15 | import MayanCircleArtifact from './MayanCircleArtifact';
16 | import MayanForwarderArtifact from './MayanForwarderArtifact';
17 | import addresses from '../addresses';
18 | import { Buffer } from 'buffer';
19 | import { getCCTPDomain, CCTP_TOKEN_DECIMALS } from '../cctp';
20 | import { Erc20Permit } from '../types';
21 |
22 | type EvmMctpBridgeParams = {
23 | lockFee: boolean,
24 | tokenIn: string,
25 | amountIn: bigint,
26 | redeemFee: bigint,
27 | gasDrop: bigint,
28 | destAddr: string,
29 | customPayload: string,
30 | payloadType: number,
31 | destDomain: number,
32 | bridgeFee: bigint,
33 | contractAddress: string,
34 | }
35 | function getEvmMctpBridgeParams(
36 | quote: Quote, destinationAddress: string, signerChainId: number | string, customPayload?: Uint8Array | Buffer | null
37 | ): EvmMctpBridgeParams {
38 | const signerWormholeChainId = getWormholeChainIdById(Number(signerChainId));
39 | const sourceChainId = getWormholeChainIdByName(quote.fromChain);
40 | const destChainId = getWormholeChainIdByName(quote.toChain);
41 | if (sourceChainId !== signerWormholeChainId) {
42 | throw new Error(`Signer chain id(${Number(signerChainId)}) and quote from chain are not same! ${sourceChainId} !== ${signerWormholeChainId}`);
43 | }
44 | const lockFee: boolean = quote.cheaperChain === quote.fromChain;
45 | if (lockFee && !!customPayload) {
46 | throw new Error('Bridge lock fee cannot have custom payload');
47 | }
48 | const destinationAddressHex = nativeAddressToHexString(destinationAddress, destChainId);
49 | const redeemFee = getAmountOfFractionalAmount(quote.redeemRelayerFee, CCTP_TOKEN_DECIMALS);
50 | const gasDrop = getAmountOfFractionalAmount(quote.gasDrop, Math.min(getGasDecimal(quote.toChain), 8));
51 | const amountIn = BigInt(quote.effectiveAmountIn64);
52 | const destDomain = getCCTPDomain(quote.toChain);
53 |
54 | if (!quote.mctpMayanContract) {
55 | throw new Error('MCTP contract address is missing');
56 | }
57 | const contractAddress = quote.mctpMayanContract;
58 |
59 | if (quote.toChain === 'solana' && lockFee) {
60 | throw new Error('Cannot lock fee for transfer to solana');
61 | }
62 |
63 | let bridgeFee = getAmountOfFractionalAmount(
64 | quote.bridgeFee, getGasDecimal(quote.fromChain)
65 | );
66 | if (lockFee) {
67 | bridgeFee = BigInt(0);
68 | }
69 |
70 |
71 |
72 | return {
73 | lockFee,
74 | tokenIn: quote.mctpInputContract,
75 | amountIn,
76 | redeemFee,
77 | gasDrop,
78 | destAddr: destinationAddressHex,
79 | destDomain,
80 | payloadType: customPayload ? MCTP_PAYLOAD_TYPE_CUSTOM_PAYLOAD : MCTP_PAYLOAD_TYPE_DEFAULT,
81 | customPayload: customPayload ? `0x${Buffer.from(customPayload).toString('hex')}` : '0x',
82 | bridgeFee,
83 | contractAddress,
84 | };
85 | }
86 |
87 | function getEvmMctpBridgeTxPayload(
88 | quote: Quote, destinationAddress: string, signerChainId: number | string,
89 | payload: Uint8Array | Buffer | null | undefined
90 | ): TransactionRequest & { _params: EvmMctpBridgeParams } {
91 | const params = getEvmMctpBridgeParams(
92 | quote, destinationAddress, signerChainId, payload
93 | );
94 | const {
95 | contractAddress, tokenIn, amountIn, destAddr,
96 | lockFee, redeemFee, gasDrop,
97 | destDomain, customPayload, payloadType, bridgeFee
98 | } = params;
99 |
100 | const mctpContract = new Contract(contractAddress, MayanCircleArtifact.abi);
101 | let data: string;
102 | let value: string | null;
103 | if (lockFee) {
104 | data = mctpContract.interface.encodeFunctionData(
105 | 'bridgeWithLockedFee',
106 | [tokenIn, amountIn, gasDrop, redeemFee, destDomain, destAddr]
107 | );
108 | } else {
109 | data = mctpContract.interface.encodeFunctionData(
110 | 'bridgeWithFee',
111 | [tokenIn, amountIn, redeemFee, gasDrop, destAddr, destDomain, payloadType, customPayload]
112 | );
113 | }
114 | value = toBeHex(bridgeFee);
115 |
116 | return {
117 | to: contractAddress,
118 | data,
119 | value,
120 | _params: params
121 | };
122 | }
123 |
124 |
125 | type EvmMctpCreateOrderParams = {
126 | params: {
127 | tokenIn: string,
128 | amountIn: bigint,
129 | gasDrop: bigint,
130 | destAddr: string,
131 | destChain: number,
132 | tokenOut: string,
133 | minAmountOut: bigint,
134 | deadline: bigint,
135 | redeemFee: bigint,
136 | referrerAddr: string,
137 | referrerBps: number,
138 | },
139 | bridgeFee: bigint,
140 | contractAddress: string,
141 | }
142 |
143 | function getEvmMctpCreateOrderParams(
144 | quote: Quote, destinationAddress: string,
145 | referrerAddress: string | null | undefined, signerChainId: string | number
146 | ): EvmMctpCreateOrderParams {
147 | const signerWormholeChainId = getWormholeChainIdById(Number(signerChainId));
148 | const sourceChainId = getWormholeChainIdByName(quote.fromChain);
149 | const destChainId = getWormholeChainIdByName(quote.toChain);
150 | if (sourceChainId !== signerWormholeChainId) {
151 | throw new Error(`Signer chain id(${Number(signerChainId)}) and quote from chain are not same! ${sourceChainId} !== ${signerWormholeChainId}`);
152 | }
153 | if (!quote.mctpMayanContract) {
154 | throw new Error('MCTP contract address is missing');
155 | }
156 | const contractAddress = quote.mctpMayanContract;
157 |
158 | const destinationAddressHex = nativeAddressToHexString(destinationAddress, destChainId);
159 | let referrerHex: string;
160 | if (referrerAddress) {
161 | referrerHex = nativeAddressToHexString(
162 | referrerAddress, destChainId
163 | );
164 | } else {
165 | referrerHex = nativeAddressToHexString(
166 | SystemProgram.programId.toString(), getWormholeChainIdByName('solana')
167 | );
168 | }
169 |
170 | const redeemFee = getAmountOfFractionalAmount(quote.redeemRelayerFee, CCTP_TOKEN_DECIMALS);
171 | const gasDrop = getAmountOfFractionalAmount(quote.gasDrop, Math.min(getGasDecimal(quote.toChain), 8));
172 |
173 | let amountIn = BigInt(quote.effectiveAmountIn64);
174 | const minAmountOut = getAmountOfFractionalAmount(
175 | quote.minAmountOut, Math.min(8, quote.toToken.decimals)
176 | );
177 |
178 | const deadline = BigInt(quote.deadline64);
179 |
180 | const tokenOut =
181 | quote.toToken.contract === ZeroAddress ?
182 | nativeAddressToHexString(SystemProgram.programId.toString(), getWormholeChainIdByName('solana')) :
183 | nativeAddressToHexString(
184 | quote.toChain === 'sui' ? quote.toToken.verifiedAddress : quote.toToken.contract,
185 | quote.toToken.wChainId,
186 | );
187 |
188 | return {
189 | params: {
190 | tokenIn: quote.mctpInputContract,
191 | amountIn,
192 | gasDrop,
193 | destAddr: destinationAddressHex,
194 | destChain: destChainId,
195 | tokenOut,
196 | minAmountOut,
197 | deadline,
198 | redeemFee,
199 | referrerAddr: referrerHex,
200 | referrerBps: quote.referrerBps || 0
201 | },
202 | bridgeFee: getAmountOfFractionalAmount(quote.bridgeFee, getGasDecimal(quote.fromChain)),
203 | contractAddress,
204 | };
205 | }
206 |
207 | function getEvmMctpCreateOrderTxPayload(
208 | quote: Quote, destinationAddress: string,
209 | referrerAddress: string | null | undefined, signerChainId: string | number
210 | ): TransactionRequest & { _params: EvmMctpCreateOrderParams } {
211 | const orderParams = getEvmMctpCreateOrderParams(
212 | quote, destinationAddress, referrerAddress, signerChainId
213 | );
214 | const {
215 | contractAddress, params, bridgeFee
216 | } = orderParams;
217 | const mctpContract = new Contract(contractAddress, MayanCircleArtifact.abi);
218 | const data = mctpContract.interface.encodeFunctionData(
219 | 'createOrder',
220 | [params]
221 | );
222 | const value = toBeHex(bridgeFee);
223 |
224 | return {
225 | to: contractAddress,
226 | data,
227 | value,
228 | _params: orderParams,
229 | };
230 | }
231 |
232 | export function getMctpFromEvmTxPayload(
233 | quote: Quote, destinationAddress: string, referrerAddress: string | null | undefined,
234 | signerChainId: number | string, permit: Erc20Permit | null, payload: Uint8Array | Buffer | null | undefined,
235 | ): TransactionRequest & { _forwarder: EvmForwarderParams } {
236 |
237 | if (quote.type !== 'MCTP') {
238 | throw new Error('Quote type is not MCTP');
239 | }
240 |
241 | if (!Number.isFinite(Number(signerChainId))) {
242 | throw new Error('Invalid signer chain id');
243 | }
244 |
245 | signerChainId = Number(signerChainId);
246 |
247 | const _permit = permit || ZeroPermit;
248 | const forwarder = new Contract(addresses.MAYAN_FORWARDER_CONTRACT, MayanForwarderArtifact.abi);
249 |
250 | const bridgeFee = getAmountOfFractionalAmount(
251 | quote.bridgeFee, getGasDecimal(quote.fromChain)
252 | );
253 |
254 | let value = toBeHex(bridgeFee);
255 |
256 | if (quote.fromToken.contract === quote.mctpInputContract) {
257 | if (quote.hasAuction) {
258 | if (!Number(quote.deadline64)) {
259 | throw new Error('MCTP order requires timeout');
260 | }
261 | const mctpPayloadIx = getEvmMctpCreateOrderTxPayload(
262 | quote, destinationAddress, referrerAddress, signerChainId
263 | );
264 |
265 | const forwarderMethod = 'forwardERC20';
266 | const forwarderParams = [
267 | quote.fromToken.contract,
268 | mctpPayloadIx._params.params.amountIn,
269 | _permit,
270 | mctpPayloadIx._params.contractAddress,
271 | mctpPayloadIx.data,
272 | ];
273 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
274 | return {
275 | data,
276 | to: addresses.MAYAN_FORWARDER_CONTRACT,
277 | value: toBeHex(value),
278 | chainId: signerChainId,
279 | _forwarder: {
280 | method: forwarderMethod,
281 | params: forwarderParams,
282 | }
283 | }
284 | } else {
285 | const mctpPayloadIx = getEvmMctpBridgeTxPayload(
286 | quote, destinationAddress, signerChainId, payload
287 | );
288 | const forwarderMethod = 'forwardERC20';
289 | const forwarderParams = [
290 | quote.fromToken.contract,
291 | mctpPayloadIx._params.amountIn,
292 | _permit,
293 | mctpPayloadIx._params.contractAddress,
294 | mctpPayloadIx.data,
295 | ];
296 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
297 | return {
298 | data,
299 | to: addresses.MAYAN_FORWARDER_CONTRACT,
300 | value: toBeHex(value),
301 | chainId: signerChainId,
302 | _forwarder: {
303 | method: forwarderMethod,
304 | params: forwarderParams,
305 | }
306 | }
307 | }
308 | } else {
309 | const { minMiddleAmount, evmSwapRouterAddress, evmSwapRouterCalldata } = quote;
310 | if (!minMiddleAmount || !evmSwapRouterAddress || !evmSwapRouterCalldata) {
311 | throw new Error('MCTP swap requires middle amount, router address and calldata');
312 | }
313 | if (quote.hasAuction) {
314 | if (!Number(quote.deadline64)) {
315 | throw new Error('MCTP order requires timeout');
316 | }
317 | const mctpPayloadIx = getEvmMctpCreateOrderTxPayload(
318 | quote, destinationAddress, referrerAddress, signerChainId
319 | );
320 | const minMiddleAmount = getAmountOfFractionalAmount(quote.minMiddleAmount, CCTP_TOKEN_DECIMALS);
321 |
322 | if (quote.fromToken.contract === ZeroAddress) {
323 |
324 | let amountIn = mctpPayloadIx._params.params.amountIn;
325 | if (amountIn <= bridgeFee) {
326 | throw new Error('Amount in is less than bridge fee');
327 | }
328 | if (bridgeFee !== BigInt(0)) {
329 | amountIn -= bridgeFee;
330 | }
331 |
332 | value = toBeHex(mctpPayloadIx._params.params.amountIn);
333 |
334 | const forwarderMethod = 'swapAndForwardEth';
335 | const forwarderParams = [
336 | amountIn,
337 | evmSwapRouterAddress,
338 | evmSwapRouterCalldata,
339 | quote.mctpInputContract,
340 | minMiddleAmount,
341 | mctpPayloadIx._params.contractAddress,
342 | mctpPayloadIx.data,
343 | ];
344 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
345 | return {
346 | data,
347 | to: addresses.MAYAN_FORWARDER_CONTRACT,
348 | value: toBeHex(value),
349 | chainId: signerChainId,
350 | _forwarder: {
351 | method: forwarderMethod,
352 | params: forwarderParams,
353 | }
354 | }
355 | } else {
356 | const forwarderMethod = 'swapAndForwardERC20';
357 | const forwarderParams = [
358 | quote.fromToken.contract,
359 | mctpPayloadIx._params.params.amountIn,
360 | _permit,
361 | evmSwapRouterAddress,
362 | evmSwapRouterCalldata,
363 | quote.mctpInputContract,
364 | minMiddleAmount,
365 | mctpPayloadIx._params.contractAddress,
366 | mctpPayloadIx.data,
367 | ];
368 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
369 | return {
370 | data,
371 | to: addresses.MAYAN_FORWARDER_CONTRACT,
372 | value: toBeHex(value),
373 | chainId: signerChainId,
374 | _forwarder: {
375 | method: forwarderMethod,
376 | params: forwarderParams,
377 | }
378 | }
379 | }
380 | } else {
381 | const mctpPayloadIx = getEvmMctpBridgeTxPayload(
382 | quote, destinationAddress, signerChainId, payload
383 | );
384 | const minMiddleAmount = getAmountOfFractionalAmount(quote.minMiddleAmount, CCTP_TOKEN_DECIMALS);
385 |
386 | if (quote.fromToken.contract === ZeroAddress) {
387 | let amountIn = mctpPayloadIx._params.amountIn;
388 | if (amountIn <= bridgeFee) {
389 | throw new Error('Amount in is less than bridge fee');
390 | }
391 | if (bridgeFee !== BigInt(0)) {
392 | amountIn -= bridgeFee;
393 | }
394 |
395 | value = toBeHex(mctpPayloadIx._params.amountIn);
396 |
397 | const forwarderMethod = 'swapAndForwardEth';
398 | const forwarderParams = [
399 | amountIn,
400 | evmSwapRouterAddress,
401 | evmSwapRouterCalldata,
402 | quote.mctpInputContract,
403 | minMiddleAmount,
404 | mctpPayloadIx._params.contractAddress,
405 | mctpPayloadIx.data,
406 | ];
407 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
408 | return {
409 | data,
410 | to: addresses.MAYAN_FORWARDER_CONTRACT,
411 | value: toBeHex(value),
412 | chainId: signerChainId,
413 | _forwarder: {
414 | method: forwarderMethod,
415 | params: forwarderParams,
416 | }
417 | }
418 | } else {
419 | const forwarderMethod = 'swapAndForwardERC20';
420 | const forwarderParams = [
421 | quote.fromToken.contract,
422 | mctpPayloadIx._params.amountIn,
423 | _permit,
424 | evmSwapRouterAddress,
425 | evmSwapRouterCalldata,
426 | quote.mctpInputContract,
427 | minMiddleAmount,
428 | mctpPayloadIx._params.contractAddress,
429 | mctpPayloadIx.data,
430 | ]
431 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
432 | return {
433 | data,
434 | to: addresses.MAYAN_FORWARDER_CONTRACT,
435 | value: toBeHex(value),
436 | chainId: signerChainId,
437 | _forwarder: {
438 | method: forwarderMethod,
439 | params: forwarderParams,
440 | }
441 | }
442 | }
443 | }
444 | }
445 | }
446 |
--------------------------------------------------------------------------------
/src/evm/evmMonoChain.ts:
--------------------------------------------------------------------------------
1 | import { Contract, toBeHex, ZeroAddress, TransactionRequest } from 'ethers';
2 | import { SystemProgram } from '@solana/web3.js';
3 | import type { EvmForwarderParams, Quote } from '../types';
4 | import {
5 | nativeAddressToHexString,
6 | getAmountOfFractionalAmount,
7 | getWormholeChainIdByName,
8 | getWormholeChainIdById,
9 | ZeroPermit,
10 | } from '../utils';
11 |
12 | import MayanMonoChainArtifact from './MayanMonoChainArtifact';
13 | import MayanForwarderArtifact from './MayanForwarderArtifact';
14 | import addresses from '../addresses';
15 | import { Erc20Permit } from '../types';
16 |
17 | function getEvmMonoChainTxPayload(
18 | quote: Quote,
19 | destinationAddress: string,
20 | referrerAddress: string | null | undefined,
21 | ): TransactionRequest & { _params: { amountIn: bigint; tokenIn: string } } {
22 | const amountOut = getAmountOfFractionalAmount(
23 | quote.expectedAmountOut,
24 | quote.toToken.decimals
25 | );
26 | const monoChainContract = new Contract(
27 | quote.monoChainMayanContract,
28 | MayanMonoChainArtifact.abi
29 | );
30 | const referrerBps = referrerAddress ? quote.referrerBps || 0 : 0;
31 |
32 | let data: string;
33 | let value: string | null;
34 | if (quote.toToken.contract === ZeroAddress) {
35 | data = monoChainContract.interface.encodeFunctionData('transferEth', [
36 | destinationAddress,
37 | referrerAddress || ZeroAddress,
38 | referrerBps,
39 | ]);
40 | } else {
41 | data = monoChainContract.interface.encodeFunctionData('transferToken', [
42 | quote.toToken.contract,
43 | amountOut,
44 | destinationAddress,
45 | referrerAddress || ZeroAddress,
46 | referrerBps,
47 | ]);
48 | }
49 | if (quote.fromToken.contract === ZeroAddress) {
50 | value = toBeHex(quote.effectiveAmountIn64);
51 | } else {
52 | value = toBeHex(0);
53 | }
54 |
55 | return {
56 | to: quote.monoChainMayanContract,
57 | data,
58 | value,
59 | _params: {
60 | amountIn: BigInt(quote.effectiveAmountIn64),
61 | tokenIn: quote.fromToken.contract,
62 | },
63 | };
64 | }
65 |
66 | export function getMonoChainFromEvmTxPayload(
67 | quote: Quote,
68 | destinationAddress: string,
69 | referrerAddress: string | null | undefined,
70 | signerChainId: number | string,
71 | permit: Erc20Permit | null,
72 | ): TransactionRequest & { _forwarder: EvmForwarderParams } {
73 | if (quote.type !== 'MONO_CHAIN') {
74 | throw new Error('Quote type is not MONO_CHAIN');
75 | }
76 | if (quote.fromChain !== quote.toChain) {
77 | throw new Error('Quote chains are not equal');
78 | }
79 | if (quote.fromToken.contract.toLowerCase() === quote.toToken.contract.toLowerCase()) {
80 | throw new Error(
81 | `From token and to token are the same: ${quote.fromToken.contract}`
82 | );
83 | }
84 |
85 | if (!Number.isFinite(Number(signerChainId))) {
86 | throw new Error('Invalid signer chain id');
87 | }
88 |
89 | const signerWormholeChainId = getWormholeChainIdById(Number(signerChainId));
90 | const sourceChainId = getWormholeChainIdByName(quote.fromChain);
91 | if (sourceChainId !== signerWormholeChainId) {
92 | throw new Error(
93 | `Signer chain id(${Number(
94 | signerChainId
95 | )}) and quote from chain are not same! ${sourceChainId} !== ${signerWormholeChainId}`
96 | );
97 | }
98 | signerChainId = Number(signerChainId);
99 |
100 | const _permit = permit || ZeroPermit;
101 | const forwarder = new Contract(
102 | addresses.MAYAN_FORWARDER_CONTRACT,
103 | MayanForwarderArtifact.abi
104 | );
105 |
106 | const { evmSwapRouterAddress, evmSwapRouterCalldata } = quote;
107 | if (!evmSwapRouterAddress || !evmSwapRouterCalldata) {
108 | throw new Error(
109 | 'Mono chain swap requires router address and calldata'
110 | );
111 | }
112 |
113 | const monoChainPayloadIx = getEvmMonoChainTxPayload(
114 | quote,
115 | destinationAddress,
116 | referrerAddress,
117 | );
118 | const minMiddleAmount = getAmountOfFractionalAmount(
119 | quote.minAmountOut,
120 | quote.toToken.decimals
121 | );
122 |
123 | if (quote.fromToken.contract === ZeroAddress) {
124 | const forwarderMethod = 'swapAndForwardEth';
125 | const forwarderParams = [
126 | monoChainPayloadIx._params.amountIn,
127 | evmSwapRouterAddress,
128 | evmSwapRouterCalldata,
129 | quote.toToken.contract,
130 | minMiddleAmount,
131 | quote.monoChainMayanContract,
132 | monoChainPayloadIx.data,
133 | ];
134 | const data = forwarder.interface.encodeFunctionData(
135 | forwarderMethod,
136 | forwarderParams
137 | );
138 | return {
139 | data,
140 | to: addresses.MAYAN_FORWARDER_CONTRACT,
141 | value: toBeHex(monoChainPayloadIx._params.amountIn),
142 | chainId: signerChainId,
143 | _forwarder: {
144 | method: forwarderMethod,
145 | params: forwarderParams,
146 | },
147 | };
148 | } else {
149 | const forwarderMethod = 'swapAndForwardERC20';
150 | const forwarderParams = [
151 | quote.fromToken.contract,
152 | monoChainPayloadIx._params.amountIn,
153 | _permit,
154 | evmSwapRouterAddress,
155 | evmSwapRouterCalldata,
156 | quote.toToken.contract,
157 | minMiddleAmount,
158 | quote.monoChainMayanContract,
159 | monoChainPayloadIx.data,
160 | ];
161 | const data = forwarder.interface.encodeFunctionData(
162 | forwarderMethod,
163 | forwarderParams
164 | );
165 | return {
166 | data,
167 | to: addresses.MAYAN_FORWARDER_CONTRACT,
168 | value: toBeHex(0),
169 | chainId: signerChainId,
170 | _forwarder: {
171 | method: forwarderMethod,
172 | params: forwarderParams,
173 | },
174 | };
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/evm/evmShuttle.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Contract,
3 | toBeHex,
4 | ZeroAddress,
5 | TransactionRequest
6 | } from 'ethers';
7 | import { Erc20Permit, EvmForwarderParams, Quote } from '../types';
8 | import {
9 | nativeAddressToHexString,
10 | getAmountOfFractionalAmount, getWormholeChainIdByName,
11 | getWormholeChainIdById, getGasDecimal, ZeroPermit
12 | } from '../utils';
13 | import addresses from '../addresses';
14 | import MayanForwarderArtifact from './MayanForwarderArtifact';
15 | import { Buffer } from 'buffer';
16 | import ShuttleArtifact from './ShuttleArtifact';
17 |
18 |
19 | const shuttleConstants = {
20 | FAST_MODE_FLAG: 1,
21 | RELAY_REDEEM_MODE: 2,
22 | EXACT_IN_FLAG: 1,
23 | USDC_INPUT_TOKEN_TYPE: 0,
24 | PRE_APPROVED_ACQUIRE_MODE: 0,
25 | OUTPUT_USDC_MODE: 0,
26 | OUTPUT_NATIVE_MODE: 1,
27 | OUTPUT_OTHER_MODE: 2,
28 | }
29 |
30 | function writeBigIntTo16BytesBuffer(value: bigint): Buffer {
31 | // Validate the range of the BigInt
32 | const maxUint128 = (1n << 128n) - 1n; // 2^128 - 1
33 | if (value < 0n || value > maxUint128) {
34 | throw new RangeError("Value must fit in an unsigned 128-bit integer (0 <= value < 2^128)");
35 | }
36 |
37 | const buffer = Buffer.alloc(16);
38 |
39 | for (let i = 15; i >= 0; i--) {
40 | buffer[i] = Number(value & 0xFFn);
41 | value >>= 8n;
42 | }
43 |
44 | return buffer;
45 | }
46 |
47 | export function getShuttleParams(
48 | quote: Quote, destinationAddress: string, signerChainId: string | number
49 | ): {
50 | destAddr: string;
51 | destChainId: number;
52 | serializedParams: string;
53 | contractAddress: string;
54 | amountIn: bigint;
55 | bridgeFee: bigint;
56 | } {
57 | const { shuttleParams } = quote;
58 | if (!shuttleParams) {
59 | throw new Error('Swap layer params are missing in quote response');
60 | }
61 | const signerWormholeChainId = getWormholeChainIdById(Number(signerChainId));
62 | const sourceChainId = getWormholeChainIdByName(quote.fromChain);
63 | const destChainId = getWormholeChainIdByName(quote.toChain);
64 | if (sourceChainId !== signerWormholeChainId) {
65 | throw new Error(`Signer chain id(${Number(signerChainId)}) and quote from chain are not same! ${sourceChainId} !== ${signerWormholeChainId}`);
66 | }
67 |
68 | let bytes = [];
69 |
70 | bytes.push(shuttleConstants.FAST_MODE_FLAG); // [0]
71 |
72 | const maxLLFeeBuffer8Bytes = Buffer.alloc(8);
73 | maxLLFeeBuffer8Bytes.writeBigUInt64BE(BigInt(shuttleParams.maxLLFee));
74 | const maxLLFeeBytes = maxLLFeeBuffer8Bytes.subarray(2);
75 | bytes.push(...maxLLFeeBytes); // [1..6]
76 |
77 | const deadLineBuffer = Buffer.alloc(4);
78 | deadLineBuffer.writeUInt32BE(shuttleParams.fastTransferDeadline);
79 | bytes.push(...deadLineBuffer); // [7..10]
80 |
81 | bytes.push(shuttleConstants.RELAY_REDEEM_MODE); // [11]
82 |
83 | const gasDrop = getAmountOfFractionalAmount(
84 | quote.gasDrop,
85 | Math.min(6, getGasDecimal(quote.toChain))
86 | );
87 | const gasDropBuffer8Bytes = Buffer.alloc(8);
88 | gasDropBuffer8Bytes.writeBigUInt64BE(gasDrop);
89 | const gasDropBytes = gasDropBuffer8Bytes.subarray(4);
90 | bytes.push(...gasDropBytes); // [12..15]
91 |
92 | const maxRelayerFeeBuffer8Bytes = Buffer.alloc(8);
93 | maxRelayerFeeBuffer8Bytes.writeBigUInt64BE(BigInt(shuttleParams.maxRelayingFee));
94 | const maxRelayerFeeBytes = maxRelayerFeeBuffer8Bytes.subarray(2);
95 | bytes.push(...maxRelayerFeeBytes); // [16..21]
96 |
97 | bytes.push(shuttleConstants.EXACT_IN_FLAG); // [22]
98 | bytes.push(shuttleConstants.USDC_INPUT_TOKEN_TYPE); // [23]
99 |
100 | const amountIn = BigInt(quote.effectiveAmountIn64);
101 | //
102 | if (quote.fromToken.contract === quote.shuttleInputContract) {
103 | // we are sure because of the input token is USDC the amount in will be fit in 8 bytes
104 | bytes.push(...Array(8).fill(0)); // offset of amount_in (8 bytes)
105 | const amountInBuffer = Buffer.alloc(8);
106 | amountInBuffer.writeBigUInt64BE(amountIn);
107 | bytes.push(...amountInBuffer);
108 |
109 | } else {
110 | // as forwarder contract overrides the amount in, we can set the u128 to 0
111 | bytes.push(...Array(16).fill(0));
112 | }
113 | //
114 |
115 | bytes.push(shuttleConstants.PRE_APPROVED_ACQUIRE_MODE);
116 |
117 | if (shuttleParams.hasDestSwap) {
118 | if (quote.toToken.contract === ZeroAddress) {
119 | bytes.push(shuttleConstants.OUTPUT_NATIVE_MODE);
120 | } else {
121 | bytes.push(shuttleConstants.OUTPUT_OTHER_MODE);
122 | const tokenOut = Buffer.from(nativeAddressToHexString(quote.toToken.contract, destChainId).slice(2), 'hex');
123 | bytes.push(...tokenOut);
124 | }
125 | const swapDeadlineBuffer = Buffer.alloc(4);
126 | swapDeadlineBuffer.writeUInt32BE(Number(BigInt(quote.deadline64)));
127 | bytes.push(...swapDeadlineBuffer);
128 |
129 | const minAmountOut = getAmountOfFractionalAmount(quote.minAmountOut, quote.toToken.decimals);
130 | if (quote.toChain === 'solana') { // limit_amount should be 8 bytes (u64)
131 | bytes.push(...Array(8).fill(0));
132 | const minAmountOutBuffer = Buffer.alloc(8);
133 | minAmountOutBuffer.writeBigUInt64BE(minAmountOut);
134 | bytes.push(...minAmountOutBuffer);
135 | } else {
136 | const minAmountOutBuffer = writeBigIntTo16BytesBuffer(minAmountOut);
137 | bytes.push(...minAmountOutBuffer);
138 | }
139 | const swapPath = Buffer.from(shuttleParams.path.slice(2), 'hex');
140 | bytes.push(...swapPath);
141 |
142 | } else {
143 | bytes.push(shuttleConstants.OUTPUT_USDC_MODE);
144 | }
145 |
146 | const destinationAddressHex = nativeAddressToHexString(destinationAddress, destChainId);
147 |
148 | return {
149 | destAddr: destinationAddressHex,
150 | destChainId,
151 | serializedParams: `0x${Buffer.from(bytes).toString('hex')}`,
152 | contractAddress: quote.shuttleContract,
153 | amountIn,
154 | bridgeFee: getAmountOfFractionalAmount(quote.bridgeFee, getGasDecimal(quote.fromChain)),
155 | }
156 | }
157 |
158 | export function getShuttleFromEvmTxPayload(
159 | quote: Quote, destinationAddress: string,
160 | signerChainId: number | string, permit: Erc20Permit | null
161 | ): TransactionRequest & { _forwarder: EvmForwarderParams } {
162 | if (quote.type !== 'SHUTTLE') {
163 | throw new Error('Quote type is not SHUTTLE');
164 | }
165 |
166 | if (!Number.isFinite(Number(signerChainId))) {
167 | throw new Error('Invalid signer chain id');
168 | }
169 |
170 | signerChainId = Number(signerChainId);
171 |
172 | const _permit = permit || ZeroPermit;
173 | const forwarder = new Contract(addresses.MAYAN_FORWARDER_CONTRACT, MayanForwarderArtifact.abi);
174 |
175 | const {
176 | destAddr,
177 | destChainId,
178 | serializedParams,
179 | contractAddress: shuttleContractAddress,
180 | amountIn,
181 | bridgeFee,
182 | } = getShuttleParams(quote, destinationAddress, signerChainId);
183 |
184 | let shuttleCallData: string;
185 | const shuttleContract = new Contract(shuttleContractAddress, ShuttleArtifact.abi);
186 |
187 | shuttleCallData = shuttleContract.interface.encodeFunctionData(
188 | 'initiate',
189 | [destAddr, amountIn, destChainId, serializedParams]
190 | );
191 |
192 | let forwarderMethod: string;
193 | let forwarderParams: any[];
194 | let value: string | null;
195 |
196 | if (quote.fromToken.contract === quote.shuttleInputContract) {
197 | forwarderMethod = 'forwardERC20';
198 | forwarderParams = [quote.shuttleInputContract, amountIn, _permit, shuttleContractAddress, shuttleCallData];
199 | value = toBeHex(bridgeFee);
200 | } else {
201 | const { evmSwapRouterAddress, evmSwapRouterCalldata } = quote;
202 | if (!quote.minMiddleAmount || !evmSwapRouterAddress || !evmSwapRouterCalldata) {
203 | throw new Error('Shuttle source chain swap requires middle amount, router address and calldata');
204 | }
205 | const tokenIn = quote.fromToken.contract;
206 |
207 | const minMiddleAmount = getAmountOfFractionalAmount(quote.minMiddleAmount, quote.shuttleInputDecimals);
208 |
209 | if (quote.fromToken.contract === ZeroAddress) {
210 | forwarderMethod = 'swapAndForwardEth';
211 | forwarderParams = [
212 | amountIn,
213 | evmSwapRouterAddress,
214 | evmSwapRouterCalldata,
215 | quote.shuttleInputContract,
216 | minMiddleAmount,
217 | shuttleContractAddress,
218 | shuttleCallData,
219 | ];
220 | value = toBeHex(amountIn);
221 | } else {
222 | forwarderMethod = 'swapAndForwardERC20';
223 | forwarderParams = [
224 | tokenIn,
225 | amountIn,
226 | _permit,
227 | evmSwapRouterAddress,
228 | evmSwapRouterCalldata,
229 | quote.shuttleInputContract,
230 | minMiddleAmount,
231 | shuttleContractAddress,
232 | shuttleCallData,
233 | ];
234 | value = toBeHex(bridgeFee);
235 | }
236 | }
237 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
238 |
239 | return {
240 | data,
241 | to: addresses.MAYAN_FORWARDER_CONTRACT,
242 | value,
243 | chainId: signerChainId,
244 | _forwarder: {
245 | method: forwarderMethod,
246 | params: forwarderParams
247 | }
248 | };
249 | }
250 |
--------------------------------------------------------------------------------
/src/evm/evmSwift.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Contract,
3 | toBeHex,
4 | ZeroAddress,
5 | TransactionRequest
6 | } from 'ethers';
7 | import { Keypair, SystemProgram } from '@solana/web3.js';
8 | import { Erc20Permit, EvmForwarderParams, Quote, SwiftEvmOrderTypedData } from '../types';
9 | import {
10 | nativeAddressToHexString,
11 | getAmountOfFractionalAmount, getWormholeChainIdByName,
12 | getWormholeChainIdById, getGasDecimal, ZeroPermit
13 | } from '../utils';
14 | import MayanSwiftArtifact from './MayanSwiftArtifact';
15 | import addresses from '../addresses';
16 | import MayanForwarderArtifact from './MayanForwarderArtifact';
17 | import { createSwiftOrderHash } from '../solana';
18 |
19 |
20 | export type SwiftOrderParams = {
21 | trader: string;
22 | tokenOut: string;
23 | minAmountOut: bigint;
24 | gasDrop: bigint;
25 | cancelFee: bigint;
26 | refundFee: bigint;
27 | deadline: bigint;
28 | destAddr: string;
29 | destChainId: number;
30 | referrerAddr: string;
31 | referrerBps: number;
32 | auctionMode: number;
33 | random: string;
34 | };
35 |
36 | export type EvmSwiftParams = {
37 | contractAddress: string;
38 | tokenIn: string;
39 | amountIn: bigint;
40 | order: SwiftOrderParams;
41 | };
42 |
43 | export function getEvmSwiftParams(
44 | quote: Quote, swapperAddress: string, destinationAddress: string,
45 | referrerAddress: string | null | undefined, signerChainId: string | number
46 | ): EvmSwiftParams {
47 | const signerWormholeChainId = getWormholeChainIdById(Number(signerChainId));
48 | const sourceChainId = getWormholeChainIdByName(quote.fromChain);
49 | const destChainId = getWormholeChainIdByName(quote.toChain);
50 | if (sourceChainId !== signerWormholeChainId) {
51 | throw new Error(`Signer chain id(${Number(signerChainId)}) and quote from chain are not same! ${sourceChainId} !== ${signerWormholeChainId}`);
52 | }
53 | if (!quote.swiftMayanContract) {
54 | throw new Error('SWIFT contract address is missing');
55 | }
56 |
57 | if (quote.toToken.wChainId !== destChainId) {
58 | throw new Error(`Destination chain ID mismatch: ${destChainId} != ${quote.toToken.wChainId}`);
59 | }
60 | const contractAddress = quote.swiftMayanContract;
61 |
62 | if (!Number(quote.deadline64)) {
63 | throw new Error('Swift order requires timeout');
64 | }
65 |
66 | const deadline = BigInt(quote.deadline64);
67 |
68 | const tokenIn = quote.swiftInputContract;
69 | const amountIn = BigInt(quote.effectiveAmountIn64);
70 | let referrerHex: string;
71 | if (referrerAddress) {
72 | referrerHex = nativeAddressToHexString(
73 | referrerAddress,
74 | destChainId
75 | );
76 | } else {
77 | referrerHex = nativeAddressToHexString(
78 | SystemProgram.programId.toString(),
79 | 1
80 | );
81 | }
82 |
83 | const random = nativeAddressToHexString(Keypair.generate().publicKey.toString(), 1);
84 |
85 | const tokenOut = quote.toToken.contract === ZeroAddress ?
86 | nativeAddressToHexString(SystemProgram.programId.toString(), 1) :
87 | nativeAddressToHexString(quote.toToken.contract, destChainId);
88 |
89 | const minAmountOut = getAmountOfFractionalAmount(
90 | quote.minAmountOut, Math.min(8, quote.toToken.decimals)
91 | );
92 |
93 | const gasDrop = getAmountOfFractionalAmount(
94 | quote.gasDrop,
95 | Math.min(8, getGasDecimal(quote.toChain))
96 | );
97 |
98 | const destinationAddressHex = nativeAddressToHexString(destinationAddress, destChainId);
99 | const orderParams: SwiftOrderParams = {
100 | trader: nativeAddressToHexString(swapperAddress, sourceChainId),
101 | tokenOut,
102 | minAmountOut,
103 | gasDrop,
104 | cancelFee: BigInt(quote.cancelRelayerFee64),
105 | refundFee: BigInt(quote.refundRelayerFee64),
106 | deadline,
107 | destAddr: destinationAddressHex,
108 | destChainId,
109 | referrerAddr: referrerHex,
110 | referrerBps: quote.referrerBps || 0,
111 | auctionMode: quote.swiftAuctionMode,
112 | random,
113 | };
114 |
115 | return {
116 | contractAddress,
117 | tokenIn,
118 | amountIn,
119 | order: orderParams
120 | };
121 | }
122 |
123 | export function getSwiftFromEvmTxPayload(
124 | quote: Quote, swapperAddress: string, destinationAddress: string, referrerAddress: string | null | undefined,
125 | signerChainId: number | string, permit: Erc20Permit | null
126 | ): TransactionRequest & { _forwarder: EvmForwarderParams } {
127 | if (quote.type !== 'SWIFT') {
128 | throw new Error('Quote type is not SWIFT');
129 | }
130 |
131 | if (!Number.isFinite(Number(signerChainId))) {
132 | throw new Error('Invalid signer chain id');
133 | }
134 |
135 | if (!Number(quote.deadline64)) {
136 | throw new Error('Swift order requires timeout');
137 | }
138 |
139 | signerChainId = Number(signerChainId);
140 |
141 | const _permit = permit || ZeroPermit;
142 | const forwarder = new Contract(addresses.MAYAN_FORWARDER_CONTRACT, MayanForwarderArtifact.abi);
143 |
144 | const {
145 | tokenIn: swiftTokenIn,
146 | amountIn,
147 | order,
148 | contractAddress: swiftContractAddress
149 | } = getEvmSwiftParams(quote, swapperAddress, destinationAddress, referrerAddress, signerChainId);
150 |
151 | let swiftCallData: string;
152 | const swiftContract = new Contract(swiftContractAddress, MayanSwiftArtifact.abi);
153 |
154 | if (quote.swiftInputContract === ZeroAddress) {
155 | swiftCallData = swiftContract.interface.encodeFunctionData(
156 | 'createOrderWithEth',
157 | [order]
158 | );
159 | } else {
160 | swiftCallData = swiftContract.interface.encodeFunctionData(
161 | 'createOrderWithToken',
162 | [swiftTokenIn, amountIn, order]
163 | );
164 | }
165 |
166 | let forwarderMethod: string;
167 | let forwarderParams: any[];
168 | let value: string | null;
169 |
170 | if (quote.fromToken.contract === quote.swiftInputContract) {
171 | if (quote.fromToken.contract === ZeroAddress) {
172 | forwarderMethod = 'forwardEth';
173 | forwarderParams = [swiftContractAddress, swiftCallData];
174 | value = toBeHex(amountIn);
175 | } else {
176 | forwarderMethod = 'forwardERC20';
177 | forwarderParams = [swiftTokenIn, amountIn, _permit, swiftContractAddress, swiftCallData];
178 | value = toBeHex(0);
179 | }
180 | } else {
181 | const { evmSwapRouterAddress, evmSwapRouterCalldata } = quote;
182 | if (!quote.minMiddleAmount || !evmSwapRouterAddress || !evmSwapRouterCalldata) {
183 | throw new Error('Swift swap requires middle amount, router address and calldata');
184 | }
185 | const tokenIn = quote.fromToken.contract;
186 |
187 | const minMiddleAmount = getAmountOfFractionalAmount(quote.minMiddleAmount, quote.swiftInputDecimals);
188 |
189 | if (quote.fromToken.contract === ZeroAddress) {
190 | forwarderMethod = 'swapAndForwardEth';
191 | forwarderParams = [
192 | amountIn,
193 | evmSwapRouterAddress,
194 | evmSwapRouterCalldata,
195 | quote.swiftInputContract,
196 | minMiddleAmount,
197 | swiftContractAddress,
198 | swiftCallData
199 | ];
200 | value = toBeHex(amountIn);
201 | } else {
202 | forwarderMethod = 'swapAndForwardERC20';
203 | forwarderParams = [
204 | tokenIn,
205 | amountIn,
206 | _permit,
207 | evmSwapRouterAddress,
208 | evmSwapRouterCalldata,
209 | quote.swiftInputContract,
210 | minMiddleAmount,
211 | swiftContractAddress,
212 | swiftCallData
213 | ];
214 | value = toBeHex(0);
215 | }
216 | }
217 | const data = forwarder.interface.encodeFunctionData(forwarderMethod, forwarderParams);
218 |
219 | return {
220 | data,
221 | to: addresses.MAYAN_FORWARDER_CONTRACT,
222 | value,
223 | chainId: signerChainId,
224 | _forwarder: {
225 | method: forwarderMethod,
226 | params: forwarderParams
227 | }
228 | };
229 | }
230 |
231 |
232 | export function getSwiftOrderTypeData(
233 | quote: Quote, orderHash: string, signerChainId: number | string
234 | ): SwiftEvmOrderTypedData {
235 | if (!Number.isFinite(Number(signerChainId))) {
236 | throw new Error('Invalid signer chain id');
237 | }
238 |
239 | const totalAmountIn = BigInt(quote.effectiveAmountIn64);
240 | const submitFee = BigInt(quote.submitRelayerFee64);
241 | return {
242 | domain: {
243 | name: 'Mayan Swift',
244 | chainId: Number(signerChainId),
245 | verifyingContract: quote.swiftMayanContract,
246 | },
247 | types: {
248 | CreateOrder: [
249 | { name: 'OrderId', type: 'bytes32' },
250 | { name: 'InputAmount', type: 'uint256' },
251 | { name: 'SubmissionFee', type: 'uint256' },
252 | ],
253 | },
254 | value: {
255 | OrderId: orderHash,
256 | InputAmount: totalAmountIn - submitFee,
257 | SubmissionFee: submitFee,
258 | }
259 | }
260 | }
261 |
262 | export type SwiftEvmGasLessParams = {
263 | permitParams: Erc20Permit;
264 | orderHash: string;
265 | orderParams: {
266 | trader: string;
267 | sourceChainId: number;
268 | tokenIn: string;
269 | amountIn: bigint;
270 | destAddr: string;
271 | destChainId: number;
272 | tokenOut: string;
273 | minAmountOut: bigint;
274 | gasDrop: bigint;
275 | cancelFee: bigint;
276 | refundFee: bigint;
277 | deadline: bigint;
278 | referrerAddr: string;
279 | referrerBps: number;
280 | auctionMode: number;
281 | random: string;
282 | submissionFee: bigint;
283 | };
284 | orderTypedData: SwiftEvmOrderTypedData;
285 | }
286 |
287 | export function getSwiftFromEvmGasLessParams(
288 | quote: Quote, swapperAddress: string, destinationAddress: string, referrerAddress: string | null | undefined,
289 | signerChainId: number | string, permit: Erc20Permit | null
290 | ): SwiftEvmGasLessParams {
291 | if (quote.type !== 'SWIFT') {
292 | throw new Error('Quote type is not SWIFT');
293 | }
294 |
295 | if (!quote.gasless) {
296 | throw new Error('Quote does not support gasless');
297 | }
298 |
299 | if (!Number.isFinite(Number(signerChainId))) {
300 | throw new Error('Invalid signer chain id');
301 | }
302 |
303 | if (!Number(quote.deadline64)) {
304 | throw new Error('Swift order requires timeout');
305 | }
306 |
307 | if (quote.fromToken.contract !== quote.swiftInputContract) {
308 | throw new Error('Swift gasless order creation does not support source swap');
309 | }
310 |
311 | const {
312 | tokenIn,
313 | amountIn,
314 | order,
315 | } = getEvmSwiftParams(
316 | quote, swapperAddress, destinationAddress,
317 | referrerAddress, Number(signerChainId)
318 | );
319 | const sourceChainId = getWormholeChainIdByName(quote.fromChain);
320 |
321 | const orderHashBuf = createSwiftOrderHash(quote, swapperAddress, destinationAddress, referrerAddress, order.random);
322 | const orderHash = `0x${orderHashBuf.toString('hex')}`
323 | const orderTypedData = getSwiftOrderTypeData(quote, orderHash, signerChainId);
324 |
325 | return {
326 | permitParams: permit,
327 | orderParams: {
328 | ...order,
329 | sourceChainId,
330 | amountIn,
331 | tokenIn,
332 | submissionFee: BigInt(quote.submitRelayerFee64),
333 | },
334 | orderHash,
335 | orderTypedData
336 | };
337 | }
338 |
--------------------------------------------------------------------------------
/src/evm/index.ts:
--------------------------------------------------------------------------------
1 | export * from './evmSwap';
2 | export * from './evmMctp';
3 | export * from './evmHyperCore';
4 | export * from './evmMonoChain';
5 | export * from './evmSwift';
6 | export * from './evmFastMctp';
7 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './api';
2 | export * from './evm';
3 | export * from './solana';
4 | export * from './sui';
5 | export * from './types';
6 | export * from './utils';
7 | export { default as addresses } from './addresses';
8 |
--------------------------------------------------------------------------------
/src/solana/index.ts:
--------------------------------------------------------------------------------
1 | export * from './utils';
2 | export * from './solanaSwap';
3 | export * from './solanaMctp';
4 | export * from './solanaSwift';
5 | export * from './solanaHyperCore';
6 | export * from './solanaMonoChain';
7 |
--------------------------------------------------------------------------------
/src/solana/solanaHyperCore.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Connection,
3 | PublicKey,
4 | Keypair,
5 | TransactionInstruction,
6 | ComputeBudgetProgram,
7 | AddressLookupTableAccount,
8 | } from '@solana/web3.js';
9 | import { Quote, ChainName, SwapMessageV0Params, SolanaBridgeOptions } from '../types';
10 | import {
11 | getAssociatedTokenAddress,
12 | getHyperCoreUSDCDepositCustomPayload,
13 | } from '../utils';
14 | import { Buffer } from 'buffer';
15 | import addresses from '../addresses';
16 | import { getSwapSolana } from '../api';
17 | import {
18 | createAssociatedTokenAccountInstruction,
19 | createInitializeRandomTokenAccountInstructions, createPayloadWriterCloseInstruction,
20 | createPayloadWriterCreateInstruction,
21 | createSplTransferInstruction,
22 | createTransferAllAndCloseInstruction,
23 | decentralizeClientSwapInstructions,
24 | getAddressLookupTableAccounts,
25 | sandwichInstructionInCpiProxy,
26 | validateJupSwap,
27 | } from './utils';
28 | import { createMctpBridgeLedgerInstruction, createMctpBridgeWithFeeInstruction } from './solanaMctp';
29 |
30 | export async function createHyperCoreDepositFromSolanaInstructions(
31 | quote: Quote,
32 | swapperAddress: string,
33 | destinationAddress: string,
34 | referrerAddress: string | null | undefined,
35 | connection: Connection,
36 | options: SolanaBridgeOptions = {}
37 | ): Promise<{
38 | instructions: TransactionInstruction[];
39 | signers: Keypair[];
40 | lookupTables: AddressLookupTableAccount[];
41 | swapMessageV0Params: SwapMessageV0Params | null;
42 | }> {
43 | if (
44 | quote.toToken.contract.toLowerCase() !== addresses.ARBITRUM_USDC_CONTRACT.toLowerCase() ||
45 | quote.type !== 'MCTP'
46 | ) {
47 | throw new Error('Unsupported quote type for USDC deposit: ' + quote.type);
48 | }
49 | if (!options?.usdcPermitSignature) {
50 | throw new Error('USDC permit signature is required for this quote');
51 | }
52 | if (!quote.hyperCoreParams) {
53 | throw new Error('HyperCore parameters are required for this quote');
54 | }
55 | if (!Number(quote.deadline64)) {
56 | throw new Error('HyperCore deposit requires timeout');
57 | }
58 |
59 | const allowSwapperOffCurve = options.allowSwapperOffCurve || false;
60 |
61 | let instructions: TransactionInstruction[] = [];
62 | let signers: Keypair[] = [];
63 | let lookupTables: AddressLookupTableAccount[] = [];
64 |
65 | let _lookupTablesAddress: string[] = [];
66 |
67 | _lookupTablesAddress.push(addresses.LOOKUP_TABLE);
68 |
69 | // using for the swap via Jito Bundle
70 | let _swapAddressLookupTables: string[] = [];
71 | let swapInstructions: TransactionInstruction[] = [];
72 | let createSwapTpmTokenAccountInstructions: TransactionInstruction[] = [];
73 | const tmpSwapTokenAccount: Keypair = Keypair.generate();
74 | let swapMessageV0Params: SwapMessageV0Params | null = null;
75 |
76 | const trader = new PublicKey(swapperAddress);
77 | const relayerAddress = quote.relayer || swapperAddress;
78 |
79 | const inputMint = new PublicKey(quote.hyperCoreParams.initiateTokenContract);
80 |
81 | const payloadNonce = Math.floor(Math.random() * 65000); // Random nonce for the payload
82 | const [payloadAccount] = PublicKey.findProgramAddressSync(
83 | [
84 | Buffer.from('PAYLOAD'),
85 | trader.toBuffer(),
86 | (() => {
87 | const buf = Buffer.alloc(2);
88 | buf.writeUInt16LE(payloadNonce, 0);
89 | return buf;
90 | })(),
91 | ],
92 | new PublicKey(addresses.PAYLOAD_WRITER_PROGRAM_ID)
93 | );
94 | const payload = getHyperCoreUSDCDepositCustomPayload(quote, destinationAddress, options.usdcPermitSignature);
95 |
96 | const mctpRandomKey = Keypair.generate();
97 |
98 | const mctpProgram = new PublicKey(addresses.MCTP_PROGRAM_ID);
99 |
100 | const [ledger] = PublicKey.findProgramAddressSync(
101 | [
102 | Buffer.from('LEDGER_BRIDGE'),
103 | trader.toBytes(),
104 | mctpRandomKey.publicKey.toBytes(),
105 | ],
106 | mctpProgram
107 | );
108 | const ledgerAccount = getAssociatedTokenAddress(
109 | inputMint, ledger, true
110 | );
111 |
112 | if (
113 | quote.fromToken.contract === quote.hyperCoreParams.initiateTokenContract
114 | ) {
115 | if (quote.suggestedPriorityFee > 0) {
116 | instructions.push(
117 | ComputeBudgetProgram.setComputeUnitPrice({
118 | microLamports: quote.suggestedPriorityFee,
119 | })
120 | );
121 | }
122 | instructions.push(
123 | sandwichInstructionInCpiProxy(createAssociatedTokenAccountInstruction(trader, ledgerAccount, ledger, inputMint))
124 | );
125 | instructions.push(
126 | sandwichInstructionInCpiProxy(createSplTransferInstruction(
127 | getAssociatedTokenAddress(
128 | inputMint, trader, allowSwapperOffCurve
129 | ),
130 | ledgerAccount,
131 | trader,
132 | BigInt(quote.hyperCoreParams.bridgeAmountUSDC64),
133 | ))
134 | );
135 | instructions.push(
136 | sandwichInstructionInCpiProxy(createPayloadWriterCreateInstruction(
137 | trader,
138 | payloadAccount,
139 | payload,
140 | payloadNonce
141 | ))
142 | );
143 | instructions.push(
144 | sandwichInstructionInCpiProxy(createMctpBridgeLedgerInstruction({
145 | ledger,
146 | randomKey: mctpRandomKey.publicKey,
147 | swapperAddress: trader.toString(),
148 | mintAddress: inputMint.toString(),
149 | mode: 'WITH_FEE',
150 | feeSolana: BigInt(0),
151 | amountInMin64: BigInt(quote.hyperCoreParams.bridgeAmountUSDC64),
152 | customPayload: payloadAccount,
153 | destinationAddress: addresses.HC_ARBITRUM_DEPOSIT_PROCESSOR,
154 | referrerAddress,
155 | feeRedeem: 0,
156 | gasDrop: quote.hyperCoreParams.failureGasDrop,
157 | toChain: 'arbitrum',
158 | relayerAddress,
159 | }), options.skipProxyMayanInstructions)
160 | );
161 | const {
162 | instruction: _instruction,
163 | signers: _signers
164 | } = createMctpBridgeWithFeeInstruction(
165 | ledger,
166 | 'arbitrum',
167 | quote.hyperCoreParams.initiateTokenContract,
168 | relayerAddress,
169 | BigInt(0),
170 | );
171 | instructions.push(sandwichInstructionInCpiProxy(_instruction, options.skipProxyMayanInstructions));
172 | signers.push(..._signers);
173 | instructions.push(sandwichInstructionInCpiProxy(createPayloadWriterCloseInstruction(
174 | trader,
175 | payloadAccount,
176 | payloadNonce,
177 | )));
178 | } else {
179 | const clientSwapRaw = await getSwapSolana({
180 | minMiddleAmount: quote.minMiddleAmount,
181 | middleToken: quote.hyperCoreParams.initiateTokenContract,
182 | userWallet: trader.toString(),
183 | slippageBps: quote.slippageBps,
184 | fromToken: quote.fromToken.contract,
185 | amountIn64: quote.effectiveAmountIn64,
186 | depositMode: 'HC_USDC',
187 | fillMaxAccounts: options?.separateSwapTx || false,
188 | tpmTokenAccount: tmpSwapTokenAccount.publicKey.toString()
189 | });
190 | const clientSwap = decentralizeClientSwapInstructions(clientSwapRaw, connection);
191 |
192 | if (options?.separateSwapTx && clientSwapRaw.maxAccountsFilled) {
193 | validateJupSwap(clientSwap, tmpSwapTokenAccount.publicKey, trader);
194 | createSwapTpmTokenAccountInstructions = await createInitializeRandomTokenAccountInstructions(
195 | connection,
196 | trader,
197 | inputMint,
198 | trader,
199 | tmpSwapTokenAccount,
200 | );
201 | swapInstructions.push(...clientSwap.computeBudgetInstructions);
202 | if (clientSwap.setupInstructions) {
203 | swapInstructions.push(...clientSwap.setupInstructions);
204 | }
205 | swapInstructions.push(clientSwap.swapInstruction);
206 | if (clientSwap.cleanupInstruction) {
207 | swapInstructions.push(clientSwap.cleanupInstruction);
208 | }
209 | _swapAddressLookupTables.push(...clientSwap.addressLookupTableAddresses);
210 | } else {
211 | validateJupSwap(clientSwap, tmpSwapTokenAccount.publicKey, trader);
212 | instructions.push(...clientSwap.computeBudgetInstructions);
213 | const _createSwapTpmTokenAccountInstructions = await createInitializeRandomTokenAccountInstructions(
214 | connection,
215 | trader,
216 | inputMint,
217 | trader,
218 | tmpSwapTokenAccount,
219 | );
220 | instructions.push(...(_createSwapTpmTokenAccountInstructions).map(ins => sandwichInstructionInCpiProxy(ins)));
221 | signers.push(tmpSwapTokenAccount);
222 | if (clientSwap.setupInstructions) {
223 | instructions.push(...(clientSwap.setupInstructions.map(ins => sandwichInstructionInCpiProxy(ins))));
224 | }
225 | instructions.push(sandwichInstructionInCpiProxy(clientSwap.swapInstruction));
226 | if (clientSwap.cleanupInstruction) {
227 | instructions.push(sandwichInstructionInCpiProxy(clientSwap.cleanupInstruction));
228 | }
229 | _lookupTablesAddress.push(...clientSwap.addressLookupTableAddresses);
230 | }
231 |
232 | const feeSolana: bigint = swapInstructions.length > 0 ? BigInt(0) : BigInt(quote.solanaRelayerFee64);
233 | let initiateAmountUSDC64 = BigInt(quote.hyperCoreParams.bridgeAmountUSDC64);
234 | if (swapInstructions.length > 0) {
235 | initiateAmountUSDC64 = initiateAmountUSDC64 - BigInt(quote.solanaRelayerFee64);
236 | }
237 |
238 | instructions.push(sandwichInstructionInCpiProxy(createAssociatedTokenAccountInstruction(
239 | trader, ledgerAccount, ledger, new PublicKey(quote.mctpInputContract)
240 | )));
241 |
242 | instructions.push(
243 | sandwichInstructionInCpiProxy(createSplTransferInstruction(
244 | tmpSwapTokenAccount.publicKey,
245 | ledgerAccount,
246 | trader,
247 | initiateAmountUSDC64,
248 | ))
249 | );
250 |
251 | const traderInputMintAccount = getAssociatedTokenAddress(
252 | inputMint, trader, allowSwapperOffCurve
253 | );
254 | const traderInputMintAccountInfo = await connection.getAccountInfo(traderInputMintAccount);
255 | if (!traderInputMintAccountInfo || !traderInputMintAccountInfo.data) {
256 | instructions.push(sandwichInstructionInCpiProxy(createAssociatedTokenAccountInstruction(
257 | trader,
258 | traderInputMintAccount,
259 | trader,
260 | inputMint
261 | )));
262 | }
263 | instructions.push(sandwichInstructionInCpiProxy(createTransferAllAndCloseInstruction(
264 | trader,
265 | inputMint,
266 | tmpSwapTokenAccount.publicKey,
267 | traderInputMintAccount,
268 | trader,
269 | )));
270 |
271 | instructions.push(
272 | sandwichInstructionInCpiProxy(createPayloadWriterCreateInstruction(
273 | trader,
274 | payloadAccount,
275 | payload,
276 | payloadNonce
277 | ))
278 | );
279 |
280 | instructions.push(sandwichInstructionInCpiProxy(createMctpBridgeLedgerInstruction({
281 | ledger,
282 | swapperAddress: trader.toString(),
283 | mintAddress: inputMint.toString(),
284 | randomKey: mctpRandomKey.publicKey,
285 | mode: 'WITH_FEE',
286 | feeSolana,
287 | amountInMin64: initiateAmountUSDC64,
288 | customPayload: payloadAccount,
289 | destinationAddress: addresses.HC_ARBITRUM_DEPOSIT_PROCESSOR,
290 | referrerAddress,
291 | feeRedeem: 0,
292 | gasDrop: quote.hyperCoreParams.failureGasDrop,
293 | toChain: 'arbitrum',
294 | relayerAddress,
295 | }), options.skipProxyMayanInstructions));
296 | instructions.push(sandwichInstructionInCpiProxy(createPayloadWriterCloseInstruction(
297 | trader,
298 | payloadAccount,
299 | payloadNonce,
300 | )));
301 | if (swapInstructions.length > 0) {
302 | const {
303 | instruction: _instruction,
304 | signers: _signers
305 | } = createMctpBridgeWithFeeInstruction(
306 | ledger,
307 | 'arbitrum',
308 | quote.hyperCoreParams.initiateTokenContract,
309 | relayerAddress,
310 | BigInt(0),
311 | );
312 | instructions.push(sandwichInstructionInCpiProxy(_instruction, options.skipProxyMayanInstructions));
313 | signers.push(..._signers);
314 | }
315 | }
316 |
317 | const totalLookupTables = await getAddressLookupTableAccounts(
318 | _lookupTablesAddress.concat(_swapAddressLookupTables), connection
319 | );
320 | lookupTables = totalLookupTables.slice(0, _lookupTablesAddress.length);
321 | if (swapInstructions.length > 0) {
322 | const swapLookupTables = totalLookupTables.slice(_lookupTablesAddress.length);
323 | swapMessageV0Params = {
324 | messageV0: {
325 | payerKey: trader,
326 | instructions: swapInstructions,
327 | addressLookupTableAccounts: swapLookupTables,
328 | },
329 | createTmpTokenAccountIxs: createSwapTpmTokenAccountInstructions,
330 | tmpTokenAccount: tmpSwapTokenAccount,
331 | };
332 | }
333 |
334 | return { instructions, signers, lookupTables, swapMessageV0Params };
335 | }
336 |
--------------------------------------------------------------------------------
/src/solana/solanaMonoChain.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AccountMeta,
3 | Connection,
4 | PublicKey,
5 | Keypair,
6 | SystemProgram,
7 | TransactionInstruction,
8 | AddressLookupTableAccount,
9 | ComputeBudgetProgram,
10 | } from '@solana/web3.js';
11 | import { blob, struct, u16, u8 } from '@solana/buffer-layout';
12 | import { Quote, SwapMessageV0Params } from '../types';
13 | import {
14 | hexToUint8Array,
15 | nativeAddressToHexString,
16 | getSafeU64Blob,
17 | getAmountOfFractionalAmount,
18 | getAssociatedTokenAddress,
19 | getWormholeChainIdByName,
20 | getGasDecimal,
21 | } from '../utils';
22 | import { Buffer } from 'buffer';
23 | import addresses from '../addresses';
24 | import { ethers, ZeroAddress } from 'ethers';
25 | import { getSwapSolana } from '../api';
26 | import {
27 | createAssociatedTokenAccountInstruction,
28 | createInitializeRandomTokenAccountInstructions,
29 | createSplTransferInstruction,
30 | createSyncNativeInstruction,
31 | createTransferAllAndCloseInstruction,
32 | decentralizeClientSwapInstructions,
33 | getAddressLookupTableAccounts,
34 | getAnchorInstructionData, sandwichInstructionInCpiProxy,
35 | solMint,
36 | validateJupSwap, validateJupSwapInstructionData
37 | } from './utils';
38 | import { swapFromSolana } from './solanaSwap';
39 |
40 | export async function createMonoChainFromSolanaInstructions(
41 | quote: Quote,
42 | swapperAddress: string,
43 | destinationAddress: string,
44 | referrerAddress: string | null | undefined,
45 | connection: Connection,
46 | options: {
47 | allowSwapperOffCurve?: boolean;
48 | separateSwapTx?: boolean;
49 | skipProxyMayanInstructions?: boolean,
50 | } = {}
51 | ): Promise<{
52 | instructions: TransactionInstruction[];
53 | signers: Keypair[];
54 | lookupTables: AddressLookupTableAccount[];
55 | swapMessageV0Params: SwapMessageV0Params | null;
56 | }> {
57 | if (quote.type !== 'MONO_CHAIN') {
58 | throw new Error('Unsupported quote type for mono chain: ' + quote.type);
59 | }
60 | if (quote.fromChain !== 'solana') {
61 | throw new Error(
62 | 'Unsupported destination chain for mono chain: ' + quote.fromChain
63 | );
64 | }
65 | if (quote.toChain !== 'solana') {
66 | throw new Error(
67 | 'Unsupported destination chain for mono chain: ' + quote.toChain
68 | );
69 | }
70 | if (quote.fromToken.contract === quote.toToken.contract) {
71 | throw new Error(
72 | 'From token and to token are the same: ' + quote.fromToken.contract
73 | );
74 | }
75 | if (destinationAddress.startsWith('0x')) {
76 | throw new Error('Destination address should not be EVM address');
77 | }
78 | try {
79 | new PublicKey(destinationAddress);
80 | } catch (e) {
81 | throw new Error('Invalid destination address: ' + destinationAddress);
82 | }
83 |
84 | let instructions: TransactionInstruction[] = [];
85 | let lookupTables: AddressLookupTableAccount[] = [];
86 |
87 | let _lookupTablesAddress: string[] = [];
88 |
89 | const swapper = new PublicKey(swapperAddress);
90 | const destination = new PublicKey(destinationAddress);
91 |
92 | const toMint = quote.toToken.contract === ZeroAddress ? solMint : new PublicKey(quote.toToken.contract);
93 | const destAcc = getAssociatedTokenAddress(
94 | toMint,
95 | destination,
96 | true,
97 | quote.toToken.standard === 'spl2022' ? new PublicKey(addresses.TOKEN_2022_PROGRAM_ID) : new PublicKey(addresses.TOKEN_PROGRAM_ID)
98 | );
99 |
100 | const expectedAmountOut64 = getAmountOfFractionalAmount(
101 | quote.expectedAmountOut, quote.toToken.decimals
102 | );
103 | const clientSwapRaw = await getSwapSolana({
104 | userWallet: swapperAddress,
105 | destinationWallet: destinationAddress,
106 | slippageBps: quote.slippageBps,
107 | fromToken: quote.fromToken.contract,
108 | middleToken: quote.toToken.contract,
109 | amountIn64: quote.effectiveAmountIn64,
110 | expectedAmountOut64: String(expectedAmountOut64),
111 | depositMode: 'MONO_CHAIN',
112 | referrerAddress: referrerAddress,
113 | referrerBps: quote.referrerBps,
114 | });
115 | const clientSwap = decentralizeClientSwapInstructions(
116 | clientSwapRaw,
117 | connection
118 | );
119 | validateJupSwap(clientSwap, destAcc, swapper, destination, swapper.equals(destination));
120 | validateJupSwapInstructionData(clientSwap.swapInstruction, quote);
121 | instructions.push(...clientSwap.computeBudgetInstructions);
122 | if (clientSwap.setupInstructions) {
123 | instructions.push(...(clientSwap.setupInstructions.map(ins => sandwichInstructionInCpiProxy(ins))));
124 | }
125 | instructions.push(sandwichInstructionInCpiProxy(clientSwap.swapInstruction, options?.skipProxyMayanInstructions));
126 | if (clientSwap.cleanupInstruction) {
127 | instructions.push(sandwichInstructionInCpiProxy(clientSwap.cleanupInstruction));
128 | }
129 | _lookupTablesAddress.push(...clientSwap.addressLookupTableAddresses);
130 | _lookupTablesAddress.push(addresses.LOOKUP_TABLE);
131 |
132 | lookupTables = await getAddressLookupTableAccounts(_lookupTablesAddress, connection);
133 |
134 |
135 |
136 | return { instructions, signers: [], lookupTables, swapMessageV0Params: null };
137 | }
138 |
--------------------------------------------------------------------------------
/src/solana/solanaSwift.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AccountMeta,
3 | Connection,
4 | PublicKey,
5 | Keypair,
6 | SystemProgram,
7 | TransactionInstruction, AddressLookupTableAccount, ComputeBudgetProgram
8 | } from '@solana/web3.js';
9 | import { blob, struct, u16, u8 } from '@solana/buffer-layout';
10 | import { Quote, SwapMessageV0Params } from '../types';
11 | import {
12 | hexToUint8Array,
13 | nativeAddressToHexString,
14 | getSafeU64Blob, getAmountOfFractionalAmount, getAssociatedTokenAddress, getWormholeChainIdByName, getGasDecimal
15 | } from '../utils';
16 | import {Buffer} from 'buffer';
17 | import addresses from '../addresses'
18 | import { ethers, ZeroAddress } from 'ethers';
19 | import { getSwapSolana } from '../api';
20 | import {
21 | createAssociatedTokenAccountInstruction,
22 | createInitializeRandomTokenAccountInstructions,
23 | createSplTransferInstruction,
24 | createSyncNativeInstruction,
25 | createTransferAllAndCloseInstruction,
26 | decentralizeClientSwapInstructions,
27 | getAddressLookupTableAccounts,
28 | getAnchorInstructionData,
29 | sandwichInstructionInCpiProxy,
30 | solMint,
31 | validateJupSwap
32 | } from './utils';
33 |
34 | export function createSwiftOrderHash(
35 | quote: Quote, swapperAddress: string, destinationAddress: string,
36 | referrerAddress: string | null | undefined, randomKeyHex: string
37 | ): Buffer {
38 | const orderDataSize = 239;
39 | const data = Buffer.alloc(orderDataSize);
40 | let offset = 0;
41 |
42 | const sourceChainId = getWormholeChainIdByName(quote.fromChain);
43 | const trader = Buffer.from(hexToUint8Array(nativeAddressToHexString(swapperAddress, sourceChainId)));
44 | data.set(trader, 0);
45 | offset += 32;
46 |
47 |
48 | data.writeUInt16BE(sourceChainId, offset);
49 | offset += 2;
50 |
51 | const _tokenIn = quote.swiftInputContract === ZeroAddress ?
52 | nativeAddressToHexString(SystemProgram.programId.toString(), getWormholeChainIdByName('solana')) :
53 | nativeAddressToHexString(quote.swiftInputContract, sourceChainId);
54 | const tokenIn = Buffer.from(hexToUint8Array(_tokenIn));
55 | data.set(tokenIn, offset);
56 | offset += 32;
57 |
58 | const destinationChainId = getWormholeChainIdByName(quote.toChain);
59 | const destAddress = Buffer.from(hexToUint8Array(nativeAddressToHexString(destinationAddress, destinationChainId)));
60 | data.set(destAddress, offset);
61 | offset += 32;
62 |
63 | data.writeUInt16BE(destinationChainId, offset);
64 | offset += 2;
65 |
66 | const _tokenOut =
67 | quote.toToken.contract === ZeroAddress ?
68 | nativeAddressToHexString(SystemProgram.programId.toString(), getWormholeChainIdByName('solana')) :
69 | nativeAddressToHexString(quote.toToken.contract, destinationChainId);
70 | const tokenOut = Buffer.from(hexToUint8Array(_tokenOut));
71 | data.set(tokenOut, offset);
72 | offset += 32;
73 |
74 | data.writeBigUInt64BE(getAmountOfFractionalAmount(quote.minAmountOut, Math.min(quote.toToken.decimals, 8)), offset);
75 | offset += 8;
76 |
77 | data.writeBigUInt64BE(getAmountOfFractionalAmount(quote.gasDrop, Math.min(getGasDecimal(quote.toChain), 8)), offset);
78 | offset += 8;
79 |
80 | data.writeBigUInt64BE(BigInt(quote.cancelRelayerFee64), offset);
81 | offset += 8;
82 |
83 | data.writeBigUInt64BE(BigInt(quote.refundRelayerFee64), offset);
84 | offset += 8;
85 |
86 | data.writeBigUInt64BE(BigInt(quote.deadline64), offset);
87 | offset += 8;
88 |
89 | const refAddress = referrerAddress ?
90 | Buffer.from(hexToUint8Array(nativeAddressToHexString(referrerAddress, destinationChainId))) :
91 | SystemProgram.programId.toBuffer();
92 | data.set(refAddress, offset);
93 | offset += 32;
94 |
95 | data.writeUInt8(quote.referrerBps, offset);
96 | offset += 1;
97 |
98 | const feeRateMayan = quote.protocolBps;
99 | data.writeUInt8(feeRateMayan, offset);
100 | offset += 1;
101 |
102 | data.writeUInt8(quote.swiftAuctionMode, offset);
103 | offset += 1;
104 |
105 | const _randomKey = Buffer.from(hexToUint8Array(randomKeyHex));
106 | data.set(_randomKey, offset);
107 | offset += 32;
108 |
109 | if (offset !== orderDataSize) {
110 | throw new Error(`Invalid order data size: ${offset}`);
111 | }
112 |
113 | const hash = ethers.keccak256(data);
114 | return Buffer.from(hexToUint8Array(hash));
115 | }
116 |
117 | type CreateInitSwiftInstructionParams = {
118 | quote: Quote,
119 | state: PublicKey,
120 | trader: PublicKey,
121 | relayer: PublicKey,
122 | stateAccount: PublicKey,
123 | relayerAccount: PublicKey,
124 | randomKey: PublicKey,
125 | destinationAddress: string,
126 | deadline: bigint,
127 | referrerAddress: string | null | undefined,
128 | }
129 |
130 | const InitSwiftLayout = struct([
131 | blob(8, 'instruction'),
132 | blob(8, 'amountInMin'),
133 | u8('nativeInput'),
134 | blob(8, 'feeSubmit'),
135 | blob(32, 'destAddress'),
136 | u16('destinationChain'),
137 | blob(32, 'tokenOut'),
138 | blob(8, 'amountOutMin'),
139 | blob(8, 'gasDrop'),
140 | blob(8, 'feeCancel'),
141 | blob(8, 'feeRefund'),
142 | blob(8, 'deadline'),
143 | blob(32, 'refAddress'),
144 | u8('feeRateRef'),
145 | u8('feeRateMayan'),
146 | u8('auctionMode'),
147 | blob(32, 'randomKey'),
148 | ]);
149 |
150 | function createSwiftInitInstruction(
151 | params: CreateInitSwiftInstructionParams,
152 | ): TransactionInstruction {
153 | const { quote } = params;
154 | const mint = quote.swiftInputContract === ZeroAddress ?
155 | solMint : new PublicKey(quote.swiftInputContract);
156 | const destinationChainId = getWormholeChainIdByName(quote.toChain);
157 |
158 | if (destinationChainId !== quote.toToken.wChainId) {
159 | throw new Error(`Destination chain ID mismatch: ${destinationChainId} != ${quote.toToken.wChainId}`);
160 | }
161 | const accounts: AccountMeta[] = [
162 | { pubkey: params.trader, isWritable: false, isSigner: true },
163 | { pubkey: params.relayer, isWritable: true, isSigner: true },
164 | { pubkey: params.state, isWritable: true, isSigner: false },
165 | { pubkey: params.stateAccount, isWritable: true, isSigner: false },
166 | { pubkey: params.relayerAccount, isWritable: true, isSigner: false },
167 | { pubkey: mint, isWritable: false, isSigner: false },
168 | { pubkey: new PublicKey(addresses.FEE_MANAGER_PROGRAM_ID), isWritable: false, isSigner: false },
169 | { pubkey: new PublicKey(addresses.TOKEN_PROGRAM_ID), isWritable: false, isSigner: false },
170 | { pubkey: SystemProgram.programId, isWritable: false, isSigner: false },
171 | ];
172 |
173 | const data = Buffer.alloc(InitSwiftLayout.span);
174 |
175 | const refAddress = params.referrerAddress ?
176 | Buffer.from(hexToUint8Array(nativeAddressToHexString(params.referrerAddress, destinationChainId))) :
177 | SystemProgram.programId.toBuffer();
178 |
179 | const minMiddleAmount: bigint =
180 | quote.fromToken.contract === quote.swiftInputContract ?
181 | BigInt(quote.effectiveAmountIn64) :
182 | getAmountOfFractionalAmount(quote.minMiddleAmount, quote.swiftInputDecimals);
183 |
184 | InitSwiftLayout.encode({
185 | instruction: getAnchorInstructionData('init_order'),
186 | amountInMin: getSafeU64Blob(minMiddleAmount),
187 | nativeInput: quote.swiftInputContract === ZeroAddress ? 1 : 0,
188 | feeSubmit: getSafeU64Blob(BigInt(quote.submitRelayerFee64)),
189 | destAddress: Buffer.from(hexToUint8Array(nativeAddressToHexString(params.destinationAddress, destinationChainId))),
190 | destinationChain: destinationChainId,
191 | tokenOut: Buffer.from(hexToUint8Array(nativeAddressToHexString(quote.toToken.contract, destinationChainId))),
192 | amountOutMin: getSafeU64Blob(getAmountOfFractionalAmount(quote.minAmountOut, Math.min(quote.toToken.decimals, 8))),
193 | gasDrop: getSafeU64Blob(getAmountOfFractionalAmount(quote.gasDrop, Math.min(getGasDecimal(quote.toChain), 8))),
194 | feeCancel: getSafeU64Blob(BigInt(quote.cancelRelayerFee64)),
195 | feeRefund: getSafeU64Blob(BigInt(quote.refundRelayerFee64)),
196 | deadline: getSafeU64Blob(params.deadline),
197 | refAddress: refAddress,
198 | feeRateRef: quote.referrerBps,
199 | feeRateMayan: quote.protocolBps,
200 | auctionMode: quote.swiftAuctionMode,
201 | randomKey: params.randomKey.toBuffer(),
202 | }, data);
203 |
204 | return new TransactionInstruction({
205 | keys: accounts,
206 | data,
207 | programId: new PublicKey(addresses.SWIFT_PROGRAM_ID)
208 | });
209 | }
210 |
211 | export async function createSwiftFromSolanaInstructions(
212 | quote: Quote, swapperAddress: string, destinationAddress: string,
213 | referrerAddress: string | null | undefined,
214 | connection: Connection, options: {
215 | allowSwapperOffCurve?: boolean,
216 | separateSwapTx?: boolean,
217 | skipProxyMayanInstructions?: boolean,
218 | } = {}
219 | ): Promise<{
220 | instructions: TransactionInstruction[],
221 | signers: Keypair[],
222 | lookupTables: AddressLookupTableAccount[],
223 | swapMessageV0Params: SwapMessageV0Params | null,
224 | }> {
225 |
226 | if (quote.type !== 'SWIFT') {
227 | throw new Error('Unsupported quote type for Swift: ' + quote.type);
228 | }
229 | if (quote.toChain === 'solana') {
230 | throw new Error('Unsupported destination chain: ' + quote.toChain);
231 | }
232 |
233 | const allowSwapperOffCurve = options.allowSwapperOffCurve || false;
234 |
235 | let instructions: TransactionInstruction[] = [];
236 | let lookupTables: AddressLookupTableAccount[] = [];
237 |
238 | let _lookupTablesAddress: string[] = [];
239 |
240 | _lookupTablesAddress.push(addresses.LOOKUP_TABLE);
241 |
242 | // using for the swap via Jito Bundle
243 | let _swapAddressLookupTables: string[] = [];
244 | let swapInstructions: TransactionInstruction[] = [];
245 | let createSwapTpmTokenAccountInstructions: TransactionInstruction[] = [];
246 | const tmpSwapTokenAccount: Keypair = Keypair.generate();
247 | let swapMessageV0Params: SwapMessageV0Params | null = null;
248 |
249 | const swiftProgram = new PublicKey(addresses.SWIFT_PROGRAM_ID);
250 | const trader = new PublicKey(swapperAddress);
251 |
252 | const randomKey = Keypair.generate();
253 |
254 | if (!Number(quote.deadline64)) {
255 | throw new Error('Swift mode requires a timeout');
256 | }
257 | const deadline = BigInt(quote.deadline64);
258 |
259 | const hash = createSwiftOrderHash(
260 | quote, swapperAddress, destinationAddress,
261 | referrerAddress, randomKey.publicKey.toBuffer().toString('hex')
262 | );
263 | const [state] = PublicKey.findProgramAddressSync(
264 | [Buffer.from('STATE_SOURCE'), hash],
265 | swiftProgram,
266 | );
267 |
268 | const swiftInputMint = quote.swiftInputContract === ZeroAddress ? solMint : new PublicKey(quote.swiftInputContract);
269 |
270 | const stateAccount = getAssociatedTokenAddress(
271 | swiftInputMint, state, true
272 | );
273 |
274 | const relayer = quote.gasless ? new PublicKey(quote.relayer) : trader;
275 | const relayerAccount = getAssociatedTokenAddress(swiftInputMint, relayer, false);
276 | if (quote.fromToken.contract === quote.swiftInputContract) {
277 | if (quote.suggestedPriorityFee > 0) {
278 | instructions.push(ComputeBudgetProgram.setComputeUnitPrice({
279 | microLamports: quote.suggestedPriorityFee,
280 | }))
281 | }
282 | instructions.push(
283 | sandwichInstructionInCpiProxy(createAssociatedTokenAccountInstruction(relayer, stateAccount, state, swiftInputMint))
284 | );
285 | if (quote.swiftInputContract === ZeroAddress) {
286 | instructions.push(
287 | sandwichInstructionInCpiProxy(SystemProgram.transfer({
288 | fromPubkey: trader,
289 | toPubkey: stateAccount,
290 | lamports: BigInt(quote.effectiveAmountIn64),
291 | })),
292 | sandwichInstructionInCpiProxy(createSyncNativeInstruction(stateAccount)),
293 | );
294 | } else {
295 | instructions.push(
296 | sandwichInstructionInCpiProxy(createSplTransferInstruction(
297 | getAssociatedTokenAddress(
298 | swiftInputMint, trader, allowSwapperOffCurve
299 | ),
300 | stateAccount,
301 | trader,
302 | BigInt(quote.effectiveAmountIn64),
303 | ))
304 | );
305 | }
306 | } else {
307 | const clientSwapRaw = await getSwapSolana({
308 | minMiddleAmount: quote.minMiddleAmount,
309 | middleToken: quote.swiftInputContract,
310 | userWallet: swapperAddress,
311 | slippageBps: quote.slippageBps,
312 | fromToken: quote.fromToken.contract,
313 | amountIn64: quote.effectiveAmountIn64,
314 | depositMode: quote.gasless ? 'SWIFT_GASLESS' : 'SWIFT',
315 | orderHash: `0x${hash.toString('hex')}`,
316 | fillMaxAccounts: options?.separateSwapTx || false,
317 | tpmTokenAccount: options?.separateSwapTx ? tmpSwapTokenAccount.publicKey.toString() : null,
318 | });
319 | const clientSwap = decentralizeClientSwapInstructions(clientSwapRaw, connection, relayer);
320 | if (options?.separateSwapTx && clientSwapRaw.maxAccountsFilled) {
321 | validateJupSwap(clientSwap, tmpSwapTokenAccount.publicKey, trader);
322 | createSwapTpmTokenAccountInstructions = await createInitializeRandomTokenAccountInstructions(
323 | connection,
324 | relayer,
325 | swiftInputMint,
326 | trader,
327 | tmpSwapTokenAccount,
328 | );
329 | swapInstructions.push(...clientSwap.computeBudgetInstructions);
330 | if (clientSwap.setupInstructions) {
331 | swapInstructions.push(...clientSwap.setupInstructions);
332 | }
333 | swapInstructions.push(clientSwap.swapInstruction);
334 | if (clientSwap.cleanupInstruction) {
335 | swapInstructions.push(clientSwap.cleanupInstruction);
336 | }
337 | _swapAddressLookupTables.push(...clientSwap.addressLookupTableAddresses);
338 | instructions.push(sandwichInstructionInCpiProxy(
339 | createAssociatedTokenAccountInstruction(relayer, stateAccount, state, swiftInputMint)
340 | ));
341 | instructions.push(sandwichInstructionInCpiProxy(createTransferAllAndCloseInstruction(
342 | trader,
343 | swiftInputMint,
344 | tmpSwapTokenAccount.publicKey,
345 | stateAccount,
346 | relayer,
347 | )));
348 | } else {
349 | validateJupSwap(clientSwap, stateAccount, trader);
350 | instructions.push(...clientSwap.computeBudgetInstructions);
351 | if (clientSwap.setupInstructions) {
352 | instructions.push(...(clientSwap.setupInstructions.map(ins => sandwichInstructionInCpiProxy(ins))));
353 | }
354 | instructions.push(sandwichInstructionInCpiProxy(clientSwap.swapInstruction));
355 | if (clientSwap.cleanupInstruction) {
356 | instructions.push(sandwichInstructionInCpiProxy(clientSwap.cleanupInstruction));
357 | }
358 | _lookupTablesAddress.push(...clientSwap.addressLookupTableAddresses);
359 | }
360 | }
361 |
362 | instructions.push(sandwichInstructionInCpiProxy(createSwiftInitInstruction({
363 | quote,
364 | state,
365 | trader,
366 | stateAccount,
367 | randomKey: randomKey.publicKey,
368 | relayerAccount,
369 | relayer,
370 | destinationAddress,
371 | deadline,
372 | referrerAddress,
373 | }), options.skipProxyMayanInstructions));
374 |
375 | const totalLookupTables = await getAddressLookupTableAccounts(_lookupTablesAddress.concat(_swapAddressLookupTables), connection);
376 | lookupTables = totalLookupTables.slice(0, _lookupTablesAddress.length);
377 |
378 | if (swapInstructions.length > 0) {
379 | const swapLookupTables = totalLookupTables.slice(_lookupTablesAddress.length);
380 | swapMessageV0Params = {
381 | messageV0: {
382 | payerKey: relayer,
383 | instructions: swapInstructions,
384 | addressLookupTableAccounts: swapLookupTables,
385 | },
386 | createTmpTokenAccountIxs: createSwapTpmTokenAccountInstructions,
387 | tmpTokenAccount: tmpSwapTokenAccount,
388 | };
389 | }
390 |
391 | return { instructions, signers: [], lookupTables, swapMessageV0Params };
392 | }
393 |
394 |
395 |
396 |
397 |
398 |
399 |
--------------------------------------------------------------------------------
/src/sui/index.ts:
--------------------------------------------------------------------------------
1 | export * from './utils';
2 | export * from './suiMctp';
3 | export * from './suiSwap';
4 |
5 |
--------------------------------------------------------------------------------
/src/sui/suiHyperCore.ts:
--------------------------------------------------------------------------------
1 | import { Transaction, TransactionResult } from '@mysten/sui/transactions';
2 | import { SuiClient } from '@mysten/sui/client';
3 | import {
4 | Quote,
5 | SuiFunctionParameter,
6 | SuiFunctionNestedResult,
7 | ComposableSuiMoveCallsOptions
8 | } from '../types';
9 | import {
10 | fetchMayanSuiPackageId,
11 | resolveInputCoin,
12 | } from './utils';
13 | import {
14 | getAmountOfFractionalAmount, getDisplayAmount,
15 | getHyperCoreUSDCDepositCustomPayload,
16 | getWormholeChainIdByName,
17 | nativeAddressToHexString
18 | } from '../utils';
19 | import addresses from '../addresses';
20 | import { CCTP_TOKEN_DECIMALS } from '../cctp';
21 | import { SystemProgram } from '@solana/web3.js';
22 | import { getSwapSui } from '../api';
23 | import { addBridgeWithFeeMoveCalls2 } from './suiMctp';
24 |
25 | export async function createHyperCoreDepositFromSuiMoveCalls(
26 | quote: Quote,
27 | swapperAddress: string,
28 | destinationAddress: string,
29 | referrerAddress: string | null | undefined,
30 | suiClient: SuiClient,
31 | options?: ComposableSuiMoveCallsOptions
32 | ): Promise {
33 | if (
34 | quote.toToken.contract.toLowerCase() !== addresses.ARBITRUM_USDC_CONTRACT.toLowerCase() ||
35 | quote.type !== 'MCTP'
36 | ) {
37 | throw new Error('Unsupported quote type for USDC deposit: ' + quote.type);
38 | }
39 | if (!quote.hyperCoreParams) {
40 | throw new Error('HyperCore parameters are required for this quote');
41 | }
42 | if (!options?.usdcPermitSignature) {
43 | throw new Error('USDC permit signature is required for this quote');
44 | }
45 | if (!Number(quote.deadline64)) {
46 | throw new Error('HyperCore deposit requires timeout');
47 | }
48 |
49 | const [mctpPackageId] = await Promise.all([
50 | fetchMayanSuiPackageId(addresses.SUI_MCTP_STATE, suiClient),
51 | ]);
52 |
53 | const amountInMin = getAmountOfFractionalAmount(
54 | quote.minMiddleAmount,
55 | CCTP_TOKEN_DECIMALS
56 | );
57 | let tx: Transaction;
58 | let inputCoin:
59 | | TransactionResult
60 | | SuiFunctionNestedResult
61 | | { $kind: 'Input'; Input: number; type?: 'object' };
62 | let whFeeCoin: SuiFunctionParameter;
63 |
64 | // Setup tx based on we should have client swap or not
65 | if (quote.fromToken.contract === quote.hyperCoreParams.initiateTokenContract) {
66 | tx = options?.builtTransaction ?? new Transaction();
67 | inputCoin = await resolveInputCoin(
68 | BigInt(quote.hyperCoreParams.bridgeAmountUSDC64),
69 | swapperAddress,
70 | quote.mctpInputContract,
71 | suiClient,
72 | tx,
73 | options?.inputCoin
74 | );
75 | } else {
76 | const {
77 | tx: serializedTx,
78 | outCoin,
79 | whFeeCoin: suiSplitViaSwap,
80 | } = await getSwapSui({
81 | amountIn64: quote.effectiveAmountIn64,
82 | inputCoinType: quote.fromToken.contract,
83 | middleCoinType: quote.hyperCoreParams.initiateTokenContract,
84 | userWallet: swapperAddress,
85 | withWhFee: true,
86 | referrerAddress,
87 | inputCoin: options?.inputCoin,
88 | transaction: options?.builtTransaction ? (await options.builtTransaction.toJSON()) : undefined,
89 | });
90 | tx = Transaction.from(serializedTx);
91 | const [initiateCoin] = tx.splitCoins(
92 | outCoin,
93 | [ BigInt(quote.hyperCoreParams.bridgeAmountUSDC64) ]
94 | );
95 |
96 | tx.transferObjects(
97 | [ outCoin ],
98 | tx.pure.address(swapperAddress)
99 | );
100 |
101 | inputCoin = initiateCoin;
102 | whFeeCoin = suiSplitViaSwap ? { result: suiSplitViaSwap } : null;
103 | }
104 |
105 | const payload = getHyperCoreUSDCDepositCustomPayload(quote, destinationAddress, options.usdcPermitSignature);
106 | await addBridgeWithFeeMoveCalls2({
107 | swapperAddress,
108 | destinationAddress: addresses.HC_ARBITRUM_DEPOSIT_PROCESSOR,
109 | mctpPackageId,
110 | toChain: 'arbitrum',
111 | minMiddleAmount: getDisplayAmount(quote.hyperCoreParams.bridgeAmountUSDC64, CCTP_TOKEN_DECIMALS),
112 | bridgeFee: quote.bridgeFee,
113 | gasDrop: quote.hyperCoreParams.failureGasDrop,
114 | mctpInputContract: quote.mctpInputContract,
115 | mctpInputTreasury: quote.mctpInputTreasury,
116 | mctpVerifiedInputAddress: quote.mctpVerifiedInputAddress,
117 | redeemRelayerFee: 0,
118 | payload,
119 | suiClient,
120 | options: {
121 | inputCoin: { result: inputCoin },
122 | whFeeCoin,
123 | builtTransaction: tx,
124 | },
125 | });
126 |
127 |
128 | // Log initial coin and amount
129 | const amountIn = BigInt(quote.effectiveAmountIn64);
130 | const _payload = Uint8Array.from(payload);
131 | tx.moveCall({
132 | package: mctpPackageId,
133 | module: 'init_order',
134 | function: 'log_initialize_mctp',
135 | typeArguments: [quote.fromToken.contract],
136 | arguments: [
137 | tx.pure.u64(amountIn),
138 | tx.object(quote.fromToken.verifiedAddress),
139 | tx.pure.vector('u8', _payload),
140 | ]
141 | });
142 |
143 | try {
144 | // Log referrer
145 | let referrerHex: string;
146 | if (referrerAddress) {
147 | referrerHex = nativeAddressToHexString(referrerAddress, getWormholeChainIdByName(quote.toChain));
148 | } else {
149 | referrerHex = nativeAddressToHexString(
150 | SystemProgram.programId.toString(),
151 | getWormholeChainIdByName('solana')
152 | );
153 | }
154 | tx.moveCall({
155 | package: addresses.SUI_LOGGER_PACKAGE_ID,
156 | module: 'referrer_logger',
157 | function: 'log_referrer',
158 | arguments: [
159 | tx.pure.address(referrerHex),
160 | tx.pure.u8(quote.referrerBps || 0),
161 | ]
162 | })
163 | } catch (err) {
164 | console.error('Failed to log referrer', err);
165 | }
166 | return tx;
167 | }
168 |
--------------------------------------------------------------------------------
/src/sui/suiSwap.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Quote,
3 | ReferrerAddresses,
4 | ComposableSuiMoveCallsOptions,
5 | } from '../types';
6 | import { SuiClient } from '@mysten/sui/client';
7 | import { Transaction } from '@mysten/sui/transactions';
8 | import { getQuoteSuitableReferrerAddress } from '../utils';
9 | import { createMctpFromSuiMoveCalls } from './suiMctp';
10 | import { Buffer } from 'buffer';
11 | import { createHyperCoreDepositFromSuiMoveCalls } from './suiHyperCore';
12 |
13 | export async function createSwapFromSuiMoveCalls(
14 | quote: Quote,
15 | swapperWalletAddress: string,
16 | destinationAddress: string,
17 | referrerAddresses: ReferrerAddresses | null | undefined,
18 | payload: Uint8Array | Buffer | null | undefined,
19 | suiClient: SuiClient,
20 | options?: ComposableSuiMoveCallsOptions
21 | ): Promise {
22 | const referrerAddress = getQuoteSuitableReferrerAddress(
23 | quote,
24 | referrerAddresses
25 | );
26 |
27 | if (quote.toChain === 'hypercore') {
28 | if (!quote.hyperCoreParams) {
29 | throw new Error('HyperCore parameters are required for this quote');
30 | }
31 | if (!options?.usdcPermitSignature) {
32 | throw new Error('USDC permit signature is required for this quote');
33 | }
34 | if (quote.type !== 'MCTP') {
35 | throw new Error('Unsupported quote type for HyperCore deposit: ' + quote.type);
36 | }
37 | if (payload) {
38 | throw new Error('Payload is not supported for HyperCore deposit quotes');
39 | }
40 | return createHyperCoreDepositFromSuiMoveCalls(
41 | quote,
42 | swapperWalletAddress,
43 | destinationAddress,
44 | referrerAddress,
45 | suiClient,
46 | options,
47 | );
48 | }
49 |
50 | if (quote.type === 'MCTP') {
51 | return createMctpFromSuiMoveCalls(
52 | quote,
53 | swapperWalletAddress,
54 | destinationAddress,
55 | referrerAddress,
56 | payload,
57 | suiClient,
58 | options
59 | );
60 | } else {
61 | throw new Error('Unsupported quote type from Sui chain');
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/sui/utils.ts:
--------------------------------------------------------------------------------
1 | import {
2 | SuiClient,
3 | SuiMoveFunctionArgType,
4 | CoinStruct,
5 | PaginatedCoins,
6 | SuiObjectResponse,
7 | } from '@mysten/sui/client';
8 | import { SuiFunctionNestedResult, SuiFunctionParameter } from '../types';
9 | import { Transaction, TransactionResult } from '@mysten/sui/transactions';
10 |
11 | /**
12 | * assertArgumentIsImmutable
13 | *
14 | * Validates whether a specific argument of a given Move function in a Sui module is immutable.
15 | * This includes checking if the argument type is either `Pure` or an object passed by immutable reference.
16 | *
17 | * @param params - An object containing the following properties:
18 | * - package: The Sui package containing the Move module.
19 | * - module: The name of the Move module.
20 | * - function: The name of the Move function to validate.
21 | * - argumentIndex: The index of the argument to check for immutability.
22 | * @param suiClient - An instance of `SuiClient` used to interact with the Sui blockchain.
23 | *
24 | * @throws Will throw an error if:
25 | * - The argument types cannot be retrieved from the Sui client.
26 | * - The specified argument is not immutable (i.e., not `Pure` or passed as an immutable reference).
27 | *
28 | * @returns A Promise that resolves if the specified argument is immutable, or rejects with an error otherwise.
29 | */
30 | export async function assertArgumentIsImmutable(
31 | params: {
32 | package: string;
33 | module: string;
34 | function: string;
35 | argumentIndex: number;
36 | },
37 | suiClient: SuiClient
38 | ): Promise {
39 | let argTypes: SuiMoveFunctionArgType[];
40 | try {
41 | argTypes = await suiClient.getMoveFunctionArgTypes({
42 | package: params.package,
43 | module: params.module,
44 | function: params.function,
45 | });
46 | } catch (error) {
47 | throw new Error(
48 | `Failed to fetch ${params.package}::${params.module}::${params.function} ArgTypes`
49 | );
50 | }
51 | if (argTypes) {
52 | if (
53 | argTypes[params.argumentIndex] !== 'Pure' &&
54 | //@ts-ignore
55 | argTypes[params.argumentIndex]?.Object !== 'ByImmutableReference'
56 | ) {
57 | throw new Error(
58 | `Argument ${params.argumentIndex} of ${params.module}::${params.function} is not immutable`
59 | );
60 | }
61 | } else {
62 | throw new Error(
63 | `Failed to fetch package::${params.module}::${params.function} ArgTypes`
64 | );
65 | }
66 | }
67 |
68 | /**
69 | * fetchCoinsUntilAmountReachedOrEnd
70 | *
71 | * This function is inspired by the `fetchAllCoins` implementation from the
72 | * Aftermath Finance SDK (reference: https://github.com/AftermathFinance/aftermath-ts-sdk/blob/74087402caf5ebf06f6c639cc5e23445d40a039f/src/packages/coin/api/coinApi.ts#L85).
73 | *
74 | * It retrieves a list of coins associated with a specified wallet address and coin type until
75 | * either the target coin amount is reached or no more coins are available. The function returns
76 | * an object containing the collected coins and the cumulative sum of their values.
77 | *
78 | * @param inputs - An object containing the following properties:
79 | * - walletAddress: The address of the wallet to fetch coins from.
80 | * - coinType: The type of coin to filter for during retrieval.
81 | * - coinAmount: The target coin amount to reach.
82 | *
83 | * @param suiClient - An instance of `SuiClient` used to interact with the Sui blockchain.
84 | *
85 | * @returns A Promise that resolves to an object containing:
86 | * - coins: An array of CoinStruct objects representing the retrieved coins.
87 | * - sum: The cumulative value of the retrieved coins.
88 | */
89 | export async function fetchAllCoins(
90 | inputs: {
91 | walletAddress: string;
92 | coinType: string;
93 | coinAmount: bigint;
94 | },
95 | suiClient: SuiClient
96 | ): Promise<{
97 | coins: CoinStruct[];
98 | sum: bigint;
99 | }> {
100 | let allCoinData: CoinStruct[] = [];
101 | let currentSum = BigInt(0);
102 | let cursor: string | undefined = undefined;
103 | do {
104 | const paginatedCoins: PaginatedCoins = await suiClient.getCoins({
105 | ...inputs,
106 | owner: inputs.walletAddress,
107 | cursor,
108 | });
109 |
110 | const coinData = paginatedCoins.data.filter(
111 | (data) => BigInt(data.balance) > BigInt(0)
112 | );
113 | allCoinData = [...allCoinData, ...coinData];
114 |
115 | coinData.forEach((coin) => {
116 | currentSum += BigInt(coin.balance);
117 | });
118 |
119 | if (
120 | paginatedCoins.data.length === 0 ||
121 | !paginatedCoins.hasNextPage ||
122 | !paginatedCoins.nextCursor ||
123 | currentSum >= inputs.coinAmount
124 | )
125 | return {
126 | coins: allCoinData.sort((b, a) =>
127 | Number(BigInt(b.coinObjectId) - BigInt(a.coinObjectId))
128 | ),
129 | sum: currentSum,
130 | };
131 |
132 | cursor = paginatedCoins.nextCursor;
133 | } while (true);
134 | }
135 |
136 |
137 | /**
138 | * Fetches the latest Mayan Sui package ID from a shared state object on the Sui blockchain.
139 | *
140 | * By Mayan standards:
141 | * - The latest package ID is stored in a shared state object to avoid forced SDK updates during package upgrades.
142 | * - The shared state object ID is hardcoded in the SDK to ensure security.
143 | * - Upgrades to Mayan packages require multiple signatures to perform, enhancing governance and security.
144 | *
145 | * @param {string} stateObjectId - The ID of the shared state object containing the latest package ID.
146 | * @param {SuiClient} suiClient - An instance of the SuiClient used to interact with the Sui blockchain.
147 | * @returns {Promise} - A promise that resolves to the latest Mayan Sui package ID.
148 | * @throws {Error} - Throws an error if the state object cannot be fetched or if the `latest_package_id` field is not found.
149 | */
150 | export async function fetchMayanSuiPackageId(
151 | stateObjectId: string,
152 | suiClient: SuiClient
153 | ): Promise {
154 | let object: SuiObjectResponse;
155 | try {
156 | object = await suiClient.getObject({
157 | id: stateObjectId,
158 | options: {
159 | showContent: true,
160 | },
161 | });
162 | } catch (err) {
163 | throw new Error(`Failed to fetch Mayan Sui package ID: \n\n ${err}`);
164 | }
165 | // @ts-ignore
166 | if (object.data?.content?.fields?.latest_package_id) {
167 | // @ts-ignore
168 | return object.data.content.fields.latest_package_id;
169 | }
170 | throw new Error('latest_package_id not found in Mayan Sui state object');
171 | }
172 |
173 | export async function resolveInputCoin(
174 | amount: bigint,
175 | owner: string,
176 | coinType: string,
177 | suiClient: SuiClient,
178 | tx: Transaction,
179 | preparedCoin: SuiFunctionParameter
180 | ) {
181 | let inputCoin:
182 | | TransactionResult
183 | | SuiFunctionNestedResult
184 | | { $kind: 'Input'; Input: number; type?: 'object' };
185 | if (preparedCoin?.result) {
186 | inputCoin = preparedCoin.result;
187 | } else if (preparedCoin?.objectId) {
188 | inputCoin = tx.object(preparedCoin.objectId);
189 | } else {
190 | const { coins, sum } = await fetchAllCoins(
191 | {
192 | walletAddress: owner,
193 | coinType: coinType,
194 | coinAmount: amount,
195 | },
196 | suiClient
197 | );
198 | if (sum < amount) {
199 | throw new Error(
200 | `Insufficient funds to create Coin ${coinType} with amount ${amount}`
201 | );
202 | }
203 | if (coins.length > 1) {
204 | tx.mergeCoins(
205 | coins[0].coinObjectId,
206 | coins.slice(1).map((c) => c.coinObjectId)
207 | );
208 | }
209 | const [spitedCoin] = tx.splitCoins(coins[0].coinObjectId, [amount]);
210 | inputCoin = spitedCoin;
211 | }
212 | return inputCoin;
213 | }
214 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CompileV0Args,
3 | Transaction,
4 | VersionedTransaction,
5 | TransactionInstruction as SolanaTransactionInstruction,
6 | Keypair as SolanaKeypair,
7 | } from '@solana/web3.js';
8 | import {
9 | Transaction as SuiTransaction,
10 | TransactionResult as SuiTransactionResult,
11 | } from '@mysten/sui/transactions';
12 | import { Buffer } from 'buffer';
13 |
14 | export type ChainName = 'solana'
15 | | 'ethereum' | 'bsc' | 'polygon' | 'avalanche' | 'arbitrum' | 'optimism' |
16 | 'base' | 'aptos' | 'sui' | 'unichain' | 'linea' | 'hypercore' | 'sonic' | 'hyperevm';
17 |
18 | export type TokenStandard = 'native' | 'erc20' | 'spl' | 'spl2022' | 'suicoin' | 'hypertoken';
19 |
20 | export type Token = {
21 | name: string,
22 | symbol: string,
23 | mint: string,
24 | contract: string,
25 | chainId: number,
26 | wChainId?: number,
27 | decimals: number,
28 | logoURI: string,
29 | coingeckoId: string,
30 | realOriginChainId?: number,
31 | realOriginContractAddress?: string,
32 | supportsPermit: boolean,
33 | verified: boolean;
34 | standard: TokenStandard,
35 | verifiedAddress: string,
36 | };
37 |
38 | export type QuoteParams = {
39 | /**
40 | * @deprecated to avoid precision issues, use {@link amountIn64} instead
41 | */
42 | amount?: number;
43 | amountIn64?: string;
44 | fromToken: string;
45 | fromChain: ChainName;
46 | toToken: string;
47 | toChain: ChainName;
48 | /**
49 | * @deprecated Use the new property {@link slippageBps} instead
50 | */
51 | slippage?: number;
52 | /**
53 | * Slippage in basis points.
54 | * One basis point (bps) = 0.01%.
55 | *
56 | * - A value of `50` means a slippage of 0.5%.
57 | * - A value of `100` means a slippage of 1%.
58 | * - If set to `'auto'`, the system will automatically determine slippage.
59 | *
60 | * @example
61 | * slippageBps: 50 // 0.5% slippage
62 | */
63 | slippageBps: 'auto' | number;
64 | gasDrop?: number;
65 | referrer?: string;
66 | referrerBps?: number;
67 | };
68 |
69 | export type QuoteError = {
70 | message: string,
71 | code: number,
72 | data: any,
73 | }
74 |
75 | export type Quote = {
76 | type: 'WH' | 'SWIFT' | 'MCTP' | 'SHUTTLE' | 'FAST_MCTP' | 'MONO_CHAIN';
77 | /**
78 | * @deprecated Use the new property {@link effectiveAmountIn64} instead
79 | */
80 | effectiveAmountIn: number;
81 | effectiveAmountIn64: string;
82 | expectedAmountOut: number;
83 | priceImpact: number;
84 | minAmountOut: number;
85 | minReceived: number;
86 | gasDrop: number;
87 | price: number;
88 | swapRelayerFee: number;
89 | redeemRelayerFee: number;
90 | refundRelayerFee: number;
91 | solanaRelayerFee: number;
92 | redeemRelayerFee64: string;
93 | refundRelayerFee64: string;
94 | cancelRelayerFee64: string;
95 | submitRelayerFee64: string;
96 | solanaRelayerFee64: string;
97 | clientRelayerFeeSuccess: number | null;
98 | clientRelayerFeeRefund: number | null;
99 | eta: number;
100 | etaSeconds: number;
101 | clientEta: string;
102 | fromToken: Token;
103 | toToken: Token;
104 | fromChain: ChainName;
105 | toChain: ChainName;
106 | slippageBps: number;
107 | priceStat: {
108 | ratio: number;
109 | status: 'GOOD' | 'NORMAL' | 'BAD';
110 | }
111 | mintDecimals: {
112 | from: number;
113 | to: number;
114 | };
115 | bridgeFee: number;
116 | suggestedPriorityFee: number;
117 | meta: {
118 | icon: string;
119 | title: string;
120 | advertisedTitle: string;
121 | advertisedDescription: string;
122 | switchText: string;
123 | };
124 | onlyBridging: boolean;
125 | deadline64: string;
126 | referrerBps?: number;
127 | protocolBps?: number;
128 | whMayanContract: string;
129 | cheaperChain: ChainName;
130 | mctpInputContract: string;
131 | mctpOutputContract: string;
132 | hasAuction: boolean;
133 | minMiddleAmount?: number;
134 | evmSwapRouterAddress?: string;
135 | evmSwapRouterCalldata?: string;
136 | mctpMayanContract?: string;
137 | swiftMayanContract?: string;
138 | shuttleContract?: string;
139 | swiftAuctionMode?: number;
140 | swiftInputContract: string;
141 | swiftInputDecimals: number;
142 | gasless: boolean;
143 | relayer: string;
144 | sendTransactionCost: number;
145 | maxUserGasDrop: number;
146 | rentCost?: bigint;
147 | shuttleParams : {
148 | maxLLFee: string;
149 | maxRelayingFee: string;
150 | fastTransferDeadline: number;
151 | hasDestSwap: boolean
152 | path: string;
153 | }
154 | shuttleInputContract: string;
155 | shuttleInputDecimals: number;
156 | mctpVerifiedInputAddress: string;
157 | mctpInputTreasury: string;
158 | circleMaxFee64: string;
159 | fastMctpMayanContract: string;
160 | fastMctpInputContract: string;
161 | fastMctpMinFinality: number;
162 | hyperCoreParams?: {
163 | depositAmountUSDC64: string;
164 | bridgeAmountUSDC64: string;
165 | initiateTokenContract: string;
166 | initiateContractAddress?: string;
167 | failureGasDrop: number;
168 | };
169 | monoChainMayanContract: string;
170 | };
171 |
172 | export type QuoteOptions = {
173 | wormhole?: boolean;
174 | swift?: boolean;
175 | mctp?: boolean;
176 | shuttle?: boolean;
177 | fastMctp?: boolean;
178 | gasless?: boolean;
179 | onlyDirect?: boolean;
180 | fullList?: boolean;
181 | payload?: string;
182 | monoChain?: boolean;
183 | };
184 |
185 | export type SolanaTransactionSigner = {
186 | (trx: Transaction): Promise;
187 | (trx: VersionedTransaction): Promise;
188 | };
189 |
190 | export type Erc20Permit = {
191 | value: bigint,
192 | deadline: number,
193 | v: number,
194 | r: string,
195 | s: string,
196 | }
197 |
198 | type BaseGetSolanaSwapParams = {
199 | amountIn64: string,
200 | fromToken: string,
201 | minMiddleAmount: number,
202 | middleToken: string,
203 | userWallet: string,
204 | slippageBps: number,
205 | referrerAddress?: string,
206 | fillMaxAccounts?: boolean,
207 | tpmTokenAccount?: string,
208 | }
209 |
210 | type MctpGetSolanaSwapParams = BaseGetSolanaSwapParams & {
211 | userLedger: string,
212 | depositMode: 'WITH_FEE' | 'LOCK_FEE' | 'SWAP',
213 | }
214 |
215 | type SwiftGetSolanaSwapParams = BaseGetSolanaSwapParams & {
216 | orderHash: string,
217 | depositMode: 'SWIFT' | 'SWIFT_GASLESS',
218 | }
219 |
220 | type HCDepositUSDCGetSolanaSwapParams = BaseGetSolanaSwapParams & {
221 | tpmTokenAccount: string,
222 | depositMode: 'HC_USDC',
223 | }
224 |
225 | type MonoChainGetSolanaSwapParams = Omit<
226 | BaseGetSolanaSwapParams,
227 | 'minMiddleAmount' | 'fillMaxAccounts' | 'tpmTokenAccount'
228 | > & {
229 | destinationWallet: string;
230 | expectedAmountOut64: string;
231 | depositMode: 'MONO_CHAIN';
232 | referrerBps: number;
233 | };
234 |
235 |
236 | export type GetSolanaSwapParams =
237 | MctpGetSolanaSwapParams | SwiftGetSolanaSwapParams | HCDepositUSDCGetSolanaSwapParams | MonoChainGetSolanaSwapParams;
238 |
239 | type BaseGetSuiSwapParams = {
240 | amountIn64: string,
241 | inputCoinType: string,
242 | middleCoinType: string,
243 | userWallet: string,
244 | referrerAddress?: string,
245 | inputCoin: SuiFunctionParameter,
246 | transaction: string,
247 | }
248 |
249 | type MctpGetSuiSwapParams = BaseGetSuiSwapParams & {
250 | withWhFee: boolean
251 | }
252 |
253 | export type GetSuiSwapParams = MctpGetSuiSwapParams;
254 |
255 | export type SolanaKeyInfo = {
256 | pubkey: string,
257 | isWritable: boolean,
258 | isSigner: boolean,
259 | }
260 | export type InstructionInfo = {
261 | accounts: SolanaKeyInfo[],
262 | data: string,
263 | programId: string,
264 | }
265 |
266 | export type SolanaClientSwap = {
267 | computeBudgetInstructions?: InstructionInfo[],
268 | setupInstructions?: InstructionInfo[],
269 | swapInstruction: InstructionInfo,
270 | cleanupInstruction: InstructionInfo,
271 | addressLookupTableAddresses: string[],
272 | maxAccountsFilled: boolean,
273 | }
274 |
275 | export type SuiFunctionNestedResult = {
276 | $kind: 'NestedResult';
277 | NestedResult: [number, number];
278 | };
279 |
280 | export type SuiFunctionParameter =
281 | {
282 | result:
283 | | SuiTransactionResult
284 | | SuiFunctionNestedResult
285 | | { $kind: 'Input'; Input: number; type?: 'object' };
286 | objectId?: undefined | null;
287 | }
288 | | {
289 | result?: undefined | null;
290 | objectId: string;
291 | };
292 |
293 | export type SuiClientSwap = {
294 | tx: string,
295 | outCoin: SuiTransactionResult,
296 | whFeeCoin?: SuiTransactionResult | SuiFunctionNestedResult,
297 | }
298 |
299 | export type ReferrerAddresses = {
300 | solana?: string | null,
301 | evm?: string | null,
302 | sui?: string | null,
303 | }
304 |
305 | export type SwiftEvmOrderTypedData = {
306 | domain: {
307 | name: "Mayan Swift",
308 | chainId: number,
309 | verifyingContract: string,
310 | },
311 | types: {
312 | CreateOrder: [
313 | { name: 'OrderId', type: 'bytes32' },
314 | { name: 'InputAmount', type: 'uint256' },
315 | { name: 'SubmissionFee', type: 'uint256' },
316 | ],
317 | },
318 | value: {
319 | OrderId: string,
320 | InputAmount: bigint,
321 | SubmissionFee: bigint,
322 | }
323 | }
324 |
325 | export type EvmForwarderParams = {
326 | method: string,
327 | params: any[],
328 | }
329 |
330 | export type JitoBundleOptions = {
331 | tipLamports: number,
332 | signAllTransactions: (transactions: T[]) => Promise
333 | jitoAccount?: string,
334 | jitoSendUrl?: string,
335 | separateSwapTx?: boolean,
336 | }
337 |
338 | export type ComposableSuiMoveCallsOptions = {
339 | builtTransaction?: SuiTransaction;
340 | inputCoin?: SuiFunctionParameter;
341 | whFeeCoin?: SuiFunctionParameter;
342 | usdcPermitSignature?: string;
343 | };
344 |
345 | export type SwapMessageV0Params = {
346 | messageV0: Omit,
347 | createTmpTokenAccountIxs: SolanaTransactionInstruction[],
348 | tmpTokenAccount: SolanaKeypair,
349 | }
350 |
351 | export type PermitDomain = {
352 | name: string;
353 | version: string;
354 | chainId: number;
355 | verifyingContract: string;
356 | };
357 |
358 | export type PermitValue = {
359 | owner: string;
360 | spender: string;
361 | value: string;
362 | nonce: string;
363 | deadline: string;
364 | }
365 |
366 | export type SolanaBridgeOptions = {
367 | allowSwapperOffCurve?: boolean,
368 | forceSkipCctpInstructions?: boolean,
369 | separateSwapTx?: boolean,
370 | usdcPermitSignature?: string;
371 | skipProxyMayanInstructions?: boolean;
372 | customPayload?: Buffer | Uint8Array | null;
373 | }
374 |
375 | export type EstimateGasEvmParams = {
376 | from: string;
377 | tokenIn: string;
378 | value: string;
379 | to: string;
380 | data: string;
381 | chainId: number;
382 | }
383 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { ethers, zeroPadValue, parseUnits, formatUnits, TypedDataEncoder, JsonRpcProvider } from 'ethers';
2 | import {PublicKey, SystemProgram} from '@solana/web3.js';
3 | import { Buffer } from 'buffer';
4 | import addresses from './addresses';
5 | import { ChainName, Erc20Permit, Quote, ReferrerAddresses, Token, PermitDomain, PermitValue } from './types';
6 | import ERC20Artifact from './evm/ERC20Artifact';
7 | import * as sha3 from 'js-sha3';
8 | import { CCTP_TOKEN_DECIMALS } from './cctp';
9 | import { checkHyperCoreDeposit } from './api';
10 | const sha3_256 = sha3.sha3_256;
11 |
12 | export const isValidAptosType = (str: string): boolean =>
13 | /^(0x)?[0-9a-fA-F]+::\w+::\w+$/.test(str);
14 |
15 | export function nativeAddressToHexString(
16 | address: string, wChainId: number) : string {
17 | if (wChainId === 1) {
18 | return zeroPadValue(new PublicKey(address).toBytes(), 32);
19 | } else if (
20 | wChainId === chains.ethereum || wChainId === chains.bsc || wChainId === chains.polygon ||
21 | wChainId === chains.avalanche || wChainId === chains.arbitrum || wChainId === chains.optimism ||
22 | wChainId === chains.base || wChainId === chains.unichain || wChainId === chains.linea ||
23 | wChainId === chains.sonic || wChainId === chains.hyperevm
24 | ) {
25 | return zeroPadValue(address, 32);
26 | } else if (wChainId === chains.aptos && isValidAptosType(address)) {
27 | return `0x${sha3_256(address)}`
28 | } else if (wChainId === chains.sui) {
29 | let addressStr = address.startsWith('0x') ? address.substring(2) : address;
30 | if (Buffer.from(addressStr, 'hex').length !== 32) {
31 | throw new Error('Invalid sui address: ' + address);
32 | }
33 | return zeroPadValue(address, 32);
34 | } else {
35 | console.log(`Unsupported chain id: ${wChainId}`, address);
36 | throw new Error('Unsupported token chain');
37 | }
38 | }
39 |
40 | export function hexToUint8Array(input: string): Uint8Array {
41 | return new Uint8Array(
42 | Buffer.from(
43 | input.startsWith('0x') ? input.substring(2) : input,
44 | "hex"
45 | )
46 | );
47 | }
48 |
49 | export function getAssociatedTokenAddress(
50 | mint: PublicKey,
51 | owner: PublicKey,
52 | allowOwnerOffCurve = false,
53 | programId = new PublicKey(addresses.TOKEN_PROGRAM_ID),
54 | associatedTokenProgramId = new PublicKey(addresses.ASSOCIATED_TOKEN_PROGRAM_ID)
55 | ): PublicKey {
56 | if (!allowOwnerOffCurve && !PublicKey.isOnCurve(owner.toBuffer())) {
57 | throw new Error('TokenOwnerOffCurveError');
58 | }
59 |
60 | const [address] = PublicKey.findProgramAddressSync(
61 | [owner.toBuffer(), programId.toBuffer(), mint.toBuffer()],
62 | associatedTokenProgramId
63 | );
64 |
65 | return address;
66 | }
67 |
68 | function isValidNumericInput(value: any): boolean {
69 | return (
70 | (typeof value === 'string' || typeof value === 'number') &&
71 | value !== '' &&
72 | value !== null &&
73 | !isNaN(Number(value)) &&
74 | Number.isFinite(Number(value))
75 | );
76 | }
77 |
78 | export function getAmountOfFractionalAmount(
79 | amount: string | number, decimals: string | number) : bigint {
80 | if (amount === null || amount === undefined) {
81 | throw new Error('getAmountOfFractionalAmount: Amount is null or undefined');
82 | }
83 | if (typeof amount !== 'string' && typeof amount !== 'number') {
84 | throw new Error('getAmountOfFractionalAmount: Amount is not a string or number');
85 | }
86 | if (typeof amount === 'string' && amount.length === 0) {
87 | throw new Error('getAmountOfFractionalAmount: Amount is empty');
88 | }
89 | if (!Number.isFinite(Number(amount))) {
90 | throw new Error('getAmountOfFractionalAmount: Amount is not a number');
91 | }
92 | if (!isValidNumericInput(decimals)) {
93 | throw new Error('getAmountOfFractionalAmount: decimals is not a number');
94 | }
95 | const cutFactor = Math.min(8, Number(decimals));
96 | const numStr = Number(amount).toFixed(cutFactor + 1);
97 | const reg = new RegExp(`^-?\\d+(?:\\.\\d{0,${cutFactor}})?`);
98 | const matchResult = numStr.match(reg);
99 | if (!matchResult) {
100 | throw new Error('getAmountOfFractionalAmount: fixedAmount is null');
101 | }
102 | const fixedAmount = matchResult[0];
103 | return parseUnits(fixedAmount, Number(decimals))
104 | }
105 |
106 | export function getDisplayAmount(
107 | inputAmount: ethers.BigNumberish, decimals: string | ethers.BigNumberish) : number {
108 | return Number(formatUnits(inputAmount, decimals))
109 | }
110 |
111 | const chains: { [index in ChainName]: number } = {
112 | solana: 1,
113 | ethereum: 2,
114 | bsc: 4,
115 | polygon: 5,
116 | avalanche: 6,
117 | arbitrum: 23,
118 | optimism: 24,
119 | base: 30,
120 | aptos: 22,
121 | sui: 21,
122 | unichain: 44,
123 | linea: 38,
124 | hypercore: 65000,
125 | sonic: 52,
126 | hyperevm: 47,
127 | };
128 |
129 | export function getWormholeChainIdByName(chain: string) : number | null {
130 | return chains[chain];
131 | }
132 |
133 | const evmChainIdMap: { [index: string]: number } = {
134 | [1]: 2,
135 | [56]: 4,
136 | [137]: 5,
137 | [43114]: 6,
138 | [42161]: 23,
139 | [10]: 24,
140 | [8453]: 30,
141 | [130]: 44,
142 | [59144]: 38,
143 | [146]: 52,
144 | [999]: 47,
145 | };
146 |
147 | export function getEvmChainIdByName(chain: ChainName) {
148 | const wormholeChainId = chains[chain];
149 | const evmIds = Object.keys(evmChainIdMap);
150 | for (const evmId of evmIds) {
151 | if (evmChainIdMap[evmId] === wormholeChainId) {
152 | return Number(evmId);
153 | }
154 | }
155 | throw new Error(`Unsupported chain: ${chain}`);
156 | }
157 |
158 |
159 |
160 | export function getWormholeChainIdById(chainId: number) : number | null {
161 | return evmChainIdMap[chainId];
162 | }
163 |
164 | const sdkVersion = [11, 4, 0];
165 |
166 | export function getSdkVersion(): string {
167 | return sdkVersion.join('_');
168 | }
169 |
170 | export function checkSdkVersionSupport(minimumVersion: [number, number, number]): boolean {
171 | //major
172 | if (sdkVersion[0] < minimumVersion[0]) {
173 | return false;
174 | }
175 | if (sdkVersion[0] > minimumVersion[0]) {
176 | return true;
177 | }
178 | //minor
179 | if (sdkVersion[1] < minimumVersion[1]) {
180 | return false;
181 | }
182 | if (sdkVersion[1] > minimumVersion[1]) {
183 | return true;
184 | }
185 | //patch
186 | if (sdkVersion[2] >= minimumVersion[2]) {
187 | return true;
188 | }
189 | return false;
190 | }
191 |
192 | export function getGasDecimal(chain: ChainName): number {
193 | if (chain === 'solana') {
194 | return 9;
195 | }
196 | return 18;
197 | }
198 |
199 | export function getGasDecimalsInSolana(chain: ChainName): number {
200 | if (chain === 'solana') {
201 | return 9;
202 | }
203 | return 8;
204 | }
205 |
206 | const MAX_U64 = BigInt(2) ** BigInt(64) - BigInt(1);
207 | export function getSafeU64Blob(value: bigint): Buffer {
208 | if (value < BigInt(0) || value > MAX_U64) {
209 | throw new Error(`Invalid u64: ${value}`);
210 | }
211 | const buf = Buffer.alloc(8);
212 | buf.writeBigUInt64LE(value);
213 | return buf;
214 | }
215 |
216 | export const ZeroPermit: Erc20Permit = {
217 | value: BigInt(0),
218 | deadline: 0,
219 | v: 0,
220 | r: `0x${SystemProgram.programId.toBuffer().toString('hex')}`,
221 | s: `0x${SystemProgram.programId.toBuffer().toString('hex')}`,
222 | }
223 |
224 | export function wait(time: number): Promise {
225 | return new Promise((resolve) => {
226 | setTimeout(() => {
227 | resolve();
228 | }, time);
229 | });
230 | }
231 |
232 | export function getQuoteSuitableReferrerAddress(
233 | quote: Quote,
234 | referrerAddresses?: ReferrerAddresses,
235 | ): string | null {
236 | if (!quote || !referrerAddresses) {
237 | return null;
238 | }
239 | if (quote.type === 'WH') {
240 | return referrerAddresses?.solana || null;
241 | }
242 | if (quote.type === 'MCTP' || quote.type === 'SWIFT') {
243 | if (quote.toChain === 'solana') {
244 | return referrerAddresses?.solana || null;
245 | }
246 | if (quote.toChain === 'sui') {
247 | return referrerAddresses?.sui || null;
248 | }
249 | return referrerAddresses?.evm || null;
250 | }
251 | if (quote.type === 'FAST_MCTP') {
252 | if (quote.toChain !== 'solana' && quote.toChain !== 'sui') {
253 | return referrerAddresses?.evm || null;
254 | }
255 | }
256 | if (quote.type === 'MONO_CHAIN') {
257 | if (quote.fromChain === 'solana') {
258 | return referrerAddresses?.solana || null;
259 | } else if (quote.fromChain === 'sui') {
260 | return referrerAddresses?.sui || null;
261 | }
262 | return referrerAddresses?.evm || null;
263 | }
264 | return null;
265 | }
266 |
267 | export const MCTP_PAYLOAD_TYPE_DEFAULT = 1;
268 | export const MCTP_PAYLOAD_TYPE_CUSTOM_PAYLOAD = 2;
269 | export const MCTP_INIT_ORDER_PAYLOAD_ID = 1;
270 | export const FAST_MCTP_PAYLOAD_TYPE_DEFAULT = 1;
271 | export const FAST_MCTP_PAYLOAD_TYPE_CUSTOM_PAYLOAD = 2;
272 | export const FAST_MCTP_PAYLOAD_TYPE_ORDER = 3;
273 |
274 | export async function getPermitDomain(token: Token, provider: JsonRpcProvider): Promise {
275 | const contract = new ethers.Contract(token.contract, ERC20Artifact.abi, provider);
276 | let domainSeparator: string;
277 | let name: string;
278 | try {
279 | let [_domainSeparator, _name] = await Promise.all([contract.DOMAIN_SEPARATOR(), contract.name()]);
280 | domainSeparator = _domainSeparator;
281 | name = _name;
282 | } catch (err) {
283 | throw {
284 | mayanError: {
285 | permitIssue: true,
286 | },
287 | };
288 | }
289 | const domain: PermitDomain = {
290 | name: name,
291 | version: '1',
292 | chainId: token.chainId,
293 | verifyingContract: token.contract,
294 | };
295 | for (let i = 1; i < 11; i++) {
296 | domain.version = String(i);
297 | const hash = TypedDataEncoder.hashDomain(domain);
298 | if (hash.toLowerCase() === domainSeparator.toLowerCase()) {
299 | return domain;
300 | }
301 | }
302 | throw {
303 | mayanError: {
304 | permitIssue: true,
305 | },
306 | };
307 | }
308 |
309 | export const PermitTypes = {
310 | Permit: [
311 | {
312 | name: 'owner',
313 | type: 'address',
314 | },
315 | {
316 | name: 'spender',
317 | type: 'address',
318 | },
319 | {
320 | name: 'value',
321 | type: 'uint256',
322 | },
323 | {
324 | name: 'nonce',
325 | type: 'uint256',
326 | },
327 | {
328 | name: 'deadline',
329 | type: 'uint256',
330 | },
331 | ],
332 | };
333 |
334 | export async function getPermitParams(
335 | token: Token,
336 | walletAddress: string,
337 | spender: string,
338 | amount: bigint,
339 | provider: JsonRpcProvider,
340 | deadline: BigInt
341 | ): Promise<{
342 | domain: PermitDomain;
343 | types: typeof PermitTypes;
344 | value: PermitValue;
345 | }> {
346 | if (token.standard !== 'erc20' && token.standard !== 'hypertoken') {
347 | throw new Error('Token is not ERC20');
348 | }
349 | if (!token.supportsPermit) {
350 | throw new Error('Token does not support permit');
351 | }
352 | const contract = new ethers.Contract(token.contract, ERC20Artifact.abi, provider);
353 | const [domain, nonce] = await Promise.all([getPermitDomain(token, provider), contract.nonces(walletAddress)]);
354 | return {
355 | domain,
356 | types: PermitTypes,
357 | value: {
358 | owner: walletAddress,
359 | spender: spender,
360 | nonce: String(nonce),
361 | value: String(amount),
362 | deadline: String(deadline),
363 | }
364 | }
365 | }
366 |
367 | export async function getHyperCoreUSDCDepositPermitParams(
368 | quote: Quote,
369 | userArbitrumAddress: string,
370 | arbProvider: JsonRpcProvider,
371 | ): Promise<{
372 | domain: PermitDomain;
373 | types: typeof PermitTypes;
374 | value: PermitValue;
375 | }> {
376 | if (!quote.hyperCoreParams) {
377 | throw new Error('Quote does not have hyperCoreParams');
378 | }
379 | if (quote.toChain !== 'hypercore') {
380 | throw new Error('Quote toChain is not hypercore');
381 | }
382 | if (quote.toToken.contract.toLowerCase() !== addresses.ARBITRUM_USDC_CONTRACT.toLowerCase()) {
383 | throw new Error('Quote toToken is not USDC on Arbitrum');
384 | }
385 |
386 | const USDC_ARB_TOKEN: Token = {
387 | name: "USDC",
388 | standard: "erc20",
389 | symbol: "USDC",
390 | mint: "CR4xnGrhsu1fWNPoX4KbTUUtqGMF3mzRLfj4S6YEs1Yo",
391 | verified: true,
392 | contract: "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
393 | chainId: 42161,
394 | wChainId: 23,
395 | decimals: 6,
396 | logoURI: "http://assets.coingecko.com/coins/images/6319/small/usdc.png?1696506694",
397 | coingeckoId: "usd-coin",
398 | realOriginContractAddress: "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
399 | realOriginChainId: 23,
400 | supportsPermit: true,
401 | verifiedAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831',
402 | }
403 | const [permitParams, isAllowed] = await Promise.all([
404 | getPermitParams(
405 | USDC_ARB_TOKEN,
406 | userArbitrumAddress,
407 | addresses.HC_ARBITRUM_BRIDGE,
408 | BigInt(quote.hyperCoreParams.depositAmountUSDC64),
409 | arbProvider,
410 | BigInt(quote.deadline64)
411 | ),
412 | checkHyperCoreDeposit(userArbitrumAddress, quote.toToken.contract)
413 | ]);
414 | if (!isAllowed) {
415 | throw new Error('Because of concurrency, deposit is not possible at the moment, please try again later');
416 | }
417 | return permitParams;
418 | }
419 |
420 | export function getHyperCoreUSDCDepositCustomPayload(
421 | quote: Quote,
422 | destinationAddress: string,
423 | usdcPermitSignature: string,
424 | ): Buffer {
425 | const payload = Buffer.alloc(109);
426 | const destAddressBuf = Buffer.from(hexToUint8Array(destinationAddress));
427 | if (destAddressBuf.length !== 20) {
428 | throw new Error('Invalid destination address length, expected 20 bytes');
429 | }
430 | const permitSignatureBuf = Buffer.from(
431 | hexToUint8Array(usdcPermitSignature)
432 | );
433 | if (permitSignatureBuf.length !== 65) {
434 | throw new Error('Invalid USDC permit signature length, expected 65 bytes');
435 | }
436 | payload.writeBigUInt64BE(BigInt(quote.redeemRelayerFee64), 0)
437 | payload.set(destAddressBuf, 8);
438 | payload.writeBigUInt64BE(BigInt(quote.hyperCoreParams.depositAmountUSDC64), 28);
439 | payload.writeBigUInt64BE(BigInt(quote.deadline64), 36);
440 | payload.set(permitSignatureBuf, 44);
441 |
442 | return payload
443 | }
444 |
--------------------------------------------------------------------------------
/src/wormhole.ts:
--------------------------------------------------------------------------------
1 | import { PublicKey } from '@solana/web3.js';
2 | import addresses from './addresses';
3 | import { Buffer } from 'buffer';
4 |
5 | export function getWormholePDAs(supplierProgram: string): {
6 | bridgeConfig: PublicKey,
7 | sequenceKey: PublicKey,
8 | feeCollector: PublicKey,
9 | emitter: PublicKey,
10 | shimEventAuth: PublicKey,
11 | shimMessage: PublicKey,
12 | } {
13 | const wormholeProgramId = new PublicKey(addresses.WORMHOLE_PROGRAM_ID);
14 | const wormholeShimProgramId = new PublicKey(addresses.WORMHOLE_SHIM_POST_MESSAGE_PROGRAM_ID);
15 | const programId = new PublicKey(supplierProgram);
16 | const [ bridgeConfig ] = PublicKey.findProgramAddressSync(
17 | [Buffer.from('Bridge')],
18 | wormholeProgramId,
19 | );
20 | const [emitter] = PublicKey.findProgramAddressSync(
21 | [Buffer.from('emitter')],
22 | programId,
23 | );
24 | const [sequenceKey] = PublicKey.findProgramAddressSync(
25 | [Buffer.from('Sequence'), emitter.toBuffer()],
26 | wormholeProgramId,
27 | );
28 | const [feeCollector] = PublicKey.findProgramAddressSync(
29 | [Buffer.from('fee_collector')],
30 | wormholeProgramId,
31 | );
32 | const [shimMessage] = PublicKey.findProgramAddressSync(
33 | [emitter.toBuffer()],
34 | wormholeShimProgramId,
35 | );
36 | const [shimEventAuth] = PublicKey.findProgramAddressSync(
37 | [Buffer.from('__event_authority')],
38 | wormholeShimProgramId,
39 | );
40 | return {
41 | bridgeConfig,
42 | sequenceKey,
43 | feeCollector,
44 | emitter,
45 | shimEventAuth,
46 | shimMessage,
47 | };
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "compilerOptions": {
4 | "lib": [
5 | "es2021"
6 | ],
7 | "target": "es2021",
8 | // TODO: Add this back
9 | "strict": false,
10 | "esModuleInterop": true,
11 | "skipLibCheck": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "sourceMap": false,
14 | "moduleResolution": "node",
15 | "baseUrl": ".",
16 | "declaration": true,
17 | "outDir": "./lib"
18 | },
19 | "include": [
20 | "src"
21 | ],
22 | "exclude": [
23 | "node_modules",
24 | "**/__tests__/*"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { Format, defineConfig } from 'tsup';
2 |
3 | // const format =
4 |
5 | export default defineConfig((options) => {
6 | const format: Format[] = ['cjs', 'esm']
7 | if (options.minify) format.push('iife')
8 |
9 | return {
10 | entry: ['src/index.ts'],
11 | splitting: false,
12 | sourcemap: false,
13 | clean: false,
14 | outDir: 'dist',
15 | dts: options.minify,
16 | format,
17 | outExtension: options.minify ? ({ format }) => {
18 | return {
19 | js: `.${format}.min.js`,
20 | };
21 | } : undefined,
22 | ...options,
23 | };
24 | });
25 |
--------------------------------------------------------------------------------