├── .DS_Store ├── .env.example ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── lib ├── .DS_Store ├── evm │ ├── .DS_Store │ ├── cross-chain │ │ ├── evm-bridge-tokens.ts │ │ ├── evm-bridges.ts │ │ ├── evm-cross-chain-quote.ts │ │ └── evm-token-pairs.ts │ └── swap │ │ ├── evm-chain.ts │ │ ├── evm-liquidity.ts │ │ ├── evm-quote.ts │ │ ├── evm-swap-data.ts │ │ ├── evm-swap.ts │ │ └── evm-tokens.ts ├── shared.ts ├── solana │ ├── .DS_Store │ ├── cross-chain │ │ ├── solana-bridge-tokens.ts │ │ ├── solana-bridges.ts │ │ ├── solana-cross-chain-quote.ts │ │ └── solana-token-pairs.ts │ └── swap │ │ ├── solana-chain.ts │ │ ├── solana-liquidity.ts │ │ ├── solana-quote.ts │ │ ├── solana-swap-data.ts │ │ ├── solana-swap-instructions.ts │ │ ├── solana-swap-mev.ts │ │ ├── solana-swap-multi-rpc.ts │ │ ├── solana-swap.ts │ │ └── solana-tokens.ts ├── sui │ ├── cross-chain │ │ ├── sui-bridge-tokens.ts │ │ ├── sui-bridges.ts │ │ ├── sui-cross-chain-quote.ts │ │ └── sui-token-pairs.ts │ └── swap │ │ ├── sui-chain.ts │ │ ├── sui-liquidity.ts │ │ ├── sui-quote.ts │ │ ├── sui-swap-data.ts │ │ ├── sui-swap.ts │ │ └── sui-tokens.ts ├── ton │ └── swap │ │ ├── ton-chain.ts │ │ ├── ton-liquidity.ts │ │ ├── ton-quote.ts │ │ ├── ton-swap-data.ts │ │ ├── ton-swap.ts │ │ └── ton-tokens.ts └── tron │ └── swap │ ├── tron-chain.ts │ ├── tron-liquidity.ts │ ├── tron-quote.ts │ ├── tron-swap-data.ts │ ├── tron-swap.ts │ └── tron-tokens.ts ├── package-lock.json ├── package.json └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okx/dex-api-library/561dfa3b5c6cbe8ba7e76f922fce2390c3b3d698/.DS_Store -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | OKX_PROJECT_ID= 2 | OKX_API_KEY= 3 | OKX_SECRET_KEY= 4 | OKX_API_PASSPHRASE= 5 | WALLET_ADDRESS= 6 | RECEIVING_ADDRESS= 7 | PRIVATE_KEY= 8 | SOLANA_RPC_URL= 9 | WS_ENDPONT= -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | License: Apache2.0 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "{}" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright 2024 OKX.com 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OKX DEX Scripts 2 | 3 | A comprehensive collection of TypeScript scripts for interacting with the OKX DEX API across Ethereum (EVM), Solana, Ton and Tron networks, supporting both single and cross-chain DEX operations. 4 | 5 | 6 | ## Prerequisites 7 | - Node v20.17.0 or higher 8 | - git 9 | - a Web3 wallet (e.g., [OKX Wallet Extension](https://www.okx.com/download)) for API key generation 10 | 11 | ## Setup 12 | 13 | 1. Clone the repository: 14 | ```bash 15 | git clone https://github.com/okx/dex-api-library.git 16 | cd dex-api-library 17 | ``` 18 | 19 | 2. Install dependencies: 20 | ```bash 21 | npm install 22 | ``` 23 | 24 | 3. Obtain your project ID, API key, secret key, and passphrase from the [OKX Developer Portal](https://www.okx.com/web3/build/docs/waas/introduction-to-developer-portal-interface) 25 | 26 | 4. Create `.env` file: 27 | ```env 28 | OKX_PROJECT_ID=YOUR_PROJECT_ID 29 | OKX_API_KEY=YOUR_API_KEY 30 | OKX_SECRET_KEY=YOUR_API_SECRET_KEY 31 | OKX_API_PASSPHRASE=YOUR_API_PASSPHRASE 32 | 33 | WALLET_ADDRESS=your_walllet_public_key 34 | PRIVATE_KEY=your_wallet_private_key 35 | 36 | # Optional: Set the network to use for the scripts 37 | SOLANA_RPC_URL=YOUR_SOLANA_RPC_URL 38 | WS_ENDPONT=YOUR_WS_ENDPOINT 39 | ``` 40 | 41 | _Note: Keep your .env file secure and never commit it to version control_ 42 | 43 | ## Authentication 44 | 45 | The project uses a shared authentication utility ([`shared.ts`](./lib/shared.ts)) for OKX API requests. The utility handles request signing and header generation: 46 | 47 | ```typescript 48 | // shared.ts 49 | import CryptoJS from 'crypto-js'; 50 | import dotenv from 'dotenv'; 51 | 52 | dotenv.config(); 53 | 54 | export function getHeaders(timestamp: string, method: string, requestPath: string, queryString = "") { 55 | const apiKey = process.env.OKX_API_KEY; 56 | const secretKey = process.env.OKX_SECRET_KEY; 57 | const apiPassphrase = process.env.OKX_API_PASSPHRASE; 58 | const projectId = process.env.OKX_PROJECT_ID; 59 | 60 | if (!apiKey || !secretKey || !apiPassphrase || !projectId) { 61 | throw new Error("Missing required environment variables"); 62 | } 63 | 64 | const stringToSign = timestamp + method + requestPath + queryString; 65 | return { 66 | "Content-Type": "application/json", 67 | "OK-ACCESS-KEY": apiKey, 68 | "OK-ACCESS-SIGN": CryptoJS.enc.Base64.stringify( 69 | CryptoJS.HmacSHA256(stringToSign, secretKey) 70 | ), 71 | "OK-ACCESS-TIMESTAMP": timestamp, 72 | "OK-ACCESS-PASSPHRASE": apiPassphrase, 73 | "OK-ACCESS-PROJECT": projectId, 74 | }; 75 | } 76 | ``` 77 | 78 | The utility takes in a timestamp, method, path, and query string to generate signed API headers. Here's how to use it with a Solana quote request: 79 | 80 | ```typescript 81 | // Example: Getting a SOL to USDC quote on Solana 82 | const params = { 83 | chainId: '501', // Solana Chain ID 84 | fromTokenAddress: 'So11111111111111111111111111111111111111112', // Wrapped SOL 85 | toTokenAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC 86 | amount: '10000000000', // Amount in lamports 87 | slippage: '0.1' // 0.1% slippage tolerance 88 | }; 89 | 90 | const timestamp = new Date().toISOString(); 91 | const requestPath = "/api/v5/dex/aggregator/quote"; 92 | const queryString = "?" + new URLSearchParams(params).toString(); 93 | 94 | // Generate headers for the request using the shared utility function 95 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 96 | 97 | // Make the request with the generated headers 98 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 99 | method: "GET", 100 | headers 101 | }); 102 | ``` 103 | 104 | The complete implementation can be found in [solana-quote.ts](./lib/solana/swap/solana-quote.ts). 105 | 106 | ## Available Scripts 107 | 108 | ### Run Individual Commands 109 | 110 | To run individual commands, you can use the following scripts with the target network as an argument (e.g., `evm`, `solana`, `ton`, `tron`): 111 | 112 | ```bash 113 | # Individual Commands 114 | npm run quote: # Get swap quotes 115 | npm run swap:solana -- # Execute a swap 116 | npm run swap-data: # Get swap data 117 | npm run chain: # Get chain info 118 | npm run tokens: # List supported tokens 119 | npm run liquidity: # Get liquidity info 120 | npm run bridge-tokens: # List bridge tokens 121 | npm run bridges: # Get bridge info 122 | npm run cross-chain-quote: # Get cross-chain quotes 123 | npm run token-pairs: # List token pairs 124 | ``` 125 | 126 | Example: 127 | 128 | ```bash 129 | npm run quote:solana 130 | ``` 131 | 132 | You can also run all scripts for a specific network using the following commands: 133 | 134 | ```bash 135 | npm run quote: 136 | ``` 137 | 138 | Example: 139 | ```bash 140 | npm run all:solana 141 | ``` 142 | 143 | ### Run All Commands 144 | ```bash 145 | npm run get-all # Run all 'GET' scripts for EVM, Solana, Ton, and Tron 146 | ``` 147 | 148 | ## Chain IDs & Common Token Addresses 149 | 150 | To retrieve token swap quotes, you need to provide the chain ID and token addresses. Here's the basic structure found in the 'quote' scripts: 151 | 152 | ```typescript 153 | const params = { 154 | chainId: '1', // Network chain ID 155 | fromTokenAddress: '', // Source token address 156 | toTokenAddress: '', // Destination token address 157 | amount: '1000000000', // Amount in token's smallest unit (consider decimals) 158 | slippage: '0.1', // 0.1% slippage tolerance 159 | }; 160 | ``` 161 | 162 | ### Native Token Addresses 163 | Each blockchain has a specific address to represent its native token (ETH, SOL, etc.). When swapping native tokens, use these addresses: 164 | 165 | | Chain | Native Token Address | 166 | |-------|---------------------| 167 | | EVM Networks | 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE | 168 | | Solana | 11111111111111111111111111111111 | 169 | | Tron | T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb | 170 | | Ton | EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c | 171 | 172 | 173 | ### EVM 174 | 175 | All scripts in the [`lib/evm`](./lib/evm/) directory accept any valid EVM chain ID, maintaining consistent functionality across networks. 176 | 177 | For Example: 178 | - Ethereum ('1') 179 | - X Layer ('196') 180 | - Polygon ('137') 181 | - Base ('8453') 182 | - Arbitrum ('42161') 183 | - You can find more supported chains in the [OKX OS Documentation](https://www.okx.com/web3/build/docs/waas/okx-waas-supported-networks) 184 | 185 | Common EVM Tokens (Ethereum addresses): 186 | ```typescript 187 | const ETH = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' // Native ETH 188 | const USDT = '0xdAC17F958D2ee523a2206206994597C13D831ec7' // USDT 189 | const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' // USDC 190 | ``` 191 | 192 | ### Solana ('501') 193 | ```typescript 194 | const SOL = '11111111111111111111111111111111' // Native SOL 195 | const WSOL = 'So11111111111111111111111111111111111111112' // Wrapped SOL 196 | const USDC = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' // USDC 197 | ``` 198 | 199 | ### TON ('607') 200 | ```typescript 201 | const TON = 'EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c' // Native TON 202 | const USDC = 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs' // USDT 203 | ``` 204 | 205 | ### TRON ('195') 206 | ```typescript 207 | const TRX = 'T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb' // Native TRX 208 | const USDT = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t' // USDT 209 | ``` 210 | 211 | ## Resources 212 | 213 | The examples in this repository were built using the [OKX DEX API Documentation](https://www.okx.com/web3/build/docs/waas/dex-api-reference). 214 | 215 | Each API endpoint is implemented in the corresponding scripts within this repository, providing a comprehensive overview of the available functionality. 216 | 217 | ### DEX Aggregator APIs 218 | - [Get Supported Chains](https://www.okx.com/web3/build/docs/waas/dex-get-aggregator-supported-chains) - List all supported chains for DEX aggregation 219 | - [Get Supported Tokens](https://www.okx.com/web3/build/docs/waas/dex-get-tokens) - Retrieve available tokens for trading 220 | - [Get Liquidity Sources](https://www.okx.com/web3/build/docs/waas/dex-get-liquidity) - View available liquidity sources 221 | - [Get Quote](https://www.okx.com/web3/build/docs/waas/dex-get-quote) - Obtain price quotes for token swaps 222 | 223 | ### Cross-Chain Bridge APIs 224 | - [Get Bridge Supported Chains](https://www.okx.com/web3/build/docs/waas/dex-get-supported-chains) - List all supported chains for cross-chain bridging 225 | - [Get Cross-Chain Tokens](https://www.okx.com/web3/build/docs/waas/dex-crosschain-get-tokens) - View tokens available for cross-chain transfers 226 | - [Get Supported Bridge Tokens](https://www.okx.com/web3/build/docs/waas/dex-get-supported-tokens) - List all tokens supported by bridges 227 | - [Get Bridge Token Pairs](https://www.okx.com/web3/build/docs/waas/dex-get-supported-bridge-tokens-pairs) - View available token pairs for bridging 228 | - [Get Supported Bridges](https://www.okx.com/web3/build/docs/waas/dex-get-supported-bridges) - List all supported bridge protocols 229 | - [Get Route Information](https://www.okx.com/web3/build/docs/waas/dex-get-route-information) - Obtain cross-chain routing details 230 | 231 | ## Ways to Contribute 232 | 233 | ### Join Community Discussions 234 | Join our [Discord community](https://discord.gg/eQ6mVN39) to help other developers troubleshoot their integration issues and share your experience with the SOR SmartContract. Our Discord is the main hub for technical discussions, questions, and real-time support. 235 | 236 | ### Open an Issue 237 | - Open [issues](https://github.com/okx/dex-api-library/issues) to suggest features or report minor bugs 238 | - Before opening a new issue, search existing issues to avoid duplicates 239 | - When requesting features, include details about use cases and potential impact 240 | 241 | ### Submit Pull Requests 242 | 1. Fork the repository 243 | 2. Create a feature branch 244 | 3. Make your changes 245 | 5. Submit a pull request 246 | 247 | ### Pull Request Guidelines 248 | - Discuss non-trivial changes in an issue first 249 | - Include tests for new functionality 250 | - Update documentation as needed 251 | - Add a changelog entry describing your changes in the PR 252 | - PRs should be focused and preferably address a single concern 253 | 254 | ## Questions? 255 | - Open a discussion [issue](https://github.com/okx/dex-api-library/issues) for general questions 256 | - Join our [community](https://discord.gg/eQ6mVN39) for real-time discussions 257 | - Review existing issues and discussions 258 | -------------------------------------------------------------------------------- /lib/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okx/dex-api-library/561dfa3b5c6cbe8ba7e76f922fce2390c3b3d698/lib/.DS_Store -------------------------------------------------------------------------------- /lib/evm/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okx/dex-api-library/561dfa3b5c6cbe8ba7e76f922fce2390c3b3d698/lib/evm/.DS_Store -------------------------------------------------------------------------------- /lib/evm/cross-chain/evm-bridge-tokens.ts: -------------------------------------------------------------------------------- 1 | // sui-supported-tokens.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const chainId = '1'; // Ethereum Chain ID 7 | const timestamp = new Date().toISOString(); 8 | const requestPath = "/api/v5/dex/cross-chain/supported/tokens"; 9 | const queryString = chainId ? `?chainId=${chainId}` : ''; 10 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 11 | 12 | console.log('Getting supported tokens for Ethereum...'); 13 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 14 | method: "GET", 15 | headers 16 | }); 17 | 18 | const data = await response.json(); 19 | console.log('Supported tokens response:', JSON.stringify(data, null, 2)); 20 | } catch (error) { 21 | console.error('Script failed:', error); 22 | process.exit(1); 23 | } 24 | } 25 | 26 | main(); -------------------------------------------------------------------------------- /lib/evm/cross-chain/evm-bridges.ts: -------------------------------------------------------------------------------- 1 | // sui-supported-bridges.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const chainId = '1'; // Ethereum Chain ID 7 | const timestamp = new Date().toISOString(); 8 | const requestPath = "/api/v5/dex/cross-chain/supported/bridges"; 9 | const queryString = chainId ? `?chainId=${chainId}` : ''; 10 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 11 | 12 | console.log('Getting supported bridges for Ethereum...'); 13 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 14 | method: "GET", 15 | headers 16 | }); 17 | 18 | const data = await response.json(); 19 | console.log('Supported bridges response:', JSON.stringify(data, null, 2)); 20 | } catch (error) { 21 | console.error('Script failed:', error); 22 | process.exit(1); 23 | } 24 | } 25 | 26 | main(); -------------------------------------------------------------------------------- /lib/evm/cross-chain/evm-cross-chain-quote.ts: -------------------------------------------------------------------------------- 1 | // scripts/solana-quote.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | fromChainId: '1', // Ethereum Chain ID 8 | toChainId: '196', // To BSC 9 | amount: '1000000000000000000', // 1 ETH (18 decimals) 10 | fromTokenAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', // Native ETH 11 | toTokenAddress: '0x74b7f16337b8972027f6196a17a631ac6de26d22', // USDC on X Layer 12 | slippage: '0.025', // 2.5% slippage for cross-chain swaps 13 | sort: '1', // Optimal route considering all factors 14 | priceImpactProtectionPercentage: '0.9', // 90% price impact allowed 15 | }; 16 | 17 | const timestamp = new Date().toISOString(); 18 | const requestPath = "/api/v5/dex/cross-chain/quote"; 19 | const queryString = "?" + new URLSearchParams(params).toString(); 20 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 21 | 22 | console.log('Getting Ethereum to X Layer cross-chain quote...'); 23 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 24 | method: "GET", 25 | headers 26 | }); 27 | 28 | const data = await response.json(); 29 | console.log('Cross-chain quote response:', JSON.stringify(data, null, 2)); 30 | } catch (error) { 31 | console.error('Script failed:', error); 32 | process.exit(1); 33 | } 34 | } 35 | 36 | main(); -------------------------------------------------------------------------------- /lib/evm/cross-chain/evm-token-pairs.ts: -------------------------------------------------------------------------------- 1 | // sui-bridge-pairs.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | fromChainId: '1' // Ethereum Chain ID 8 | }; 9 | 10 | const timestamp = new Date().toISOString(); 11 | const requestPath = "/api/v5/dex/cross-chain/supported/bridge-tokens-pairs"; 12 | const queryString = "?" + new URLSearchParams(params).toString(); 13 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 14 | 15 | console.log('Getting bridge token pairs for Ethereum...'); 16 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 17 | method: "GET", 18 | headers 19 | }); 20 | 21 | const data = await response.json(); 22 | console.log('Bridge pairs response:', JSON.stringify(data, null, 2)); 23 | } catch (error) { 24 | console.error('Script failed:', error); 25 | process.exit(1); 26 | } 27 | } 28 | 29 | main(); -------------------------------------------------------------------------------- /lib/evm/swap/evm-chain.ts: -------------------------------------------------------------------------------- 1 | import { getHeaders } from '../../shared'; 2 | 3 | async function main() { 4 | try { 5 | const chainId = '1'; // Ethereum Chain ID 6 | const timestamp = new Date().toISOString(); 7 | const requestPath = "/api/v5/dex/aggregator/supported/chain"; 8 | const queryString = chainId ? `?chainId=${chainId}` : ''; 9 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 10 | 11 | console.log('Getting supported chain info for Ethereum...'); 12 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 13 | method: "GET", 14 | headers 15 | }); 16 | 17 | const data = await response.json(); 18 | console.log('Supported chains response:', JSON.stringify(data, null, 2)); 19 | } catch (error) { 20 | console.error('Script failed:', error); 21 | process.exit(1); 22 | } 23 | } 24 | 25 | main(); -------------------------------------------------------------------------------- /lib/evm/swap/evm-liquidity.ts: -------------------------------------------------------------------------------- 1 | 2 | // scripts/get-liquidity.ts 3 | import { getHeaders } from '../../shared'; 4 | 5 | async function main() { 6 | try { 7 | const params = { 8 | chainId: '1' // Ethereum Chain ID 9 | }; 10 | 11 | const timestamp = new Date().toISOString(); 12 | const requestPath = "/api/v5/dex/aggregator/get-liquidity"; 13 | const queryString = "?" + new URLSearchParams(params).toString(); 14 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 15 | 16 | console.log('Getting Ethereum liquidity sources...'); 17 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 18 | method: "GET", 19 | headers 20 | }); 21 | 22 | const data = await response.json(); 23 | console.log('Liquidity sources response:', JSON.stringify(data, null, 2)); 24 | } catch (error) { 25 | console.error('Script failed:', error); 26 | process.exit(1); 27 | } 28 | } 29 | 30 | main(); 31 | -------------------------------------------------------------------------------- /lib/evm/swap/evm-quote.ts: -------------------------------------------------------------------------------- 1 | 2 | // scripts/evm-quote.ts 3 | import { getHeaders } from '../../shared'; 4 | 5 | async function main() { 6 | try { 7 | const params = { 8 | chainId: '1', // Ethereum mainnet 9 | amount: '10000000000000000000', // 10 ETH 10 | fromTokenAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', // Native ETH 11 | toTokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT 12 | slippage: '0.1', 13 | }; 14 | 15 | const timestamp = new Date().toISOString(); 16 | const requestPath = "/api/v5/dex/aggregator/quote"; 17 | const queryString = "?" + new URLSearchParams(params).toString(); 18 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 19 | 20 | console.log('Getting EVM quote...'); 21 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 22 | method: "GET", 23 | headers 24 | }); 25 | 26 | const data = await response.json(); 27 | console.log('Quote response:', JSON.stringify(data, null, 2)); 28 | } catch (error) { 29 | console.error('Script failed:', error); 30 | process.exit(1); 31 | } 32 | } 33 | 34 | main(); 35 | -------------------------------------------------------------------------------- /lib/evm/swap/evm-swap-data.ts: -------------------------------------------------------------------------------- 1 | // scripts/evm-swap.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | chainId: '1', // Ethereum mainnet 8 | amount: '10000000000000000000', // 10 ETH 9 | fromTokenAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', // Native ETH 10 | toTokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT 11 | userWalletAddress: "0x9163756d2a83a334de2cc0c3aa1df9a5fc21369d", 12 | slippage: "0.1", 13 | autoSlippage: "true", 14 | maxAutoSlippageBps: "100" 15 | }; 16 | 17 | const timestamp = new Date().toISOString(); 18 | const requestPath = "/api/v5/dex/aggregator/swap"; 19 | const queryString = "?" + new URLSearchParams(params).toString(); 20 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 21 | 22 | console.log('Getting EVM swap data...'); 23 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 24 | method: "GET", 25 | headers 26 | }); 27 | 28 | const data = await response.json(); 29 | console.log('Quote response:', JSON.stringify(data, null, 2)); 30 | } catch (error) { 31 | console.error('Script failed:', error); 32 | process.exit(1); 33 | } 34 | } 35 | 36 | main(); 37 | -------------------------------------------------------------------------------- /lib/evm/swap/evm-swap.ts: -------------------------------------------------------------------------------- 1 | // scripts/evm-swap.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | chainId: '1', // Ethereum mainnet 8 | amount: '10000000000000000000', // 10 ETH 9 | fromTokenAddress: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', // Native ETH 10 | toTokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT 11 | userWalletAddress: "0x9163756d2a83a334de2cc0c3aa1df9a5fc21369d", 12 | slippage: "0.5", 13 | autoSlippage: "true", 14 | maxAutoSlippageBps: "100" 15 | }; 16 | 17 | const timestamp = new Date().toISOString(); 18 | const requestPath = "/api/v5/dex/aggregator/swap"; 19 | const queryString = "?" + new URLSearchParams(params).toString(); 20 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 21 | 22 | console.log('Getting EVM quote...'); 23 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 24 | method: "GET", 25 | headers 26 | }); 27 | 28 | const data = await response.json(); 29 | console.log('Quote response:', JSON.stringify(data, null, 2)); 30 | } catch (error) { 31 | console.error('Script failed:', error); 32 | process.exit(1); 33 | } 34 | } 35 | 36 | main(); 37 | -------------------------------------------------------------------------------- /lib/evm/swap/evm-tokens.ts: -------------------------------------------------------------------------------- 1 | import { getHeaders } from '../../shared'; 2 | 3 | async function main() { 4 | try { 5 | const params = { 6 | chainId: '1' // Ethereum Chain ID 7 | }; 8 | 9 | const timestamp = new Date().toISOString(); 10 | const requestPath = "/api/v5/dex/aggregator/all-tokens"; 11 | const queryString = "?" + new URLSearchParams(params).toString(); 12 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 13 | 14 | console.log('Getting Ethereum tokens...'); 15 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 16 | method: "GET", 17 | headers 18 | }); 19 | 20 | const data = await response.json(); 21 | console.log('Tokens response:', JSON.stringify(data, null, 2)); 22 | } catch (error) { 23 | console.error('Script failed:', error); 24 | process.exit(1); 25 | } 26 | } 27 | 28 | main(); 29 | -------------------------------------------------------------------------------- /lib/shared.ts: -------------------------------------------------------------------------------- 1 | import CryptoJS from 'crypto-js'; 2 | import dotenv from 'dotenv'; 3 | 4 | dotenv.config(); 5 | 6 | export function getHeaders(timestamp: string, method: string, requestPath: string, queryString = "") { 7 | const apiKey = process.env.OKX_API_KEY; 8 | const secretKey = process.env.OKX_SECRET_KEY; 9 | const apiPassphrase = process.env.OKX_API_PASSPHRASE; 10 | const projectId = process.env.OKX_PROJECT_ID; 11 | 12 | if (!apiKey || !secretKey || !apiPassphrase || !projectId) { 13 | throw new Error("Missing required environment variables"); 14 | } 15 | 16 | const stringToSign = timestamp + method + requestPath + queryString; 17 | return { 18 | "Content-Type": "application/json", 19 | "OK-ACCESS-KEY": apiKey, 20 | "OK-ACCESS-SIGN": CryptoJS.enc.Base64.stringify( 21 | CryptoJS.HmacSHA256(stringToSign, secretKey) 22 | ), 23 | "OK-ACCESS-TIMESTAMP": timestamp, 24 | "OK-ACCESS-PASSPHRASE": apiPassphrase, 25 | "OK-ACCESS-PROJECT": projectId, 26 | }; 27 | } -------------------------------------------------------------------------------- /lib/solana/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/okx/dex-api-library/561dfa3b5c6cbe8ba7e76f922fce2390c3b3d698/lib/solana/.DS_Store -------------------------------------------------------------------------------- /lib/solana/cross-chain/solana-bridge-tokens.ts: -------------------------------------------------------------------------------- 1 | // sui-supported-tokens.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const chainId = '501' // Solana Chain ID 7 | const timestamp = new Date().toISOString(); 8 | const requestPath = "/api/v5/dex/cross-chain/supported/tokens"; 9 | const queryString = chainId ? `?chainId=${chainId}` : ''; 10 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 11 | 12 | console.log('Getting supported tokens for Solana...'); 13 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 14 | method: "GET", 15 | headers 16 | }); 17 | 18 | const data = await response.json(); 19 | console.log('Supported tokens response:', JSON.stringify(data, null, 2)); 20 | } catch (error) { 21 | console.error('Script failed:', error); 22 | process.exit(1); 23 | } 24 | } 25 | 26 | main(); -------------------------------------------------------------------------------- /lib/solana/cross-chain/solana-bridges.ts: -------------------------------------------------------------------------------- 1 | // sui-supported-bridges.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const chainId = '501'; // Solana Chain ID 7 | const timestamp = new Date().toISOString(); 8 | const requestPath = "/api/v5/dex/cross-chain/supported/bridges"; 9 | const queryString = chainId ? `?chainId=${chainId}` : ''; 10 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 11 | 12 | console.log('Getting supported bridges for Solana...'); 13 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 14 | method: "GET", 15 | headers 16 | }); 17 | 18 | const data = await response.json(); 19 | console.log('Supported bridges response:', JSON.stringify(data, null, 2)); 20 | } catch (error) { 21 | console.error('Script failed:', error); 22 | process.exit(1); 23 | } 24 | } 25 | 26 | main(); -------------------------------------------------------------------------------- /lib/solana/cross-chain/solana-cross-chain-quote.ts: -------------------------------------------------------------------------------- 1 | // scripts/solana-quote.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | fromChainId: '501', // Solana Chain ID 8 | toChainId: '1', // To Ethereum 9 | amount: '10000000000', 10 | fromTokenAddress: 'So11111111111111111111111111111111111111112', // Wrapped SOL 11 | toTokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum 12 | slippage: '0.025', // 2.5% slippage for cross-chain swaps 13 | sort: '1', // Optimal route considering all factors 14 | }; 15 | 16 | const timestamp = new Date().toISOString(); 17 | const requestPath = "/api/v5/dex/cross-chain/quote"; 18 | const queryString = "?" + new URLSearchParams(params).toString(); 19 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 20 | 21 | console.log('Getting Solana to Solana cross-chain quote...'); 22 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 23 | method: "GET", 24 | headers 25 | }); 26 | 27 | const data = await response.json(); 28 | console.log('Cross-chain quote response:', JSON.stringify(data, null, 2)); 29 | } catch (error) { 30 | console.error('Script failed:', error); 31 | process.exit(1); 32 | } 33 | } 34 | 35 | main(); -------------------------------------------------------------------------------- /lib/solana/cross-chain/solana-token-pairs.ts: -------------------------------------------------------------------------------- 1 | // sui-bridge-pairs.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | fromChainId: '501' // Solana Chain ID 8 | }; 9 | 10 | const timestamp = new Date().toISOString(); 11 | const requestPath = "/api/v5/dex/cross-chain/supported/bridge-tokens-pairs"; 12 | const queryString = "?" + new URLSearchParams(params).toString(); 13 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 14 | 15 | console.log('Getting bridge token pairs for Solana...'); 16 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 17 | method: "GET", 18 | headers 19 | }); 20 | 21 | const data = await response.json(); 22 | console.log('Bridge pairs response:', JSON.stringify(data, null, 2)); 23 | } catch (error) { 24 | console.error('Script failed:', error); 25 | process.exit(1); 26 | } 27 | } 28 | 29 | main(); -------------------------------------------------------------------------------- /lib/solana/swap/solana-chain.ts: -------------------------------------------------------------------------------- 1 | 2 | // scripts/get-supported-chains.ts 3 | import { getHeaders } from '../../shared'; 4 | 5 | async function main() { 6 | try { 7 | const chainId = '501'; // Solana Chain ID 8 | const timestamp = new Date().toISOString(); 9 | const requestPath = "/api/v5/dex/aggregator/supported/chain"; 10 | const queryString = chainId ? `?chainId=${chainId}` : ''; 11 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 12 | 13 | console.log('Getting supported chain info for Solana...'); 14 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 15 | method: "GET", 16 | headers 17 | }); 18 | 19 | const data = await response.json(); 20 | console.log('Supported chains response:', JSON.stringify(data, null, 2)); 21 | } catch (error) { 22 | console.error('Script failed:', error); 23 | process.exit(1); 24 | } 25 | } 26 | 27 | main(); 28 | -------------------------------------------------------------------------------- /lib/solana/swap/solana-liquidity.ts: -------------------------------------------------------------------------------- 1 | 2 | // scripts/get-liquidity.ts 3 | import { getHeaders } from '../../shared'; 4 | 5 | async function main() { 6 | try { 7 | const params = { 8 | chainId: '501' // Solana Chain ID 9 | }; 10 | 11 | const timestamp = new Date().toISOString(); 12 | const requestPath = "/api/v5/dex/aggregator/get-liquidity"; 13 | const queryString = "?" + new URLSearchParams(params).toString(); 14 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 15 | 16 | console.log('Getting Solana liquidity sources...'); 17 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 18 | method: "GET", 19 | headers 20 | }); 21 | 22 | const data = await response.json(); 23 | console.log('Liquidity sources response:', JSON.stringify(data, null, 2)); 24 | } catch (error) { 25 | console.error('Script failed:', error); 26 | process.exit(1); 27 | } 28 | } 29 | 30 | main(); 31 | -------------------------------------------------------------------------------- /lib/solana/swap/solana-quote.ts: -------------------------------------------------------------------------------- 1 | // scripts/solana-quote.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function getQuote(params: any) { 5 | const timestamp = new Date().toISOString(); 6 | const requestPath = "/api/v5/dex/aggregator/quote"; 7 | const queryString = "?" + new URLSearchParams({ 8 | ...params, 9 | }).toString(); 10 | 11 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 12 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 13 | method: "GET", 14 | headers 15 | }); 16 | 17 | const data = await response.json(); 18 | return data; 19 | } 20 | 21 | async function main() { 22 | try { 23 | console.log('Getting Solana quote...'); 24 | 25 | const quote = await getQuote({ 26 | chainId: '501', 27 | amount: '10000000000', 28 | fromTokenAddress: 'So11111111111111111111111111111111111111112', 29 | toTokenAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', 30 | slippage: '0.1', 31 | }); 32 | 33 | console.log(JSON.stringify(quote, null, 2)); 34 | 35 | } catch (error) { 36 | console.error('Failed to get quote:', error); 37 | process.exit(1); 38 | } 39 | } 40 | 41 | main(); -------------------------------------------------------------------------------- /lib/solana/swap/solana-swap-data.ts: -------------------------------------------------------------------------------- 1 | // scripts/solana-swap.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function getQuote(params: any) { 5 | const timestamp = new Date().toISOString(); 6 | const requestPath = "/api/v5/dex/aggregator/swap"; 7 | const queryString = "?" + new URLSearchParams({ 8 | ...params, 9 | }).toString(); 10 | 11 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 12 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 13 | method: "GET", 14 | headers 15 | }); 16 | 17 | const data = await response.json(); 18 | return data; 19 | } 20 | 21 | async function main() { 22 | try { 23 | console.log('Getting Solana swap data...'); 24 | 25 | const quote = await getQuote({ 26 | chainId: '501', 27 | amount: '10000000000', 28 | fromTokenAddress: 'So11111111111111111111111111111111111111112', 29 | toTokenAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', 30 | userWalletAddress: "HghFVR3KBYcbgh63cJYmCCu9mzUYMYQRPT5aMrCutMct", 31 | slippage: '0.1', 32 | autoSlippage: "true", 33 | maxAutoSlippageBps: "100" 34 | }); 35 | 36 | console.log(JSON.stringify(quote, null, 2)); 37 | 38 | } catch (error) { 39 | console.error('Failed to get quote:', error); 40 | process.exit(1); 41 | } 42 | } 43 | 44 | main(); -------------------------------------------------------------------------------- /lib/solana/swap/solana-swap-instructions.ts: -------------------------------------------------------------------------------- 1 | // Required Solana dependencies for DEX interaction 2 | import { 3 | Connection, // Handles RPC connections to Solana network 4 | Keypair, // Manages wallet keypairs for signing 5 | PublicKey, // Handles Solana public key conversion and validation 6 | TransactionInstruction, // Core transaction instruction type 7 | TransactionMessage, // Builds transaction messages (v0 format) 8 | VersionedTransaction, // Supports newer transaction format with lookup tables 9 | RpcResponseAndContext, // RPC response wrapper type 10 | SimulatedTransactionResponse, // Simulation result type 11 | AddressLookupTableAccount, // For transaction size optimization 12 | PublicKeyInitData // Public key input type 13 | } from "@solana/web3.js"; 14 | import base58 from "bs58"; // Required for private key decoding 15 | import dotenv from "dotenv"; // Environment variable management 16 | dotenv.config(); 17 | 18 | async function main() { 19 | // Initialize Solana RPC connection 20 | // Note: Consider using a reliable RPC endpoint with high rate limits for production 21 | const connection = new Connection( 22 | process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com" 23 | ); 24 | 25 | // Initialize wallet for signing 26 | // This wallet will be the fee payer and transaction signer 27 | // Ensure it has sufficient SOL for transaction fees 28 | const wallet = Keypair.fromSecretKey( 29 | Uint8Array.from(base58.decode(process.env.PRIVATE_KEY?.toString() || "")) 30 | ); 31 | 32 | // DEX aggregator API endpoint 33 | // This endpoint provides optimized swap routes across multiple DEXs 34 | const baseUrl = "https://beta.okex.org/api/v5/dex/aggregator/swap-instruction"; 35 | 36 | // Swap configuration parameters 37 | const params = { 38 | chainId: "501", // Solana mainnet chain ID 39 | feePercent: "1", // Platform fee percentage 40 | amount: "1000000", // Amount in smallest denomination (e.g., lamports for SOL) 41 | fromTokenAddress: "11111111111111111111111111111111", // SOL mint address 42 | toTokenAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC mint address 43 | slippage: "0.1", // Slippage tolerance in percentage 44 | userWalletAddress: process.env.WALLET_ADDRESS || "", // Wallet performing the swap 45 | priceTolerance: "0", // Maximum allowed price impact 46 | autoSlippage: "false", // Use fixed slippage instead of auto 47 | fromTokenReferrerWalletAddress: process.env.WALLET_ADDRESS || "", // For referral fees 48 | pathNum: "3" // Maximum routes to consider 49 | } 50 | 51 | // Helper function to convert DEX API instructions to Solana format 52 | // The DEX returns instructions in a custom format that needs conversion 53 | function createTransactionInstruction(instruction: any): TransactionInstruction { 54 | return new TransactionInstruction({ 55 | programId: new PublicKey(instruction.programId), // DEX program ID 56 | keys: instruction.accounts.map((key: any) => ({ 57 | pubkey: new PublicKey(key.pubkey), // Account address 58 | isSigner: key.isSigner, // True if account must sign tx 59 | isWritable: key.isWritable // True if instruction modifies account 60 | })), 61 | data: Buffer.from(instruction.data, 'base64') // Instruction parameters 62 | }); 63 | } 64 | 65 | // Fetch optimal swap route and instructions from DEX 66 | // This call finds the best price across different DEX liquidity pools 67 | const url = `${baseUrl}?${new URLSearchParams(params).toString()}`; 68 | const { data: { instructionLists, addressLookupTableAddresses } } = 69 | await fetch(url, { 70 | method: 'GET', 71 | headers: { 'Content-Type': 'application/json' } 72 | }).then(res => res.json()); 73 | 74 | // Process DEX instructions into Solana-compatible format 75 | const instructions: TransactionInstruction[] = []; 76 | // Remove duplicate lookup table addresses returned by DEX 77 | const addressLookupTableAddresses2 = Array.from(new Set(addressLookupTableAddresses)); 78 | console.log("Lookup tables to load:", addressLookupTableAddresses2); 79 | 80 | // Convert each DEX instruction to Solana format 81 | if (instructionLists?.length) { 82 | instructions.push(...instructionLists.map(createTransactionInstruction)); 83 | } 84 | 85 | // Process lookup tables for transaction optimization 86 | // Lookup tables are crucial for complex swaps that interact with many accounts 87 | // They significantly reduce transaction size and cost 88 | const addressLookupTableAccounts: AddressLookupTableAccount[] = []; 89 | if (addressLookupTableAddresses2?.length > 0) { 90 | console.log("Loading address lookup tables..."); 91 | // Fetch all lookup tables in parallel for better performance 92 | const lookupTableAccounts = await Promise.all( 93 | addressLookupTableAddresses2.map(async (address: unknown) => { 94 | const pubkey = new PublicKey(address as PublicKeyInitData); 95 | // Get lookup table account data from Solana 96 | const account = await connection 97 | .getAddressLookupTable(pubkey) 98 | .then((res) => res.value); 99 | if (!account) { 100 | throw new Error(`Could not fetch lookup table account ${address}`); 101 | } 102 | return account; 103 | }) 104 | ); 105 | addressLookupTableAccounts.push(...lookupTableAccounts); 106 | } 107 | console.log("Loaded lookup tables:", addressLookupTableAccounts); 108 | 109 | // Get recent blockhash for transaction timing and uniqueness 110 | // Transactions are only valid for a limited time after this blockhash 111 | const latestBlockhash = await connection.getLatestBlockhash('finalized'); 112 | 113 | // Create versioned transaction message 114 | // V0 message format required for lookup table support 115 | const messageV0 = new TransactionMessage({ 116 | payerKey: wallet.publicKey, // Fee payer address 117 | recentBlockhash: latestBlockhash.blockhash, // Transaction timing 118 | instructions // Swap instructions from DEX 119 | }).compileToV0Message(addressLookupTableAccounts); // Include lookup tables 120 | 121 | console.log("Swap instructions:", JSON.stringify(instructions)); 122 | 123 | // Create new versioned transaction with optimizations 124 | const transaction = new VersionedTransaction(messageV0); 125 | 126 | // Simulate transaction to check for errors 127 | // This helps catch issues before paying fees 128 | const result: RpcResponseAndContext = 129 | await connection.simulateTransaction(transaction); 130 | 131 | // Sign transaction with fee payer wallet 132 | const feePayer = Keypair.fromSecretKey( 133 | base58.decode(process.env.PRIVATE_KEY?.toString() || "") 134 | ); 135 | transaction.sign([feePayer]) 136 | 137 | // Send transaction to Solana 138 | // skipPreflight=false ensures additional validation 139 | // maxRetries helps handle network issues 140 | const txId = await connection.sendRawTransaction(transaction.serialize(), { 141 | skipPreflight: false, // Run preflight validation 142 | maxRetries: 5 // Retry on failure 143 | }); 144 | 145 | // Log debugging information 146 | console.log("Raw transaction:", transaction.serialize()); 147 | console.log("Base58 transaction:", base58.encode(transaction.serialize())); 148 | 149 | // Log simulation results for debugging 150 | console.log("=========simulate result========="); 151 | result.value.logs?.forEach((log) => { 152 | console.log(log); 153 | }); 154 | 155 | // Log transaction results 156 | console.log("Transaction ID:", txId); 157 | console.log("Explorer URL:", `https://solscan.io/tx/${txId}`); 158 | 159 | process.exit(0); 160 | } 161 | 162 | // Execute swap 163 | main() -------------------------------------------------------------------------------- /lib/solana/swap/solana-swap-mev.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MEV-Resistant Solana Swap Implementation with OKX DEX Integration 3 | */ 4 | 5 | import base58 from "bs58"; 6 | import BN from "bn.js"; 7 | import { 8 | Connection, 9 | ComputeBudgetProgram, 10 | Transaction, 11 | VersionedTransaction, 12 | MessageV0 as VersionedMessage, 13 | Keypair, 14 | PublicKey 15 | } from "@solana/web3.js"; 16 | import cryptoJS from "crypto-js"; 17 | import dotenv from 'dotenv'; 18 | import { getHeaders } from "../../shared"; 19 | 20 | // Load environment variables 21 | dotenv.config(); 22 | 23 | // ================= 24 | // Types & Interfaces 25 | // ================= 26 | 27 | interface TokenInfo { 28 | symbol: string; 29 | decimals: number; 30 | price: string; 31 | isHoneyPot?: boolean; 32 | } 33 | 34 | interface SwapQuote { 35 | fromToken: TokenInfo; 36 | toToken: TokenInfo; 37 | quote?: any; 38 | } 39 | 40 | interface SwapParams { 41 | chainId: string; 42 | amount: string; 43 | fromTokenAddress: string; 44 | toTokenAddress: string; 45 | slippage: string; 46 | priceImpactProtectionPercentage: string; 47 | userWalletAddress: string; 48 | } 49 | 50 | interface TradeChunk { 51 | amount: string; 52 | fromTokenAddress: string; 53 | toTokenAddress: string; 54 | minAmountOut: string; 55 | } 56 | 57 | // ================= 58 | // Configuration 59 | // ================= 60 | 61 | const MEV_PROTECTION = { 62 | // Trade Protection 63 | MAX_PRICE_IMPACT: "0.05", // 5% max price impact 64 | SLIPPAGE: "0.05", // 5% slippage tolerance 65 | MIN_ROUTES: 2, // Minimum DEX routes 66 | 67 | // Priority Fees 68 | MIN_PRIORITY_FEE: 10_000, 69 | MAX_PRIORITY_FEE: 1_000_000, 70 | PRIORITY_MULTIPLIER: 2, 71 | 72 | // TWAP Settings 73 | TWAP_ENABLED: true, 74 | TWAP_INTERVALS: 4, // Split into 4 parts 75 | TWAP_DELAY_MS: 2000, // 2s between trades 76 | 77 | // Transaction Settings 78 | COMPUTE_UNITS: 300_000, 79 | MAX_RETRIES: 3, 80 | CONFIRMATION_TIMEOUT: 60_000, 81 | 82 | // Block Targeting 83 | TARGET_SPECIFIC_BLOCKS: true, 84 | PREFERRED_SLOT_OFFSET: 2, // Target blocks with slot % 4 == 2 85 | } as const; 86 | 87 | const CONFIG = { 88 | CHAIN_ID: "501", // Solana mainnet 89 | BASE_COMPUTE_UNITS: 300000, 90 | MAX_RETRIES: 3, 91 | SLIPPAGE: "0.5" 92 | } as const; 93 | 94 | // Environment validation 95 | function getRequiredEnvVar(name: string): string { 96 | const value = process.env[name]; 97 | if (!value) throw new Error(`${name} is required`); 98 | return value; 99 | } 100 | 101 | const ENV = { 102 | OKX_API_KEY: getRequiredEnvVar('OKX_API_KEY'), 103 | OKX_SECRET_KEY: getRequiredEnvVar('OKX_SECRET_KEY'), 104 | OKX_API_PASSPHRASE: getRequiredEnvVar('OKX_API_PASSPHRASE'), 105 | OKX_PROJECT_ID: getRequiredEnvVar('OKX_PROJECT_ID'), 106 | WALLET_ADDRESS: getRequiredEnvVar('WALLET_ADDRESS'), 107 | PRIVATE_KEY: getRequiredEnvVar('PRIVATE_KEY'), 108 | RPC_URL: getRequiredEnvVar('SOLANA_RPC_URL') 109 | } as const; 110 | 111 | // ================= 112 | // RPC Management 113 | // ================= 114 | 115 | const connection = new Connection(ENV.RPC_URL, { 116 | commitment: 'confirmed', 117 | confirmTransactionInitialTimeout: MEV_PROTECTION.CONFIRMATION_TIMEOUT 118 | }); 119 | 120 | // ================= 121 | // OKX API Integration 122 | // ================= 123 | 124 | class OKXApi { 125 | private static readonly BASE_URL = "https://www.okx.com"; 126 | 127 | static async getQuote(params: any) { 128 | const timestamp = new Date().toISOString(); 129 | const requestPath = "/api/v5/dex/aggregator/quote"; 130 | const queryString = "?" + new URLSearchParams(params).toString(); 131 | 132 | console.log('Requesting quote with params:', params); 133 | 134 | const headers = { 135 | ...getHeaders(timestamp, "GET", requestPath, queryString), 136 | 'Cookie': 'locale=en-US' 137 | }; 138 | 139 | const response = await fetch(`${this.BASE_URL}${requestPath}${queryString}`, { 140 | method: "GET", 141 | headers 142 | }); 143 | 144 | const data = await response.json(); 145 | console.log('Quote response:', JSON.stringify(data, null, 2)); 146 | 147 | if (!response.ok || data.code !== "0") { 148 | throw new Error(`Failed to get quote: ${data.msg || response.statusText}`); 149 | } 150 | 151 | if (!data.data?.[0]) { 152 | throw new Error(`No quote data available: ${JSON.stringify(data)}`); 153 | } 154 | 155 | return data.data[0]; 156 | } 157 | 158 | static async getSwapTransaction(params: any): Promise { 159 | const timestamp = new Date().toISOString(); 160 | const requestPath = "/api/v5/dex/aggregator/swap"; // Changed from swap-instruction to swap 161 | const queryString = "?" + new URLSearchParams(params).toString(); 162 | 163 | console.log('Requesting swap transaction with params:', params); 164 | 165 | const headers = { 166 | ...getHeaders(timestamp, "GET", requestPath, queryString), 167 | 'Cookie': 'locale=en-US' 168 | }; 169 | 170 | const response = await fetch(`${this.BASE_URL}${requestPath}${queryString}`, { 171 | method: "GET", 172 | headers 173 | }); 174 | 175 | const data = await response.json(); 176 | console.log('Swap transaction response:', JSON.stringify(data, null, 2)); 177 | 178 | if (!response.ok || data.code !== "0") { 179 | throw new Error(`Failed to get swap transaction: ${data.msg || response.statusText}`); 180 | } 181 | 182 | if (!data.data?.[0]) { 183 | throw new Error(`No swap transaction data available: ${JSON.stringify(data)}`); 184 | } 185 | 186 | return data.data[0]; 187 | } 188 | } 189 | 190 | // ================= 191 | // TWAP Implementation 192 | // ================= 193 | 194 | class TWAPExecution { 195 | static async splitTrade( 196 | totalAmount: string, 197 | fromTokenAddress: string, 198 | toTokenAddress: string 199 | ): Promise { 200 | const amount = new BN(totalAmount); 201 | const chunkSize = amount.divn(MEV_PROTECTION.TWAP_INTERVALS); 202 | 203 | return Array(MEV_PROTECTION.TWAP_INTERVALS) 204 | .fill(null) 205 | .map(() => ({ 206 | amount: chunkSize.toString(), 207 | fromTokenAddress, 208 | toTokenAddress, 209 | minAmountOut: "0" // Will be calculated per chunk 210 | })); 211 | } 212 | 213 | static async executeTWAP(chunks: TradeChunk[]): Promise { 214 | const txIds: string[] = []; 215 | 216 | for (const chunk of chunks) { 217 | // Wait for preferred block if enabled 218 | if (MEV_PROTECTION.TARGET_SPECIFIC_BLOCKS) { 219 | await BlockTargeting.waitForPreferredBlock(); 220 | } 221 | 222 | // Execute chunk 223 | const txId = await executeSwapChunk(chunk); 224 | txIds.push(txId); 225 | 226 | // Random delay between chunks 227 | const randomDelay = MEV_PROTECTION.TWAP_DELAY_MS * (0.8 + Math.random() * 0.4); 228 | await new Promise(resolve => setTimeout(resolve, randomDelay)); 229 | } 230 | 231 | return txIds; 232 | } 233 | } 234 | 235 | // ================= 236 | // Block Targeting 237 | // ================= 238 | 239 | class BlockTargeting { 240 | static async waitForPreferredBlock(): Promise { 241 | while (true) { 242 | const slot = await connection.getSlot(); 243 | if (slot % 4 === MEV_PROTECTION.PREFERRED_SLOT_OFFSET) { 244 | return; 245 | } 246 | await new Promise(resolve => setTimeout(resolve, 400)); 247 | } 248 | } 249 | } 250 | 251 | // ================= 252 | // Transaction Building 253 | // ================= 254 | class TransactionBuilder { 255 | static async buildAndSignTransaction( 256 | txData: string, 257 | feePayer: Keypair, 258 | priorityFee: number 259 | ): Promise { 260 | try { 261 | // OKX provides base58 encoded transaction data 262 | const decodedTx = base58.decode(txData); 263 | console.log("Decoded transaction length:", decodedTx.length); 264 | 265 | // Create versioned transaction directly 266 | const versionedTx = VersionedTransaction.deserialize(decodedTx); 267 | console.log("Successfully decoded versioned transaction"); 268 | 269 | // Get latest blockhash 270 | const { blockhash } = await connection.getLatestBlockhash('finalized'); 271 | 272 | // Check if transaction already contains a compute unit price instruction 273 | const existingPriorityFeeIx = versionedTx.message.compiledInstructions.find(ix => 274 | versionedTx.message.staticAccountKeys[ix.programIdIndex].equals(ComputeBudgetProgram.programId) && 275 | ix.data[0] === 3 // ComputeUnitPrice instruction discriminator 276 | ); 277 | 278 | let newInstructions = [...versionedTx.message.compiledInstructions]; 279 | let newStaticAccountKeys = [...versionedTx.message.staticAccountKeys]; 280 | 281 | // Only add priority fee if it doesn't exist 282 | if (!existingPriorityFeeIx) { 283 | // Create priority fee instruction 284 | const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({ 285 | microLamports: priorityFee 286 | }); 287 | 288 | // Get or add program ID 289 | let priorityFeeProgramIndex = newStaticAccountKeys.findIndex( 290 | key => key.equals(ComputeBudgetProgram.programId) 291 | ); 292 | 293 | if (priorityFeeProgramIndex === -1) { 294 | priorityFeeProgramIndex = newStaticAccountKeys.length; 295 | newStaticAccountKeys.push(ComputeBudgetProgram.programId); 296 | } 297 | 298 | // Add priority fee instruction 299 | const compiledPriorityFeeIx = { 300 | programIdIndex: priorityFeeProgramIndex, 301 | accountKeyIndexes: [], 302 | data: priorityFeeIx.data 303 | }; 304 | 305 | newInstructions = [compiledPriorityFeeIx, ...newInstructions]; 306 | } else { 307 | console.log("Transaction already contains priority fee instruction"); 308 | } 309 | 310 | // Create new versioned message 311 | const newMessage = new VersionedMessage({ 312 | header: versionedTx.message.header, 313 | staticAccountKeys: newStaticAccountKeys, 314 | recentBlockhash: blockhash, 315 | compiledInstructions: newInstructions, 316 | addressTableLookups: versionedTx.message.addressTableLookups 317 | }); 318 | 319 | // Create and sign new transaction 320 | const newTx = new VersionedTransaction(newMessage); 321 | newTx.sign([feePayer]); 322 | 323 | return newTx; 324 | 325 | } catch (error) { 326 | console.error("Error building versioned transaction:", error); 327 | const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; 328 | throw new Error(`Failed to process transaction data: ${errorMessage}`); 329 | } 330 | } 331 | 332 | static async getPriorityFee(): Promise { 333 | try { 334 | const recentFees = await connection.getRecentPrioritizationFees(); 335 | if (recentFees.length === 0) return MEV_PROTECTION.MAX_PRIORITY_FEE; 336 | 337 | const maxFee = Math.max(...recentFees.map(fee => fee.prioritizationFee)); 338 | const medianFee = recentFees.sort((a, b) => a.prioritizationFee - b.prioritizationFee)[ 339 | Math.floor(recentFees.length / 2) 340 | ].prioritizationFee; 341 | 342 | const baseFee = Math.max(maxFee, medianFee * MEV_PROTECTION.PRIORITY_MULTIPLIER); 343 | return Math.min(baseFee * 1.5, MEV_PROTECTION.MAX_PRIORITY_FEE); 344 | } catch { 345 | return MEV_PROTECTION.MAX_PRIORITY_FEE; 346 | } 347 | } 348 | } 349 | 350 | // ================= 351 | // Swap Execution 352 | // ================= 353 | 354 | async function executeSwapChunk(chunk: TradeChunk): Promise { 355 | // Get optimal priority fee 356 | const priorityFee = await TransactionBuilder.getPriorityFee(); 357 | console.log("Using priority fee:", priorityFee); 358 | 359 | // First get swap quote 360 | const swapParams = { 361 | chainId: CONFIG.CHAIN_ID, 362 | amount: chunk.amount, 363 | fromTokenAddress: chunk.fromTokenAddress, 364 | toTokenAddress: chunk.toTokenAddress, 365 | slippage: CONFIG.SLIPPAGE, 366 | priceImpactProtectionPercentage: MEV_PROTECTION.MAX_PRICE_IMPACT, 367 | userWalletAddress: ENV.WALLET_ADDRESS 368 | }; 369 | 370 | console.log('Requesting swap with params:', swapParams); 371 | 372 | // Get swap transaction 373 | const swapData = await OKXApi.getSwapTransaction(swapParams); 374 | console.log("Got swap transaction data"); 375 | 376 | // Log additional transaction data details for debugging 377 | if (swapData.tx?.data) { 378 | console.log("Transaction data length:", swapData.tx.data.length); 379 | console.log("Transaction data starts with:", swapData.tx.data.slice(0, 50)); 380 | } 381 | 382 | if (!swapData.tx?.data) { 383 | throw new Error("No transaction data received from OKX"); 384 | } 385 | 386 | // Build and sign transaction 387 | const feePayer = Keypair.fromSecretKey( 388 | Uint8Array.from(base58.decode(ENV.PRIVATE_KEY)) 389 | ); 390 | 391 | const tx = await TransactionBuilder.buildAndSignTransaction( 392 | swapData.tx.data, 393 | feePayer, 394 | priorityFee 395 | ); 396 | 397 | console.log("Successfully built transaction"); 398 | 399 | // Send transaction with simulation first 400 | const txId = await connection.sendRawTransaction(tx.serialize(), { 401 | skipPreflight: false, 402 | preflightCommitment: 'processed', 403 | maxRetries: MEV_PROTECTION.MAX_RETRIES 404 | }); 405 | 406 | console.log(`Transaction sent: ${txId}`); 407 | console.log(`Explorer URL: https://solscan.io/tx/${txId}`); 408 | 409 | // Get latest blockhash for confirmation 410 | const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(); 411 | 412 | // Wait for confirmation 413 | const confirmation = await connection.confirmTransaction({ 414 | signature: txId, 415 | blockhash, 416 | lastValidBlockHeight 417 | }, 'confirmed'); 418 | 419 | if (confirmation.value.err) { 420 | throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`); 421 | } 422 | 423 | return txId; 424 | } 425 | 426 | // ================= 427 | // Main Entry Point 428 | // ================= 429 | 430 | async function executeMEVResistantSwap( 431 | amount: string, 432 | fromTokenAddress: string, 433 | toTokenAddress: string 434 | ): Promise { 435 | console.log("Starting MEV-resistant swap with parameters:"); 436 | console.log("Amount:", amount); 437 | console.log("From Token:", fromTokenAddress); 438 | console.log("To Token:", toTokenAddress); 439 | 440 | // Get quote to validate tokens 441 | const quoteParams = { 442 | chainId: CONFIG.CHAIN_ID, 443 | fromTokenAddress, 444 | toTokenAddress, 445 | amount: "1000000", // Use a small amount for initial quote 446 | slippage: CONFIG.SLIPPAGE 447 | }; 448 | 449 | console.log("Getting initial quote to validate tokens..."); 450 | const quoteData = await OKXApi.getQuote(quoteParams); 451 | 452 | if (quoteData.toToken.isHoneyPot) { 453 | throw new Error("Destination token detected as potential honeypot"); 454 | } 455 | 456 | // Convert amount to proper decimals 457 | const rawAmount = new BN( 458 | Math.floor(parseFloat(amount) * Math.pow(10, parseInt(quoteData.fromToken.decimal))) 459 | ).toString(); 460 | 461 | console.log("Amount in base units:", rawAmount); 462 | 463 | // Execute as TWAP if enabled 464 | if (MEV_PROTECTION.TWAP_ENABLED) { 465 | console.log("TWAP enabled, splitting trade into chunks..."); 466 | const chunks = await TWAPExecution.splitTrade( 467 | rawAmount, 468 | fromTokenAddress, 469 | toTokenAddress 470 | ); 471 | return await TWAPExecution.executeTWAP(chunks); 472 | } 473 | 474 | // Otherwise execute as single transaction 475 | const txId = await executeSwapChunk({ 476 | amount: rawAmount, 477 | fromTokenAddress, 478 | toTokenAddress, 479 | minAmountOut: "0" 480 | }); 481 | 482 | return [txId]; 483 | } 484 | 485 | // ================= 486 | // CLI Execution 487 | // ================= 488 | 489 | async function main() { 490 | try { 491 | const [amount, fromTokenAddress, toTokenAddress] = process.argv.slice(2); 492 | 493 | if (!amount || !fromTokenAddress || !toTokenAddress) { 494 | console.log("Usage: ts-node swap.ts "); 495 | console.log("Example: ts-node swap.ts 1.5 11111111111111111111111111111111 EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); 496 | process.exit(1); 497 | } 498 | 499 | const txIds = await executeMEVResistantSwap( 500 | amount, 501 | fromTokenAddress, 502 | toTokenAddress 503 | ); 504 | 505 | console.log("\nSwap completed successfully!"); 506 | console.log("Transaction IDs:", txIds.join(", ")); 507 | console.log("Explorer URLs:"); 508 | txIds.forEach(txId => { 509 | console.log(`https://solscan.io/tx/${txId}`); 510 | }); 511 | process.exit(0); 512 | 513 | } catch (error) { 514 | console.error("\nError:", error instanceof Error ? error.message : "Unknown error"); 515 | process.exit(1); 516 | } 517 | } 518 | 519 | // Execute if running directly 520 | if (require.main === module) { 521 | main().catch(console.error); 522 | } 523 | 524 | // Export key functionality 525 | export { 526 | executeMEVResistantSwap, 527 | OKXApi, 528 | TransactionBuilder, 529 | TWAPExecution, 530 | BlockTargeting 531 | }; -------------------------------------------------------------------------------- /lib/solana/swap/solana-swap-multi-rpc.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MEV-Resistant Solana Swap Implementation with OKX DEX Integration 3 | */ 4 | 5 | import base58 from "bs58"; 6 | import BN from "bn.js"; 7 | import { 8 | Connection, 9 | ComputeBudgetProgram, 10 | Transaction, 11 | VersionedTransaction, 12 | MessageV0 as VersionedMessage, 13 | Keypair, 14 | PublicKey 15 | } from "@solana/web3.js"; 16 | import cryptoJS from "crypto-js"; 17 | import dotenv from 'dotenv'; 18 | import { getHeaders } from "../../shared"; 19 | 20 | // Load environment variables 21 | dotenv.config(); 22 | 23 | // ================= 24 | // Types & Interfaces 25 | // ================= 26 | 27 | interface TokenInfo { 28 | symbol: string; 29 | decimals: number; 30 | price: string; 31 | isHoneyPot?: boolean; 32 | } 33 | 34 | interface SwapQuote { 35 | fromToken: TokenInfo; 36 | toToken: TokenInfo; 37 | quote?: any; 38 | } 39 | 40 | interface SwapParams { 41 | chainId: string; 42 | amount: string; 43 | fromTokenAddress: string; 44 | toTokenAddress: string; 45 | slippage: string; 46 | priceImpactProtectionPercentage: string; 47 | userWalletAddress: string; 48 | } 49 | 50 | interface TradeChunk { 51 | amount: string; 52 | fromTokenAddress: string; 53 | toTokenAddress: string; 54 | minAmountOut: string; 55 | } 56 | 57 | // ================= 58 | // Configuration 59 | // ================= 60 | 61 | const MEV_PROTECTION = { 62 | // Trade Protection 63 | MAX_PRICE_IMPACT: "0.05", // 5% max price impact 64 | SLIPPAGE: "0.05", // 5% slippage tolerance 65 | MIN_ROUTES: 2, // Minimum DEX routes 66 | 67 | // Priority Fees 68 | MIN_PRIORITY_FEE: 10_000, 69 | MAX_PRIORITY_FEE: 1_000_000, 70 | PRIORITY_MULTIPLIER: 2, 71 | 72 | // TWAP Settings 73 | TWAP_ENABLED: true, 74 | TWAP_INTERVALS: 4, // Split into 4 parts 75 | TWAP_DELAY_MS: 2000, // 2s between trades 76 | 77 | // Transaction Settings 78 | COMPUTE_UNITS: 300_000, 79 | MAX_RETRIES: 3, 80 | CONFIRMATION_TIMEOUT: 60_000, 81 | 82 | // Block Targeting 83 | TARGET_SPECIFIC_BLOCKS: true, 84 | PREFERRED_SLOT_OFFSET: 2, // Target blocks with slot % 4 == 2 85 | } as const; 86 | 87 | const CONFIG = { 88 | CHAIN_ID: "501", // Solana mainnet 89 | BASE_COMPUTE_UNITS: 300000, 90 | MAX_RETRIES: 3, 91 | SLIPPAGE: "0.5" 92 | } as const; 93 | 94 | // Environment validation 95 | function getRequiredEnvVar(name: string): string { 96 | const value = process.env[name]; 97 | if (!value) throw new Error(`${name} is required`); 98 | return value; 99 | } 100 | 101 | const ENV = { 102 | OKX_API_KEY: getRequiredEnvVar('OKX_API_KEY'), 103 | OKX_SECRET_KEY: getRequiredEnvVar('OKX_SECRET_KEY'), 104 | OKX_API_PASSPHRASE: getRequiredEnvVar('OKX_API_PASSPHRASE'), 105 | OKX_PROJECT_ID: getRequiredEnvVar('OKX_PROJECT_ID'), 106 | WALLET_ADDRESS: getRequiredEnvVar('WALLET_ADDRESS'), 107 | PRIVATE_KEY: getRequiredEnvVar('PRIVATE_KEY'), 108 | RPC_URL: getRequiredEnvVar('SOLANA_RPC_URL') 109 | } as const; 110 | 111 | // ================= 112 | // RPC Configuration 113 | // ================= 114 | 115 | const RPC_CONFIG = { 116 | ENDPOINTS: [ 117 | ENV.RPC_URL, 118 | 'https://api.mainnet-beta.solana.com', 119 | // 'https://solana-api.projectserum.com', 120 | // 'https://rpc.ankr.com/solana', 121 | // 'https://solana.getblock.io/mainnet' 122 | ], 123 | RETRY_DELAY: 1000, 124 | MAX_RETRIES: 3 125 | } as const; 126 | 127 | class RPCManager { 128 | private static currentIndex = 0; 129 | private static connections: Map = new Map(); 130 | 131 | static getCurrentConnection(): Connection { 132 | const endpoint = RPC_CONFIG.ENDPOINTS[this.currentIndex]; 133 | if (!this.connections.has(endpoint)) { 134 | this.connections.set(endpoint, new Connection(endpoint, { 135 | commitment: 'confirmed', 136 | confirmTransactionInitialTimeout: MEV_PROTECTION.CONFIRMATION_TIMEOUT 137 | })); 138 | } 139 | return this.connections.get(endpoint)!; 140 | } 141 | 142 | static async withFallback(operation: (connection: Connection) => Promise): Promise { 143 | for (let retry = 0; retry < RPC_CONFIG.MAX_RETRIES; retry++) { 144 | try { 145 | return await operation(this.getCurrentConnection()); 146 | } catch (error) { 147 | console.warn(`RPC error with endpoint ${RPC_CONFIG.ENDPOINTS[this.currentIndex]}:`, error); 148 | this.currentIndex = (this.currentIndex + 1) % RPC_CONFIG.ENDPOINTS.length; 149 | if (retry < RPC_CONFIG.MAX_RETRIES - 1) { 150 | await new Promise(resolve => setTimeout(resolve, RPC_CONFIG.RETRY_DELAY)); 151 | } 152 | } 153 | } 154 | throw new Error('All RPC endpoints failed'); 155 | } 156 | } 157 | 158 | // Replace existing connection with RPCManager 159 | const connection = RPCManager.getCurrentConnection(); 160 | 161 | // ================= 162 | // OKX API Integration 163 | // ================= 164 | 165 | class OKXApi { 166 | private static readonly BASE_URL = "https://www.okx.com"; 167 | 168 | static async getQuote(params: any) { 169 | const timestamp = new Date().toISOString(); 170 | const requestPath = "/api/v5/dex/aggregator/quote"; 171 | const queryString = "?" + new URLSearchParams(params).toString(); 172 | 173 | console.log('Requesting quote with params:', params); 174 | 175 | const headers = { 176 | ...getHeaders(timestamp, "GET", requestPath, queryString), 177 | 'Cookie': 'locale=en-US' 178 | }; 179 | 180 | const response = await fetch(`${this.BASE_URL}${requestPath}${queryString}`, { 181 | method: "GET", 182 | headers 183 | }); 184 | 185 | const data = await response.json(); 186 | console.log('Quote response:', JSON.stringify(data, null, 2)); 187 | 188 | if (!response.ok || data.code !== "0") { 189 | throw new Error(`Failed to get quote: ${data.msg || response.statusText}`); 190 | } 191 | 192 | if (!data.data?.[0]) { 193 | throw new Error(`No quote data available: ${JSON.stringify(data)}`); 194 | } 195 | 196 | return data.data[0]; 197 | } 198 | 199 | static async getSwapTransaction(params: any): Promise { 200 | const timestamp = new Date().toISOString(); 201 | const requestPath = "/api/v5/dex/aggregator/swap"; // Changed from swap-instruction to swap 202 | const queryString = "?" + new URLSearchParams(params).toString(); 203 | 204 | console.log('Requesting swap transaction with params:', params); 205 | 206 | const headers = { 207 | ...getHeaders(timestamp, "GET", requestPath, queryString), 208 | 'Cookie': 'locale=en-US' 209 | }; 210 | 211 | const response = await fetch(`${this.BASE_URL}${requestPath}${queryString}`, { 212 | method: "GET", 213 | headers 214 | }); 215 | 216 | const data = await response.json(); 217 | console.log('Swap transaction response:', JSON.stringify(data, null, 2)); 218 | 219 | if (!response.ok || data.code !== "0") { 220 | throw new Error(`Failed to get swap transaction: ${data.msg || response.statusText}`); 221 | } 222 | 223 | if (!data.data?.[0]) { 224 | throw new Error(`No swap transaction data available: ${JSON.stringify(data)}`); 225 | } 226 | 227 | return data.data[0]; 228 | } 229 | } 230 | 231 | // ================= 232 | // TWAP Implementation 233 | // ================= 234 | 235 | class TWAPExecution { 236 | static async splitTrade( 237 | totalAmount: string, 238 | fromTokenAddress: string, 239 | toTokenAddress: string 240 | ): Promise { 241 | const amount = new BN(totalAmount); 242 | const chunkSize = amount.divn(MEV_PROTECTION.TWAP_INTERVALS); 243 | 244 | return Array(MEV_PROTECTION.TWAP_INTERVALS) 245 | .fill(null) 246 | .map(() => ({ 247 | amount: chunkSize.toString(), 248 | fromTokenAddress, 249 | toTokenAddress, 250 | minAmountOut: "0" // Will be calculated per chunk 251 | })); 252 | } 253 | 254 | static async executeTWAP(chunks: TradeChunk[]): Promise { 255 | const txIds: string[] = []; 256 | 257 | for (const chunk of chunks) { 258 | // Wait for preferred block if enabled 259 | if (MEV_PROTECTION.TARGET_SPECIFIC_BLOCKS) { 260 | await BlockTargeting.waitForPreferredBlock(); 261 | } 262 | 263 | // Execute chunk 264 | const txId = await executeSwapChunk(chunk); 265 | txIds.push(txId); 266 | 267 | // Random delay between chunks 268 | const randomDelay = MEV_PROTECTION.TWAP_DELAY_MS * (0.8 + Math.random() * 0.4); 269 | await new Promise(resolve => setTimeout(resolve, randomDelay)); 270 | } 271 | 272 | return txIds; 273 | } 274 | } 275 | 276 | // ================= 277 | // Block Targeting 278 | // ================= 279 | 280 | class BlockTargeting { 281 | static async waitForPreferredBlock(): Promise { 282 | while (true) { 283 | const slot = await connection.getSlot(); 284 | if (slot % 4 === MEV_PROTECTION.PREFERRED_SLOT_OFFSET) { 285 | return; 286 | } 287 | await new Promise(resolve => setTimeout(resolve, 400)); 288 | } 289 | } 290 | } 291 | 292 | // ================= 293 | // Transaction Building 294 | // ================= 295 | class TransactionBuilder { 296 | static async buildAndSignTransaction( 297 | txData: string, 298 | feePayer: Keypair, 299 | priorityFee: number 300 | ): Promise { 301 | try { 302 | // OKX provides base58 encoded transaction data 303 | const decodedTx = base58.decode(txData); 304 | console.log("Decoded transaction length:", decodedTx.length); 305 | 306 | // Create versioned transaction directly 307 | const versionedTx = VersionedTransaction.deserialize(decodedTx); 308 | console.log("Successfully decoded versioned transaction"); 309 | 310 | // Get latest blockhash with fallback 311 | const { blockhash } = await RPCManager.withFallback( 312 | conn => conn.getLatestBlockhash('finalized') 313 | ); 314 | 315 | // Check if transaction already contains a compute unit price instruction 316 | const existingPriorityFeeIx = versionedTx.message.compiledInstructions.find(ix => 317 | versionedTx.message.staticAccountKeys[ix.programIdIndex].equals(ComputeBudgetProgram.programId) && 318 | ix.data[0] === 3 // ComputeUnitPrice instruction discriminator 319 | ); 320 | 321 | let newInstructions = [...versionedTx.message.compiledInstructions]; 322 | let newStaticAccountKeys = [...versionedTx.message.staticAccountKeys]; 323 | 324 | // Only add priority fee if it doesn't exist 325 | if (!existingPriorityFeeIx) { 326 | // Create priority fee instruction 327 | const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({ 328 | microLamports: priorityFee 329 | }); 330 | 331 | // Get or add program ID 332 | let priorityFeeProgramIndex = newStaticAccountKeys.findIndex( 333 | key => key.equals(ComputeBudgetProgram.programId) 334 | ); 335 | 336 | if (priorityFeeProgramIndex === -1) { 337 | priorityFeeProgramIndex = newStaticAccountKeys.length; 338 | newStaticAccountKeys.push(ComputeBudgetProgram.programId); 339 | } 340 | 341 | // Add priority fee instruction 342 | const compiledPriorityFeeIx = { 343 | programIdIndex: priorityFeeProgramIndex, 344 | accountKeyIndexes: [], 345 | data: priorityFeeIx.data 346 | }; 347 | 348 | newInstructions = [compiledPriorityFeeIx, ...newInstructions]; 349 | } else { 350 | console.log("Transaction already contains priority fee instruction"); 351 | } 352 | 353 | // Create new versioned message 354 | const newMessage = new VersionedMessage({ 355 | header: versionedTx.message.header, 356 | staticAccountKeys: newStaticAccountKeys, 357 | recentBlockhash: blockhash, 358 | compiledInstructions: newInstructions, 359 | addressTableLookups: versionedTx.message.addressTableLookups 360 | }); 361 | 362 | // Create and sign new transaction 363 | const newTx = new VersionedTransaction(newMessage); 364 | newTx.sign([feePayer]); 365 | 366 | return newTx; 367 | 368 | } catch (error) { 369 | console.error("Error building versioned transaction:", error); 370 | const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; 371 | throw new Error(`Failed to process transaction data: ${errorMessage}`); 372 | } 373 | } 374 | 375 | static async getPriorityFee(): Promise { 376 | try { 377 | const recentFees = await RPCManager.withFallback( 378 | conn => conn.getRecentPrioritizationFees() 379 | ); 380 | if (recentFees.length === 0) return MEV_PROTECTION.MAX_PRIORITY_FEE; 381 | 382 | const maxFee = Math.max(...recentFees.map(fee => fee.prioritizationFee)); 383 | const medianFee = recentFees.sort((a, b) => a.prioritizationFee - b.prioritizationFee)[ 384 | Math.floor(recentFees.length / 2) 385 | ].prioritizationFee; 386 | 387 | const baseFee = Math.max(maxFee, medianFee * MEV_PROTECTION.PRIORITY_MULTIPLIER); 388 | return Math.min(baseFee * 1.5, MEV_PROTECTION.MAX_PRIORITY_FEE); 389 | } catch { 390 | return MEV_PROTECTION.MAX_PRIORITY_FEE; 391 | } 392 | } 393 | } 394 | 395 | // ================= 396 | // Swap Execution 397 | // ================= 398 | 399 | async function executeSwapChunk(chunk: TradeChunk): Promise { 400 | // Get optimal priority fee 401 | const priorityFee = await TransactionBuilder.getPriorityFee(); 402 | console.log("Using priority fee:", priorityFee); 403 | 404 | // First get swap quote 405 | const swapParams = { 406 | chainId: CONFIG.CHAIN_ID, 407 | amount: chunk.amount, 408 | fromTokenAddress: chunk.fromTokenAddress, 409 | toTokenAddress: chunk.toTokenAddress, 410 | slippage: CONFIG.SLIPPAGE, 411 | priceImpactProtectionPercentage: MEV_PROTECTION.MAX_PRICE_IMPACT, 412 | userWalletAddress: ENV.WALLET_ADDRESS 413 | }; 414 | 415 | console.log('Requesting swap with params:', swapParams); 416 | 417 | // Get swap transaction 418 | const swapData = await OKXApi.getSwapTransaction(swapParams); 419 | console.log("Got swap transaction data"); 420 | 421 | // Log additional transaction data details for debugging 422 | if (swapData.tx?.data) { 423 | console.log("Transaction data length:", swapData.tx.data.length); 424 | console.log("Transaction data starts with:", swapData.tx.data.slice(0, 50)); 425 | } 426 | 427 | if (!swapData.tx?.data) { 428 | throw new Error("No transaction data received from OKX"); 429 | } 430 | 431 | // Build and sign transaction 432 | const feePayer = Keypair.fromSecretKey( 433 | Uint8Array.from(base58.decode(ENV.PRIVATE_KEY)) 434 | ); 435 | 436 | const tx = await TransactionBuilder.buildAndSignTransaction( 437 | swapData.tx.data, 438 | feePayer, 439 | priorityFee 440 | ); 441 | 442 | console.log("Successfully built transaction"); 443 | 444 | // Send transaction with RPC fallback 445 | const txId = await RPCManager.withFallback(async (conn) => { 446 | return await conn.sendRawTransaction(tx.serialize(), { 447 | skipPreflight: false, 448 | preflightCommitment: 'processed', 449 | maxRetries: MEV_PROTECTION.MAX_RETRIES 450 | }); 451 | }); 452 | 453 | console.log(`Transaction sent: ${txId}`); 454 | console.log(`Explorer URL: https://solscan.io/tx/${txId}`); 455 | 456 | // Get confirmation with RPC fallback 457 | const confirmation = await RPCManager.withFallback(async (conn) => { 458 | const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash(); 459 | return await conn.confirmTransaction({ 460 | signature: txId, 461 | blockhash, 462 | lastValidBlockHeight 463 | }, 'confirmed'); 464 | }); 465 | 466 | if (confirmation.value.err) { 467 | throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`); 468 | } 469 | 470 | return txId; 471 | } 472 | 473 | // ================= 474 | // Main Entry Point 475 | // ================= 476 | 477 | async function executeMEVResistantSwap( 478 | amount: string, 479 | fromTokenAddress: string, 480 | toTokenAddress: string 481 | ): Promise { 482 | console.log("Starting MEV-resistant swap with parameters:"); 483 | console.log("Amount:", amount); 484 | console.log("From Token:", fromTokenAddress); 485 | console.log("To Token:", toTokenAddress); 486 | 487 | // Get quote to validate tokens 488 | const quoteParams = { 489 | chainId: CONFIG.CHAIN_ID, 490 | fromTokenAddress, 491 | toTokenAddress, 492 | amount: "1000000", // Use a small amount for initial quote 493 | slippage: CONFIG.SLIPPAGE 494 | }; 495 | 496 | console.log("Getting initial quote to validate tokens..."); 497 | const quoteData = await OKXApi.getQuote(quoteParams); 498 | 499 | if (quoteData.toToken.isHoneyPot) { 500 | throw new Error("Destination token detected as potential honeypot"); 501 | } 502 | 503 | // Convert amount to proper decimals 504 | const rawAmount = new BN( 505 | Math.floor(parseFloat(amount) * Math.pow(10, parseInt(quoteData.fromToken.decimal))) 506 | ).toString(); 507 | 508 | console.log("Amount in base units:", rawAmount); 509 | 510 | // Execute as TWAP if enabled 511 | if (MEV_PROTECTION.TWAP_ENABLED) { 512 | console.log("TWAP enabled, splitting trade into chunks..."); 513 | const chunks = await TWAPExecution.splitTrade( 514 | rawAmount, 515 | fromTokenAddress, 516 | toTokenAddress 517 | ); 518 | return await TWAPExecution.executeTWAP(chunks); 519 | } 520 | 521 | // Otherwise execute as single transaction 522 | const txId = await executeSwapChunk({ 523 | amount: rawAmount, 524 | fromTokenAddress, 525 | toTokenAddress, 526 | minAmountOut: "0" 527 | }); 528 | 529 | return [txId]; 530 | } 531 | 532 | // ================= 533 | // CLI Execution 534 | // ================= 535 | 536 | async function main() { 537 | try { 538 | const [amount, fromTokenAddress, toTokenAddress] = process.argv.slice(2); 539 | 540 | if (!amount || !fromTokenAddress || !toTokenAddress) { 541 | console.log("Usage: ts-node swap.ts "); 542 | console.log("Example: ts-node swap.ts 1.5 11111111111111111111111111111111 EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); 543 | process.exit(1); 544 | } 545 | 546 | const txIds = await executeMEVResistantSwap( 547 | amount, 548 | fromTokenAddress, 549 | toTokenAddress 550 | ); 551 | 552 | console.log("\nSwap completed successfully!"); 553 | console.log("Transaction IDs:", txIds.join(", ")); 554 | console.log("Explorer URLs:"); 555 | txIds.forEach(txId => { 556 | console.log(`https://solscan.io/tx/${txId}`); 557 | }); 558 | process.exit(0); 559 | 560 | } catch (error) { 561 | console.error("\nError:", error instanceof Error ? error.message : "Unknown error"); 562 | process.exit(1); 563 | } 564 | } 565 | 566 | // Execute if running directly 567 | if (require.main === module) { 568 | main().catch(console.error); 569 | } 570 | 571 | // Export key functionality 572 | export { 573 | executeMEVResistantSwap, 574 | OKXApi, 575 | TransactionBuilder, 576 | TWAPExecution, 577 | BlockTargeting 578 | }; -------------------------------------------------------------------------------- /lib/solana/swap/solana-swap.ts: -------------------------------------------------------------------------------- 1 | // swap.ts 2 | import base58 from "bs58"; 3 | import BN from "bn.js"; 4 | import * as solanaWeb3 from "@solana/web3.js"; 5 | import { Connection, GetVersionedTransactionConfig } from "@solana/web3.js"; 6 | import cryptoJS from "crypto-js"; 7 | import dotenv from 'dotenv'; 8 | 9 | dotenv.config(); 10 | 11 | // Environment variables 12 | const apiKey = process.env.OKX_API_KEY; 13 | const secretKey = process.env.OKX_SECRET_KEY; 14 | const apiPassphrase = process.env.OKX_API_PASSPHRASE; 15 | const projectId = process.env.OKX_PROJECT_ID; 16 | const userAddress = process.env.WALLET_ADDRESS; 17 | const userPrivateKey = process.env.PRIVATE_KEY; 18 | const solanaRpcUrl = process.env.SOLANA_RPC_URL; 19 | 20 | // Constants 21 | const SOLANA_CHAIN_ID = "501"; 22 | const COMPUTE_UNITS = 300000; 23 | const MAX_RETRIES = 3; 24 | const INITIAL_RETRY_DELAY = 1000; // 1 second 25 | const MAX_RETRY_DELAY = 10000; // 10 seconds 26 | 27 | const connection = new Connection(`${solanaRpcUrl}`, { 28 | confirmTransactionInitialTimeout: 5000 29 | }); 30 | 31 | type TransactionStatus = 'confirmed' | 'finalized' | 'processed' | 'dropped' | 'unknown'; 32 | 33 | async function getTransactionStatus(txId: string): Promise { 34 | try { 35 | const confirmedTx: GetVersionedTransactionConfig = { 36 | maxSupportedTransactionVersion: 0, 37 | commitment: 'confirmed' 38 | }; 39 | 40 | const finalizedTx: GetVersionedTransactionConfig = { 41 | maxSupportedTransactionVersion: 0, 42 | commitment: 'finalized' 43 | }; 44 | 45 | 46 | if (finalizedTx) return 'finalized'; 47 | if (confirmedTx) return 'confirmed'; 48 | 49 | // Check if transaction is still in memory pool 50 | const signatureStatus = await connection.getSignatureStatus(txId); 51 | if (signatureStatus.value === null) { 52 | return 'dropped'; 53 | } 54 | 55 | return 'unknown'; 56 | } catch (error) { 57 | console.error('Error checking transaction status:', error); 58 | return 'unknown'; 59 | } 60 | } 61 | 62 | async function isTransactionConfirmed(txId: string) { 63 | const status = await getTransactionStatus(txId); 64 | return status === 'confirmed' || status === 'finalized'; 65 | } 66 | 67 | function calculateRetryDelay(retryCount: number): number { 68 | // Exponential backoff with jitter 69 | const exponentialDelay = INITIAL_RETRY_DELAY * Math.pow(2, retryCount); 70 | const jitter = Math.random() * 1000; // Random delay between 0-1000ms 71 | return Math.min(exponentialDelay + jitter, MAX_RETRY_DELAY); 72 | } 73 | 74 | function getHeaders(timestamp: string, method: string, requestPath: string, queryString = "") { 75 | if (!apiKey || !secretKey || !apiPassphrase || !projectId) { 76 | throw new Error("Missing required environment variables"); 77 | } 78 | 79 | const stringToSign = timestamp + method + requestPath + queryString; 80 | return { 81 | "Content-Type": "application/json", 82 | "OK-ACCESS-KEY": apiKey, 83 | "OK-ACCESS-SIGN": cryptoJS.enc.Base64.stringify( 84 | cryptoJS.HmacSHA256(stringToSign, secretKey) 85 | ), 86 | "OK-ACCESS-TIMESTAMP": timestamp, 87 | "OK-ACCESS-PASSPHRASE": apiPassphrase, 88 | "OK-ACCESS-PROJECT": projectId, 89 | }; 90 | } 91 | 92 | async function getTokenInfo(fromTokenAddress: string, toTokenAddress: string) { 93 | const timestamp = new Date().toISOString(); 94 | const requestPath = "/api/v5/dex/aggregator/quote"; 95 | const params = { 96 | chainId: SOLANA_CHAIN_ID, 97 | fromTokenAddress, 98 | toTokenAddress, 99 | amount: "1000000", // small amount just to get token info 100 | slippage: "0.5", 101 | }; 102 | 103 | const queryString = "?" + new URLSearchParams(params).toString(); 104 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 105 | 106 | const response = await fetch( 107 | `https://www.okx.com${requestPath}${queryString}`, 108 | { method: "GET", headers } 109 | ); 110 | 111 | if (!response.ok) { 112 | throw new Error(`Failed to get quote: ${await response.text()}`); 113 | } 114 | 115 | const data = await response.json(); 116 | if (data.code !== "0" || !data.data?.[0]) { 117 | throw new Error("Failed to get token information"); 118 | } 119 | 120 | const quoteData = data.data[0]; 121 | return { 122 | fromToken: { 123 | symbol: quoteData.fromToken.tokenSymbol, 124 | decimals: parseInt(quoteData.fromToken.decimal), 125 | price: quoteData.fromToken.tokenUnitPrice 126 | }, 127 | toToken: { 128 | symbol: quoteData.toToken.tokenSymbol, 129 | decimals: parseInt(quoteData.toToken.decimal), 130 | price: quoteData.toToken.tokenUnitPrice 131 | } 132 | }; 133 | } 134 | 135 | function convertAmount(amount: string, decimals: number) { 136 | try { 137 | if (!amount || isNaN(parseFloat(amount))) { 138 | throw new Error("Invalid amount"); 139 | } 140 | const value = parseFloat(amount); 141 | if (value <= 0) { 142 | throw new Error("Amount must be greater than 0"); 143 | } 144 | return new BN(value * Math.pow(10, decimals)).toString(); 145 | } catch (err) { 146 | console.error("Amount conversion error:", err); 147 | throw new Error("Invalid amount format"); 148 | } 149 | } 150 | 151 | async function main() { 152 | try { 153 | const args = process.argv.slice(2); 154 | if (args.length < 3) { 155 | console.log("Usage: ts-node swap.ts "); 156 | console.log("Example: ts-node swap.ts 1.5 11111111111111111111111111111111 EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); 157 | process.exit(1); 158 | } 159 | 160 | const [amount, fromTokenAddress, toTokenAddress] = args; 161 | 162 | if (!userPrivateKey || !userAddress) { 163 | throw new Error("Private key or user address not found"); 164 | } 165 | 166 | // Get token information 167 | console.log("Getting token information..."); 168 | const tokenInfo = await getTokenInfo(fromTokenAddress, toTokenAddress); 169 | console.log(`From: ${tokenInfo.fromToken.symbol} (${tokenInfo.fromToken.decimals} decimals)`); 170 | console.log(`To: ${tokenInfo.toToken.symbol} (${tokenInfo.toToken.decimals} decimals)`); 171 | 172 | // Convert amount using fetched decimals 173 | const rawAmount = convertAmount(amount, tokenInfo.fromToken.decimals); 174 | console.log(`Amount in ${tokenInfo.fromToken.symbol} base units:`, rawAmount); 175 | 176 | // Get swap quote 177 | const quoteParams = { 178 | chainId: SOLANA_CHAIN_ID, 179 | amount: rawAmount, 180 | fromTokenAddress, 181 | toTokenAddress, 182 | slippage: "0.5", 183 | userWalletAddress: userAddress, 184 | } as Record; 185 | 186 | // Get swap data 187 | const timestamp = new Date().toISOString(); 188 | const requestPath = "/api/v5/dex/aggregator/swap"; 189 | const queryString = "?" + new URLSearchParams(quoteParams).toString(); 190 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 191 | 192 | console.log("Requesting swap quote..."); 193 | const response = await fetch( 194 | `https://www.okx.com${requestPath}${queryString}`, 195 | { method: "GET", headers } 196 | ); 197 | 198 | const data = await response.json(); 199 | if (data.code !== "0") { 200 | throw new Error(`API Error: ${data.msg}`); 201 | } 202 | 203 | const swapData = data.data[0]; 204 | 205 | // Show estimated output and price impact 206 | const outputAmount = parseFloat(swapData.routerResult.toTokenAmount) / Math.pow(10, tokenInfo.toToken.decimals); 207 | console.log("\nSwap Quote:"); 208 | console.log(`Input: ${amount} ${tokenInfo.fromToken.symbol} ($${(parseFloat(amount) * parseFloat(tokenInfo.fromToken.price)).toFixed(2)})`); 209 | console.log(`Output: ${outputAmount.toFixed(tokenInfo.toToken.decimals)} ${tokenInfo.toToken.symbol} ($${(outputAmount * parseFloat(tokenInfo.toToken.price)).toFixed(2)})`); 210 | if (swapData.priceImpactPercentage) { 211 | console.log(`Price Impact: ${swapData.priceImpactPercentage}%`); 212 | } 213 | 214 | console.log("\nExecuting swap transaction..."); 215 | let retryCount = 0; 216 | let txId; 217 | while (retryCount < MAX_RETRIES) { 218 | try { 219 | if (!swapData || (!swapData.tx && !swapData.data)) { 220 | throw new Error("Invalid swap data structure"); 221 | } 222 | 223 | const transactionData = swapData.tx?.data || swapData.data; 224 | if (!transactionData || typeof transactionData !== 'string') { 225 | throw new Error("Invalid transaction data"); 226 | } 227 | 228 | const recentBlockHash = await connection.getLatestBlockhash(); 229 | console.log("Got blockhash:", recentBlockHash.blockhash); 230 | 231 | const decodedTransaction = base58.decode(transactionData); 232 | let tx; 233 | 234 | try { 235 | tx = solanaWeb3.VersionedTransaction.deserialize(decodedTransaction); 236 | console.log("Successfully created versioned transaction"); 237 | tx.message.recentBlockhash = recentBlockHash.blockhash; 238 | } catch (e) { 239 | console.log("Versioned transaction failed, trying legacy:", e); 240 | tx = solanaWeb3.Transaction.from(decodedTransaction); 241 | console.log("Successfully created legacy transaction"); 242 | tx.recentBlockhash = recentBlockHash.blockhash; 243 | } 244 | 245 | const computeBudgetIx = solanaWeb3.ComputeBudgetProgram.setComputeUnitLimit({ 246 | units: COMPUTE_UNITS 247 | }); 248 | 249 | const feePayer = solanaWeb3.Keypair.fromSecretKey( 250 | base58.decode(userPrivateKey) 251 | ); 252 | 253 | if (tx instanceof solanaWeb3.VersionedTransaction) { 254 | tx.sign([feePayer]); 255 | } else { 256 | tx.partialSign(feePayer); 257 | } 258 | 259 | txId = await connection.sendRawTransaction(tx.serialize(), { 260 | skipPreflight: false, 261 | maxRetries: MAX_RETRIES 262 | }); 263 | 264 | const confirmation = await connection.confirmTransaction({ 265 | signature: txId, 266 | blockhash: recentBlockHash.blockhash, 267 | lastValidBlockHeight: recentBlockHash.lastValidBlockHeight 268 | }, 'confirmed'); 269 | 270 | if (confirmation?.value?.err) { 271 | throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`); 272 | } 273 | 274 | console.log("\nSwap completed successfully!"); 275 | console.log("Transaction ID:", txId); 276 | console.log("Explorer URL:", `https://solscan.io/tx/${txId}`); 277 | 278 | process.exit(0); 279 | } catch (error) { 280 | console.error(`Attempt ${retryCount + 1} failed:`, error); 281 | 282 | if (txId) { 283 | const status = await getTransactionStatus(txId); 284 | console.log(`Transaction status: ${status}`); 285 | 286 | switch (status) { 287 | case 'finalized': 288 | case 'confirmed': 289 | console.log("Transaction confirmed successfully, no retry needed."); 290 | process.exit(0); 291 | case 'processed': 292 | console.log("Transaction processed but not confirmed, waiting longer..."); 293 | await new Promise(resolve => setTimeout(resolve, 5000)); // Extra wait for confirmation 294 | break; 295 | case 'dropped': 296 | console.log("Transaction dropped, will retry with new blockhash"); 297 | break; 298 | case 'unknown': 299 | console.log("Transaction status unknown, proceeding with retry"); 300 | break; 301 | } 302 | } 303 | 304 | retryCount++; 305 | 306 | if (retryCount === MAX_RETRIES) { 307 | throw error; 308 | } 309 | 310 | const delay = calculateRetryDelay(retryCount); 311 | console.log(`Waiting ${delay}ms before retry ${retryCount + 1}/${MAX_RETRIES}...`); 312 | await new Promise(resolve => setTimeout(resolve, delay)); 313 | } 314 | } 315 | } catch (error) { 316 | console.error("Error:", error instanceof Error ? error.message : "Unknown error"); 317 | process.exit(1); 318 | } 319 | } 320 | 321 | if (require.main === module) { 322 | main(); 323 | } -------------------------------------------------------------------------------- /lib/solana/swap/solana-tokens.ts: -------------------------------------------------------------------------------- 1 | 2 | // scripts/get-tokens.ts 3 | import { getHeaders } from '../../shared'; 4 | 5 | async function main() { 6 | try { 7 | const params = { 8 | chainId: '501' // Solana Chain ID 9 | }; 10 | 11 | const timestamp = new Date().toISOString(); 12 | const requestPath = "/api/v5/dex/aggregator/all-tokens"; 13 | const queryString = "?" + new URLSearchParams(params).toString(); 14 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 15 | 16 | console.log('Getting Solana tokens...'); 17 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 18 | method: "GET", 19 | headers 20 | }); 21 | 22 | const data = await response.json(); 23 | console.log('Tokens response:', JSON.stringify(data, null, 2)); 24 | } catch (error) { 25 | console.error('Script failed:', error); 26 | process.exit(1); 27 | } 28 | } 29 | 30 | main(); -------------------------------------------------------------------------------- /lib/sui/cross-chain/sui-bridge-tokens.ts: -------------------------------------------------------------------------------- 1 | // sui-supported-tokens.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const chainId = '784'; // SUI Chain ID 7 | const timestamp = new Date().toISOString(); 8 | const requestPath = "/api/v5/dex/cross-chain/supported/tokens"; 9 | const queryString = chainId ? `?chainId=${chainId}` : ''; 10 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 11 | 12 | console.log('Getting supported tokens for SUI...'); 13 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 14 | method: "GET", 15 | headers 16 | }); 17 | 18 | const data = await response.json(); 19 | console.log('Supported tokens response:', JSON.stringify(data, null, 2)); 20 | } catch (error) { 21 | console.error('Script failed:', error); 22 | process.exit(1); 23 | } 24 | } 25 | 26 | main(); -------------------------------------------------------------------------------- /lib/sui/cross-chain/sui-bridges.ts: -------------------------------------------------------------------------------- 1 | // sui-supported-bridges.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const chainId = '784'; // SUI Chain ID 7 | const timestamp = new Date().toISOString(); 8 | const requestPath = "/api/v5/dex/cross-chain/supported/bridges"; 9 | const queryString = chainId ? `?chainId=${chainId}` : ''; 10 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 11 | 12 | console.log('Getting supported bridges for SUI...'); 13 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 14 | method: "GET", 15 | headers 16 | }); 17 | 18 | const data = await response.json(); 19 | console.log('Supported bridges response:', JSON.stringify(data, null, 2)); 20 | } catch (error) { 21 | console.error('Script failed:', error); 22 | process.exit(1); 23 | } 24 | } 25 | 26 | main(); -------------------------------------------------------------------------------- /lib/sui/cross-chain/sui-cross-chain-quote.ts: -------------------------------------------------------------------------------- 1 | // sui-cross-chain-quote.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | fromChainId: '784', // SUI Chain ID 8 | toChainId: '1', // To Ethereum 9 | amount: '10000000000', 10 | fromTokenAddress: '0x2::sui::SUI', // Native SUI 11 | toTokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum 12 | slippage: '0.025', // 2.5% slippage for cross-chain swaps 13 | sort: '1', // Optimal route considering all factors 14 | priceImpactProtectionPercentage: '0.9' // 90% price impact protection 15 | }; 16 | 17 | const timestamp = new Date().toISOString(); 18 | const requestPath = "/api/v5/dex/cross-chain/quote"; 19 | const queryString = "?" + new URLSearchParams(params).toString(); 20 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 21 | 22 | console.log('Getting SUI to Ethereum cross-chain quote...'); 23 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 24 | method: "GET", 25 | headers 26 | }); 27 | 28 | const data = await response.json(); 29 | console.log('Cross-chain quote response:', JSON.stringify(data, null, 2)); 30 | } catch (error) { 31 | console.error('Script failed:', error); 32 | process.exit(1); 33 | } 34 | } 35 | 36 | main(); -------------------------------------------------------------------------------- /lib/sui/cross-chain/sui-token-pairs.ts: -------------------------------------------------------------------------------- 1 | // sui-bridge-pairs.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | fromChainId: '784' // SUI Chain ID 8 | }; 9 | 10 | const timestamp = new Date().toISOString(); 11 | const requestPath = "/api/v5/dex/cross-chain/supported/bridge-tokens-pairs"; 12 | const queryString = "?" + new URLSearchParams(params).toString(); 13 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 14 | 15 | console.log('Getting bridge token pairs for SUI...'); 16 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 17 | method: "GET", 18 | headers 19 | }); 20 | 21 | const data = await response.json(); 22 | console.log('Bridge pairs response:', JSON.stringify(data, null, 2)); 23 | } catch (error) { 24 | console.error('Script failed:', error); 25 | process.exit(1); 26 | } 27 | } 28 | 29 | main(); -------------------------------------------------------------------------------- /lib/sui/swap/sui-chain.ts: -------------------------------------------------------------------------------- 1 | import { getHeaders } from '../../shared'; 2 | 3 | async function main() { 4 | try { 5 | const chainId = '784'; // SUI Chain ID 6 | const timestamp = new Date().toISOString(); 7 | const requestPath = "/api/v5/dex/aggregator/supported/chain"; 8 | const queryString = chainId ? `?chainId=${chainId}` : ''; 9 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 10 | 11 | console.log('Getting supported chain info for SUI...'); 12 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 13 | method: "GET", 14 | headers 15 | }); 16 | 17 | const data = await response.json(); 18 | console.log('Supported chains response:', JSON.stringify(data, null, 2)); 19 | } catch (error) { 20 | console.error('Script failed:', error); 21 | process.exit(1); 22 | } 23 | } 24 | 25 | main(); -------------------------------------------------------------------------------- /lib/sui/swap/sui-liquidity.ts: -------------------------------------------------------------------------------- 1 | // scripts/get-liquidity.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | chainId: '784' // SUI Chain ID 8 | }; 9 | 10 | const timestamp = new Date().toISOString(); 11 | const requestPath = "/api/v5/dex/aggregator/get-liquidity"; 12 | const queryString = "?" + new URLSearchParams(params).toString(); 13 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 14 | 15 | console.log('Getting SUI liquidity sources...'); 16 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 17 | method: "GET", 18 | headers 19 | }); 20 | 21 | const data = await response.json(); 22 | console.log('Liquidity sources response:', JSON.stringify(data, null, 2)); 23 | } catch (error) { 24 | console.error('Script failed:', error); 25 | process.exit(1); 26 | } 27 | } 28 | 29 | main(); -------------------------------------------------------------------------------- /lib/sui/swap/sui-quote.ts: -------------------------------------------------------------------------------- 1 | // scripts/sui-quote.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | chainId: '784', // SUI Chain ID 8 | amount: '10000000000', 9 | fromTokenAddress: '0x2::sui::SUI', 10 | toTokenAddress: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC', 11 | slippage: '0.1', 12 | }; 13 | 14 | const timestamp = new Date().toISOString(); 15 | const requestPath = "/api/v5/dex/aggregator/quote"; 16 | const queryString = "?" + new URLSearchParams(params).toString(); 17 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 18 | 19 | console.log('Getting SUI quote...'); 20 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 21 | method: "GET", 22 | headers 23 | }); 24 | 25 | const data = await response.json(); 26 | console.log('Quote response:', JSON.stringify(data, null, 2)); 27 | } catch (error) { 28 | console.error('Script failed:', error); 29 | process.exit(1); 30 | } 31 | } 32 | 33 | main(); -------------------------------------------------------------------------------- /lib/sui/swap/sui-swap-data.ts: -------------------------------------------------------------------------------- 1 | // scripts/sui-swap.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | chainId: '784', // SUI Chain ID 8 | amount: '10000000000', 9 | fromTokenAddress: '0x2::sui::SUI', 10 | toTokenAddress: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC', 11 | userWalletAddress: "0xac5bceec1b789ff840d7d4e6ce4ce61c90d190a7f8c4f4ddf0bff6ee2413c33c", 12 | slippage: '0.1', 13 | autoSlippage: "true", 14 | maxAutoSlippageBps: "100" 15 | }; 16 | 17 | const timestamp = new Date().toISOString(); 18 | const requestPath = "/api/v5/dex/aggregator/swap"; 19 | const queryString = "?" + new URLSearchParams(params).toString(); 20 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 21 | 22 | console.log('Getting SUI swap data...'); 23 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 24 | method: "GET", 25 | headers 26 | }); 27 | 28 | const data = await response.json(); 29 | console.log('Quote response:', JSON.stringify(data, null, 2)); 30 | } catch (error) { 31 | console.error('Script failed:', error); 32 | process.exit(1); 33 | } 34 | } 35 | 36 | main(); -------------------------------------------------------------------------------- /lib/sui/swap/sui-swap.ts: -------------------------------------------------------------------------------- 1 | import { SuiWallet } from "@okxweb3/coin-sui"; 2 | import { getFullnodeUrl, SuiClient } from '@mysten/sui/client'; 3 | import { Transaction } from '@mysten/sui/transactions'; 4 | import dotenv from 'dotenv'; 5 | import { getHeaders } from '../../shared'; 6 | 7 | dotenv.config(); 8 | 9 | // Environment variables 10 | const userPrivateKey = process.env.PRIVATE_KEY; 11 | const rawWalletAddress = process.env.WALLET_ADDRESS; 12 | 13 | // Token list (helper) 14 | const TOKENS = { 15 | SUI: "0x2::sui::SUI", 16 | USDC: "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC" 17 | } as const; 18 | 19 | const CONFIG = { 20 | MAX_RETRIES: 3, 21 | BASE_URL: 'https://www.okx.com', 22 | CHAIN_ID: '784', 23 | SLIPPAGE: '0.5', 24 | DEFAULT_GAS_BUDGET: 50000000, 25 | MIN_GAS_PRICE: 1000 26 | }; 27 | 28 | // Validate wallet address 29 | if (!rawWalletAddress) { 30 | throw new Error('WALLET_ADDRESS is required in environment variables'); 31 | } 32 | 33 | // Initialize Sui wallet and client 34 | const wallet = new SuiWallet(); 35 | const client = new SuiClient({ 36 | url: getFullnodeUrl('mainnet') 37 | }); 38 | 39 | // Types 40 | interface TokenInfo { 41 | symbol: string; 42 | decimals: number; 43 | price: string; 44 | } 45 | 46 | interface SwapQuoteResponse { 47 | code: string; 48 | data: [{ 49 | tx: { 50 | data: string; 51 | gas?: string; 52 | }; 53 | routerResult: { 54 | toTokenAmount: string; 55 | fromTokenAmount: string; 56 | }; 57 | fromToken: { 58 | tokenSymbol: string; 59 | decimal: string; 60 | tokenUnitPrice: string; 61 | }; 62 | toToken: { 63 | tokenSymbol: string; 64 | decimal: string; 65 | tokenUnitPrice: string; 66 | }; 67 | priceImpactPercentage?: string; 68 | }]; 69 | msg?: string; 70 | } 71 | 72 | // Utility function to normalize Sui address 73 | function normalizeSuiAddress(address: string): string { 74 | if (typeof address !== 'string') { 75 | throw new Error('Address must be a string'); 76 | } 77 | const normalized = address.toLowerCase(); 78 | if (!normalized.startsWith('0x')) { 79 | return `0x${normalized}`; 80 | } 81 | return normalized; 82 | } 83 | 84 | // Normalize wallet address 85 | const normalizedWalletAddress = normalizeSuiAddress(rawWalletAddress); 86 | 87 | 88 | async function getTokenInfo(fromTokenAddress: string, toTokenAddress: string): Promise<{ 89 | fromToken: TokenInfo; 90 | toToken: TokenInfo; 91 | }> { 92 | const timestamp = new Date().toISOString(); 93 | const requestPath = "/api/v5/dex/aggregator/quote"; 94 | const params = { 95 | chainId: CONFIG.CHAIN_ID, 96 | fromTokenAddress, 97 | toTokenAddress, 98 | amount: "1000000", 99 | slippage: CONFIG.SLIPPAGE, 100 | }; 101 | 102 | const queryString = "?" + new URLSearchParams(params).toString(); 103 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 104 | 105 | const response = await fetch( 106 | `${CONFIG.BASE_URL}${requestPath}${queryString}`, 107 | { method: "GET", headers } 108 | ); 109 | 110 | const data: SwapQuoteResponse = await response.json(); 111 | if (data.code !== "0" || !data.data?.[0]) { 112 | throw new Error("Failed to get token information"); 113 | } 114 | 115 | const quoteData = data.data[0]; 116 | return { 117 | fromToken: { 118 | symbol: quoteData.fromToken.tokenSymbol, 119 | decimals: parseInt(quoteData.fromToken.decimal), 120 | price: quoteData.fromToken.tokenUnitPrice 121 | }, 122 | toToken: { 123 | symbol: quoteData.toToken.tokenSymbol, 124 | decimals: parseInt(quoteData.toToken.decimal), 125 | price: quoteData.toToken.tokenUnitPrice 126 | } 127 | }; 128 | } 129 | 130 | async function getSwapQuote(amount: string, fromToken: string, toToken: string) { 131 | const params = { 132 | chainId: CONFIG.CHAIN_ID, 133 | amount: amount, 134 | fromTokenAddress: fromToken, 135 | toTokenAddress: toToken, 136 | userWalletAddress: normalizedWalletAddress, 137 | slippage: CONFIG.SLIPPAGE, 138 | autoSlippage: "true", 139 | maxAutoSlippageBps: "100" 140 | }; 141 | 142 | const timestamp = new Date().toISOString(); 143 | const path = '/api/v5/dex/aggregator/swap'; 144 | const query = '?' + new URLSearchParams( 145 | Object.entries(params).map(([key, value]) => [key, value.toString()]) 146 | ).toString(); 147 | 148 | const response = await fetch(`${CONFIG.BASE_URL}${path}${query}`, { 149 | method: 'GET', 150 | headers: getHeaders(timestamp, 'GET', path, query) 151 | }); 152 | 153 | const data: SwapQuoteResponse = await response.json(); 154 | if (data.code !== '0' || !data.data?.[0]) { 155 | throw new Error(`API Error: ${data.msg || 'Unknown error'}`); 156 | } 157 | 158 | return data.data[0]; 159 | } 160 | 161 | function convertAmount(amount: string, decimals: number): string { 162 | try { 163 | if (!amount || isNaN(parseFloat(amount))) { 164 | throw new Error("Invalid amount"); 165 | } 166 | const value = parseFloat(amount); 167 | if (value <= 0) { 168 | throw new Error("Amount must be greater than 0"); 169 | } 170 | return (BigInt(Math.floor(value * Math.pow(10, decimals)))).toString(); 171 | } catch (err) { 172 | console.error("Amount conversion error:", err); 173 | throw new Error("Invalid amount format"); 174 | } 175 | } 176 | 177 | async function executeSwap(txData: string, privateKey: string) { 178 | let retryCount = 0; 179 | 180 | while (retryCount < CONFIG.MAX_RETRIES) { 181 | try { 182 | // Create transaction block 183 | const txBlock = Transaction.from(txData); 184 | 185 | // Set sender 186 | txBlock.setSender(normalizedWalletAddress); 187 | 188 | // Set gas parameters 189 | const referenceGasPrice = await client.getReferenceGasPrice(); 190 | txBlock.setGasPrice(BigInt(referenceGasPrice)); 191 | txBlock.setGasBudget(BigInt(CONFIG.DEFAULT_GAS_BUDGET)); 192 | 193 | // Build the transaction 194 | const builtTx = await txBlock.build({ client }); 195 | 196 | // Convert transaction bytes to base64 for signing 197 | const txBytes = Buffer.from(builtTx).toString('base64'); 198 | 199 | // Sign transaction using OKX SDK 200 | const signParams = { 201 | privateKey, 202 | data: { 203 | type: 'raw', 204 | data: txBytes 205 | } 206 | }; 207 | 208 | console.log("Signing transaction..."); 209 | const signedTx = await wallet.signTransaction(signParams); 210 | 211 | if (!signedTx || !signedTx.signature) { 212 | throw new Error("Failed to sign transaction"); 213 | } 214 | 215 | // Execute the signed transaction 216 | console.log("Executing transaction..."); 217 | const result = await client.executeTransactionBlock({ 218 | transactionBlock: builtTx, 219 | signature: [signedTx.signature], 220 | options: { 221 | showEffects: true, 222 | showEvents: true, 223 | } 224 | }); 225 | 226 | if (!result.digest) { 227 | throw new Error('Transaction failed: No digest received'); 228 | } 229 | 230 | // Wait for confirmation 231 | console.log("Waiting for confirmation..."); 232 | const confirmation = await client.waitForTransaction({ 233 | digest: result.digest, 234 | options: { 235 | showEffects: true, 236 | showEvents: true, 237 | } 238 | }); 239 | 240 | // Check transaction status 241 | const status = confirmation.effects?.status?.status; 242 | if (status !== 'success') { 243 | throw new Error(`Transaction failed with status: ${status}`); 244 | } 245 | 246 | return { 247 | txId: result.digest, 248 | confirmation, 249 | signature: signedTx.signature 250 | }; 251 | 252 | } catch (error) { 253 | console.error(`Attempt ${retryCount + 1} failed:`, error); 254 | retryCount++; 255 | 256 | if (retryCount === CONFIG.MAX_RETRIES) { 257 | throw error; 258 | } 259 | 260 | await new Promise(resolve => setTimeout(resolve, 2000 * retryCount)); 261 | } 262 | } 263 | 264 | throw new Error('Max retries exceeded'); 265 | } 266 | 267 | async function main() { 268 | try { 269 | const args = process.argv.slice(2); 270 | if (args.length < 3) { 271 | console.log("Usage: ts-node sui-swap.ts "); 272 | console.log("Example: ts-node sui-swap.ts 1.5 0x2::sui::SUI 0xdba...::usdc::USDC"); 273 | process.exit(1); 274 | } 275 | 276 | const [amount, fromTokenAddress, toTokenAddress] = args; 277 | 278 | if (!userPrivateKey) { 279 | throw new Error("Private key not found"); 280 | } 281 | 282 | // Get token information 283 | console.log("Getting token information..."); 284 | const tokenInfo = await getTokenInfo(fromTokenAddress, toTokenAddress); 285 | console.log(`From: ${tokenInfo.fromToken.symbol} (${tokenInfo.fromToken.decimals} decimals)`); 286 | console.log(`To: ${tokenInfo.toToken.symbol} (${tokenInfo.toToken.decimals} decimals)`); 287 | 288 | // Convert amount using fetched decimals 289 | const rawAmount = convertAmount(amount, tokenInfo.fromToken.decimals); 290 | console.log(`Amount in ${tokenInfo.fromToken.symbol} base units:`, rawAmount); 291 | 292 | // Get swap quote 293 | console.log("Requesting swap quote..."); 294 | const swapData = await getSwapQuote(rawAmount, fromTokenAddress, toTokenAddress); 295 | 296 | // Show estimated output and price impact 297 | const outputAmount = parseFloat(swapData.routerResult.toTokenAmount) / Math.pow(10, tokenInfo.toToken.decimals); 298 | console.log("\nSwap Quote:"); 299 | console.log(`Input: ${amount} ${tokenInfo.fromToken.symbol} ($${(parseFloat(amount) * parseFloat(tokenInfo.fromToken.price)).toFixed(2)})`); 300 | console.log(`Output: ${outputAmount.toFixed(tokenInfo.toToken.decimals)} ${tokenInfo.toToken.symbol} ($${(outputAmount * parseFloat(tokenInfo.toToken.price)).toFixed(2)})`); 301 | if (swapData.priceImpactPercentage) { 302 | console.log(`Price Impact: ${swapData.priceImpactPercentage}%`); 303 | } 304 | 305 | // Execute the swap 306 | console.log("\nExecuting swap transaction..."); 307 | const result = await executeSwap(swapData.tx.data, userPrivateKey); 308 | 309 | console.log("\nSwap completed successfully!"); 310 | console.log("Transaction ID:", result.txId); 311 | console.log("Explorer URL:", `https://suiscan.xyz/mainnet/tx/${result.txId}`); 312 | 313 | process.exit(0); 314 | } catch (error) { 315 | console.error("Error:", error instanceof Error ? error.message : "Unknown error"); 316 | process.exit(1); 317 | } 318 | } 319 | 320 | if (require.main === module) { 321 | main(); 322 | } 323 | 324 | export { 325 | getTokenInfo, 326 | convertAmount, 327 | executeSwap, 328 | getSwapQuote, 329 | normalizeSuiAddress, 330 | type TokenInfo, 331 | type SwapQuoteResponse 332 | }; -------------------------------------------------------------------------------- /lib/sui/swap/sui-tokens.ts: -------------------------------------------------------------------------------- 1 | import { getHeaders } from '../../shared'; 2 | 3 | async function main() { 4 | try { 5 | const params = { 6 | chainId: '784' // SUI Chain ID 7 | }; 8 | 9 | const timestamp = new Date().toISOString(); 10 | const requestPath = "/api/v5/dex/aggregator/all-tokens"; 11 | const queryString = "?" + new URLSearchParams(params).toString(); 12 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 13 | 14 | console.log('Getting SUI tokens...'); 15 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 16 | method: "GET", 17 | headers 18 | }); 19 | 20 | const data = await response.json(); 21 | console.log('Tokens response:', JSON.stringify(data, null, 2)); 22 | } catch (error) { 23 | console.error('Script failed:', error); 24 | process.exit(1); 25 | } 26 | } 27 | 28 | main(); 29 | -------------------------------------------------------------------------------- /lib/ton/swap/ton-chain.ts: -------------------------------------------------------------------------------- 1 | 2 | // scripts/get-supported-chains.ts 3 | import { getHeaders } from '../../shared'; 4 | 5 | async function main() { 6 | try { 7 | const chainId = '607'; // Ton Chain ID 8 | const timestamp = new Date().toISOString(); 9 | const requestPath = "/api/v5/dex/aggregator/supported/chain"; 10 | const queryString = chainId ? `?chainId=${chainId}` : ''; 11 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 12 | 13 | console.log('Getting supported chain info for Ton...'); 14 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 15 | method: "GET", 16 | headers 17 | }); 18 | 19 | const data = await response.json(); 20 | console.log('Supported chains response:', JSON.stringify(data, null, 2)); 21 | } catch (error) { 22 | console.error('Script failed:', error); 23 | process.exit(1); 24 | } 25 | } 26 | 27 | main(); 28 | -------------------------------------------------------------------------------- /lib/ton/swap/ton-liquidity.ts: -------------------------------------------------------------------------------- 1 | 2 | // scripts/get-liquidity.ts 3 | import { getHeaders } from '../../shared'; 4 | 5 | async function main() { 6 | try { 7 | const params = { 8 | chainId: '607' // Ton Chain ID 9 | }; 10 | 11 | const timestamp = new Date().toISOString(); 12 | const requestPath = "/api/v5/dex/aggregator/get-liquidity"; 13 | const queryString = "?" + new URLSearchParams(params).toString(); 14 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 15 | 16 | console.log('Getting Ton liquidity sources...'); 17 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 18 | method: "GET", 19 | headers 20 | }); 21 | 22 | const data = await response.json(); 23 | console.log('Liquidity sources response:', JSON.stringify(data, null, 2)); 24 | } catch (error) { 25 | console.error('Script failed:', error); 26 | process.exit(1); 27 | } 28 | } 29 | 30 | main(); 31 | -------------------------------------------------------------------------------- /lib/ton/swap/ton-quote.ts: -------------------------------------------------------------------------------- 1 | // scripts/ton-quote.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | chainId: '607', // Ton Chain ID 8 | amount: '10000000000', 9 | fromTokenAddress: 'EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c', // TON Native Token 10 | toTokenAddress: 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs', // USDC 11 | slippage: '0.1', 12 | }; 13 | 14 | const timestamp = new Date().toISOString(); 15 | const requestPath = "/api/v5/dex/aggregator/quote"; 16 | const queryString = "?" + new URLSearchParams(params).toString(); 17 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 18 | 19 | console.log('Getting Ton quote...'); 20 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 21 | method: "GET", 22 | headers 23 | }); 24 | 25 | const data = await response.json(); 26 | console.log('Quote response:', JSON.stringify(data, null, 2)); 27 | } catch (error) { 28 | console.error('Script failed:', error); 29 | process.exit(1); 30 | } 31 | } 32 | 33 | main(); -------------------------------------------------------------------------------- /lib/ton/swap/ton-swap-data.ts: -------------------------------------------------------------------------------- 1 | // scripts/ton-swap.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | chainId: '607', // Ton Chain ID 8 | amount: '10000000000', 9 | fromTokenAddress: 'EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c', // TON Native Token 10 | toTokenAddress: 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs', // USDC 11 | userWalletAddress: "UQA88qDUSmU9QpYYOwlwKZ1rmrSPEKVus0zLX56FJxD1cd6l", 12 | slippage: "0.5", 13 | autoSlippage: "true", 14 | maxAutoSlippageBps: "100" 15 | }; 16 | 17 | const timestamp = new Date().toISOString(); 18 | const requestPath = "/api/v5/dex/aggregator/swap"; 19 | const queryString = "?" + new URLSearchParams(params).toString(); 20 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 21 | 22 | console.log('Getting Ton swap data...'); 23 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 24 | method: "GET", 25 | headers 26 | }); 27 | 28 | const data = await response.json(); 29 | console.log('Quote response:', JSON.stringify(data, null, 2)); 30 | } catch (error) { 31 | console.error('Script failed:', error); 32 | process.exit(1); 33 | } 34 | } 35 | 36 | main(); -------------------------------------------------------------------------------- /lib/ton/swap/ton-swap.ts: -------------------------------------------------------------------------------- 1 | // scripts/ton-swap.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | chainId: '607', // Ton Chain ID 8 | amount: '10000000000', 9 | fromTokenAddress: 'EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c', // TON Native Token 10 | toTokenAddress: 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs', // USDC 11 | userWalletAddress: "UQA88qDUSmU9QpYYOwlwKZ1rmrSPEKVus0zLX56FJxD1cd6l", 12 | slippage: "0.5", 13 | autoSlippage: "true", 14 | maxAutoSlippageBps: "100" 15 | }; 16 | 17 | const timestamp = new Date().toISOString(); 18 | const requestPath = "/api/v5/dex/aggregator/swap"; 19 | const queryString = "?" + new URLSearchParams(params).toString(); 20 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 21 | 22 | console.log('Getting Ton quote...'); 23 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 24 | method: "GET", 25 | headers 26 | }); 27 | 28 | const data = await response.json(); 29 | console.log('Quote response:', JSON.stringify(data, null, 2)); 30 | } catch (error) { 31 | console.error('Script failed:', error); 32 | process.exit(1); 33 | } 34 | } 35 | 36 | main(); -------------------------------------------------------------------------------- /lib/ton/swap/ton-tokens.ts: -------------------------------------------------------------------------------- 1 | 2 | // scripts/get-tokens.ts 3 | import { getHeaders } from '../../shared'; 4 | 5 | async function main() { 6 | try { 7 | const params = { 8 | chainId: '607' // Ton Chain ID 9 | }; 10 | 11 | const timestamp = new Date().toISOString(); 12 | const requestPath = "/api/v5/dex/aggregator/all-tokens"; 13 | const queryString = "?" + new URLSearchParams(params).toString(); 14 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 15 | 16 | console.log('Getting Ton tokens...'); 17 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 18 | method: "GET", 19 | headers 20 | }); 21 | 22 | const data = await response.json(); 23 | console.log('Tokens response:', JSON.stringify(data, null, 2)); 24 | } catch (error) { 25 | console.error('Script failed:', error); 26 | process.exit(1); 27 | } 28 | } 29 | 30 | main(); -------------------------------------------------------------------------------- /lib/tron/swap/tron-chain.ts: -------------------------------------------------------------------------------- 1 | 2 | // scripts/get-supported-chains.ts 3 | import { getHeaders } from '../../shared'; 4 | 5 | async function main() { 6 | try { 7 | const chainId = '195'; // Tron Chain ID 8 | const timestamp = new Date().toISOString(); 9 | const requestPath = "/api/v5/dex/aggregator/supported/chain"; 10 | const queryString = chainId ? `?chainId=${chainId}` : ''; 11 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 12 | 13 | console.log('Getting supported chain info for Tron...'); 14 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 15 | method: "GET", 16 | headers 17 | }); 18 | 19 | const data = await response.json(); 20 | console.log('Supported chains response:', JSON.stringify(data, null, 2)); 21 | } catch (error) { 22 | console.error('Script failed:', error); 23 | process.exit(1); 24 | } 25 | } 26 | 27 | main(); 28 | -------------------------------------------------------------------------------- /lib/tron/swap/tron-liquidity.ts: -------------------------------------------------------------------------------- 1 | 2 | // scripts/get-liquidity.ts 3 | import { getHeaders } from '../../shared'; 4 | 5 | async function main() { 6 | try { 7 | const params = { 8 | chainId: '195' // Tron Chain ID 9 | }; 10 | 11 | const timestamp = new Date().toISOString(); 12 | const requestPath = "/api/v5/dex/aggregator/get-liquidity"; 13 | const queryString = "?" + new URLSearchParams(params).toString(); 14 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 15 | 16 | console.log('Getting Tron liquidity sources...'); 17 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 18 | method: "GET", 19 | headers 20 | }); 21 | 22 | const data = await response.json(); 23 | console.log('Liquidity sources response:', JSON.stringify(data, null, 2)); 24 | } catch (error) { 25 | console.error('Script failed:', error); 26 | process.exit(1); 27 | } 28 | } 29 | 30 | main(); 31 | -------------------------------------------------------------------------------- /lib/tron/swap/tron-quote.ts: -------------------------------------------------------------------------------- 1 | // scripts/tron-quote.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | chainId: '195', // Tron Chain ID 8 | amount: '10000000000', 9 | fromTokenAddress: 'T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb', // TRON 10 | toTokenAddress: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', // USDT 11 | slippage: '0.1', 12 | }; 13 | 14 | const timestamp = new Date().toISOString(); 15 | const requestPath = "/api/v5/dex/aggregator/quote"; 16 | const queryString = "?" + new URLSearchParams(params).toString(); 17 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 18 | 19 | console.log('Getting Tron quote...'); 20 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 21 | method: "GET", 22 | headers 23 | }); 24 | 25 | const data = await response.json(); 26 | console.log('Quote response:', JSON.stringify(data, null, 2)); 27 | } catch (error) { 28 | console.error('Script failed:', error); 29 | process.exit(1); 30 | } 31 | } 32 | 33 | main(); -------------------------------------------------------------------------------- /lib/tron/swap/tron-swap-data.ts: -------------------------------------------------------------------------------- 1 | // scripts/tron-swap.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | chainId: '195', // Tron Chain ID 8 | amount: '10000000000', 9 | fromTokenAddress: 'T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb', // TRON 10 | toTokenAddress: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', // USDT 11 | userWalletAddress: "TKKfuzgajEECM93gVwUEqmeNTsMTZ2JEuq", 12 | slippage: "0.5", 13 | autoSlippage: "true", 14 | maxAutoSlippageBps: "100" 15 | }; 16 | 17 | const timestamp = new Date().toISOString(); 18 | const requestPath = "/api/v5/dex/aggregator/swap"; 19 | const queryString = "?" + new URLSearchParams(params).toString(); 20 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 21 | 22 | console.log('Getting Tron swap data...'); 23 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 24 | method: "GET", 25 | headers 26 | }); 27 | 28 | const data = await response.json(); 29 | console.log('Quote response:', JSON.stringify(data, null, 2)); 30 | } catch (error) { 31 | console.error('Script failed:', error); 32 | process.exit(1); 33 | } 34 | } 35 | 36 | main(); -------------------------------------------------------------------------------- /lib/tron/swap/tron-swap.ts: -------------------------------------------------------------------------------- 1 | // scripts/tron-swap.ts 2 | import { getHeaders } from '../../shared'; 3 | 4 | async function main() { 5 | try { 6 | const params = { 7 | chainId: '195', // Tron Chain ID 8 | amount: '10000000000', 9 | fromTokenAddress: 'T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb', // TRON 10 | toTokenAddress: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', // USDT 11 | userWalletAddress: "TKKfuzgajEECM93gVwUEqmeNTsMTZ2JEuq", 12 | slippage: "0.5", 13 | autoSlippage: "true", 14 | maxAutoSlippageBps: "100" 15 | }; 16 | 17 | const timestamp = new Date().toISOString(); 18 | const requestPath = "/api/v5/dex/aggregator/swap"; 19 | const queryString = "?" + new URLSearchParams(params).toString(); 20 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 21 | 22 | console.log('Getting Tron quote...'); 23 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 24 | method: "GET", 25 | headers 26 | }); 27 | 28 | const data = await response.json(); 29 | console.log('Quote response:', JSON.stringify(data, null, 2)); 30 | } catch (error) { 31 | console.error('Script failed:', error); 32 | process.exit(1); 33 | } 34 | } 35 | 36 | main(); -------------------------------------------------------------------------------- /lib/tron/swap/tron-tokens.ts: -------------------------------------------------------------------------------- 1 | 2 | // scripts/get-tokens.ts 3 | import { getHeaders } from '../../shared'; 4 | 5 | async function main() { 6 | try { 7 | const params = { 8 | chainId: '195' // Tron Chain ID 9 | }; 10 | 11 | const timestamp = new Date().toISOString(); 12 | const requestPath = "/api/v5/dex/aggregator/all-tokens"; 13 | const queryString = "?" + new URLSearchParams(params).toString(); 14 | const headers = getHeaders(timestamp, "GET", requestPath, queryString); 15 | 16 | console.log('Getting Tron tokens...'); 17 | const response = await fetch(`https://www.okx.com${requestPath}${queryString}`, { 18 | method: "GET", 19 | headers 20 | }); 21 | 22 | const data = await response.json(); 23 | console.log('Tokens response:', JSON.stringify(data, null, 2)); 24 | } catch (error) { 25 | console.error('Script failed:', error); 26 | process.exit(1); 27 | } 28 | } 29 | 30 | main(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dex-api-library", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "quote:solana": "ts-node lib/solana/swap/solana-quote.ts", 6 | "swap-data:solana": "ts-node lib/solana/swap/solana-swap-data.ts", 7 | "swap:solana": "ts-node lib/solana/swap/solana-swap.ts", 8 | "chain:solana": "ts-node lib/solana/swap/solana-chain.ts", 9 | "tokens:solana": "ts-node lib/solana/swap/solana-tokens.ts", 10 | "liquidity:solana": "ts-node lib/solana/swap/solana-liquidity.ts", 11 | "bridge-tokens:solana": "ts-node lib/solana/cross-chain/solana-bridge-tokens.ts", 12 | "bridges:solana": "ts-node lib/solana/cross-chain/solana-bridges.ts", 13 | "cross-chain-quote:solana": "ts-node lib/solana/cross-chain/solana-cross-chain-quote.ts", 14 | "token-pairs:solana": "ts-node lib/solana/cross-chain/solana-token-pairs.ts", 15 | "all:solana": "npm run quote:solana && npm run chain:solana && npm run tokens:solana && npm run liquidity:solana && npm run bridge-tokens:solana && npm run bridges:solana && npm run cross-chain-quote:solana && npm run token-pairs:solana", 16 | "quote:evm": "ts-node lib/evm/swap/evm-quote.ts", 17 | "swap-data:evm": "ts-node lib/evm/swap/evm-swap-data.ts", 18 | "chain:evm": "ts-node lib/evm/swap/evm-chain.ts", 19 | "tokens:evm": "ts-node lib/evm/swap/evm-tokens.ts", 20 | "liquidity:evm": "ts-node lib/evm/swap/evm-liquidity.ts", 21 | "bridge-tokens:evm": "ts-node lib/evm/cross-chain/evm-bridge-tokens.ts", 22 | "bridges:evm": "ts-node lib/evm/cross-chain/evm-bridges.ts", 23 | "cross-chain-quote:evm": "ts-node lib/evm/cross-chain/evm-cross-chain-quote.ts", 24 | "all:evm": "npm run quote:evm && npm run chain:evm && npm run tokens:evm && npm run liquidity:evm && npm run bridge-tokens:evm && npm run bridges:evm && npm run cross-chain-quote:evm", 25 | "quote:sui": "ts-node lib/sui/swap/sui-quote.ts", 26 | "swap-data:sui": "ts-node lib/sui/swap/sui-swap-data.ts", 27 | "chain:sui": "ts-node lib/sui/swap/sui-chain.ts", 28 | "tokens:sui": "ts-node lib/sui/swap/sui-tokens.ts", 29 | "liquidity:sui": "ts-node lib/sui/swap/sui-liquidity.ts", 30 | "bridge-tokens:sui": "ts-node lib/sui/cross-chain/sui-bridge-tokens.ts", 31 | "bridges:sui": "ts-node lib/sui/cross-chain/sui-bridges.ts", 32 | "cross-chain-quote:sui": "ts-node lib/sui/cross-chain/sui-cross-chain-quote.ts", 33 | "all:sui": "npm run quote:sui && npm run chain:sui && npm run tokens:sui && npm run liquidity:sui && npm run bridge-tokens:sui && npm run bridges:sui && npm run cross-chain-quote:sui", 34 | "quote:ton": "ts-node lib/ton/swap/ton-quote.ts", 35 | "swap-data:ton": "ts-node lib/ton/swap/ton-swap-data.ts", 36 | "chain:ton": "ts-node lib/ton/swap/ton-chain.ts", 37 | "tokens:ton": "ts-node lib/ton/swap/ton-tokens.ts", 38 | "liquidity:ton": "ts-node lib/ton/swap/ton-liquidity.ts", 39 | "all:ton": "npm run quote:ton && npm run chain:ton && npm run tokens:ton && npm run liquidity:ton", 40 | "quote:tron": "ts-node lib/tron/swap/tron-quote.ts", 41 | "swap-data:tron": "ts-node lib/tron/swap/tron-swap-data.ts", 42 | "chain:tron": "ts-node lib/tron/swap/tron-chain.ts", 43 | "tokens:tron": "ts-node lib/tron/swap/tron-tokens.ts", 44 | "liquidity:tron": "ts-node lib/tron/swap/tron-liquidity.ts", 45 | "all:tron": "npm run quote:tron && npm run chain:tron && npm run tokens:tron && npm run liquidity:tron ", 46 | "get-all": "npm run all:solana && npm run all:evm && npm run all:ton && npm run all:tron" 47 | }, 48 | "dependencies": { 49 | "@mysten/bcs": "^1.2.0", 50 | "@mysten/sui": "^1.17.0", 51 | "@okxweb3/coin-sui": "^1.1.0", 52 | "@solana/web3.js": "^1.95.8", 53 | "@types/chalk": "^0.4.31", 54 | "bn.js": "^5.2.1", 55 | "bs58": "^6.0.0", 56 | "chalk": "^4.1.2", 57 | "commander": "^12.1.0", 58 | "crypto-js": "^4.2.0", 59 | "dotenv": "^16.4.7", 60 | "node-fetch": "^3.3.2", 61 | "ora": "^5.4.1" 62 | }, 63 | "devDependencies": { 64 | "@types/bn.js": "^5.1.6", 65 | "@types/crypto-js": "^4.2.1", 66 | "@types/node": "^20.10.5", 67 | "@types/node-fetch": "^2.6.9", 68 | "ts-node": "^10.9.2", 69 | "typescript": "^5.3.3" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "commonjs", 5 | "lib": ["ES2020", "DOM"], 6 | "outDir": "./dist", 7 | "rootDir": "./lib", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "declaration": true, 15 | "sourceMap": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "*": ["node_modules/*"] 19 | } 20 | }, 21 | "include": [ 22 | "lib/**/*" 23 | ], 24 | "exclude": [ 25 | "node_modules" 26 | ] 27 | } 28 | --------------------------------------------------------------------------------