diff --git a/package.json b/package.json index 648ce0f..2ff727a 100644 --- a/package.json +++ b/package.json @@ -44,20 +44,21 @@ "@pythnetwork/price-service-client": "^1.9.0", "@raydium-io/raydium-sdk-v2": "0.1.95-alpha", "@solana/spl-token": "^0.4.9", - "ai": "^4.0.22", - "@tensor-oss/tensorswap-sdk": "^4.5.0", "@solana/web3.js": "^1.98.0", + "@tensor-oss/tensorswap-sdk": "^4.5.0", "@tiplink/api": "^0.3.1", + "ai": "^4.0.22", "bn.js": "^5.2.1", "bs58": "^6.0.0", "chai": "^5.1.2", "decimal.js": "^10.4.3", "dotenv": "^16.4.7", + "flash-sdk": "^2.24.3", "form-data": "^4.0.1", - "zod": "^3.24.1", "langchain": "^0.3.8", "openai": "^4.77.0", - "typedoc": "^0.27.6" + "typedoc": "^0.27.6", + "zod": "^3.24.1" }, "devDependencies": { "@types/bn.js": "^5.1.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05add17..8965ad3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,6 +101,9 @@ importers: dotenv: specifier: ^16.4.7 version: 16.4.7 + flash-sdk: + specifier: ^2.24.3 + version: 2.24.3(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) form-data: specifier: ^4.0.1 version: 4.0.1 @@ -217,6 +220,10 @@ packages: resolution: {integrity: sha512-PxRl+wu5YyptWiR9F2MBHOLLibm87Z4IMUBPreX+DYBtPM+xggvcPi0KAN7+kIL4IrIhXI8ma5V0MCXxSN1pHg==} engines: {node: '>=11'} + '@coral-xyz/anchor@0.27.0': + resolution: {integrity: sha512-+P/vPdORawvg3A9Wj02iquxb4T0C5m4P6aZBVYysKl4Amk+r6aMPZkUhilBkD6E4Nuxnoajv3CFykUfkGE0n5g==} + engines: {node: '>=11'} + '@coral-xyz/anchor@0.29.0': resolution: {integrity: sha512-eny6QNG0WOwqV0zQ7cs/b1tIuzZGmP7U7EcH+ogt4Gdbl8HDmIYVMh/9aTmYZPaFWjtUaI8qSn73uYEXWfATdA==} engines: {node: '>=11'} @@ -227,6 +234,18 @@ packages: peerDependencies: '@solana/web3.js': ^1.68.0 + '@coral-xyz/borsh@0.27.0': + resolution: {integrity: sha512-tJKzhLukghTWPLy+n8K8iJKgBq1yLT/AxaNd10yJrX8mI56ao5+OFAKAqW/h0i79KCvb4BK0VGO5ECmmolFz9A==} + engines: {node: '>=10'} + peerDependencies: + '@solana/web3.js': ^1.68.0 + + '@coral-xyz/borsh@0.28.0': + resolution: {integrity: sha512-/u1VTzw7XooK7rqeD7JLUSwOyRSesPUk0U37BV9zK0axJc1q0nRbKFGFLYCQ16OtdOJTTwGfGp11Lx9B45bRCQ==} + engines: {node: '>=10'} + peerDependencies: + '@solana/web3.js': ^1.68.0 + '@coral-xyz/borsh@0.29.0': resolution: {integrity: sha512-s7VFVa3a0oqpkuRloWVPdCK7hMbAMY270geZOGfCnaqexrP5dTIpbEHL33req6IYPPJ0hYa71cdvJ1h6V55/oQ==} engines: {node: '>=10'} @@ -596,6 +615,11 @@ packages: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@pythnetwork/client@2.22.0': + resolution: {integrity: sha512-Cyv23YqewKUL1pcm99jfmdetUa2aaUXjyRF9jvSeFcY895FddRu7uSWftYiaevsnx7vn4WbJgQR6ExxH+aONow==} + peerDependencies: + '@solana/web3.js': ^1.30.2 + '@pythnetwork/price-service-client@1.9.0': resolution: {integrity: sha512-SLm3IFcfmy9iMqHeT4Ih6qMNZhJEefY14T9yTlpsH2D/FE5+BaGGnfcexUifVlfH6M7mwRC4hEFdNvZ6ebZjJg==} deprecated: This package is deprecated and is no longer maintained. Please use @pythnetwork/hermes-client instead. @@ -819,6 +843,9 @@ packages: '@solana/web3.js@1.95.3': resolution: {integrity: sha512-O6rPUN0w2fkNqx/Z3QJMB9L225Ex10PRDH8bTaIUPZXMPV0QP8ZpPvjQnXK+upUczlRgzHzd6SjKIha1p+I6og==} + '@solana/web3.js@1.95.8': + resolution: {integrity: sha512-sBHzNh7dHMrmNS5xPD1d0Xa2QffW/RXaxu/OysRXBfwTp+LYqGGmMtCYYwrHPrN5rjAmJCsQRNAwv4FM0t3B6g==} + '@solana/web3.js@1.98.0': resolution: {integrity: sha512-nz3Q5OeyGFpFCR+erX2f6JPt3sKhzhYcSycBCSPkWjzSVDh/Rr1FqTVMRe58FKO16/ivTUcuJjeS5MyBvpkbzA==} @@ -882,6 +909,9 @@ packages: '@types/node@18.19.69': resolution: {integrity: sha512-ECPdY1nlaiO/Y6GUnwgtAAhLNaQ53AyIVz+eILxpEo5OvuqE6yWkqWBIb5dU0DqhKQtMeny+FBD3PK6lm7L5xQ==} + '@types/node@20.17.11': + resolution: {integrity: sha512-Ept5glCK35R8yeyIeYlRIZtX6SLRyqMhOFTgj5SOkMpLTdw3SEHI9fHx60xaUZ+V1aJxQJODE+7/j5ocZydYTg==} + '@types/node@22.10.5': resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} @@ -1228,6 +1258,10 @@ packages: crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} @@ -1479,6 +1513,10 @@ packages: fastq@1.18.0: resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -1502,6 +1540,9 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + flash-sdk@2.24.3: + resolution: {integrity: sha512-3JdmHZksBgcRlCXVVFZEV64NGKxVHURHoHAMc3+Ev1BdN0Re2S44wxTaQmO6EIvwPYscVG0BPbp6GibpEuMdsw==} + flat-cache@3.2.0: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} @@ -1540,9 +1581,16 @@ packages: resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} engines: {node: '>= 12.20'} + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fs@0.0.1-security: + resolution: {integrity: sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==} + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -1753,6 +1801,9 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsbi@4.3.0: + resolution: {integrity: sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==} + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -2018,6 +2069,10 @@ packages: encoding: optional: true + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-gyp-build@4.8.4: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true @@ -2468,6 +2523,9 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} @@ -2751,6 +2809,28 @@ snapshots: - encoding - utf-8-validate + '@coral-xyz/anchor@0.27.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + dependencies: + '@coral-xyz/borsh': 0.27.0(@solana/web3.js@1.98.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.98.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + base64-js: 1.5.1 + bn.js: 5.2.1 + bs58: 4.0.1 + buffer-layout: 1.2.2 + camelcase: 6.3.0 + cross-fetch: 3.2.0 + crypto-hash: 1.3.0 + eventemitter3: 4.0.7 + js-sha256: 0.9.0 + pako: 2.1.0 + snake-case: 3.0.4 + superstruct: 0.15.5 + toml: 3.0.0 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + '@coral-xyz/anchor@0.29.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@coral-xyz/borsh': 0.29.0(@solana/web3.js@1.98.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) @@ -2778,6 +2858,18 @@ snapshots: bn.js: 5.2.1 buffer-layout: 1.2.2 + '@coral-xyz/borsh@0.27.0(@solana/web3.js@1.98.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + dependencies: + '@solana/web3.js': 1.98.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + bn.js: 5.2.1 + buffer-layout: 1.2.2 + + '@coral-xyz/borsh@0.28.0(@solana/web3.js@1.95.8(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + dependencies: + '@solana/web3.js': 1.95.8(bufferutil@4.0.9)(utf-8-validate@5.0.10) + bn.js: 5.2.1 + buffer-layout: 1.2.2 + '@coral-xyz/borsh@0.29.0(@solana/web3.js@1.98.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: '@solana/web3.js': 1.98.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -3332,6 +3424,17 @@ snapshots: '@pkgr/core@0.1.1': {} + '@pythnetwork/client@2.22.0(@solana/web3.js@1.95.8(bufferutil@4.0.9)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + dependencies: + '@coral-xyz/anchor': 0.29.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@coral-xyz/borsh': 0.28.0(@solana/web3.js@1.95.8(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@solana/web3.js': 1.95.8(bufferutil@4.0.9)(utf-8-validate@5.0.10) + buffer: 6.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + '@pythnetwork/price-service-client@1.9.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@pythnetwork/price-service-sdk': 1.8.0 @@ -3706,6 +3809,14 @@ snapshots: - fastestsmallesttextencoderdecoder - typescript + '@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.95.8(bufferutil@4.0.9)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': + dependencies: + '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/web3.js': 1.95.8(bufferutil@4.0.9)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + - typescript + '@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.98.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5)': dependencies: '@solana/codecs': 2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5) @@ -3735,6 +3846,20 @@ snapshots: - encoding - utf-8-validate + '@solana/spl-token@0.3.11(@solana/web3.js@1.95.8(bufferutil@4.0.9)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10)': + dependencies: + '@solana/buffer-layout': 4.0.1 + '@solana/buffer-layout-utils': 0.2.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@solana/spl-token-metadata': 0.1.6(@solana/web3.js@1.95.8(bufferutil@4.0.9)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/web3.js': 1.95.8(bufferutil@4.0.9)(utf-8-validate@5.0.10) + buffer: 6.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + '@solana/spl-token@0.3.11(@solana/web3.js@1.98.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@4.9.5)(utf-8-validate@5.0.10)': dependencies: '@solana/buffer-layout': 4.0.1 @@ -3834,6 +3959,28 @@ snapshots: - encoding - utf-8-validate + '@solana/web3.js@1.95.8(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + dependencies: + '@babel/runtime': 7.26.0 + '@noble/curves': 1.8.0 + '@noble/hashes': 1.7.0 + '@solana/buffer-layout': 4.0.1 + agentkeepalive: 4.6.0 + bigint-buffer: 1.1.5 + bn.js: 5.2.1 + borsh: 0.7.0 + bs58: 4.0.1 + buffer: 6.0.3 + fast-stable-stringify: 1.0.0 + jayson: 4.1.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) + node-fetch: 2.7.0 + rpc-websockets: 9.0.4 + superstruct: 2.0.2 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + '@solana/web3.js@1.98.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@babel/runtime': 7.26.0 @@ -3977,6 +4124,10 @@ snapshots: dependencies: undici-types: 5.26.5 + '@types/node@20.17.11': + dependencies: + undici-types: 6.19.8 + '@types/node@22.10.5': dependencies: undici-types: 6.20.0 @@ -4348,6 +4499,8 @@ snapshots: crypto-js@4.2.0: {} + data-uri-to-buffer@4.0.1: {} + dayjs@1.11.13: {} debug@4.4.0: @@ -4622,6 +4775,11 @@ snapshots: dependencies: reusify: 1.0.4 + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -4654,6 +4812,34 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + flash-sdk@2.24.3(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10): + dependencies: + '@coral-xyz/anchor': 0.27.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@pythnetwork/client': 2.22.0(@solana/web3.js@1.95.8(bufferutil@4.0.9)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@pythnetwork/price-service-client': 1.9.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@solana/spl-token': 0.3.11(@solana/web3.js@1.95.8(bufferutil@4.0.9)(utf-8-validate@5.0.10))(bufferutil@4.0.9)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.95.8(bufferutil@4.0.9)(utf-8-validate@5.0.10) + '@types/node': 20.17.11 + bignumber.js: 9.1.2 + bs58: 5.0.0 + dotenv: 16.4.7 + fs: 0.0.1-security + js-sha256: 0.9.0 + jsbi: 4.3.0 + node-fetch: 3.3.2 + rimraf: 5.0.10 + ts-node: 10.9.2(@types/node@20.17.11)(typescript@5.7.2) + tweetnacl: 1.0.3 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - bufferutil + - debug + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + flat-cache@3.2.0: dependencies: flatted: 3.3.2 @@ -4691,8 +4877,14 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 4.0.0-beta.3 + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + fs.realpath@1.0.0: {} + fs@0.0.1-security: {} + function-bind@1.1.2: {} get-intrinsic@1.2.7: @@ -4946,6 +5138,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsbi@4.3.0: {} + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -5174,6 +5368,12 @@ snapshots: dependencies: whatwg-url: 5.0.0 + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + node-gyp-build@4.8.4: {} number-to-bn@1.7.0: @@ -5549,6 +5749,24 @@ snapshots: ts-log@2.2.7: {} + ts-node@10.9.2(@types/node@20.17.11)(typescript@5.7.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.17.11 + acorn: 8.14.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.7.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + ts-node@10.9.2(@types/node@22.10.5)(typescript@5.7.2): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -5617,6 +5835,8 @@ snapshots: undici-types@5.26.5: {} + undici-types@6.19.8: {} + undici-types@6.20.0: {} unicode-trie@2.0.0: diff --git a/src/agent/index.ts b/src/agent/index.ts index ae0c367..69397ad 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -59,6 +59,10 @@ import { fetchTokenReportSummary, fetchTokenDetailedReport, OrderParams, + FlashTradeParams, + FlashCloseTradeParams, + flashOpenTrade, + flashCloseTrade, } from "../tools"; import { @@ -537,4 +541,22 @@ export class SolanaAgentKit { async fetchTokenDetailedReport(mint: string): Promise { return fetchTokenDetailedReport(mint); } + + /** + * Opens a new trading position on Flash.Trade + * @param params Flash trade parameters including market, side, collateral, leverage, and pool name + * @returns Transaction signature + */ + async flashOpenTrade(params: FlashTradeParams): Promise { + return flashOpenTrade(this, params); + } + + /** + * Closes an existing trading position on Flash.Trade + * @param params Flash trade close parameters + * @returns Transaction signature + */ + async flashCloseTrade(params: FlashCloseTradeParams): Promise { + return flashCloseTrade(this, params); + } } diff --git a/src/langchain/index.ts b/src/langchain/index.ts index d5d17e5..0d653d1 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -774,6 +774,131 @@ export class SolanaGetWalletAddressTool extends Tool { } } +export class SolanaFlashOpenTrade extends Tool { + name = "solana_flash_open_trade"; + description = `Opens a new leveraged trading position on Flash.Trade exchange. + + Inputs (input is a JSON string): + token: string, one of ["SOL", "BTC", "ETH"] (required) + side: string, either "long" or "short" (required) + collateralUsd: number, amount in USD for collateral eg 10 (required) + leverage: number, eg 5 for 5x leverage (required) + + Example: + { + "token": "SOL", + "side": "long", + "collateralUsd": 10, + "leverage": 5 + }`; + + constructor(private solanaKit: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const parsedInput = JSON.parse(input); + + // Validate input parameters + if (!parsedInput.token) { + throw new Error("Token is required"); + } + if (!["SOL", "BTC", "ETH"].includes(parsedInput.token)) { + throw new Error('Token must be one of ["SOL", "BTC", "ETH"]'); + } + if (!["long", "short"].includes(parsedInput.side)) { + throw new Error('Side must be either "long" or "short"'); + } + if (!parsedInput.collateralUsd || parsedInput.collateralUsd <= 0) { + throw new Error("Collateral USD amount must be positive"); + } + if (!parsedInput.leverage || parsedInput.leverage <= 0) { + throw new Error("Leverage must be positive"); + } + + const tx = await this.solanaKit.flashOpenTrade({ + token: parsedInput.token, + side: parsedInput.side, + collateralUsd: parsedInput.collateralUsd, + leverage: parsedInput.leverage, + }); + + return JSON.stringify({ + status: "success", + message: "Flash trade position opened successfully", + transaction: tx, + token: parsedInput.token, + side: parsedInput.side, + collateralUsd: parsedInput.collateralUsd, + leverage: parsedInput.leverage, + }); + } catch (error: any) { + return JSON.stringify({ + status: "error", + message: error.message, + code: error.code || "UNKNOWN_ERROR", + }); + } + } +} + +export class SolanaFlashCloseTrade extends Tool { + name = "solana_flash_close_trade"; + description = `Closes an existing leveraged trading position on Flash.Trade exchange. + + Inputs (input is a JSON string): + token: string, one of ["SOL", "BTC", "ETH"] (required) + side: string, either "long" or "short" (required) + + Example: + { + "token": "SOL", + "side": "long" + }`; + + constructor(private solanaKit: SolanaAgentKit) { + super(); + } + + protected async _call(input: string): Promise { + try { + const parsedInput = JSON.parse(input); + + // Validate input parameters + if (!parsedInput.token) { + throw new Error("Token is required"); + } + if (!["SOL", "BTC", "ETH"].includes(parsedInput.token)) { + throw new Error('Token must be one of ["SOL", "BTC", "ETH"]'); + } + if (!["long", "short"].includes(parsedInput.side)) { + throw new Error('Side must be either "long" or "short"'); + } + + const tx = await this.solanaKit.flashCloseTrade({ + token: parsedInput.token, + side: parsedInput.side, + }); + + return JSON.stringify({ + status: "success", + message: "Flash trade position closed successfully", + transaction: tx, + token: parsedInput.token, + side: parsedInput.side, + }); + } catch (error: any) { + return JSON.stringify({ + status: "error", + message: error.message, + code: error.code || "UNKNOWN_ERROR", + }); + } + } +} + + export class SolanaPumpfunTokenLaunchTool extends Tool { name = "solana_launch_pumpfun_token"; @@ -2175,5 +2300,8 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) { new SolanaFetchTokenDetailedReportTool(solanaKit), new SolanaPerpOpenTradeTool(solanaKit), new SolanaPerpCloseTradeTool(solanaKit), + new SolanaFlashOpenTrade(solanaKit), + new SolanaFlashCloseTrade(solanaKit), ]; } + diff --git a/src/tools/flash_close_trade.ts b/src/tools/flash_close_trade.ts new file mode 100644 index 0000000..2ad248f --- /dev/null +++ b/src/tools/flash_close_trade.ts @@ -0,0 +1,124 @@ +import { PublicKey, ComputeBudgetProgram } from "@solana/web3.js"; +import { + PerpetualsClient, + OraclePrice, + PoolConfig, + Privilege, + Side, +} from "flash-sdk"; +import { BN } from "@coral-xyz/anchor"; +import { SolanaAgentKit } from "../index"; +import { + CLOSE_POSITION_CU, + marketSdkInfo, + marketTokenMap, + getNftTradingAccountInfo, + fetchOraclePrice, + createPerpClient, +} from "../utils/flashUtils"; + +export interface FlashCloseTradeParams { + token: string; + side: "long" | "short"; +} + +/** + * Closes an existing position on Flash.Trade + * @param agent SolanaAgentKit instance + * @param params Trade parameters + * @returns Transaction signature + */ +export async function flashCloseTrade( + agent: SolanaAgentKit, + params: FlashCloseTradeParams, +): Promise { + try { + const { token, side } = params; + + // Get market ID from token and side using marketTokenMap + const tokenMarkets = marketTokenMap[token]; + if (!tokenMarkets) { + throw new Error(`Token ${token} not supported for trading`); + } + + const sideEntry = tokenMarkets[side]; + if (!sideEntry) { + throw new Error(`${side} side not available for ${token}`); + } + + const market = sideEntry.marketID; + + // Validate market data using marketSdkInfo + const marketData = marketSdkInfo[market]; + if (!marketData) { + throw new Error(`Invalid market configuration for ${token}/${side}`); + } + + // Get token information + const [targetSymbol, collateralSymbol] = marketData.tokenPair.split("/"); + + // Fetch oracle prices + const [targetPrice, collateralPrice] = await Promise.all([ + fetchOraclePrice(targetSymbol), + fetchOraclePrice(collateralSymbol), + ]); + + // Initialize pool configuration and perpClient + const poolConfig = PoolConfig.fromIdsByName(marketData.pool, "mainnet-beta"); + const perpClient = createPerpClient(agent.connection, agent.wallet); + + // Calculate price after slippage + const slippageBpsBN = new BN(100); // 1% slippage + const sideEnum = side === "long" ? Side.Long : Side.Short; + const priceWithSlippage = perpClient.getPriceAfterSlippage( + false, // isEntry = false for closing position + slippageBpsBN, + targetPrice.price, + sideEnum, + ); + + // Get NFT trading account info + const tradingAccounts = await getNftTradingAccountInfo( + agent.wallet_address, + perpClient, + poolConfig, + collateralSymbol, + ); + + if ( + !tradingAccounts.nftTradingAccountPk || + !tradingAccounts.nftReferralAccountPK || + !tradingAccounts.nftOwnerRebateTokenAccountPk + ) { + throw new Error("Required NFT trading accounts not found"); + } + + // Build and send transaction + const { instructions, additionalSigners } = await perpClient.closePosition( + targetSymbol, + collateralSymbol, + priceWithSlippage, + sideEnum, + poolConfig, + Privilege.Referral, + tradingAccounts.nftTradingAccountPk, + tradingAccounts.nftReferralAccountPK, + tradingAccounts.nftOwnerRebateTokenAccountPk, + ); + + const computeBudgetIx = ComputeBudgetProgram.setComputeUnitLimit({ + units: CLOSE_POSITION_CU, + }); + + return await perpClient.sendTransaction( + [computeBudgetIx, ...instructions], + { + additionalSigners: additionalSigners, + alts: perpClient.addressLookupTables, + prioritizationFee: 5000000, + }, + ); + } catch (error) { + throw new Error(`Flash trade close failed: ${error}`); + } +} diff --git a/src/tools/flash_open_trade.ts b/src/tools/flash_open_trade.ts new file mode 100644 index 0000000..b0a968f --- /dev/null +++ b/src/tools/flash_open_trade.ts @@ -0,0 +1,254 @@ +import { PublicKey, ComputeBudgetProgram } from "@solana/web3.js"; +import { + PerpetualsClient, + OraclePrice, + PoolConfig, + Privilege, + Side, + CustodyAccount, + Custody, +} from "flash-sdk"; +import { BN } from "@coral-xyz/anchor"; +import { SolanaAgentKit } from "../index"; +import { + ALL_TOKENS, + marketSdkInfo, + marketTokenMap, + getNftTradingAccountInfo, + OPEN_POSITION_CU, + fetchOraclePrice, + createPerpClient, +} from "../utils/flashUtils"; + +export interface FlashTradeParams { + token: string; + side: "long" | "short"; + collateralUsd: number; + leverage: number; +} + +/** + * Opens a new position on Flash.Trade + * @param agent SolanaAgentKit instance + * @param params Trade parameters + * @returns Transaction signature + */ +export async function flashOpenTrade( + agent: SolanaAgentKit, + params: FlashTradeParams, +): Promise { + try { + const { token, side, collateralUsd, leverage } = params; + + // Get market ID from token and side using marketTokenMap + const tokenMarkets = marketTokenMap[token]; + if (!tokenMarkets) { + throw new Error(`Token ${token} not supported for trading`); + } + + const sideEntry = tokenMarkets[side]; + if (!sideEntry) { + throw new Error(`${side} side not available for ${token}`); + } + + const market = sideEntry.marketID; + + // Validate market data using marketSdkInfo + const marketData = marketSdkInfo[market]; + if (!marketData) { + throw new Error(`Invalid market configuration for ${token}/${side}`); + } + + // Get token information + const [targetSymbol, collateralSymbol] = marketData.tokenPair.split("/"); + const targetToken = ALL_TOKENS.find((t) => t.symbol === targetSymbol); + const collateralToken = ALL_TOKENS.find( + (t) => t.symbol === collateralSymbol, + ); + + if (!targetToken || !collateralToken) { + throw new Error(`Token not found for pair ${marketData.tokenPair}`); + } + + // Fetch oracle prices + const [targetPrice, collateralPrice] = await Promise.all([ + fetchOraclePrice(targetSymbol), + fetchOraclePrice(collateralSymbol), + ]); + + // Initialize pool configuration and perpClient + const poolConfig = PoolConfig.fromIdsByName(marketData.pool, "mainnet-beta"); + const perpClient = createPerpClient(agent.connection, agent.wallet); + + // Calculate position parameters + const leverageBN = new BN(leverage); + const collateralTokenPrice = convertPriceToNumber(collateralPrice.price); + const collateralAmount = calculateCollateralAmount( + collateralUsd, + collateralTokenPrice, + collateralToken.decimals, + ); + + // Get custody accounts + const { targetCustody, collateralCustody } = await fetchCustodyAccounts( + perpClient, + poolConfig, + targetSymbol, + collateralSymbol, + ); + + // Calculate position size + const positionSize = calculatePositionSize( + perpClient, + collateralAmount, + leverageBN, + targetToken, + collateralToken, + side, + targetPrice.price, + collateralPrice.price, + targetCustody, + collateralCustody, + ); + + // Get NFT trading account info + const tradingAccounts = await getNftTradingAccountInfo( + agent.wallet_address, + perpClient, + poolConfig, + collateralSymbol, + ); + + if ( + !tradingAccounts.nftTradingAccountPk || + !tradingAccounts.nftReferralAccountPK + ) { + throw new Error("Required NFT trading accounts not found"); + } + + // Prepare transaction + const slippageBps = new BN(1000); + const priceWithSlippage = perpClient.getPriceAfterSlippage( + true, + slippageBps, + targetPrice.price, + side === "long" ? Side.Long : Side.Short, + ); + + // Build and send transaction + const { instructions, additionalSigners } = await perpClient.openPosition( + targetSymbol, + collateralSymbol, + priceWithSlippage, + collateralAmount, + positionSize, + side === "long" ? Side.Long : Side.Short, + poolConfig, + Privilege.Referral, + tradingAccounts.nftTradingAccountPk, + tradingAccounts.nftReferralAccountPK, + tradingAccounts.nftOwnerRebateTokenAccountPk!, + false, + ); + + const computeBudgetIx = ComputeBudgetProgram.setComputeUnitLimit({ + units: OPEN_POSITION_CU, + }); + + return await perpClient.sendTransaction( + [computeBudgetIx, ...instructions], + { + additionalSigners: additionalSigners, + alts: perpClient.addressLookupTables, + prioritizationFee: 5000000, + }, + ); + } catch (error) { + throw new Error(`Flash trade failed: ${error}`); + } +} + +// Helper functions +function convertPriceToNumber(oraclePrice: OraclePrice): number { + const price = parseInt(oraclePrice.price.toString("hex"), 16); + const exponent = parseInt(oraclePrice.exponent.toString("hex"), 16); + return price * Math.pow(10, exponent); +} + +function calculateCollateralAmount( + usdAmount: number, + tokenPrice: number, + decimals: number, +): BN { + return new BN((usdAmount / tokenPrice) * Math.pow(10, decimals)); +} + +async function fetchCustodyAccounts( + perpClient: PerpetualsClient, + poolConfig: PoolConfig, + targetSymbol: string, + collateralSymbol: string, +) { + const targetConfig = poolConfig.custodies.find( + (c) => c.symbol === targetSymbol, + ); + const collateralConfig = poolConfig.custodies.find( + (c) => c.symbol === collateralSymbol, + ); + + if (!targetConfig || !collateralConfig) { + throw new Error("Custody configuration not found"); + } + + const accounts = await perpClient.provider.connection.getMultipleAccountsInfo( + [targetConfig.custodyAccount, collateralConfig.custodyAccount], + ); + + if (!accounts[0] || !accounts[1]) { + throw new Error("Failed to fetch custody accounts"); + } + + return { + targetCustody: CustodyAccount.from( + targetConfig.custodyAccount, + perpClient.program.coder.accounts.decode( + "custody", + accounts[0].data, + ), + ), + collateralCustody: CustodyAccount.from( + collateralConfig.custodyAccount, + perpClient.program.coder.accounts.decode( + "custody", + accounts[1].data, + ), + ), + }; +} + +function calculatePositionSize( + perpClient: PerpetualsClient, + collateralAmount: BN, + leverage: BN, + targetToken: any, + collateralToken: any, + side: "long" | "short", + targetPrice: OraclePrice, + collateralPrice: OraclePrice, + targetCustody: CustodyAccount, + collateralCustody: CustodyAccount, +): BN { + return perpClient.getSizeAmountFromLeverageAndCollateral( + collateralAmount, + leverage.toString(), + targetToken, + collateralToken, + side === "long" ? Side.Long : Side.Short, + targetPrice, + targetPrice, + targetCustody, + collateralPrice, + collateralPrice, + collateralCustody, + ); +} diff --git a/src/tools/index.ts b/src/tools/index.ts index 62a11de..8dcf8a4 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -59,3 +59,6 @@ export * from "./create_tiplinks"; export * from "./tensor_trade"; export * from "./rugcheck"; + +export * from "./flash_open_trade"; +export * from "./flash_close_trade"; \ No newline at end of file diff --git a/src/utils/flashUtils.ts b/src/utils/flashUtils.ts new file mode 100644 index 0000000..0b1a2ef --- /dev/null +++ b/src/utils/flashUtils.ts @@ -0,0 +1,310 @@ +import { PriceServiceConnection } from "@pythnetwork/price-service-client"; +import { OraclePrice } from "flash-sdk"; +import { AnchorProvider, BN, Wallet } from "@coral-xyz/anchor"; +import { PoolConfig, Token, Referral, PerpetualsClient } from "flash-sdk"; +import { Cluster, PublicKey, Connection, Keypair } from "@solana/web3.js"; +import { getAssociatedTokenAddressSync } from "@solana/spl-token"; + +const POOL_NAMES = [ + "Crypto.1", + "Virtual.1", + "Governance.1", + "Community.1", + "Community.2", + "Community.3", +]; + +const DEFAULT_CLUSTER: Cluster = "mainnet-beta"; +export const POOL_CONFIGS = POOL_NAMES.map((f) => + PoolConfig.fromIdsByName(f, DEFAULT_CLUSTER), +); + +const DUPLICATE_TOKENS = POOL_CONFIGS.map((f) => f.tokens).flat(); +const tokenMap = new Map(); +for (const token of DUPLICATE_TOKENS) { + tokenMap.set(token.symbol, token); +} +export const ALL_TOKENS: Token[] = Array.from(tokenMap.values()); +export const ALL_CUSTODIES = POOL_CONFIGS.map((f) => f.custodies).flat(); +const PROGRAM_ID = POOL_CONFIGS[0].programId; + +// CU for trade instructions +export const OPEN_POSITION_CU = 150_000; +export const CLOSE_POSITION_CU = 180_000; + +const HERMES_URL = "https://hermes.pyth.network"; // Replace with the actual Hermes URL if different + +// Create a map of symbol to Pyth price ID +const PRICE_FEED_IDS = ALL_TOKENS.reduce( + (acc, token) => { + acc[token.symbol] = token.pythPriceId; + return acc; + }, + {} as { [key: string]: string }, +); + +const priceServiceConnection = new PriceServiceConnection(HERMES_URL, { + priceFeedRequestConfig: { + binary: true, + }, +}); + +export interface PythPriceEntry { + price: OraclePrice; + emaPrice: OraclePrice; + isStale: boolean; + status: PriceStatus; +} + +export enum PriceStatus { + Trading, + Unknown, + Halted, + Auction, +} + +export const fetchOraclePrice = async ( + symbol: string, +): Promise => { + const priceFeedId = PRICE_FEED_IDS[symbol]; + if (!priceFeedId) { + throw new Error(`Price feed ID not found for symbol: ${symbol}`); + } + + try { + const priceFeed = await priceServiceConnection.getLatestPriceFeeds([ + priceFeedId, + ]); + + if (!priceFeed || priceFeed.length === 0) { + throw new Error(`No price feed received for ${symbol}`); + } + + const price = priceFeed[0].getPriceUnchecked(); + const emaPrice = priceFeed[0].getEmaPriceUnchecked(); + + const priceOracle = new OraclePrice({ + price: new BN(price.price), + exponent: new BN(price.expo), + confidence: new BN(price.conf), + timestamp: new BN(price.publishTime), + }); + + const emaPriceOracle = new OraclePrice({ + price: new BN(emaPrice.price), + exponent: new BN(emaPrice.expo), + confidence: new BN(emaPrice.conf), + timestamp: new BN(emaPrice.publishTime), + }); + + const token = ALL_TOKENS.find((t) => t.pythPriceId === priceFeedId); + if (!token) { + throw new Error(`Token not found for price feed ID: ${priceFeedId}`); + } + + const status = !token.isVirtual ? PriceStatus.Trading : PriceStatus.Unknown; + + const pythPriceEntry: PythPriceEntry = { + price: priceOracle, + emaPrice: emaPriceOracle, + isStale: false, + status: status, + }; + + return pythPriceEntry; + } catch (error) { + console.error(`Error in fetchOraclePrice for ${symbol}:`, error); + throw error; + } +}; + +// If you need to get all price IDs for subscription or other purposes +export const getAllPriceIds = () => ALL_TOKENS.map((t) => t.pythPriceId); + +export const subscribeToPriceFeeds = ( + callback: (symbol: string, priceEntry: PythPriceEntry) => void, +) => { + const priceIds = getAllPriceIds(); + priceServiceConnection.subscribePriceFeedUpdates(priceIds, (priceFeed) => { + const token = ALL_TOKENS.find((f) => f.pythPriceId === `0x${priceFeed.id}`); + if (token) { + const priceOracle = new OraclePrice({ + price: new BN(priceFeed.getPriceUnchecked().price), + exponent: new BN(priceFeed.getPriceUnchecked().expo), + confidence: new BN(priceFeed.getPriceUnchecked().conf), + timestamp: new BN(priceFeed.getPriceUnchecked().publishTime), + }); + const emaPriceOracle = new OraclePrice({ + price: new BN(priceFeed.getEmaPriceUnchecked().price), + exponent: new BN(priceFeed.getEmaPriceUnchecked().expo), + confidence: new BN(priceFeed.getEmaPriceUnchecked().conf), + timestamp: new BN(priceFeed.getEmaPriceUnchecked().publishTime), + }); + + const status = !token.isVirtual + ? PriceStatus.Trading + : PriceStatus.Unknown; + const priceEntry: PythPriceEntry = { + price: priceOracle, + emaPrice: emaPriceOracle, + isStale: false, + status: status, + }; + callback(token.symbol, priceEntry); + } + }); +}; + +export interface MarketInfo { + [key: string]: { + tokenPair: string; + token: string; + side: string; + pool: string; + }; +} + +const marketSdkInfo: MarketInfo = {}; + +// Loop through POOL_CONFIGS to process each market +POOL_CONFIGS.forEach((poolConfig) => { + poolConfig.markets.forEach((market) => { + const targetToken = ALL_TOKENS.find( + (token) => token.mintKey.toString() === market.targetMint.toString(), + ); + + // Find collateral token by matching mintKey + const collateralToken = ALL_TOKENS.find( + (token) => token.mintKey.toString() === market.collateralMint.toString(), + ); + + if (targetToken?.symbol && collateralToken?.symbol) { + marketSdkInfo[market.marketAccount.toString()] = { + tokenPair: `${targetToken.symbol}/${collateralToken.symbol}`, + token: targetToken.symbol, + side: Object.keys(market.side)[0], + pool: poolConfig.poolName, + }; + } + }); +}); + +export { marketSdkInfo }; + +export interface MarketTokenSides { + [token: string]: { + long?: { marketID: string }; + short?: { marketID: string }; + }; +} + +const marketTokenMap: MarketTokenSides = {}; + +// Convert marketSdkInfo into marketTokenMap +Object.entries(marketSdkInfo).forEach(([marketID, info]) => { + if (!marketTokenMap[info.token]) { + marketTokenMap[info.token] = {}; + } + + marketTokenMap[info.token][info.side.toLowerCase() as 'long' | 'short'] = { + marketID + }; +}); + +export { marketTokenMap }; + +interface TradingAccountResult { + nftReferralAccountPK: PublicKey | null; + nftTradingAccountPk: PublicKey | null; + nftOwnerRebateTokenAccountPk: PublicKey | null; +} + +export async function getNftTradingAccountInfo( + userPublicKey: PublicKey, + perpClient: PerpetualsClient, + poolConfig: PoolConfig, + collateralCustodySymbol: string, +): Promise { + const getNFTReferralAccountPK = (publicKey: PublicKey) => { + return PublicKey.findProgramAddressSync( + [Buffer.from("referral"), publicKey.toBuffer()], + PROGRAM_ID, + )[0]; + }; + const nftReferralAccountPK = getNFTReferralAccountPK(userPublicKey); + const nftReferralAccountInfo = + await perpClient.provider.connection.getAccountInfo(nftReferralAccountPK); + + let nftTradingAccountPk: PublicKey | null = null; + let nftOwnerRebateTokenAccountPk: PublicKey | null = null; + + if (nftReferralAccountInfo) { + const nftReferralAccountData = perpClient.program.coder.accounts.decode( + "referral", + nftReferralAccountInfo.data, + ) as Referral; + + nftTradingAccountPk = nftReferralAccountData.refererTradingAccount; + + if (nftTradingAccountPk) { + const nftTradingAccountInfo = + await perpClient.provider.connection.getAccountInfo( + nftTradingAccountPk, + ); + if (nftTradingAccountInfo) { + const nftTradingAccount = perpClient.program.coder.accounts.decode( + "trading", + nftTradingAccountInfo.data, + ) as { owner: PublicKey }; + + nftOwnerRebateTokenAccountPk = getAssociatedTokenAddressSync( + poolConfig.getTokenFromSymbol(collateralCustodySymbol).mintKey, + nftTradingAccount.owner, + ); + // Check if the account exists + const accountExists = + await perpClient.provider.connection.getAccountInfo( + nftOwnerRebateTokenAccountPk, + ); + if (!accountExists) { + console.log( + "NFT owner rebate token account does not exist and may need to be created", + ); + } + } + } + } + + return { + nftReferralAccountPK, + nftTradingAccountPk, + nftOwnerRebateTokenAccountPk, + }; +} + +/** + * Creates a new PerpetualsClient instance with the given connection and wallet + * @param connection Solana connection + * @param wallet Solana wallet + * @returns PerpetualsClient instance + */ +export function createPerpClient(connection: Connection, wallet: Keypair): PerpetualsClient { + const provider = new AnchorProvider( + connection, + new Wallet(wallet), + { + commitment: 'confirmed', + preflightCommitment: 'confirmed', + skipPreflight: true + } + ); + + return new PerpetualsClient( + provider, + POOL_CONFIGS[0].programId, + POOL_CONFIGS[0].perpComposibilityProgramId, + POOL_CONFIGS[0].fbNftRewardProgramId, + POOL_CONFIGS[0].rewardDistributionProgram.programId, + {} + ); +} \ No newline at end of file