From c13ab12aeee04c6e8d1f32150425418921c1f03e Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Fri, 20 Dec 2024 06:12:30 +0000 Subject: [PATCH] add airdrop --- package.json | 2 + pnpm-lock.yaml | 208 ++++++++ src/agent/index.ts | 18 +- src/langchain/index.ts | 12 +- src/tools/airdrop_compressed_tokens/index.ts | 468 ++++++++++-------- src/tools/airdrop_compressed_tokens/types.ts | 69 --- src/tools/airdrop_compressed_tokens/worker.ts | 128 ----- 7 files changed, 483 insertions(+), 422 deletions(-) delete mode 100644 src/tools/airdrop_compressed_tokens/types.ts delete mode 100644 src/tools/airdrop_compressed_tokens/worker.ts diff --git a/package.json b/package.json index ccc197d..1421ad5 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ "@langchain/groq": "^0.1.2", "@langchain/langgraph": "^0.2.27", "@langchain/openai": "^0.3.13", + "@lightprotocol/compressed-token": "^0.17.1", + "@lightprotocol/stateless.js": "^0.17.1", "@metaplex-foundation/mpl-core": "^1.1.1", "@metaplex-foundation/mpl-token-metadata": "^3.3.0", "@metaplex-foundation/umi": "^0.9.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d459533..ed7c5f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,12 @@ importers: '@langchain/openai': specifier: ^0.3.13 version: 0.3.14(@langchain/core@0.3.23(openai@4.76.3(zod@3.24.1))) + '@lightprotocol/compressed-token': + specifier: ^0.17.1 + version: 0.17.1(@lightprotocol/stateless.js@0.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@lightprotocol/stateless.js': + specifier: ^0.17.1 + version: 0.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@metaplex-foundation/mpl-core': specifier: ^1.1.1 version: 1.1.1(@metaplex-foundation/umi@0.9.2)(@noble/hashes@1.6.1) @@ -180,9 +186,17 @@ packages: peerDependencies: '@lightprotocol/stateless.js': 0.13.1 + '@lightprotocol/compressed-token@0.17.1': + resolution: {integrity: sha512-493KCmZGw1BcHVRJaeRm8EEs+L7gX8dwY7JG13w2pfgOMtZXZ7Wxt261jFJxQJzRLTrUSlrbRJOmfW1+S1Y8SQ==} + peerDependencies: + '@lightprotocol/stateless.js': 0.17.1 + '@lightprotocol/stateless.js@0.13.1': resolution: {integrity: sha512-3dmsQJwDl/6oQWAvmai8DvYYi0LNi6yLST3WK6XQDSAX4hc8pMd0gjX7feSaX9aMPKrA3xvH6QsljGB5OKCXBw==} + '@lightprotocol/stateless.js@0.17.1': + resolution: {integrity: sha512-EjId1n33A6dBwpce33Wsa/fs/CDKtMtRrkxbApH0alXrnEXmbW6QhIViXOrKYXjZ4uJQM1xsBtsKe0vqJ4nbtQ==} + '@metaplex-foundation/mpl-core@1.1.1': resolution: {integrity: sha512-h1kLw+cGaV8SiykoHDb1/G01+VYqtJXAt0uGuO5+2Towsdtc6ET4M62iqUnh4EacTVMIW1yYHsKsG/LYWBCKaA==} peerDependencies: @@ -283,6 +297,10 @@ packages: resolution: {integrity: sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==} engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.5.0': + resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} + engines: {node: ^14.21.3 || >=16} + '@noble/hashes@1.6.0': resolution: {integrity: sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==} engines: {node: ^14.21.3 || >=16} @@ -320,6 +338,11 @@ packages: '@solana/codecs-core@2.0.0-preview.2': resolution: {integrity: sha512-gLhCJXieSCrAU7acUJjbXl+IbGnqovvxQLlimztPoGgfLQ1wFYu+XJswrEVQqknZYK1pgxpxH3rZ+OKFs0ndQg==} + '@solana/codecs-core@2.0.0-preview.4': + resolution: {integrity: sha512-A0VVuDDA5kNKZUinOqHxJQK32aKTucaVbvn31YenGzHX1gPqq+SOnFwgaEY6pq4XEopSmaK16w938ZQS8IvCnw==} + peerDependencies: + typescript: '>=5' + '@solana/codecs-core@2.0.0-rc.1': resolution: {integrity: sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ==} peerDependencies: @@ -328,6 +351,11 @@ packages: '@solana/codecs-data-structures@2.0.0-preview.2': resolution: {integrity: sha512-Xf5vIfromOZo94Q8HbR04TbgTwzigqrKII0GjYr21K7rb3nba4hUW2ir8kguY7HWFBcjHGlU5x3MevKBOLp3Zg==} + '@solana/codecs-data-structures@2.0.0-preview.4': + resolution: {integrity: sha512-nt2k2eTeyzlI/ccutPcG36M/J8NAYfxBPI9h/nQjgJ+M+IgOKi31JV8StDDlG/1XvY0zyqugV3I0r3KAbZRJpA==} + peerDependencies: + typescript: '>=5' + '@solana/codecs-data-structures@2.0.0-rc.1': resolution: {integrity: sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog==} peerDependencies: @@ -336,6 +364,11 @@ packages: '@solana/codecs-numbers@2.0.0-preview.2': resolution: {integrity: sha512-aLZnDTf43z4qOnpTcDsUVy1Ci9im1Md8thWipSWbE+WM9ojZAx528oAql+Cv8M8N+6ALKwgVRhPZkto6E59ARw==} + '@solana/codecs-numbers@2.0.0-preview.4': + resolution: {integrity: sha512-Q061rLtMadsO7uxpguT+Z7G4UHnjQ6moVIxAQxR58nLxDPCC7MB1Pk106/Z7NDhDLHTcd18uO6DZ7ajHZEn2XQ==} + peerDependencies: + typescript: '>=5' + '@solana/codecs-numbers@2.0.0-rc.1': resolution: {integrity: sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ==} peerDependencies: @@ -346,6 +379,12 @@ packages: peerDependencies: fastestsmallesttextencoderdecoder: ^1.0.22 + '@solana/codecs-strings@2.0.0-preview.4': + resolution: {integrity: sha512-YDbsQePRWm+xnrfS64losSGRg8Wb76cjK1K6qfR8LPmdwIC3787x9uW5/E4icl/k+9nwgbIRXZ65lpF+ucZUnw==} + peerDependencies: + fastestsmallesttextencoderdecoder: ^1.0.22 + typescript: '>=5' + '@solana/codecs-strings@2.0.0-rc.1': resolution: {integrity: sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g==} peerDependencies: @@ -355,6 +394,11 @@ packages: '@solana/codecs@2.0.0-preview.2': resolution: {integrity: sha512-4HHzCD5+pOSmSB71X6w9ptweV48Zj1Vqhe732+pcAQ2cMNnN0gMPMdDq7j3YwaZDZ7yrILVV/3+HTnfT77t2yA==} + '@solana/codecs@2.0.0-preview.4': + resolution: {integrity: sha512-gLMupqI4i+G4uPi2SGF/Tc1aXcviZF2ybC81x7Q/fARamNSgNOCUUoSCg9nWu1Gid6+UhA7LH80sWI8XjKaRog==} + peerDependencies: + typescript: '>=5' + '@solana/codecs@2.0.0-rc.1': resolution: {integrity: sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ==} peerDependencies: @@ -364,6 +408,12 @@ packages: resolution: {integrity: sha512-H2DZ1l3iYF5Rp5pPbJpmmtCauWeQXRJapkDg8epQ8BJ7cA2Ut/QEtC3CMmw/iMTcuS6uemFNLcWvlOfoQhvQuA==} hasBin: true + '@solana/errors@2.0.0-preview.4': + resolution: {integrity: sha512-kadtlbRv2LCWr8A9V22On15Us7Nn8BvqNaOB4hXsTB3O0fU40D1ru2l+cReqLcRPij4znqlRzW9Xi0m6J5DIhA==} + hasBin: true + peerDependencies: + typescript: '>=5' + '@solana/errors@2.0.0-rc.1': resolution: {integrity: sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ==} hasBin: true @@ -373,6 +423,11 @@ packages: '@solana/options@2.0.0-preview.2': resolution: {integrity: sha512-FAHqEeH0cVsUOTzjl5OfUBw2cyT8d5Oekx4xcn5hn+NyPAfQJgM3CEThzgRD6Q/4mM5pVUnND3oK/Mt1RzSE/w==} + '@solana/options@2.0.0-preview.4': + resolution: {integrity: sha512-tv2O/Frxql/wSe3jbzi5nVicIWIus/BftH+5ZR+r9r3FO0/htEllZS5Q9XdbmSboHu+St87584JXeDx3xm4jaA==} + peerDependencies: + typescript: '>=5' + '@solana/options@2.0.0-rc.1': resolution: {integrity: sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA==} peerDependencies: @@ -384,6 +439,12 @@ packages: peerDependencies: '@solana/web3.js': ^1.91.6 + '@solana/spl-token-group@0.0.5': + resolution: {integrity: sha512-CLJnWEcdoUBpQJfx9WEbX3h6nTdNiUzswfFdkABUik7HVwSNA98u5AYvBVK2H93d9PGMOHAak2lHW9xr+zAJGQ==} + engines: {node: '>=16'} + peerDependencies: + '@solana/web3.js': ^1.94.0 + '@solana/spl-token-group@0.0.7': resolution: {integrity: sha512-V1N/iX7Cr7H0uazWUT2uk27TMqlqedpXHRqqAbVO2gvmJyT0E0ummMEAVQeXZ05ZhQ/xF39DLSdBp90XebWEug==} engines: {node: '>=16'} @@ -402,6 +463,12 @@ packages: peerDependencies: '@solana/web3.js': ^1.91.6 + '@solana/spl-token@0.4.8': + resolution: {integrity: sha512-RO0JD9vPRi4LsAbMUdNbDJ5/cv2z11MGhtAvFeRzT4+hAGE/FUzRi0tkkWtuCfSIU3twC6CtmAihRp/+XXjWsA==} + engines: {node: '>=16'} + peerDependencies: + '@solana/web3.js': ^1.94.0 + '@solana/spl-token@0.4.9': resolution: {integrity: sha512-g3wbj4F4gq82YQlwqhPB0gHFXfgsC6UmyGMxtSLf/BozT/oKd59465DbnlUK8L8EcimKMavxsVAMoLcEdeCicg==} engines: {node: '>=16'} @@ -412,6 +479,9 @@ packages: resolution: {integrity: sha512-JBMGB0oR4lPttOZ5XiUGyvylwLQjt1CPJa6qQ5oM+MBCndfjz2TKKkw0eATlLLcYmq1jBVsNlJ2cD6ns2GR7lA==} engines: {node: '>=16'} + '@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==} @@ -1690,6 +1760,21 @@ snapshots: - typescript - utf-8-validate + '@lightprotocol/compressed-token@0.17.1(@lightprotocol/stateless.js@0.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10)': + dependencies: + '@coral-xyz/anchor': 0.29.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@lightprotocol/stateless.js': 0.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@solana/spl-token': 0.4.8(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) + '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + buffer: 6.0.3 + tweetnacl: 1.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + '@lightprotocol/stateless.js@0.13.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)': dependencies: '@coral-xyz/anchor': 0.29.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -1703,6 +1788,19 @@ snapshots: - encoding - utf-8-validate + '@lightprotocol/stateless.js@0.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + dependencies: + '@coral-xyz/anchor': 0.29.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) + '@noble/hashes': 1.5.0 + '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + buffer: 6.0.3 + superstruct: 2.0.2 + tweetnacl: 1.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + '@metaplex-foundation/mpl-core@1.1.1(@metaplex-foundation/umi@0.9.2)(@noble/hashes@1.6.1)': dependencies: '@metaplex-foundation/umi': 0.9.2 @@ -1817,6 +1915,8 @@ snapshots: dependencies: '@noble/hashes': 1.6.0 + '@noble/hashes@1.5.0': {} + '@noble/hashes@1.6.0': {} '@noble/hashes@1.6.1': {} @@ -1869,6 +1969,11 @@ snapshots: dependencies: '@solana/errors': 2.0.0-preview.2 + '@solana/codecs-core@2.0.0-preview.4(typescript@5.7.2)': + dependencies: + '@solana/errors': 2.0.0-preview.4(typescript@5.7.2) + typescript: 5.7.2 + '@solana/codecs-core@2.0.0-rc.1(typescript@5.7.2)': dependencies: '@solana/errors': 2.0.0-rc.1(typescript@5.7.2) @@ -1880,6 +1985,13 @@ snapshots: '@solana/codecs-numbers': 2.0.0-preview.2 '@solana/errors': 2.0.0-preview.2 + '@solana/codecs-data-structures@2.0.0-preview.4(typescript@5.7.2)': + dependencies: + '@solana/codecs-core': 2.0.0-preview.4(typescript@5.7.2) + '@solana/codecs-numbers': 2.0.0-preview.4(typescript@5.7.2) + '@solana/errors': 2.0.0-preview.4(typescript@5.7.2) + typescript: 5.7.2 + '@solana/codecs-data-structures@2.0.0-rc.1(typescript@5.7.2)': dependencies: '@solana/codecs-core': 2.0.0-rc.1(typescript@5.7.2) @@ -1892,6 +2004,12 @@ snapshots: '@solana/codecs-core': 2.0.0-preview.2 '@solana/errors': 2.0.0-preview.2 + '@solana/codecs-numbers@2.0.0-preview.4(typescript@5.7.2)': + dependencies: + '@solana/codecs-core': 2.0.0-preview.4(typescript@5.7.2) + '@solana/errors': 2.0.0-preview.4(typescript@5.7.2) + typescript: 5.7.2 + '@solana/codecs-numbers@2.0.0-rc.1(typescript@5.7.2)': dependencies: '@solana/codecs-core': 2.0.0-rc.1(typescript@5.7.2) @@ -1905,6 +2023,14 @@ snapshots: '@solana/errors': 2.0.0-preview.2 fastestsmallesttextencoderdecoder: 1.0.22 + '@solana/codecs-strings@2.0.0-preview.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': + dependencies: + '@solana/codecs-core': 2.0.0-preview.4(typescript@5.7.2) + '@solana/codecs-numbers': 2.0.0-preview.4(typescript@5.7.2) + '@solana/errors': 2.0.0-preview.4(typescript@5.7.2) + fastestsmallesttextencoderdecoder: 1.0.22 + typescript: 5.7.2 + '@solana/codecs-strings@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': dependencies: '@solana/codecs-core': 2.0.0-rc.1(typescript@5.7.2) @@ -1923,6 +2049,17 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder + '@solana/codecs@2.0.0-preview.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': + dependencies: + '@solana/codecs-core': 2.0.0-preview.4(typescript@5.7.2) + '@solana/codecs-data-structures': 2.0.0-preview.4(typescript@5.7.2) + '@solana/codecs-numbers': 2.0.0-preview.4(typescript@5.7.2) + '@solana/codecs-strings': 2.0.0-preview.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/options': 2.0.0-preview.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + typescript: 5.7.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + '@solana/codecs@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': dependencies: '@solana/codecs-core': 2.0.0-rc.1(typescript@5.7.2) @@ -1939,6 +2076,12 @@ snapshots: chalk: 5.3.0 commander: 12.1.0 + '@solana/errors@2.0.0-preview.4(typescript@5.7.2)': + dependencies: + chalk: 5.3.0 + commander: 12.1.0 + typescript: 5.7.2 + '@solana/errors@2.0.0-rc.1(typescript@5.7.2)': dependencies: chalk: 5.3.0 @@ -1950,6 +2093,17 @@ snapshots: '@solana/codecs-core': 2.0.0-preview.2 '@solana/codecs-numbers': 2.0.0-preview.2 + '@solana/options@2.0.0-preview.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': + dependencies: + '@solana/codecs-core': 2.0.0-preview.4(typescript@5.7.2) + '@solana/codecs-data-structures': 2.0.0-preview.4(typescript@5.7.2) + '@solana/codecs-numbers': 2.0.0-preview.4(typescript@5.7.2) + '@solana/codecs-strings': 2.0.0-preview.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/errors': 2.0.0-preview.4(typescript@5.7.2) + typescript: 5.7.2 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + '@solana/options@2.0.0-rc.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': dependencies: '@solana/codecs-core': 2.0.0-rc.1(typescript@5.7.2) @@ -1969,6 +2123,15 @@ snapshots: transitivePeerDependencies: - fastestsmallesttextencoderdecoder + '@solana/spl-token-group@0.0.5(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)': + dependencies: + '@solana/codecs': 2.0.0-preview.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/spl-type-length-value': 0.1.0 + '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + - typescript + '@solana/spl-token-group@0.0.7(@solana/web3.js@1.95.8(bufferutil@4.0.8)(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) @@ -1977,6 +2140,14 @@ snapshots: - fastestsmallesttextencoderdecoder - typescript + '@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.95.3(bufferutil@4.0.8)(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.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + - typescript + '@solana/spl-token-metadata@0.1.6(@solana/web3.js@1.95.8(bufferutil@4.0.8)(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) @@ -2000,6 +2171,21 @@ snapshots: - typescript - utf-8-validate + '@solana/spl-token@0.4.8(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(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.8)(utf-8-validate@5.0.10) + '@solana/spl-token-group': 0.0.5(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/spl-token-metadata': 0.1.6(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2) + '@solana/web3.js': 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) + buffer: 6.0.3 + transitivePeerDependencies: + - bufferutil + - encoding + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + '@solana/spl-token@0.4.9(@solana/web3.js@1.95.8(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10)': dependencies: '@solana/buffer-layout': 4.0.1 @@ -2019,6 +2205,28 @@ snapshots: dependencies: buffer: 6.0.3 + '@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10)': + dependencies: + '@babel/runtime': 7.26.0 + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.1 + '@solana/buffer-layout': 4.0.1 + agentkeepalive: 4.5.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.8)(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.95.8(bufferutil@4.0.8)(utf-8-validate@5.0.10)': dependencies: '@babel/runtime': 7.26.0 diff --git a/src/agent/index.ts b/src/agent/index.ts index efe4925..58e5188 100644 --- a/src/agent/index.ts +++ b/src/agent/index.ts @@ -17,7 +17,6 @@ import { getTokenDataByAddress, getTokenDataByTicker, stakeWithJup, - createCompressedAirdrop, sendCompressedAirdrop, } from "../tools"; import { CollectionOptions, PumpFunTokenOptions } from "../types"; @@ -142,17 +141,20 @@ export class SolanaAgentKit { return stakeWithJup(this, amount); } - async airdropCompressedTokens( + async sendCompressedAirdrop( mintAddress: string, amount: number, - recipients: string[] - ) { - await createCompressedAirdrop( + recipients: string[], + priorityFeeInLamports: number, + shouldLog: boolean + ): Promise { + return await sendCompressedAirdrop( this, new PublicKey(mintAddress), - BigInt(amount), - recipients.map((recipient) => new PublicKey(recipient)) + amount, + recipients.map((recipient) => new PublicKey(recipient)), + priorityFeeInLamports, + shouldLog ); - return await sendCompressedAirdrop(this); } } diff --git a/src/langchain/index.ts b/src/langchain/index.ts index c2c1d8b..9e46783 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -710,6 +710,7 @@ export class SolanaAirdropCompressedTokensTool extends Tool { - mintAddress: string, the mint address of the token, e.g., "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN" - amount: number, the amount of tokens to airdrop per recipient, e.g., 42 - recipients: string[], the recipient addresses, e.g., ["JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN", "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN"] + - priorityFeeInLamports: number, the priority fee in lamports, e.g., 10_000. Default is 30_000. `; constructor(private solanaKit: SolanaAgentKit) { @@ -719,18 +720,19 @@ export class SolanaAirdropCompressedTokensTool extends Tool { protected async _call(input: string): Promise { try { const parsedInput = JSON.parse(input); - if (parsedInput.recipients.length <= 100) { - throw new Error("Recipients array must contain at least 420 addresses"); - } - await this.solanaKit.airdropCompressedTokens( + + const txs = await this.solanaKit.sendCompressedAirdrop( parsedInput.mintAddress, parsedInput.amount, - parsedInput.recipients + parsedInput.recipients, + parsedInput.priorityFeeInLamports || 30_000, + false // no logging ); return JSON.stringify({ status: "success", message: `Airdropped ${parsedInput.amount} tokens to ${parsedInput.recipients.length} recipients.`, + transactionHashes: txs, }); } catch (error: any) { return JSON.stringify({ diff --git a/src/tools/airdrop_compressed_tokens/index.ts b/src/tools/airdrop_compressed_tokens/index.ts index 86a25ee..91d59a8 100644 --- a/src/tools/airdrop_compressed_tokens/index.ts +++ b/src/tools/airdrop_compressed_tokens/index.ts @@ -1,228 +1,272 @@ -import { PublicKey } from "@solana/web3.js"; -import type { DrizzleDb, WorkerMessage, WorkerData } from "./types"; +import { + AddressLookupTableAccount, + ComputeBudgetProgram, + Connection, + Keypair, + PublicKey, + TransactionInstruction, +} from "@solana/web3.js"; import { SolanaAgentKit } from "../../agent/index.js"; +import { + buildAndSignTx, + calculateComputeUnitPrice, + createRpc, + Rpc, + sendAndConfirmTx, + sleep, +} from "@lightprotocol/stateless.js"; +import { + CompressedTokenProgram, + createTokenPool, +} from "@lightprotocol/compressed-token"; +import { Account, getOrCreateAssociatedTokenAccount } from "@solana/spl-token"; -let db: DrizzleDb | null = null; -let dbInitPromise: Promise | null = null; - -async function configureForBrowser() { - try { - const [{ SQLocalDrizzle }, { drizzle }, { sql }, heliusCore] = - await Promise.all([ - // @ts-ignore - import("sqlocal/drizzle"), - import("drizzle-orm/sqlite-proxy"), - import("drizzle-orm"), - import("helius-airship-core"), - ]); - - const { databaseFile } = heliusCore; - - const { driver, batchDriver } = new SQLocalDrizzle({ - databasePath: databaseFile, - verbose: false, - }); - - const database = drizzle(driver, batchDriver); - await database.run(sql`PRAGMA journal_mode = WAL;`); - await database.run(sql`PRAGMA synchronous = normal;`); - - return database; - } catch (error) { - console.error("Browser database configuration failed:", error); - throw error; - } -} - -async function configureForNode() { - try { - const [{ drizzle }, { default: Database }, { databaseFile }] = - await Promise.all([ - import("drizzle-orm/better-sqlite3"), - import("better-sqlite3"), - import("helius-airship-core"), - ]); - - const sqlite = new Database(databaseFile); - sqlite.exec("PRAGMA journal_mode = WAL;"); - sqlite.exec("PRAGMA synchronous = normal;"); - - return drizzle(sqlite); - } catch (error) { - console.error("Node database configuration failed:", error); - throw error; - } -} - -async function configureDatabase(): Promise { - if (!dbInitPromise) { - dbInitPromise = (async () => { - if (!db) { - db = - typeof window !== "undefined" - ? await configureForBrowser() - : await configureForNode(); - } - return db; - })(); - } - - const database = await dbInitPromise; - if (!database) throw new Error("Database initialization failed"); - return database; -} -async function createWorker(): Promise< - Worker | import("worker_threads").Worker -> { - if (typeof window !== "undefined") { - const origin = new URL(window.location.href).origin; - if ( - !origin.startsWith("https://") && - !origin.startsWith("http://localhost") - ) { - throw new Error("Invalid origin protocol"); - } - - const workerCode = ` - self.importScripts('${origin}/airdrop-worker.js'.replace(/[<>'"]/g, '')); - `; - - const blobOptions = { - type: "application/javascript", - headers: { - "Content-Security-Policy": "default-src 'self'", - }, - }; - - const blob = new Blob([workerCode], blobOptions); - const workerUrl = URL.createObjectURL(blob); - const worker = new Worker(workerUrl); - URL.revokeObjectURL(workerUrl); - return worker; - } else { - // Node - const { Worker } = await import("worker_threads"); - const path = await import("path"); - - return new Worker(path.resolve(__dirname, "worker.js")); - } -} - +const MAX_AIRDROP_RECIPIENTS = 1000; +const MAX_CONCURRENT = 30; /** - * Create airdrop with zk compression - * @param agent Agent - * @param mintAddress Token mint public key (non token-2022) - * @param amount amount of tokens to airdrop per recipient - * @param recipients Recipient public keys - */ -export async function createCompressedAirdrop( - agent: SolanaAgentKit, - mintAddress: PublicKey, - amount: bigint, - recipients: PublicKey[] -): Promise { - try { - const database = await configureDatabase(); - const { create, init } = await import("helius-airship-core"); - - await init({ - db: database as any, - }); - - await create({ - db: database as any, - signer: agent.wallet.publicKey, - addresses: recipients, - amount, - mintAddress, - }); - } catch (error) { - console.error("Create operation failed:", error); - throw error; - } -} - -/** - * Send airdrop. must be called after `createCompressedAirdrop` - * @param agent Agent - * @param onProgress Callback for progress updates - * @param onError Callback for error handling + * Send airdrop with ZK Compressed Tokens. + * @param agent Agent + * @param mintAddress SPL Mint address + * @param amount Amount to send per recipient + * @param recipients Recipient wallet addresses (no ATAs) + * @param shouldLog Whether to log progress to stdout. Defaults to false. */ export async function sendCompressedAirdrop( agent: SolanaAgentKit, - onProgress?: (progress: number) => void, - onError?: (error: Error) => void -): Promise { - let worker: Worker | import("worker_threads").Worker | null = null; - const { databaseFile } = await import("helius-airship-core"); + mintAddress: PublicKey, + amount: number, + recipients: PublicKey[], + prioFeeInLamports: number, + shouldLog: boolean = false +): Promise { + if (recipients.length > MAX_AIRDROP_RECIPIENTS) { + throw new Error( + `Max airdrop can be ${MAX_AIRDROP_RECIPIENTS} recipients at a time. For more scale, use open source ZK Compression airdrop tools such as https://github.com/helius-labs/airship.` + ); + } + let sourceTokenAccount: Account; + try { + sourceTokenAccount = await getOrCreateAssociatedTokenAccount( + agent.connection, + agent.wallet, + mintAddress, + agent.wallet.publicKey + ); + } catch (error) { + throw new Error( + "Source token account not found and failed to create it. Please add funds to your wallet and try again." + ); + } - return new Promise(async (resolve, reject) => { - const cleanup = () => { - if (worker) { - try { - if ("terminate" in worker) { - worker.terminate(); - } - } catch (error) { - console.error("[Main] Worker cleanup error:", error); - } - } - }; + try { + await createTokenPool( + agent.connection as unknown as Rpc, + agent.wallet, + mintAddress + ); + } catch (error: any) { + if (error.message.includes("already in use")) { + // skip + } else { + throw error; + } + } - try { - worker = await createWorker(); + return await processAll( + agent, + amount, + mintAddress, + recipients, + prioFeeInLamports, + shouldLog + ); +} +async function processAll( + agent: SolanaAgentKit, + amount: number, + mint: PublicKey, + recipients: PublicKey[], + prioFeeInLamports: number, + shouldLog: boolean +): Promise { + const mintAddress = mint; + const payer = agent.wallet; - const message: WorkerData = { - type: "send", - data: { - secretKey: Array.from(agent.wallet.secretKey), - url: agent.connection.rpcEndpoint, - dbPath: databaseFile, - }, - }; + const sourceTokenAccount = await getOrCreateAssociatedTokenAccount( + agent.connection, + agent.wallet, + mintAddress, + agent.wallet.publicKey + ); - const handleMessage = (event: MessageEvent) => { - if (event.data === undefined) { - cleanup(); - reject(new Error()); - return; - } - const { type, data, error } = event.data; - switch (type) { - case "progress": - onProgress?.(data!); - break; - case "error": - cleanup(); - const errorObj = new Error(error); - onError?.(errorObj); - reject(errorObj); - break; - case "complete": - cleanup(); - resolve(); - break; - } - }; + const maxRecipientsPerInstruction = 5; + const maxIxs = 3; // empirically determined (as of 12/15/2024) + const lookupTableAddress = new PublicKey( + "9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ" + ); - if (typeof window !== "undefined") { - (worker as Worker).onmessage = handleMessage; - } else { - (worker as import("worker_threads").Worker).on( - "message", - handleMessage + const lookupTableAccount = ( + await agent.connection.getAddressLookupTable(lookupTableAddress) + ).value!; + + const batches: PublicKey[][] = []; + for ( + let i = 0; + i < recipients.length; + i += maxRecipientsPerInstruction * maxIxs + ) { + batches.push(recipients.slice(i, i + maxRecipientsPerInstruction * maxIxs)); + } + + const instructionSets = await Promise.all( + batches.map(async (recipientBatch) => { + const instructions: TransactionInstruction[] = [ + ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 }), + ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: calculateComputeUnitPrice(prioFeeInLamports, 500_000), + }), + ]; + + const compressIxPromises = []; + for ( + let i = 0; + i < recipientBatch.length; + i += maxRecipientsPerInstruction + ) { + const batch = recipientBatch.slice(i, i + maxRecipientsPerInstruction); + compressIxPromises.push( + CompressedTokenProgram.compress({ + payer: payer.publicKey, + owner: payer.publicKey, + source: sourceTokenAccount.address, + toAddress: batch, + amount: batch.map(() => amount), + mint: mintAddress, + }) ); } - worker.postMessage(message); - } catch (error) { - cleanup(); - console.error("[Main] Send operation failed:", { - error: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined, - }); - onError?.(error instanceof Error ? error : new Error(String(error))); - reject(error); + const compressIxs = await Promise.all(compressIxPromises); + return [...instructions, ...compressIxs]; + }) + ); + + const url = agent.connection.rpcEndpoint; + if (url.includes("devnet")) { + throw new Error("Devnet is not supported for airdrop. Please use mainnet."); + } + if (!url.includes("helius")) { + console.warn( + "Warning: Must use RPC with ZK Compression support. Double check with your RPC provider if in doubt." + ); + } + const rpc = createRpc(url, url, url); + + const results = []; + let confirmedCount = 0; + const totalBatches = instructionSets.length; + + const renderProgressBar = (current: number, total: number) => { + const percentage = Math.floor((current / total) * 100); + const filled = Math.floor((percentage / 100) * 20); + const empty = 20 - filled; + const bar = "█".repeat(filled) + "░".repeat(empty); + return `Airdropped to ${Math.min(current * 15, recipients.length)}/${ + recipients.length + } recipients [${bar}] ${percentage}%`; + }; + + const log = (message: string) => { + if (shouldLog && typeof process !== "undefined" && process.stdout) { + process.stdout.write(message); } - }); + }; + + for (let i = 0; i < instructionSets.length; i += MAX_CONCURRENT) { + const batchPromises = instructionSets + .slice(i, i + MAX_CONCURRENT) + .map((instructions, idx) => + sendTransactionWithRetry( + rpc, + instructions, + payer, + lookupTableAccount, + i + idx + ).then((signature) => { + confirmedCount++; + log("\r" + renderProgressBar(confirmedCount, totalBatches)); + return signature; + }) + ); + + const batchResults = await Promise.allSettled(batchPromises); + results.push(...batchResults); + } + + log("\n"); + + const failures = results + .filter((r) => r.status === "rejected") + .map((r, idx) => ({ + index: idx, + error: (r as PromiseRejectedResult).reason, + })); + + if (failures.length > 0) { + throw new Error( + `Failed to process ${failures.length} batches: ${failures + .map((f) => f.error) + .join(", ")}` + ); + } + + return results.map((r) => (r as PromiseFulfilledResult).value); +} + +async function sendTransactionWithRetry( + connection: Rpc, + instructions: TransactionInstruction[], + payer: Keypair, + lookupTableAccount: AddressLookupTableAccount, + batchIndex: number +): Promise { + const MAX_RETRIES = 3; + const INITIAL_BACKOFF = 500; // ms + + for (let attempt = 0; attempt < MAX_RETRIES; attempt++) { + try { + const { blockhash } = await connection.getLatestBlockhash(); + const tx = buildAndSignTx( + instructions, + payer, + blockhash, + [], + [lookupTableAccount] + ); + + const signature = await sendAndConfirmTx(connection, tx); + + return signature; + } catch (error: any) { + const isRetryable = + error.message?.includes("blockhash not found") || + error.message?.includes("timeout") || + error.message?.includes("rate limit") || + error.message?.includes("too many requests"); + + if (!isRetryable || attempt === MAX_RETRIES - 1) { + throw new Error( + `Batch ${batchIndex} failed after ${attempt + 1} attempts: ${ + error.message + }` + ); + } + + const backoff = + INITIAL_BACKOFF * Math.pow(2, attempt) * (0.5 + Math.random()); + await sleep(backoff); + } + } + + throw new Error("Unreachable"); } diff --git a/src/tools/airdrop_compressed_tokens/types.ts b/src/tools/airdrop_compressed_tokens/types.ts deleted file mode 100644 index d4353b8..0000000 --- a/src/tools/airdrop_compressed_tokens/types.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3"; -import type { SqliteRemoteDatabase } from "drizzle-orm/sqlite-proxy"; - -export interface Closeable { - close(): Promise | void; -} - -export type DrizzleDb = BetterSQLite3Database | SqliteRemoteDatabase; - -export interface WorkerMessage { - type: "progress" | "error" | "complete"; - data?: number; - error?: string; -} - -export interface WorkerData { - type: "send" | "poll"; - data: { - dbPath: string; - url: string; - secretKey?: number[]; - }; -} - -export interface WorkerError extends Error { - code?: string; - type: "worker_error"; -} - -export interface DatabaseError extends Error { - code?: string; - type: "database_error"; -} - -export function isWorkerMessage(message: any): message is WorkerMessage { - return ( - message && - typeof message === "object" && - "type" in message && - (message.type === "progress" || - message.type === "error" || - message.type === "complete") - ); -} - -export function isWorkerData(data: any): data is WorkerData { - return ( - data && - typeof data === "object" && - "type" in data && - (data.type === "send" || data.type === "poll") && - "data" in data && - typeof data.data === "object" && - typeof data.data.dbPath === "string" && - typeof data.data.url === "string" - ); -} - -export function isWorkerError(error: any): error is WorkerError { - return ( - error instanceof Error && "type" in error && error.type === "worker_error" - ); -} - -export function isDatabaseError(error: any): error is DatabaseError { - return ( - error instanceof Error && "type" in error && error.type === "database_error" - ); -} diff --git a/src/tools/airdrop_compressed_tokens/worker.ts b/src/tools/airdrop_compressed_tokens/worker.ts deleted file mode 100644 index f43123c..0000000 --- a/src/tools/airdrop_compressed_tokens/worker.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { send } from "helius-airship-core"; -import { Keypair } from "@solana/web3.js"; -import type { WorkerMessage, WorkerData, DrizzleDb, Closeable } from "./types"; - -let db: DrizzleDb | null = null; -let dbInitPromise: Promise | null = null; - -async function initializeDb(dbPath: string): Promise { - if (!dbInitPromise) { - dbInitPromise = (async () => { - if (!db) { - try { - if (typeof window !== "undefined") { - const [{ SQLocalDrizzle }, { drizzle }, { sql }] = - await Promise.all([ - // @ts-ignore - import("sqlocal/drizzle"), - import("drizzle-orm/sqlite-proxy"), - import("drizzle-orm"), - ]); - - const { driver, batchDriver } = new SQLocalDrizzle({ - databasePath: dbPath, - verbose: false, - }); - - db = drizzle(driver, batchDriver); - await db.run(sql`PRAGMA journal_mode = WAL;`); - await db.run(sql`PRAGMA synchronous = normal;`); - } else { - const [{ drizzle }, { default: Database }] = await Promise.all([ - import("drizzle-orm/better-sqlite3"), - import("better-sqlite3"), - ]); - - const sqlite = new Database(dbPath); - sqlite.exec("PRAGMA journal_mode = WAL;"); - sqlite.exec("PRAGMA synchronous = normal;"); - db = drizzle(sqlite); - } - } catch (error) { - console.error("Worker database initialization failed:", error); - throw error; - } - } - return db; - })(); - } - - const database = await dbInitPromise; - if (!database) throw new Error("Worker database initialization failed"); - return database; -} - -function postMessage(message: WorkerMessage) { - if (typeof window !== "undefined") { - self.postMessage(message); - } else { - const { parentPort } = require("worker_threads"); - parentPort?.postMessage(message); - } -} - -async function handleMessage(data: WorkerData) { - let database: DrizzleDb | null = null; - - try { - database = await initializeDb(data.data.dbPath); - - switch (data.type) { - case "send": - if (!data.data.secretKey) { - throw new Error("Secret key is required for send operation"); - } - - await send({ - db: database, - keypair: Keypair.fromSecretKey(new Uint8Array(data.data.secretKey)), - url: data.data.url, - }); - - break; - } - - postMessage({ type: "complete" }); - } catch (error) { - console.error("[Worker] Operation failed:", { - error: error instanceof Error ? error.message : String(error), - stack: error instanceof Error ? error.stack : undefined, - type: data.type, - url: data.data.url, - }); - - postMessage({ - type: "error", - error: error instanceof Error ? error.message : String(error), - }); - } finally { - if (database && "close" in database) { - try { - await (database as Closeable).close(); - } catch (error) { - console.error("Error closing database connection:", error); - } - } - } -} - -if (typeof window !== "undefined") { - self.onmessage = (event: MessageEvent) => { - handleMessage(event.data).catch((error) => { - postMessage({ - type: "error", - error: error instanceof Error ? error.message : String(error), - }); - }); - }; -} else { - const { parentPort } = require("worker_threads"); - parentPort?.on("message", (data: WorkerData) => { - handleMessage(data).catch((error) => { - postMessage({ - type: "error", - error: error instanceof Error ? error.message : String(error), - }); - }); - }); -}