diff --git a/Cargo.lock b/Cargo.lock index 2a87face1..561e962ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1670,6 +1670,18 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fundraiser-pinocchio-program" +version = "0.1.0" +dependencies = [ + "pinocchio", + "pinocchio-associated-token-account", + "pinocchio-log", + "pinocchio-system", + "pinocchio-token", + "solana-address 2.6.1", +] + [[package]] name = "funty" version = "2.0.0" diff --git a/Cargo.toml b/Cargo.toml index f703dde94..364442673 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ members = [ "tokens/nft-operations/pinocchio/program", "tokens/pda-mint-authority/native/program", "tokens/pda-mint-authority/pinocchio/program", + "tokens/token-fundraiser/pinocchio/program", "tokens/transfer-tokens/native/program", "tokens/transfer-tokens/pinocchio/program", "tokens/token-2022/mint-close-authority/native/program", diff --git a/README.md b/README.md index afbd9cdca..5feaa6f25 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ Allow two users to swap digital assets with each other, each getting 100% of wha Create a fundraiser account specifying a target mint and amount, allowing contributors to deposit tokens until the goal is reached. -[anchor](./tokens/token-fundraiser/anchor) +[anchor](./tokens/token-fundraiser/anchor) [pinocchio](./tokens/token-fundraiser/pinocchio) ### Distributing tokens with Merkle-proof claims diff --git a/tokens/token-fundraiser/pinocchio/package.json b/tokens/token-fundraiser/pinocchio/package.json new file mode 100644 index 000000000..887eefe3a --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/package.json @@ -0,0 +1,23 @@ +{ + "scripts": { + "test": "mocha --import=tsx -t 1000000 ./tests/test.ts", + "build-and-test": "cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./tests/fixtures && pnpm test", + "build": "cargo build-sbf --manifest-path=./program/Cargo.toml --sbf-out-dir=./program/target/so", + "deploy": "solana program deploy ./program/target/so/program.so" + }, + "dependencies": { + "@solana-program/system": "^0.13.0", + "@solana-program/token": "^0.15.0", + "@solana/kit": "^7.0.0" + }, + "devDependencies": { + "@types/chai": "^5.2.3", + "@types/mocha": "^10.0.10", + "@types/node": "^26.1.0", + "chai": "^6.2.2", + "mocha": "^11.7.5", + "litesvm": "^1.3.0", + "typescript": "^5.9.3", + "tsx": "^4.19.2" + } +} diff --git a/tokens/token-fundraiser/pinocchio/pnpm-lock.yaml b/tokens/token-fundraiser/pinocchio/pnpm-lock.yaml new file mode 100644 index 000000000..51cfcba6b --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/pnpm-lock.yaml @@ -0,0 +1,2879 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@solana-program/system': + specifier: ^0.13.0 + version: 0.13.0(@solana/kit@7.1.1(typescript@5.9.3)) + '@solana-program/token': + specifier: ^0.15.0 + version: 0.15.0(@solana/kit@7.1.1(typescript@5.9.3)) + '@solana/kit': + specifier: ^7.0.0 + version: 7.1.1(typescript@5.9.3) + devDependencies: + '@types/chai': + specifier: ^5.2.3 + version: 5.2.3 + '@types/mocha': + specifier: ^10.0.10 + version: 10.0.10 + '@types/node': + specifier: ^26.1.0 + version: 26.3.0 + chai: + specifier: ^6.2.2 + version: 6.2.2 + litesvm: + specifier: ^1.3.0 + version: 1.4.1(typescript@5.9.3) + mocha: + specifier: ^11.7.5 + version: 11.8.0 + tsx: + specifier: ^4.19.2 + version: 4.23.12 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + +packages: + + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@solana-program/system@0.13.0': + resolution: {integrity: sha512-Id6QQxCG7ByImXPD5X/c3Ag2kySD+49aJ9waIkfxyUFyokhMQgxYQAx2rKuaygaBlaBQY6VVfBS46pqxZV+99A==} + peerDependencies: + '@solana/kit': ^7.0.0 + + '@solana-program/system@0.14.0': + resolution: {integrity: sha512-Pjs2RINZHYmk/pqWNBCuQmfNjZT9woPJ/w7QkzzCSwcqcokbARtxsnIdgj4lJVxvr1DuxG8JGIbKnq6z1QRY6Q==} + peerDependencies: + '@solana/kit': ^8.0.0 + + '@solana-program/token@0.15.0': + resolution: {integrity: sha512-SeLm08EYIfT453I6lo7wTGgfMbz1s7bJ1jSHFHBFebSJHD3xF46zRgMxq3YKRlr/RM8jN0cG8SjZVu+IDvMxEA==} + engines: {node: '>=24.0.0'} + peerDependencies: + '@solana/kit': ^7.0.0 + + '@solana-program/token@0.16.0': + resolution: {integrity: sha512-VuFIu5vXsw1zwqls4/sGB88234oucmpuig553A33UnrbYnPZ8yXmqLYULBD92/k1pCGnMK+NMLWvsKzlZQ/1Kw==} + engines: {node: '>=24.0.0'} + peerDependencies: + '@solana/kit': ^8.0.0 + + '@solana/accounts@7.1.1': + resolution: {integrity: sha512-7sy9VIFMdmu/7+2kVBRMU6mEvYx/DDjfam8DsBXbh+JszaTNZH3V8FutQYHRxrca3jxiumVfsy5USwKfVg8ElQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/accounts@8.0.0': + resolution: {integrity: sha512-+uqFCoI/P9oo0FGR3t3TJJxi0aoAdaEvFXzZCfNoLH5txZLTiw6/d/9QCSE/FXpbNZfk0VPGWcGN+QAXp/yrRA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/addresses@7.1.1': + resolution: {integrity: sha512-/Tk2aTOT7UEcaJrdEcB3+SK09v5cZ/92NWqHBzEobxusTPNoeOFxa3MwV74Q1MgPecWdLz7TPreEhAKpcvV/vw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/addresses@8.0.0': + resolution: {integrity: sha512-hPtZOGxeMVEVoXleEsYcOj1mhxtvUTeEivE3iqCT7HJGGnYZFaV0/MduoqsEMiaBM+2ggjpVh9V7QoXPJIhbhw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/assertions@7.1.1': + resolution: {integrity: sha512-tD07UKuw5i9Tw5xloVH+TUMrLzbHKixaB4DlGXumMEQU+JbYRPtFNqtMlrpVLuVM45nkbPfFp2Da0sXNRIqFHA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/assertions@8.0.0': + resolution: {integrity: sha512-qvS9Jicl3ZKc4QkRhz6ZT32E/hmPMR3CkGaQccG8JhMVbOSJBK6+18IO2T03K1kNGnP335FDDL6H/Yzw0IhQ7g==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/codecs-core@7.1.1': + resolution: {integrity: sha512-C1UOAQ7LH8RuCTfaij6hthTZeBlpp8GuD2g9Nag/xgiNKJGvAfVrcorb+kO/sufdYI8Lu+BiVvPeKOjQDQiKqg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/codecs-core@8.0.0': + resolution: {integrity: sha512-WU/W1IEssIae1rKfJHAwuxvbnhwFH9+WCjD+l4oK2TiKgRDIdO3ndF58ABl5hFqgISCthHxeiFqBtd8C8EhzWw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/codecs-data-structures@7.1.1': + resolution: {integrity: sha512-MO+wMAuaatAQ96N3HhTsd7Uno+79rJw88P+OiU2n+pHK/rZuyu1yB2j12veqK4jUNWPR0aOyCuZ4rB2idrDjpg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/codecs-data-structures@8.0.0': + resolution: {integrity: sha512-iCwbuANIuUbr7f69wx8E7tzT5tHcrJpKq8Ni06Y7H8aKhzJUlTeNQnpCbQr/e2bhvM6VI65yaXLUNEXfM7OS6Q==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/codecs-numbers@7.1.1': + resolution: {integrity: sha512-TWWrQq6Wp5Yf5bSc6RbxDDYCRUBBmRtRgjvUMHGp0P9K+4uFwxllRjSZFzBPHgXj3UTeZmFJdcoD271QhAgibg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/codecs-numbers@8.0.0': + resolution: {integrity: sha512-Islv8gZPQhVA1DvCk3KspTTw9r0z/qL6EWwN5/vvA9LYWe11RmNarJ+C0qxg1vPh2lQh8W6t/GcpZS/TJbHI0A==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/codecs-strings@7.1.1': + resolution: {integrity: sha512-6qWl+atG60D2LmP47GyGapQFhVsG1sVhVrEaM3lV09vwrGOMeOZARZ4ObaQ5m6uQmM04G+f8dY9d17nCMbGHGg==} + engines: {node: '>=20.18.0'} + peerDependencies: + fastestsmallesttextencoderdecoder: ^1.0.22 + typescript: '>=5.4.0' + peerDependenciesMeta: + fastestsmallesttextencoderdecoder: + optional: true + typescript: + optional: true + + '@solana/codecs-strings@8.0.0': + resolution: {integrity: sha512-4l8XCWIFt9DYX/zH22Jygmu+DRjKpFjFE1CNqnin24+LZ6WQu6Zk3cU+nftV3OrLns2Yi+o4cSlv/eVUsxA3qA==} + engines: {node: '>=20.18.0'} + peerDependencies: + fastestsmallesttextencoderdecoder: ^1.0.22 + typescript: '>=5.4.0' + peerDependenciesMeta: + fastestsmallesttextencoderdecoder: + optional: true + typescript: + optional: true + + '@solana/codecs@7.1.1': + resolution: {integrity: sha512-1ZErMXzbbz7+jusem58dMO1haVWNlvFYKftc2dPhFu9MqlmZRfPS06GiZDjlLnD/6PHHBy7OKFMASoYw+fBAKg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/codecs@8.0.0': + resolution: {integrity: sha512-Pk33P602YQSYHJka2IH4LhTg9vcVskYQb/XfOCqXyXDsgJS2x3au7kpXB7Q6M5yiw8VBw8ZNu/a0iGBX44v7tw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/errors@7.1.1': + resolution: {integrity: sha512-q35qck8rBnNvJlPU00mnHEQm7gWvghzYz8khqVoLhjgodTzGp46VqnIOyI1LXOAF6XDal58bMNDO980MQgq8yw==} + engines: {node: '>=20.18.0'} + hasBin: true + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/errors@8.0.0': + resolution: {integrity: sha512-S7vuD1EWVkp2OX9J7fQW7j7nGP3e+iuscDktheMDxpM/5d9GwfjjNiFDTOQB/SwEKuFd9PnZ5Wq5MmMBJxVSrw==} + engines: {node: '>=20.18.0'} + hasBin: true + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/fast-stable-stringify@7.1.1': + resolution: {integrity: sha512-eVxOeAXYVBIWOIRfGwvuGQ6gPetQiiiOqSCK0xjwQo/yjXNVlSbpF6wLtQRnd3lbYkWsKdeV99FYe9cVY/g7/Q==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/fast-stable-stringify@8.0.0': + resolution: {integrity: sha512-2cg1e6ytDOtID8AnoqdIC0vzmEo5J2N96RtON6+nuyYjaOKH+i8T5nLmUAmVpcozcdntp899MfGGrUcxHJ/7Sw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/fixed-points@7.1.1': + resolution: {integrity: sha512-qPj/V7kcFG/P0kuEa1U529+5L6mbkMwRIwvYBTDgBqPOt6wOdk9/c7Qn2VUQgSV0HgCXWxdHUdwaxBrYqO2ibg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/fixed-points@8.0.0': + resolution: {integrity: sha512-KA7ZA3xUIGNqDlagrTpzUtXsVSZn+vZC4odBDT3iXoRQysqV9t5g191HFH1OiTM68+7H8Rrte1Z0pTZSrQceTg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/functional@7.1.1': + resolution: {integrity: sha512-AnFohvUHGrqAu3kTxxC0rZyU4JddA08hOUZ1/DT9XogCZWetBpNJktX9uNuXQe+sN4FKTX2MfL//iBFBAsxtDA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/functional@8.0.0': + resolution: {integrity: sha512-w5GZeeLJQyIBc21PRWPzOs7M+7aKJdbklA5cqzSx7u5cNjJQ9VDs7/JkPTtgTzrhNIqa7jqZRNf97ecx7dQVvg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/instruction-plans@7.1.1': + resolution: {integrity: sha512-KQGpsjeDflMcbKCdcF4KZVHcotYMS94DveRs0ZiBOsxb45dgkYrFmQ6ExJ/2exUOVF/rlp73knAP5ACrPMIAzA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/instruction-plans@8.0.0': + resolution: {integrity: sha512-sD9W72Pn59UT1swV5wwR7uFpBdAUAc+o2Am3fEFZbm24HWQVhcV5rOmCZqzu/dlU10puFFra28AEVq20o5crAg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/instructions@7.1.1': + resolution: {integrity: sha512-VxMpJI++RTwaqSBLIP1GTjOPLtlYOqxOJ93ug6DlSdu9jku8M/or77DiXDUi62VmEh3bbsECR646vTJXUaPArw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/instructions@8.0.0': + resolution: {integrity: sha512-xcToa7n5IBnjaKfOBwHwj2UU+wmE7Rvh8jLFHRrSeAXzqZkABDviMrrKAPdcgD+/lhnofyuz1cYLH6cuc5hdnQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/keys@7.1.1': + resolution: {integrity: sha512-Rx+vzWAXUa/Ko7W6wG1HOG9B3nQFZ5dALz113WoAmBZf7iQvaLG7U4ECDM/ydR+Nbdy6wYww7zQKRIye+1d+Nw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/keys@8.0.0': + resolution: {integrity: sha512-nUg748MurpMmfVoWYwshpxrWsJZIcJ31mYf9xm1Z7NV0fySWyZYhgQbrEtONpMW6TPt2kpgBYDVKWs/wAyPokw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/kit@7.1.1': + resolution: {integrity: sha512-By3kv5d8fIMr2SPmvI41hBXUwn0XuDu2MC8B7anaLHtY8MENTKhjg8sSfybf/RTH3387ErxhxmrAijTiHqTy/g==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/kit@8.0.0': + resolution: {integrity: sha512-HAruLcW5OPVJu/T/NeMHFTPC8c6De1TjXsOGF8ZablZV14ytlyx2CwyIkOJeU2WTRq1WNM933RI/XuOBUCPrtA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/nominal-types@7.1.1': + resolution: {integrity: sha512-do4rmmOlSplVYN92zV6nROJxkiRn0c7juoH1lka2Pmq+cAC3QhQXKYAwWffTFYDJJDO9qCOh00uv2hhSZi6RDw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/nominal-types@8.0.0': + resolution: {integrity: sha512-aCvkLVfJLy7q0xSbzJXLMZkLEDtxbQZtJKJJAsdi7u40KwkK4ZLITLYc7wbIVKCpxLJcs1XspwJKLZGlcFGwSQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/offchain-messages@7.1.1': + resolution: {integrity: sha512-Py0/8HaIF0y+KSXHAWdQYDdZlGNoaqOZonTFuGKyDsrkgvod3wRc0cpZ5I/D/Rei4tVpvjNUfCXLpyoiJAZ7kg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/offchain-messages@8.0.0': + resolution: {integrity: sha512-wynBOsq3bwwqDG+KIUK9/5hNlo/tpRjmUf+VcFWUdD0LgSRr9NtF8QL2/Modj/m+Xj1iFkjfP+p8f94iGUF56g==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/options@7.1.1': + resolution: {integrity: sha512-iuPpfMbnRFjC4IoAIpKNA9UpizomxjW0E3F1LxUYQHXm1vCoF78kThp4qqgx2V3DmDFbhXGtXGSJPwmDdpLoBg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/options@8.0.0': + resolution: {integrity: sha512-OOrtPfPOuJY2+jNUUUgm6d9pQewhs+fTaR6R6nRAGJOl2IVgmTOMQ6+mpj2nfH36bLXRNuXmJR7SCG64mMi8Dg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/plugin-core@7.1.1': + resolution: {integrity: sha512-35h7+QssfnT9Rwq5spUvdGc41WtTVWzJUCbiwvnUHtVwm3z96xSPwj765UtX/enfrcRHJRTDvej2ZjsvO1aeuQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/plugin-core@8.0.0': + resolution: {integrity: sha512-8ciqi0mT7TocVt/ZjW0rzch8jgmaYbZeW6J2EFM2Oxe1vG2QloK+voikRxkYtsHMxGtSLfiTZDnklX24xBSa9Q==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/plugin-interfaces@7.1.1': + resolution: {integrity: sha512-2SHkGiftGmxg5xA5esxbwiY5wf+BOUsJG5rxD64/SHadUMaN3L0SDUSJfAXETJq5dCmVWxWGGPf2yVox4RIfdA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/plugin-interfaces@8.0.0': + resolution: {integrity: sha512-xi8co+87aAT51XBt0GJDntww172Rke+Bb+hjxPHMoNHd4C1WdMf9VQHwpGhh/km3DYYGx75JBxwpNwPZIbzNxQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/program-client-core@7.1.1': + resolution: {integrity: sha512-zoMq8Qg6psj6tFYpA35xKhSrF/LpdiVflV6nKohxZg3ocwtRqRhQfbY8/mjCA9EfH8vqvsn5il5CnxALRqmJJA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/program-client-core@8.0.0': + resolution: {integrity: sha512-LS2cgz6imea+PLCQ9J3wKbacR127YpO8aKo/gl1x60GoBasdT1km5Dw63gsv7HEkmEF0iQZGjA9TBwYZNAOXFw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/programs@7.1.1': + resolution: {integrity: sha512-nWpJKDBxj+cRpzH+lzdp+ztEm6MAothts56s7PIPFIKJe3zGRUpske0G3jIVHOGvtzCgQtmZUMCS1uBRiDRKDQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/programs@8.0.0': + resolution: {integrity: sha512-7n40XqVlTntQWYGwSiFyY4pQSOmCdLmI13oXWbHeSh/e1f4hJvPrVIpKe/h/liT630mSdjgxy1Je+2c7cV24Sw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/promises@7.1.1': + resolution: {integrity: sha512-d3lhCfiFwiVyTRR6zhy1lcjDIh+l0a5gp1WPe64Vfa2gP0myC8P5C1Tknfjrp4d97DF3uBPqKAb5vV8hahNcNA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/promises@8.0.0': + resolution: {integrity: sha512-KYjpZeKOkW4eAserk48349BlWjPorhjP2dkGjYMG0ZRWg67tpby708Yizu1g3S4CAwOAH40EFFYTW6NXaZqcpQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-api@7.1.1': + resolution: {integrity: sha512-ELGIqNbz8apeAxCLdD1PEPAnu6KtgHmWvUbH4VqqNg2jgWquyUOfTI5rblvdflx2Hcb7GK05w8/iiUaknOQKnw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-api@8.0.0': + resolution: {integrity: sha512-NT2fBS9wivcMuNAwFiiiVpQ1EIR29mTzq0j7fnq9T5D4EBrl/OmA7xZXN+Z6TzUPpNLAaLADzPceXHrqqeaRSw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-parsed-types@7.1.1': + resolution: {integrity: sha512-DiQSj2rNnhOKOTs6YDjQ2y4KuOkv8lh6moLFiQnY0+TvanJrGrQjxLSrHdCUQxymQcUxzrraZL9k6vxNzId4gw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-parsed-types@8.0.0': + resolution: {integrity: sha512-UEzab+gqgWZ84e1SwMoofInmu5Q0a8+DB7YYC+YnzfJJjbEL/qyFPdNpIvKX3DFyHJ+Hg5rjE9iVUVOP6Pqk2w==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-spec-types@7.1.1': + resolution: {integrity: sha512-FDDgXfAPfq28sQNnGhZr/+2kp5QTTcNZ5m/Q9y9Jrlux6brdPsbf9Sh1Q/lgz7i76arzNW/rzRY125RzqGWANg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-spec-types@8.0.0': + resolution: {integrity: sha512-o9/NGXsY2fBOMsJxt/exjDyV+zPsDDUl0szspDdLuh2w/YUb3U9D2ZvsCmxjtWotgbmIqFn6uKKf1/0pL/Wtdw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-spec@7.1.1': + resolution: {integrity: sha512-1FwhgL18qasRYlWffdBNIUoxQyGLzdh3YMQW3y33M61vk/q1ldEGJRypV9B/SrXmxwqDiUbQ+m+zgainCTp1ZA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-spec@8.0.0': + resolution: {integrity: sha512-8Mji0374hnloSXSMgLQSCj+E/c2A8qMCa9IOm1xbdHtDu91q/1fB7v88j9jT/lHmSBbShVJLyGCI9OKzxT1Tiw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-subscriptions-api@7.1.1': + resolution: {integrity: sha512-QTiQHmw2I/+fzeYsqII1guASiZskS7KZwPCI+6Arfk6Hxo9hBH8APuIu6uS8beSlVfnBCoOMybVDsnPF88fAoQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-subscriptions-api@8.0.0': + resolution: {integrity: sha512-ILmWLSRMs0cmes78FDln79OCVpRn5WwVg8aIgkH+phR6qsybg9ZA6JmVKPsNjRJzWhnvKw3cnY3YscRNk/uW2A==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-subscriptions-channel-websocket@7.1.1': + resolution: {integrity: sha512-DRakQwjJsvbLDMDafMC9hM82YotUwSnrzqUDsrm6+e4YpKvzjXCyWlQ9kDJtrYeAA15sNVRpfs4L9Y7SoiWgrw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-subscriptions-channel-websocket@8.0.0': + resolution: {integrity: sha512-5gOIiaA+UsDGTuVIszwUzwlGI0AKOxsStXg5abk9BdWsx8B0iEqRZ0FTXeZxHOs6t4k25yhLFkAeam7i18qKeg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-subscriptions-spec@7.1.1': + resolution: {integrity: sha512-yrqd+fV2Ae4hkzXX42aKRmTrz3FwvzKqO4h0ahs88dzSvXpy8l6Svu1k+uRgMlZ64g2P83jfQW+3Pj5fBoxmdw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-subscriptions-spec@8.0.0': + resolution: {integrity: sha512-RdKaVmuC1ATsCiiFzb8Fd5uJIouS32TvF+eVCRx/fmqHmbW7J7testCqi/gjITJGjr8JE/7qPJIoUU1dccWzLg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-subscriptions@7.1.1': + resolution: {integrity: sha512-5BaCgzPnf9WSEXBdASnM7iuPWtdrXKtG16qVTfm+9icLHDAkv2y62XF6XMSQQllH2k1RvLH1yOryZazAbaIyHA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-subscriptions@8.0.0': + resolution: {integrity: sha512-Ar2UgTHqx6W1xFJ8JmkZiY2xvcSzrW7FrXF2cg7AiBw89PIeMcpQOUHokaZtI8bN2CHp3f5VT1d4CcKFe0UIJg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-transformers@7.1.1': + resolution: {integrity: sha512-k8a/JZFso/nvPBgurOGJ/ZC6sXCtYE3lCvTj95i29pl45C4SgjdetJrAH/pWEEhQXcxaJs7OLBGmCh2enazlJw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-transformers@8.0.0': + resolution: {integrity: sha512-OA4wEcLte+WJirlYfZcsFcX6Sn5/deWaJ+G1x1VbfDYXwGEzo39JBk7A23VZ9T9MOmDfpgfds7lCjCix9lFZJQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-transport-http@7.1.1': + resolution: {integrity: sha512-Tljuh/sSkKMHrTqdYNaguUOkZ0T+Oj4+yHsUjKAzRsyffNLa67jx9gd5CtGfz3tpBpxDLVdNvdIaZgkhFwXk9g==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-transport-http@8.0.0': + resolution: {integrity: sha512-fGk4ym3zApquQnKI2vyO+wdEwfKEKsRQfUunLVgjAMLYSd2dxV4rQ2rFErxzJpJE0gMWE3vBH9JTPm9NXHgiuA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-types@7.1.1': + resolution: {integrity: sha512-yHlSUWgynaaqevuqipGhhcZnmFVNs+F6KIlbUZ0kQY6NMVtaSzx5AQrtQjbtAQzNRsRTDYkKuQg8nOruiDoseQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc-types@8.0.0': + resolution: {integrity: sha512-OvuMUHeeuK7cySRGs8pMd/qXvYyUiAAeTI6NRgQCR0moRbegQ9DqtQycnS1y9wkpywv8bEjpoJoi8QZfMpx/WQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc@7.1.1': + resolution: {integrity: sha512-yXEzCrLrWb1m5QqAJEGodJ9cqu5QiwfirSZTUWiMg1JtUl4uXOm1sqWQVLrwBz/+ctvtZTvG8+bQQAemVQiqPA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/rpc@8.0.0': + resolution: {integrity: sha512-wzo+xjw0oTVOf8jX1dfowegVv8kjpSE7AMoAcBWRiTOOzrLViYiVEPSHvJAow27TTgLoI8ujkfCl+emXFFrA9A==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/signers@7.1.1': + resolution: {integrity: sha512-UfWYnAglm21q5hS3Op1bU5mYiWfx0VesovZcIur0uYPc3EJlfBVikl8pf745x+bB77xG6lIzNbb+99t48SQbkg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/signers@8.0.0': + resolution: {integrity: sha512-yKqD54Hh8a+BBgjdcyRDYukKRG9d2Zj9aLAOuXapIcj1fWG1bWGIf3LEdVAjdoNJPTa2NEb787ouM6QOA+DAMA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/subscribable@7.1.1': + resolution: {integrity: sha512-l4Wzc2L+7WQPeC/kaCiia1aaUY/SJJdrNHnLibKchS2pb7YbF7J2OygsweHXliWCVpG0jZwcvIVpusgygbZQLw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/subscribable@8.0.0': + resolution: {integrity: sha512-/vu8j2WeAklP5h7xBgbrM4zG9/W+lju0HN50f0qMRqcmo1VhQejHFb42MVaHFb3iRUvsuvC2M627DGUFb8CMZw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/sysvars@7.1.1': + resolution: {integrity: sha512-EscE/HKbBRKedG6AtQ0t9LOX87bw409viferac5YSuvuDxtZvbMpCCJs89uEQPVpgvHtjMJhfsjEwMK97uG9pA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/sysvars@8.0.0': + resolution: {integrity: sha512-z8bZDr/RfvEeQr5t2UVtdG/qzS5yjBnTpcuVvpQcoWqkfzMISb5+dMTc1qiYtnwGz2176QZROgZ7+OQogfDbnw==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/transaction-confirmation@7.1.1': + resolution: {integrity: sha512-Kb3V5smmMbvdft/Z/Iu9MlsF+i44f3GLK8c8TVu0+yHo41rF7OeTnOvlQ+uw1NpCfOto4GgPMFDXklIiKdauuA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/transaction-confirmation@8.0.0': + resolution: {integrity: sha512-vz72wtWChtoqzCXmYpQwD0ZRtA1qM10FGg+hwtmUeoxvoq014MAJ5JEJ4PN5X3D5ST/qdmv3TmmFyfdyJ8kfCQ==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/transaction-introspection@7.1.1': + resolution: {integrity: sha512-o0f/wbwmgqRSqzr+RtCG8xZ5zTMVxnYv8LBmcDDCYvTeDPEF0EfHN8Oe28c7grQsXCIeaE+zXZ2p40lkTGZy4g==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/transaction-introspection@8.0.0': + resolution: {integrity: sha512-GkmR+SXN70Amh1f+MvzEGUiqr3/YL/FyEqeT6oCAMTR2AidXfyZIKNkoTB4+1jABeXFFMaYiCfPhKD6+BznnXA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/transaction-messages@7.1.1': + resolution: {integrity: sha512-UBgd/TU0c8blrYDC9sD9P+wgmOOEK4OdODxD8CxB7oumN4ZAENv20u0AdZuvu1n4OwWwc1ikhtKjwg18e4wTIg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/transaction-messages@8.0.0': + resolution: {integrity: sha512-/3QXUWIPLDicPLOt2aVczgTRJrWih59mHndLOwCD3i7/phGFnH66cjoyYy7+ootJJJwDlsgMoOmgdGJJHnqyFA==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/transactions@7.1.1': + resolution: {integrity: sha512-jop8y4+xiDJlocRLxqN++33Z5PDTe72HwmtgGrcIuGB4zq7U/lUX0uZM1JVcs9d3lJ3wBy2CcLTCyoYb78Swhg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@solana/transactions@8.0.0': + resolution: {integrity: sha512-8HQmyVN9qv0v1ylTqOR38T8834oQ+sPLSRpPueTd5yfwiv60CtUwndHg1M+y5n7Q4DP1jJZ0M5YReJ5BpnD2Zg==} + engines: {node: '>=20.18.0'} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/mocha@10.0.10': + resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} + + '@types/node@26.3.0': + resolution: {integrity: sha512-L3fgrnchriRC2ExBflb8j4uZZURHZfQsmQeyVzhjcHW4kkwVyo8/0h1B2MVzMTrYUJYu6G7EWs14hW/L9putqw==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.3.0: + resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@2.1.4: + resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} + + browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + + diff@7.0.0: + resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==} + engines: {node: '>=0.3.1'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + js-yaml@4.3.1: + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} + hasBin: true + + litesvm-darwin-arm64@1.4.1: + resolution: {integrity: sha512-6dofWLOxtknSL0W6N9W3f/kdnitsJ3xR0oE1W37CLcfd5kX4qCMVbaejZWJkLo4G2JzAk+hFZi965Z2OPyJACg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + litesvm-darwin-x64@1.4.1: + resolution: {integrity: sha512-Rjyg6SfnSKAO5/vMpnZpIHTcBWEUzjFj/GshXwzdyiWorrpC0poQjK3OJ9RJneJW2YDd+oUsGZCfwAplTXPGfg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + litesvm-linux-arm64-gnu@1.4.1: + resolution: {integrity: sha512-hyL/tcuFisAwNEwVzDGjtDRuLYDc+8PoX9QZU/M46vsLrdU2WCBU3g4WdV5z0z1nPl16TzcHVboqydyBy3ImFQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + + litesvm-linux-arm64-musl@1.4.1: + resolution: {integrity: sha512-Folc4nWAXwkWvwK5iqf3C74eNAPRERg8Yn2QGVW+6pMgCXtaQt77PuIB9m32mLo+W+LZxiU+t3et0ZiFEm4YgQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + + litesvm-linux-x64-gnu@1.4.1: + resolution: {integrity: sha512-sAKax22lAMS0DpWQcT6ICt8vs6phEjRJXT84LhhftnlNkcU03b0RJziFDr34LUTrkY5SMi/uqZEy0T86Xokg+w==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + + litesvm-linux-x64-musl@1.4.1: + resolution: {integrity: sha512-kKsOVhF7o8/sMiP6mgZCoIYr7zWEzwOBQdC0WrWp29JvOJpKOOgz3jioFdgWv/LnSYfkbJ+wZNWI8tMhAkmNOw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + + litesvm@1.4.1: + resolution: {integrity: sha512-SGMdN6c44m5Deo27nZX7GIn42e/OlfQ4jIv0JIVxR3F5vU8ZbbjsLRGUj3b/r5jCITyN22u14JnuP+mhDyNLlg==} + engines: {node: '>= 20'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mocha@11.8.0: + resolution: {integrity: sha512-VyCeUdGN3A9lmCTTgG4yuvY9ixxaDk+xt2R/7/+1AP6EqNG+G9OKkzBwhVtVYoNX8YsxNSgAl8mOv3IAeOpFbw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + tsx@4.23.12: + resolution: {integrity: sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@8.10.0: + resolution: {integrity: sha512-ibvdovq3nCFs8Msrd95BW+zUOq+aOVbT+wpHUoPWhztbHEoPc6oof51iFDB6Es8lTKvNvVW9jNSAB8dwrKTMGg==} + + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + workerpool@9.3.4: + resolution: {integrity: sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true + + '@esbuild/android-x64@0.28.2': + optional: true + + '@esbuild/darwin-arm64@0.28.2': + optional: true + + '@esbuild/darwin-x64@0.28.2': + optional: true + + '@esbuild/freebsd-arm64@0.28.2': + optional: true + + '@esbuild/freebsd-x64@0.28.2': + optional: true + + '@esbuild/linux-arm64@0.28.2': + optional: true + + '@esbuild/linux-arm@0.28.2': + optional: true + + '@esbuild/linux-ia32@0.28.2': + optional: true + + '@esbuild/linux-loong64@0.28.2': + optional: true + + '@esbuild/linux-mips64el@0.28.2': + optional: true + + '@esbuild/linux-ppc64@0.28.2': + optional: true + + '@esbuild/linux-riscv64@0.28.2': + optional: true + + '@esbuild/linux-s390x@0.28.2': + optional: true + + '@esbuild/linux-x64@0.28.2': + optional: true + + '@esbuild/netbsd-arm64@0.28.2': + optional: true + + '@esbuild/netbsd-x64@0.28.2': + optional: true + + '@esbuild/openbsd-arm64@0.28.2': + optional: true + + '@esbuild/openbsd-x64@0.28.2': + optional: true + + '@esbuild/openharmony-arm64@0.28.2': + optional: true + + '@esbuild/sunos-x64@0.28.2': + optional: true + + '@esbuild/win32-arm64@0.28.2': + optional: true + + '@esbuild/win32-ia32@0.28.2': + optional: true + + '@esbuild/win32-x64@0.28.2': + optional: true + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@solana-program/system@0.13.0(@solana/kit@7.1.1(typescript@5.9.3))': + dependencies: + '@solana/kit': 7.1.1(typescript@5.9.3) + + '@solana-program/system@0.14.0(@solana/kit@8.0.0(typescript@5.9.3))': + dependencies: + '@solana/kit': 8.0.0(typescript@5.9.3) + + '@solana-program/token@0.15.0(@solana/kit@7.1.1(typescript@5.9.3))': + dependencies: + '@solana-program/system': 0.13.0(@solana/kit@7.1.1(typescript@5.9.3)) + '@solana/kit': 7.1.1(typescript@5.9.3) + + '@solana-program/token@0.16.0(@solana/kit@8.0.0(typescript@5.9.3))': + dependencies: + '@solana-program/system': 0.14.0(@solana/kit@8.0.0(typescript@5.9.3)) + '@solana/kit': 8.0.0(typescript@5.9.3) + + '@solana/accounts@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.1.1(typescript@5.9.3) + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/codecs-strings': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/rpc-spec': 7.1.1(typescript@5.9.3) + '@solana/rpc-types': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/accounts@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 8.0.0(typescript@5.9.3) + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/codecs-strings': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/rpc-spec': 8.0.0(typescript@5.9.3) + '@solana/rpc-types': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/addresses@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/assertions': 7.1.1(typescript@5.9.3) + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/codecs-strings': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/nominal-types': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/addresses@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/assertions': 8.0.0(typescript@5.9.3) + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/codecs-strings': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/nominal-types': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/assertions@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/assertions@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs-core@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs-core@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs-data-structures@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/codecs-numbers': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs-data-structures@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs-numbers@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs-numbers@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs-strings@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/codecs-numbers': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs-strings@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/codecs@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/codecs-data-structures': 7.1.1(typescript@5.9.3) + '@solana/codecs-numbers': 7.1.1(typescript@5.9.3) + '@solana/codecs-strings': 7.1.1(typescript@5.9.3) + '@solana/fixed-points': 7.1.1(typescript@5.9.3) + '@solana/options': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/codecs@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/codecs-data-structures': 8.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 8.0.0(typescript@5.9.3) + '@solana/codecs-strings': 8.0.0(typescript@5.9.3) + '@solana/fixed-points': 8.0.0(typescript@5.9.3) + '@solana/options': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/errors@7.1.1(typescript@5.9.3)': + dependencies: + chalk: 5.6.2 + commander: 15.0.0 + optionalDependencies: + typescript: 5.9.3 + + '@solana/errors@8.0.0(typescript@5.9.3)': + dependencies: + chalk: 5.6.2 + commander: 15.0.0 + optionalDependencies: + typescript: 5.9.3 + + '@solana/fast-stable-stringify@7.1.1(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/fast-stable-stringify@8.0.0(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/fixed-points@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/fixed-points@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/functional@7.1.1(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/functional@8.0.0(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/instruction-plans@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/instructions': 7.1.1(typescript@5.9.3) + '@solana/keys': 7.1.1(typescript@5.9.3) + '@solana/promises': 7.1.1(typescript@5.9.3) + '@solana/transaction-messages': 7.1.1(typescript@5.9.3) + '@solana/transactions': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/instruction-plans@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/instructions': 8.0.0(typescript@5.9.3) + '@solana/keys': 8.0.0(typescript@5.9.3) + '@solana/promises': 8.0.0(typescript@5.9.3) + '@solana/transaction-messages': 8.0.0(typescript@5.9.3) + '@solana/transactions': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/instructions@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/instructions@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/keys@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/assertions': 7.1.1(typescript@5.9.3) + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/codecs-strings': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/nominal-types': 7.1.1(typescript@5.9.3) + '@solana/promises': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/keys@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/assertions': 8.0.0(typescript@5.9.3) + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/codecs-strings': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/nominal-types': 8.0.0(typescript@5.9.3) + '@solana/promises': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/kit@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/accounts': 7.1.1(typescript@5.9.3) + '@solana/addresses': 7.1.1(typescript@5.9.3) + '@solana/codecs': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/functional': 7.1.1(typescript@5.9.3) + '@solana/instruction-plans': 7.1.1(typescript@5.9.3) + '@solana/instructions': 7.1.1(typescript@5.9.3) + '@solana/keys': 7.1.1(typescript@5.9.3) + '@solana/offchain-messages': 7.1.1(typescript@5.9.3) + '@solana/plugin-core': 7.1.1(typescript@5.9.3) + '@solana/plugin-interfaces': 7.1.1(typescript@5.9.3) + '@solana/program-client-core': 7.1.1(typescript@5.9.3) + '@solana/programs': 7.1.1(typescript@5.9.3) + '@solana/promises': 7.1.1(typescript@5.9.3) + '@solana/rpc': 7.1.1(typescript@5.9.3) + '@solana/rpc-api': 7.1.1(typescript@5.9.3) + '@solana/rpc-parsed-types': 7.1.1(typescript@5.9.3) + '@solana/rpc-spec-types': 7.1.1(typescript@5.9.3) + '@solana/rpc-subscriptions': 7.1.1(typescript@5.9.3) + '@solana/rpc-types': 7.1.1(typescript@5.9.3) + '@solana/signers': 7.1.1(typescript@5.9.3) + '@solana/subscribable': 7.1.1(typescript@5.9.3) + '@solana/sysvars': 7.1.1(typescript@5.9.3) + '@solana/transaction-confirmation': 7.1.1(typescript@5.9.3) + '@solana/transaction-introspection': 7.1.1(typescript@5.9.3) + '@solana/transaction-messages': 7.1.1(typescript@5.9.3) + '@solana/transactions': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - fastestsmallesttextencoderdecoder + - utf-8-validate + + '@solana/kit@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/accounts': 8.0.0(typescript@5.9.3) + '@solana/addresses': 8.0.0(typescript@5.9.3) + '@solana/codecs': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/functional': 8.0.0(typescript@5.9.3) + '@solana/instruction-plans': 8.0.0(typescript@5.9.3) + '@solana/instructions': 8.0.0(typescript@5.9.3) + '@solana/keys': 8.0.0(typescript@5.9.3) + '@solana/offchain-messages': 8.0.0(typescript@5.9.3) + '@solana/plugin-core': 8.0.0(typescript@5.9.3) + '@solana/plugin-interfaces': 8.0.0(typescript@5.9.3) + '@solana/program-client-core': 8.0.0(typescript@5.9.3) + '@solana/programs': 8.0.0(typescript@5.9.3) + '@solana/promises': 8.0.0(typescript@5.9.3) + '@solana/rpc': 8.0.0(typescript@5.9.3) + '@solana/rpc-api': 8.0.0(typescript@5.9.3) + '@solana/rpc-parsed-types': 8.0.0(typescript@5.9.3) + '@solana/rpc-spec-types': 8.0.0(typescript@5.9.3) + '@solana/rpc-subscriptions': 8.0.0(typescript@5.9.3) + '@solana/rpc-types': 8.0.0(typescript@5.9.3) + '@solana/signers': 8.0.0(typescript@5.9.3) + '@solana/subscribable': 8.0.0(typescript@5.9.3) + '@solana/sysvars': 8.0.0(typescript@5.9.3) + '@solana/transaction-confirmation': 8.0.0(typescript@5.9.3) + '@solana/transaction-introspection': 8.0.0(typescript@5.9.3) + '@solana/transaction-messages': 8.0.0(typescript@5.9.3) + '@solana/transactions': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - fastestsmallesttextencoderdecoder + - utf-8-validate + + '@solana/nominal-types@7.1.1(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/nominal-types@8.0.0(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/offchain-messages@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.1.1(typescript@5.9.3) + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/codecs-data-structures': 7.1.1(typescript@5.9.3) + '@solana/codecs-numbers': 7.1.1(typescript@5.9.3) + '@solana/codecs-strings': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/keys': 7.1.1(typescript@5.9.3) + '@solana/nominal-types': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/offchain-messages@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 8.0.0(typescript@5.9.3) + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/codecs-data-structures': 8.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 8.0.0(typescript@5.9.3) + '@solana/codecs-strings': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/keys': 8.0.0(typescript@5.9.3) + '@solana/nominal-types': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/options@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/codecs-data-structures': 7.1.1(typescript@5.9.3) + '@solana/codecs-numbers': 7.1.1(typescript@5.9.3) + '@solana/codecs-strings': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/options@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/codecs-data-structures': 8.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 8.0.0(typescript@5.9.3) + '@solana/codecs-strings': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/plugin-core@7.1.1(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/plugin-core@8.0.0(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/plugin-interfaces@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/accounts': 7.1.1(typescript@5.9.3) + '@solana/addresses': 7.1.1(typescript@5.9.3) + '@solana/instruction-plans': 7.1.1(typescript@5.9.3) + '@solana/keys': 7.1.1(typescript@5.9.3) + '@solana/rpc-spec': 7.1.1(typescript@5.9.3) + '@solana/rpc-subscriptions-spec': 7.1.1(typescript@5.9.3) + '@solana/rpc-types': 7.1.1(typescript@5.9.3) + '@solana/signers': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/plugin-interfaces@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/accounts': 8.0.0(typescript@5.9.3) + '@solana/addresses': 8.0.0(typescript@5.9.3) + '@solana/instruction-plans': 8.0.0(typescript@5.9.3) + '@solana/keys': 8.0.0(typescript@5.9.3) + '@solana/rpc-spec': 8.0.0(typescript@5.9.3) + '@solana/rpc-subscriptions-spec': 8.0.0(typescript@5.9.3) + '@solana/rpc-types': 8.0.0(typescript@5.9.3) + '@solana/signers': 8.0.0(typescript@5.9.3) + '@solana/transactions': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/program-client-core@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/accounts': 7.1.1(typescript@5.9.3) + '@solana/addresses': 7.1.1(typescript@5.9.3) + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/instruction-plans': 7.1.1(typescript@5.9.3) + '@solana/instructions': 7.1.1(typescript@5.9.3) + '@solana/plugin-interfaces': 7.1.1(typescript@5.9.3) + '@solana/rpc-api': 7.1.1(typescript@5.9.3) + '@solana/signers': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/program-client-core@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/accounts': 8.0.0(typescript@5.9.3) + '@solana/addresses': 8.0.0(typescript@5.9.3) + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/instruction-plans': 8.0.0(typescript@5.9.3) + '@solana/instructions': 8.0.0(typescript@5.9.3) + '@solana/plugin-interfaces': 8.0.0(typescript@5.9.3) + '@solana/rpc-api': 8.0.0(typescript@5.9.3) + '@solana/signers': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/programs@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/programs@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/promises@7.1.1(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/promises@8.0.0(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-api@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.1.1(typescript@5.9.3) + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/codecs-strings': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/keys': 7.1.1(typescript@5.9.3) + '@solana/rpc-parsed-types': 7.1.1(typescript@5.9.3) + '@solana/rpc-spec': 7.1.1(typescript@5.9.3) + '@solana/rpc-transformers': 7.1.1(typescript@5.9.3) + '@solana/rpc-types': 7.1.1(typescript@5.9.3) + '@solana/transaction-messages': 7.1.1(typescript@5.9.3) + '@solana/transactions': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc-api@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 8.0.0(typescript@5.9.3) + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/codecs-strings': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/keys': 8.0.0(typescript@5.9.3) + '@solana/rpc-parsed-types': 8.0.0(typescript@5.9.3) + '@solana/rpc-spec': 8.0.0(typescript@5.9.3) + '@solana/rpc-transformers': 8.0.0(typescript@5.9.3) + '@solana/rpc-types': 8.0.0(typescript@5.9.3) + '@solana/transaction-messages': 8.0.0(typescript@5.9.3) + '@solana/transactions': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc-parsed-types@7.1.1(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-parsed-types@8.0.0(typescript@5.9.3)': + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-spec-types@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-spec-types@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-spec@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/rpc-spec-types': 7.1.1(typescript@5.9.3) + '@solana/subscribable': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-spec@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/rpc-spec-types': 8.0.0(typescript@5.9.3) + '@solana/subscribable': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-subscriptions-api@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.1.1(typescript@5.9.3) + '@solana/keys': 7.1.1(typescript@5.9.3) + '@solana/rpc-subscriptions-spec': 7.1.1(typescript@5.9.3) + '@solana/rpc-transformers': 7.1.1(typescript@5.9.3) + '@solana/rpc-types': 7.1.1(typescript@5.9.3) + '@solana/transaction-messages': 7.1.1(typescript@5.9.3) + '@solana/transactions': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc-subscriptions-api@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 8.0.0(typescript@5.9.3) + '@solana/keys': 8.0.0(typescript@5.9.3) + '@solana/rpc-subscriptions-spec': 8.0.0(typescript@5.9.3) + '@solana/rpc-transformers': 8.0.0(typescript@5.9.3) + '@solana/rpc-types': 8.0.0(typescript@5.9.3) + '@solana/transaction-messages': 8.0.0(typescript@5.9.3) + '@solana/transactions': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc-subscriptions-channel-websocket@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/functional': 7.1.1(typescript@5.9.3) + '@solana/rpc-subscriptions-spec': 7.1.1(typescript@5.9.3) + '@solana/subscribable': 7.1.1(typescript@5.9.3) + ws: 8.21.3 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@solana/rpc-subscriptions-channel-websocket@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/functional': 8.0.0(typescript@5.9.3) + '@solana/rpc-subscriptions-spec': 8.0.0(typescript@5.9.3) + '@solana/subscribable': 8.0.0(typescript@5.9.3) + ws: 8.21.3 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@solana/rpc-subscriptions-spec@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/promises': 7.1.1(typescript@5.9.3) + '@solana/rpc-spec-types': 7.1.1(typescript@5.9.3) + '@solana/subscribable': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-subscriptions-spec@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/promises': 8.0.0(typescript@5.9.3) + '@solana/rpc-spec-types': 8.0.0(typescript@5.9.3) + '@solana/subscribable': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-subscriptions@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/fast-stable-stringify': 7.1.1(typescript@5.9.3) + '@solana/functional': 7.1.1(typescript@5.9.3) + '@solana/promises': 7.1.1(typescript@5.9.3) + '@solana/rpc-spec-types': 7.1.1(typescript@5.9.3) + '@solana/rpc-subscriptions-api': 7.1.1(typescript@5.9.3) + '@solana/rpc-subscriptions-channel-websocket': 7.1.1(typescript@5.9.3) + '@solana/rpc-subscriptions-spec': 7.1.1(typescript@5.9.3) + '@solana/rpc-transformers': 7.1.1(typescript@5.9.3) + '@solana/rpc-types': 7.1.1(typescript@5.9.3) + '@solana/subscribable': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - fastestsmallesttextencoderdecoder + - utf-8-validate + + '@solana/rpc-subscriptions@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/fast-stable-stringify': 8.0.0(typescript@5.9.3) + '@solana/functional': 8.0.0(typescript@5.9.3) + '@solana/promises': 8.0.0(typescript@5.9.3) + '@solana/rpc-spec-types': 8.0.0(typescript@5.9.3) + '@solana/rpc-subscriptions-api': 8.0.0(typescript@5.9.3) + '@solana/rpc-subscriptions-channel-websocket': 8.0.0(typescript@5.9.3) + '@solana/rpc-subscriptions-spec': 8.0.0(typescript@5.9.3) + '@solana/rpc-transformers': 8.0.0(typescript@5.9.3) + '@solana/rpc-types': 8.0.0(typescript@5.9.3) + '@solana/subscribable': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - fastestsmallesttextencoderdecoder + - utf-8-validate + + '@solana/rpc-transformers@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/functional': 7.1.1(typescript@5.9.3) + '@solana/nominal-types': 7.1.1(typescript@5.9.3) + '@solana/rpc-spec-types': 7.1.1(typescript@5.9.3) + '@solana/rpc-types': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc-transformers@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/functional': 8.0.0(typescript@5.9.3) + '@solana/nominal-types': 8.0.0(typescript@5.9.3) + '@solana/rpc-spec-types': 8.0.0(typescript@5.9.3) + '@solana/rpc-types': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc-transport-http@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/rpc-spec': 7.1.1(typescript@5.9.3) + '@solana/rpc-spec-types': 7.1.1(typescript@5.9.3) + undici-types: 8.10.0 + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-transport-http@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/rpc-spec': 8.0.0(typescript@5.9.3) + '@solana/rpc-spec-types': 8.0.0(typescript@5.9.3) + undici-types: 8.10.0 + optionalDependencies: + typescript: 5.9.3 + + '@solana/rpc-types@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.1.1(typescript@5.9.3) + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/codecs-numbers': 7.1.1(typescript@5.9.3) + '@solana/codecs-strings': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/fixed-points': 7.1.1(typescript@5.9.3) + '@solana/nominal-types': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc-types@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 8.0.0(typescript@5.9.3) + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 8.0.0(typescript@5.9.3) + '@solana/codecs-strings': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/fixed-points': 8.0.0(typescript@5.9.3) + '@solana/nominal-types': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/fast-stable-stringify': 7.1.1(typescript@5.9.3) + '@solana/functional': 7.1.1(typescript@5.9.3) + '@solana/rpc-api': 7.1.1(typescript@5.9.3) + '@solana/rpc-spec': 7.1.1(typescript@5.9.3) + '@solana/rpc-spec-types': 7.1.1(typescript@5.9.3) + '@solana/rpc-transformers': 7.1.1(typescript@5.9.3) + '@solana/rpc-transport-http': 7.1.1(typescript@5.9.3) + '@solana/rpc-types': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/rpc@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/fast-stable-stringify': 8.0.0(typescript@5.9.3) + '@solana/functional': 8.0.0(typescript@5.9.3) + '@solana/rpc-api': 8.0.0(typescript@5.9.3) + '@solana/rpc-spec': 8.0.0(typescript@5.9.3) + '@solana/rpc-spec-types': 8.0.0(typescript@5.9.3) + '@solana/rpc-transformers': 8.0.0(typescript@5.9.3) + '@solana/rpc-transport-http': 8.0.0(typescript@5.9.3) + '@solana/rpc-types': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/signers@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.1.1(typescript@5.9.3) + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/instructions': 7.1.1(typescript@5.9.3) + '@solana/keys': 7.1.1(typescript@5.9.3) + '@solana/nominal-types': 7.1.1(typescript@5.9.3) + '@solana/offchain-messages': 7.1.1(typescript@5.9.3) + '@solana/transaction-messages': 7.1.1(typescript@5.9.3) + '@solana/transactions': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/signers@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 8.0.0(typescript@5.9.3) + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/instructions': 8.0.0(typescript@5.9.3) + '@solana/keys': 8.0.0(typescript@5.9.3) + '@solana/nominal-types': 8.0.0(typescript@5.9.3) + '@solana/offchain-messages': 8.0.0(typescript@5.9.3) + '@solana/transaction-messages': 8.0.0(typescript@5.9.3) + '@solana/transactions': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/subscribable@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/promises': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/subscribable@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/promises': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + + '@solana/sysvars@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/accounts': 7.1.1(typescript@5.9.3) + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/codecs-data-structures': 7.1.1(typescript@5.9.3) + '@solana/codecs-numbers': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/rpc-types': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/sysvars@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/accounts': 8.0.0(typescript@5.9.3) + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/codecs-data-structures': 8.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/rpc-types': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/transaction-confirmation@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.1.1(typescript@5.9.3) + '@solana/codecs-strings': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/keys': 7.1.1(typescript@5.9.3) + '@solana/promises': 7.1.1(typescript@5.9.3) + '@solana/rpc': 7.1.1(typescript@5.9.3) + '@solana/rpc-subscriptions': 7.1.1(typescript@5.9.3) + '@solana/rpc-types': 7.1.1(typescript@5.9.3) + '@solana/transaction-messages': 7.1.1(typescript@5.9.3) + '@solana/transactions': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - fastestsmallesttextencoderdecoder + - utf-8-validate + + '@solana/transaction-confirmation@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 8.0.0(typescript@5.9.3) + '@solana/codecs-strings': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/keys': 8.0.0(typescript@5.9.3) + '@solana/promises': 8.0.0(typescript@5.9.3) + '@solana/rpc': 8.0.0(typescript@5.9.3) + '@solana/rpc-subscriptions': 8.0.0(typescript@5.9.3) + '@solana/rpc-types': 8.0.0(typescript@5.9.3) + '@solana/transaction-messages': 8.0.0(typescript@5.9.3) + '@solana/transactions': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - fastestsmallesttextencoderdecoder + - utf-8-validate + + '@solana/transaction-introspection@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.1.1(typescript@5.9.3) + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/codecs-strings': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/instructions': 7.1.1(typescript@5.9.3) + '@solana/rpc-types': 7.1.1(typescript@5.9.3) + '@solana/transaction-messages': 7.1.1(typescript@5.9.3) + '@solana/transactions': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/transaction-introspection@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 8.0.0(typescript@5.9.3) + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/codecs-strings': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/instructions': 8.0.0(typescript@5.9.3) + '@solana/rpc-types': 8.0.0(typescript@5.9.3) + '@solana/transaction-messages': 8.0.0(typescript@5.9.3) + '@solana/transactions': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/transaction-messages@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.1.1(typescript@5.9.3) + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/codecs-data-structures': 7.1.1(typescript@5.9.3) + '@solana/codecs-numbers': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/functional': 7.1.1(typescript@5.9.3) + '@solana/instructions': 7.1.1(typescript@5.9.3) + '@solana/nominal-types': 7.1.1(typescript@5.9.3) + '@solana/rpc-types': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/transaction-messages@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 8.0.0(typescript@5.9.3) + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/codecs-data-structures': 8.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/functional': 8.0.0(typescript@5.9.3) + '@solana/instructions': 8.0.0(typescript@5.9.3) + '@solana/nominal-types': 8.0.0(typescript@5.9.3) + '@solana/rpc-types': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/transactions@7.1.1(typescript@5.9.3)': + dependencies: + '@solana/addresses': 7.1.1(typescript@5.9.3) + '@solana/codecs-core': 7.1.1(typescript@5.9.3) + '@solana/codecs-data-structures': 7.1.1(typescript@5.9.3) + '@solana/codecs-numbers': 7.1.1(typescript@5.9.3) + '@solana/codecs-strings': 7.1.1(typescript@5.9.3) + '@solana/errors': 7.1.1(typescript@5.9.3) + '@solana/functional': 7.1.1(typescript@5.9.3) + '@solana/instructions': 7.1.1(typescript@5.9.3) + '@solana/keys': 7.1.1(typescript@5.9.3) + '@solana/nominal-types': 7.1.1(typescript@5.9.3) + '@solana/rpc-types': 7.1.1(typescript@5.9.3) + '@solana/transaction-messages': 7.1.1(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@solana/transactions@8.0.0(typescript@5.9.3)': + dependencies: + '@solana/addresses': 8.0.0(typescript@5.9.3) + '@solana/codecs-core': 8.0.0(typescript@5.9.3) + '@solana/codecs-data-structures': 8.0.0(typescript@5.9.3) + '@solana/codecs-numbers': 8.0.0(typescript@5.9.3) + '@solana/codecs-strings': 8.0.0(typescript@5.9.3) + '@solana/errors': 8.0.0(typescript@5.9.3) + '@solana/functional': 8.0.0(typescript@5.9.3) + '@solana/instructions': 8.0.0(typescript@5.9.3) + '@solana/keys': 8.0.0(typescript@5.9.3) + '@solana/nominal-types': 8.0.0(typescript@5.9.3) + '@solana/rpc-types': 8.0.0(typescript@5.9.3) + '@solana/transaction-messages': 8.0.0(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - fastestsmallesttextencoderdecoder + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/mocha@10.0.10': {} + + '@types/node@26.3.0': + dependencies: + undici-types: 8.3.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.3.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + argparse@2.0.1: {} + + assertion-error@2.0.1: {} + + balanced-match@1.0.2: {} + + brace-expansion@2.1.4: + dependencies: + balanced-match: 1.0.2 + + browser-stdout@1.3.1: {} + + camelcase@6.3.0: {} + + chai@6.2.2: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@15.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + decamelize@4.0.0: {} + + diff@7.0.0: {} + + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat@5.0.2: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fsevents@2.3.3: + optional: true + + get-caller-file@2.0.5: {} + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + has-flag@4.0.0: {} + + he@1.2.0: {} + + is-fullwidth-code-point@3.0.0: {} + + is-path-inside@3.0.3: {} + + is-plain-obj@2.1.0: {} + + is-unicode-supported@0.1.0: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + js-yaml@4.3.1: + dependencies: + argparse: 2.0.1 + + litesvm-darwin-arm64@1.4.1: + optional: true + + litesvm-darwin-x64@1.4.1: + optional: true + + litesvm-linux-arm64-gnu@1.4.1: + optional: true + + litesvm-linux-arm64-musl@1.4.1: + optional: true + + litesvm-linux-x64-gnu@1.4.1: + optional: true + + litesvm-linux-x64-musl@1.4.1: + optional: true + + litesvm@1.4.1(typescript@5.9.3): + dependencies: + '@solana-program/system': 0.14.0(@solana/kit@8.0.0(typescript@5.9.3)) + '@solana-program/token': 0.16.0(@solana/kit@8.0.0(typescript@5.9.3)) + '@solana/kit': 8.0.0(typescript@5.9.3) + optionalDependencies: + litesvm-darwin-arm64: 1.4.1 + litesvm-darwin-x64: 1.4.1 + litesvm-linux-arm64-gnu: 1.4.1 + litesvm-linux-arm64-musl: 1.4.1 + litesvm-linux-x64-gnu: 1.4.1 + litesvm-linux-x64-musl: 1.4.1 + transitivePeerDependencies: + - bufferutil + - fastestsmallesttextencoderdecoder + - typescript + - utf-8-validate + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + lru-cache@10.4.3: {} + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.4 + + minipass@7.1.3: {} + + mocha@11.8.0: + dependencies: + browser-stdout: 1.3.1 + chokidar: 4.0.3 + debug: 4.4.3(supports-color@8.1.1) + diff: 7.0.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 10.5.0 + he: 1.2.0 + is-path-inside: 3.0.3 + js-yaml: 4.3.1 + log-symbols: 4.1.0 + minimatch: 9.0.9 + ms: 2.1.3 + picocolors: 1.1.1 + serialize-javascript: 6.0.2 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 9.3.4 + yargs: 17.7.3 + yargs-parser: 21.1.1 + yargs-unparser: 2.0.0 + + ms@2.1.3: {} + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + picocolors@1.1.1: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + readdirp@4.1.2: {} + + require-directory@2.1.1: {} + + safe-buffer@5.2.1: {} + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + signal-exit@4.1.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.3.0 + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + tsx@4.23.12: + dependencies: + esbuild: 0.28.2 + optionalDependencies: + fsevents: 2.3.3 + + typescript@5.9.3: {} + + undici-types@8.10.0: {} + + undici-types@8.3.0: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + workerpool@9.3.4: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + ws@8.21.3: {} + + y18n@5.0.8: {} + + yargs-parser@21.1.1: {} + + yargs-unparser@2.0.0: + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} diff --git a/tokens/token-fundraiser/pinocchio/program/Cargo.toml b/tokens/token-fundraiser/pinocchio/program/Cargo.toml new file mode 100644 index 000000000..c7d3e3134 --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/program/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "fundraiser-pinocchio-program" +version = "0.1.0" +edition = "2021" + +[dependencies] +pinocchio.workspace = true +solana-address.workspace = true +pinocchio-log.workspace = true +pinocchio-system.workspace = true +pinocchio-token.workspace = true +pinocchio-associated-token-account.workspace = true + +[lib] +crate-type = ["cdylib", "lib"] + +[features] +custom-heap = [] +custom-panic = [] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/tokens/token-fundraiser/pinocchio/program/src/constants.rs b/tokens/token-fundraiser/pinocchio/program/src/constants.rs new file mode 100644 index 000000000..0fc834d5a --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/program/src/constants.rs @@ -0,0 +1,17 @@ +/// Base of the minimum campaign target. +/// +/// The effective minimum is `MIN_AMOUNT_TO_RAISE.pow(decimals)` base units, +/// carried over unchanged from the Anchor version of this example so the two +/// behave identically. +pub const MIN_AMOUNT_TO_RAISE: u64 = 3; + +/// Seconds in a day, used to convert elapsed seconds from the clock sysvar into +/// the whole days a campaign duration is measured in. +pub const SECONDS_TO_DAYS: i64 = 86400; + +/// Share of the campaign target a single contributor may supply, as a +/// percentage scaled by [`PERCENTAGE_SCALER`]. +pub const MAX_CONTRIBUTION_PERCENTAGE: u64 = 10; + +/// Denominator for [`MAX_CONTRIBUTION_PERCENTAGE`]. +pub const PERCENTAGE_SCALER: u64 = 100; diff --git a/tokens/token-fundraiser/pinocchio/program/src/error.rs b/tokens/token-fundraiser/pinocchio/program/src/error.rs new file mode 100644 index 000000000..69bd24153 --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/program/src/error.rs @@ -0,0 +1,33 @@ +use pinocchio::error::ProgramError; + +/// Program-specific failures, surfaced to clients as `ProgramError::Custom(n)` +/// where `n` is the variant's discriminant. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum FundraiserError { + /// The campaign target has not been reached yet. + TargetNotMet, + /// The campaign target was reached, so contributions cannot be refunded. + TargetMet, + /// The contribution exceeds the per-contributor cap. + ContributionTooBig, + /// The contribution is below the minimum contribution. + ContributionTooSmall, + /// This contributor has already supplied their maximum share. + MaximumContributionsReached, + /// The campaign is still running. + FundraiserNotEnded, + /// The campaign has already ended. + FundraiserEnded, + /// The campaign target is below the minimum allowed target. + InvalidAmount, + /// A supplied account is not the account the program derived. + InvalidAccount, + /// An arithmetic operation overflowed. + ArithmeticOverflow, +} + +impl From for ProgramError { + fn from(e: FundraiserError) -> Self { + ProgramError::Custom(e as u32) + } +} diff --git a/tokens/token-fundraiser/pinocchio/program/src/instructions/check_contributions.rs b/tokens/token-fundraiser/pinocchio/program/src/instructions/check_contributions.rs new file mode 100644 index 000000000..8b887bbb5 --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/program/src/instructions/check_contributions.rs @@ -0,0 +1,120 @@ +use pinocchio::{ + cpi::{Seed, Signer}, + error::ProgramError, + AccountView, Address, ProgramResult, +}; +use pinocchio_associated_token_account::instructions::CreateIdempotent; +use pinocchio_log::log; +use pinocchio_token::{ + instructions::{CloseAccount, TransferChecked}, + state::{Account as TokenAccount, Mint}, +}; + +use crate::{ + error::FundraiserError, + instructions::{assert_associated_token_account, assert_fundraiser_pda}, + state::Fundraiser, +}; + +/// Settles a successful campaign: if the vault holds at least the target, the +/// whole balance goes to the maker and the vault and fundraiser accounts are +/// closed, returning their rent. +/// +/// Accounts: +/// 0. `[signer, writable]` maker (receives the raise and the reclaimed rent) +/// 1. `[]` mint to raise +/// 2. `[writable]` fundraiser account (PDA `[b"fundraiser", maker]`, closed here) +/// 3. `[writable]` vault (drained and closed) +/// 4. `[writable]` maker's associated token account (created if needed) +/// 5. `[]` token program +/// 6. `[]` associated token program +/// 7. `[]` system program +/// +/// Instruction data: none. +pub fn check_contributions(program_id: &Address, accounts: &mut [AccountView], _data: &[u8]) -> ProgramResult { + let [maker, mint_to_raise, fundraiser, vault, maker_ata, token_program, _associated_token_program, system_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if !maker.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + // Load the campaign terms (the borrow is released at the block's end). + let state = { + let fundraiser_data = fundraiser.try_borrow()?; + Fundraiser::deserialize(&fundraiser_data)? + }; + + // Only the maker who opened the campaign may collect it, and only in the + // campaign's own mint. + if &state.maker != maker.address().as_array() || &state.mint_to_raise != mint_to_raise.address().as_array() { + return Err(FundraiserError::InvalidAccount.into()); + } + + assert_fundraiser_pda(fundraiser, &state, maker.address(), program_id)?; + + // Verify the vault is the campaign's real vault, not a substitute token + // account that also happens to be owned by the fundraiser PDA. + assert_associated_token_account(vault, fundraiser.address(), mint_to_raise.address())?; + + let decimals = Mint::from_account_view(mint_to_raise)?.decimals(); + let vault_amount = TokenAccount::from_account_view(vault)?.amount(); + + // The raise only pays out once it has actually hit its target. + if vault_amount < state.amount_to_raise { + return Err(FundraiserError::TargetNotMet.into()); + } + + // Make sure the maker has somewhere to receive the raise. The associated + // token program checks this is the maker's canonical ATA. + log!("Ensuring maker token account exists"); + CreateIdempotent { + funding_account: maker, + account: maker_ata, + wallet: maker, + mint: mint_to_raise, + system_program, + token_program, + } + .invoke()?; + + // Release the raise to the maker, signed by the fundraiser PDA. + let bump_bytes = [state.bump]; + let seeds = [Seed::from(Fundraiser::SEED_PREFIX), Seed::from(maker.address().as_ref()), Seed::from(&bump_bytes)]; + let signers = [Signer::from(&seeds)]; + + log!("Releasing raise to maker"); + TransferChecked { + from: vault, + mint: mint_to_raise, + to: maker_ata, + authority: fundraiser, + multisig_signers: &[] as &[&AccountView], + amount: vault_amount, + decimals, + } + .invoke_signed(&signers)?; + + // Close the now-empty vault, returning its rent to the maker. + log!("Closing vault"); + CloseAccount { + account: vault, + destination: maker, + authority: fundraiser, + multisig_signers: &[] as &[&AccountView], + } + .invoke_signed(&signers)?; + + // Close the fundraiser account, returning its rent to the maker. + log!("Closing fundraiser account"); + let new_maker_lamports = + maker.lamports().checked_add(fundraiser.lamports()).ok_or(FundraiserError::ArithmeticOverflow)?; + maker.set_lamports(new_maker_lamports); + fundraiser.close()?; + + log!("Fundraiser settled successfully"); + Ok(()) +} diff --git a/tokens/token-fundraiser/pinocchio/program/src/instructions/contribute.rs b/tokens/token-fundraiser/pinocchio/program/src/instructions/contribute.rs new file mode 100644 index 000000000..be75c6f8c --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/program/src/instructions/contribute.rs @@ -0,0 +1,152 @@ +use pinocchio::{ + cpi::{Seed, Signer}, + error::ProgramError, + sysvars::{clock::Clock, rent::Rent, Sysvar}, + AccountView, Address, ProgramResult, +}; +use pinocchio_log::log; +use pinocchio_system::instructions::CreateAccount; +use pinocchio_token::{instructions::TransferChecked, state::Mint}; + +use crate::{ + error::FundraiserError, + instructions::{assert_associated_token_account, assert_fundraiser_pda, elapsed_days, max_contribution, read_u64}, + state::{Contributor, Fundraiser}, +}; + +/// Contributes tokens to an open campaign, creating the contributor's running +/// total on first use. +/// +/// Accounts: +/// 0. `[signer, writable]` contributor (funds the contributor account) +/// 1. `[]` mint to raise +/// 2. `[writable]` fundraiser account (PDA `[b"fundraiser", maker]`) +/// 3. `[writable]` contributor account (PDA `[b"contributor", fundraiser, contributor]`) +/// 4. `[writable]` contributor's associated token account (source) +/// 5. `[writable]` vault (fundraiser PDA's associated token account) +/// 6. `[]` token program +/// 7. `[]` system program +/// +/// Instruction data: `[amount: u64 (LE), contributor_bump: u8]` +pub fn contribute(program_id: &Address, accounts: &mut [AccountView], data: &[u8]) -> ProgramResult { + let [contributor, mint_to_raise, fundraiser, contributor_account, contributor_ata, vault, _token_program, _system_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if !contributor.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + let amount = read_u64(data, 0)?; + let contributor_bump = *data.get(8).ok_or(ProgramError::InvalidInstructionData)?; + + // Load the campaign terms (the borrow is released at the block's end). + let mut state = { + let fundraiser_data = fundraiser.try_borrow()?; + Fundraiser::deserialize(&fundraiser_data)? + }; + + // The fundraiser PDA is keyed by its maker, which the state records. + let maker = Address::from(state.maker); + assert_fundraiser_pda(fundraiser, &state, &maker, program_id)?; + + // Contributions must be in the campaign's mint. + if &state.mint_to_raise != mint_to_raise.address().as_array() { + return Err(FundraiserError::InvalidAccount.into()); + } + + let decimals = Mint::from_account_view(mint_to_raise)?.decimals(); + + if amount == 0 { + return Err(FundraiserError::ContributionTooSmall.into()); + } + + // No single contributor may supply more than their share of the target. + let cap = max_contribution(state.amount_to_raise)?; + if amount > cap { + return Err(FundraiserError::ContributionTooBig.into()); + } + + // The campaign must still be running. + let elapsed = elapsed_days(Clock::get()?.unix_timestamp, state.time_started)?; + if (state.duration as i64) <= elapsed { + return Err(FundraiserError::FundraiserEnded.into()); + } + + // Both token accounts must be the canonical ATAs, so contributions can only + // come from the signer's own account and can only land in the real vault. + assert_associated_token_account(vault, fundraiser.address(), mint_to_raise.address())?; + assert_associated_token_account(contributor_ata, contributor.address(), mint_to_raise.address())?; + + // Verify the contributor account is the canonical PDA for these seeds. + let bump_bytes = [contributor_bump]; + let seeds = [ + Seed::from(Contributor::SEED_PREFIX), + Seed::from(fundraiser.address().as_ref()), + Seed::from(contributor.address().as_ref()), + Seed::from(&bump_bytes), + ]; + let contributor_pda = Address::create_program_address( + &[Contributor::SEED_PREFIX, fundraiser.address().as_ref(), contributor.address().as_ref(), &bump_bytes], + program_id, + ) + .map_err(|_| ProgramError::InvalidSeeds)?; + if contributor_account.address() != &contributor_pda { + return Err(ProgramError::InvalidSeeds); + } + + // Create the contributor's running total on their first contribution. This + // is the Pinocchio equivalent of Anchor's `init_if_needed`. + if contributor_account.is_data_empty() { + log!("Creating contributor account"); + let lamports = Rent::get()?.try_minimum_balance(Contributor::LEN)?; + let signers = [Signer::from(&seeds)]; + CreateAccount { + from: contributor, + to: contributor_account, + lamports, + space: Contributor::LEN as u64, + owner: program_id, + } + .invoke_signed(&signers)?; + } else if !contributor_account.owned_by(program_id) { + return Err(ProgramError::InvalidAccountOwner); + } + + let mut contributed = { + let contributor_data = contributor_account.try_borrow()?; + Contributor::deserialize(&contributor_data)? + }; + + // Their running total, including this contribution, must stay under the cap. + let new_total = contributed.amount.checked_add(amount).ok_or(FundraiserError::ArithmeticOverflow)?; + if new_total > cap { + return Err(FundraiserError::MaximumContributionsReached.into()); + } + + // Move the tokens into the vault. `TransferChecked` makes the token program + // verify the mint and decimals rather than trusting the caller's accounts. + log!("Transferring contribution to vault"); + TransferChecked { + from: contributor_ata, + mint: mint_to_raise, + to: vault, + authority: contributor, + multisig_signers: &[] as &[&AccountView], + amount, + decimals, + } + .invoke()?; + + // Record the contribution on both the campaign and the contributor. + state.current_amount = state.current_amount.checked_add(amount).ok_or(FundraiserError::ArithmeticOverflow)?; + state.serialize(&mut fundraiser.try_borrow_mut()?)?; + + contributed.amount = new_total; + contributed.serialize(&mut contributor_account.try_borrow_mut()?)?; + + log!("Contribution recorded"); + Ok(()) +} diff --git a/tokens/token-fundraiser/pinocchio/program/src/instructions/initialize.rs b/tokens/token-fundraiser/pinocchio/program/src/instructions/initialize.rs new file mode 100644 index 000000000..25d5f80c8 --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/program/src/instructions/initialize.rs @@ -0,0 +1,101 @@ +use pinocchio::{ + cpi::{Seed, Signer}, + error::ProgramError, + sysvars::{clock::Clock, rent::Rent, Sysvar}, + AccountView, Address, ProgramResult, +}; +use pinocchio_associated_token_account::instructions::CreateIdempotent; +use pinocchio_log::log; +use pinocchio_system::instructions::CreateAccount; +use pinocchio_token::state::Mint; + +use crate::{ + constants::MIN_AMOUNT_TO_RAISE, + error::FundraiserError, + instructions::{read_u16, read_u64}, + state::Fundraiser, +}; + +/// Opens a fundraising campaign: creates the fundraiser PDA that records the +/// target and deadline, plus the vault that will hold contributions. +/// +/// Accounts: +/// 0. `[signer, writable]` maker (funds the fundraiser account and the vault) +/// 1. `[]` mint to raise +/// 2. `[writable]` fundraiser account (PDA `[b"fundraiser", maker]`, created here) +/// 3. `[writable]` vault (fundraiser PDA's associated token account, created here) +/// 4. `[]` token program +/// 5. `[]` associated token program +/// 6. `[]` system program +/// +/// Instruction data: `[amount: u64 (LE), duration: u16 (LE), bump: u8]` +pub fn initialize(program_id: &Address, accounts: &mut [AccountView], data: &[u8]) -> ProgramResult { + let [maker, mint_to_raise, fundraiser, vault, token_program, _associated_token_program, system_program] = accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if !maker.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + let amount = read_u64(data, 0)?; + let duration = read_u16(data, 8)?; + let bump = *data.get(10).ok_or(ProgramError::InvalidInstructionData)?; + + // The minimum target scales with the mint's decimals, so a campaign is + // always worth more than dust. `checked_pow` matters here: `decimals` comes + // from the mint account, and an absurd value would otherwise overflow and + // abort the program rather than returning a clean error. + let decimals = Mint::from_account_view(mint_to_raise)?.decimals(); + let minimum = MIN_AMOUNT_TO_RAISE.checked_pow(decimals as u32).ok_or(FundraiserError::InvalidAmount)?; + if amount < minimum { + return Err(FundraiserError::InvalidAmount.into()); + } + + // Verify the supplied fundraiser account is the canonical PDA for these seeds. + let bump_bytes = [bump]; + let fundraiser_pda = + Address::create_program_address(&[Fundraiser::SEED_PREFIX, maker.address().as_ref(), &bump_bytes], program_id) + .map_err(|_| ProgramError::InvalidSeeds)?; + if fundraiser.address() != &fundraiser_pda { + return Err(ProgramError::InvalidSeeds); + } + + // Create the fundraiser account, signed by the fundraiser PDA itself. + let lamports = Rent::get()?.try_minimum_balance(Fundraiser::LEN)?; + let seeds = [Seed::from(Fundraiser::SEED_PREFIX), Seed::from(maker.address().as_ref()), Seed::from(&bump_bytes)]; + let signers = [Signer::from(&seeds)]; + + log!("Creating fundraiser account"); + CreateAccount { from: maker, to: fundraiser, lamports, space: Fundraiser::LEN as u64, owner: program_id } + .invoke_signed(&signers)?; + + // Create the vault: an associated token account for the campaign mint owned + // by the fundraiser PDA, so only this program can move contributions. + log!("Creating vault"); + CreateIdempotent { + funding_account: maker, + account: vault, + wallet: fundraiser, + mint: mint_to_raise, + system_program, + token_program, + } + .invoke()?; + + // Persist the campaign terms. + let state = Fundraiser { + maker: *maker.address().as_array(), + mint_to_raise: *mint_to_raise.address().as_array(), + amount_to_raise: amount, + current_amount: 0, + time_started: Clock::get()?.unix_timestamp, + duration, + bump, + }; + state.serialize(&mut fundraiser.try_borrow_mut()?)?; + + log!("Fundraiser initialized"); + Ok(()) +} diff --git a/tokens/token-fundraiser/pinocchio/program/src/instructions/mod.rs b/tokens/token-fundraiser/pinocchio/program/src/instructions/mod.rs new file mode 100644 index 000000000..3ca1e6010 --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/program/src/instructions/mod.rs @@ -0,0 +1,94 @@ +use pinocchio::{error::ProgramError, AccountView, Address}; + +use crate::{error::FundraiserError, state::Fundraiser}; + +mod check_contributions; +mod contribute; +mod initialize; +mod refund; + +pub use check_contributions::*; +pub use contribute::*; +pub use initialize::*; +pub use refund::*; + +/// Reads a little-endian `u64` starting at `offset` within `data`. +pub(crate) fn read_u64(data: &[u8], offset: usize) -> Result { + let bytes: [u8; 8] = data + .get(offset..offset + 8) + .ok_or(ProgramError::InvalidInstructionData)? + .try_into() + .map_err(|_| ProgramError::InvalidInstructionData)?; + Ok(u64::from_le_bytes(bytes)) +} + +/// Reads a little-endian `u16` starting at `offset` within `data`. +pub(crate) fn read_u16(data: &[u8], offset: usize) -> Result { + let bytes: [u8; 2] = data + .get(offset..offset + 2) + .ok_or(ProgramError::InvalidInstructionData)? + .try_into() + .map_err(|_| ProgramError::InvalidInstructionData)?; + Ok(u16::from_le_bytes(bytes)) +} + +/// Confirms `account` is the canonical associated token account of `wallet` for +/// `mint`. +/// +/// Anchor expresses this with an `associated_token::{mint, authority}` +/// constraint; without it a caller could substitute any token account they +/// control wherever the program only checks the mint, redirecting funds. +pub(crate) fn assert_associated_token_account( + account: &AccountView, + wallet: &Address, + mint: &Address, +) -> Result<(), ProgramError> { + let (expected, _) = Address::find_program_address( + &[wallet.as_ref(), pinocchio_token::ID.as_ref(), mint.as_ref()], + &pinocchio_associated_token_account::ID, + ); + if account.address() != &expected { + return Err(FundraiserError::InvalidAccount.into()); + } + Ok(()) +} + +/// Confirms `fundraiser_account` is this program's PDA for +/// `[b"fundraiser", maker]`, using the bump recorded in the account itself. +/// +/// The ownership check is defense in depth: only this program can sign for its +/// own PDAs, so an account at this address cannot hold attacker-controlled +/// data. Checking anyway keeps the invariant local and obvious. +pub(crate) fn assert_fundraiser_pda( + fundraiser_account: &AccountView, + fundraiser: &Fundraiser, + maker: &Address, + program_id: &Address, +) -> Result<(), ProgramError> { + if !fundraiser_account.owned_by(program_id) { + return Err(ProgramError::InvalidAccountOwner); + } + let bump_bytes = [fundraiser.bump]; + let expected = Address::create_program_address(&[Fundraiser::SEED_PREFIX, maker.as_ref(), &bump_bytes], program_id) + .map_err(|_| ProgramError::InvalidSeeds)?; + if fundraiser_account.address() != &expected { + return Err(ProgramError::InvalidSeeds); + } + Ok(()) +} + +/// The largest amount a single contributor may put into a campaign with the +/// given target. +pub(crate) fn max_contribution(amount_to_raise: u64) -> Result { + amount_to_raise + .checked_mul(crate::constants::MAX_CONTRIBUTION_PERCENTAGE) + .map(|scaled| scaled / crate::constants::PERCENTAGE_SCALER) + .ok_or_else(|| FundraiserError::ArithmeticOverflow.into()) +} + +/// Whole days elapsed since `time_started`, per the clock sysvar. +pub(crate) fn elapsed_days(now: i64, time_started: i64) -> Result { + now.checked_sub(time_started) + .map(|elapsed| elapsed / crate::constants::SECONDS_TO_DAYS) + .ok_or_else(|| FundraiserError::ArithmeticOverflow.into()) +} diff --git a/tokens/token-fundraiser/pinocchio/program/src/instructions/refund.rs b/tokens/token-fundraiser/pinocchio/program/src/instructions/refund.rs new file mode 100644 index 000000000..80bc77729 --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/program/src/instructions/refund.rs @@ -0,0 +1,132 @@ +use pinocchio::{ + cpi::{Seed, Signer}, + error::ProgramError, + sysvars::{clock::Clock, Sysvar}, + AccountView, Address, ProgramResult, +}; +use pinocchio_log::log; +use pinocchio_token::{ + instructions::TransferChecked, + state::{Account as TokenAccount, Mint}, +}; + +use crate::{ + error::FundraiserError, + instructions::{assert_associated_token_account, assert_fundraiser_pda, elapsed_days}, + state::{Contributor, Fundraiser}, +}; + +/// Refunds a contributor after a campaign expired without reaching its target. +/// Returns exactly what this contributor put in and closes their contributor +/// account. +/// +/// Accounts: +/// 0. `[signer, writable]` contributor (receives the refund and the reclaimed rent) +/// 1. `[]` mint to raise +/// 2. `[writable]` fundraiser account (PDA `[b"fundraiser", maker]`) +/// 3. `[writable]` contributor account (PDA `[b"contributor", fundraiser, contributor]`, closed here) +/// 4. `[writable]` contributor's associated token account (destination) +/// 5. `[writable]` vault (source of the refund) +/// 6. `[]` token program +/// +/// Instruction data: `[contributor_bump: u8]` +pub fn refund(program_id: &Address, accounts: &mut [AccountView], data: &[u8]) -> ProgramResult { + let [contributor, mint_to_raise, fundraiser, contributor_account, contributor_ata, vault, _token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if !contributor.is_signer() { + return Err(ProgramError::MissingRequiredSignature); + } + + let contributor_bump = *data.first().ok_or(ProgramError::InvalidInstructionData)?; + + // Load the campaign terms (the borrow is released at the block's end). + let mut state = { + let fundraiser_data = fundraiser.try_borrow()?; + Fundraiser::deserialize(&fundraiser_data)? + }; + + // The fundraiser PDA is keyed by its maker, which the state records. + let maker = Address::from(state.maker); + assert_fundraiser_pda(fundraiser, &state, &maker, program_id)?; + + if &state.mint_to_raise != mint_to_raise.address().as_array() { + return Err(FundraiserError::InvalidAccount.into()); + } + + // Refunds only open once the campaign has run its course. + let elapsed = elapsed_days(Clock::get()?.unix_timestamp, state.time_started)?; + if (state.duration as i64) > elapsed { + return Err(FundraiserError::FundraiserNotEnded.into()); + } + + // Both token accounts must be the canonical ATAs, so the refund can only + // leave the real vault and can only land in the signer's own account. + assert_associated_token_account(vault, fundraiser.address(), mint_to_raise.address())?; + assert_associated_token_account(contributor_ata, contributor.address(), mint_to_raise.address())?; + + let decimals = Mint::from_account_view(mint_to_raise)?.decimals(); + let vault_amount = TokenAccount::from_account_view(vault)?.amount(); + + // A campaign that hit its target belongs to the maker, not the contributors. + if vault_amount >= state.amount_to_raise { + return Err(FundraiserError::TargetMet.into()); + } + + // Verify the contributor account is the canonical PDA for these seeds, and + // that this program actually owns it. + let bump_bytes = [contributor_bump]; + let contributor_pda = Address::create_program_address( + &[Contributor::SEED_PREFIX, fundraiser.address().as_ref(), contributor.address().as_ref(), &bump_bytes], + program_id, + ) + .map_err(|_| ProgramError::InvalidSeeds)?; + if contributor_account.address() != &contributor_pda { + return Err(ProgramError::InvalidSeeds); + } + if !contributor_account.owned_by(program_id) { + return Err(ProgramError::InvalidAccountOwner); + } + + let contributed = { + let contributor_data = contributor_account.try_borrow()?; + Contributor::deserialize(&contributor_data)? + }; + + // Return exactly what this contributor put in, signed by the fundraiser PDA. + let fundraiser_bump_bytes = [state.bump]; + let seeds = [Seed::from(Fundraiser::SEED_PREFIX), Seed::from(maker.as_ref()), Seed::from(&fundraiser_bump_bytes)]; + let signers = [Signer::from(&seeds)]; + + log!("Refunding contribution"); + TransferChecked { + from: vault, + mint: mint_to_raise, + to: contributor_ata, + authority: fundraiser, + multisig_signers: &[] as &[&AccountView], + amount: contributed.amount, + decimals, + } + .invoke_signed(&signers)?; + + // Drop the refunded amount from the campaign total. + state.current_amount = + state.current_amount.checked_sub(contributed.amount).ok_or(FundraiserError::ArithmeticOverflow)?; + state.serialize(&mut fundraiser.try_borrow_mut()?)?; + + // Close the contributor account, returning its rent to the contributor. + log!("Closing contributor account"); + let new_contributor_lamports = contributor + .lamports() + .checked_add(contributor_account.lamports()) + .ok_or(FundraiserError::ArithmeticOverflow)?; + contributor.set_lamports(new_contributor_lamports); + contributor_account.close()?; + + log!("Refund complete"); + Ok(()) +} diff --git a/tokens/token-fundraiser/pinocchio/program/src/lib.rs b/tokens/token-fundraiser/pinocchio/program/src/lib.rs new file mode 100644 index 000000000..6966c4796 --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/program/src/lib.rs @@ -0,0 +1,12 @@ +#![no_std] + +pub mod constants; +pub mod error; +pub mod instructions; +pub mod processor; +pub mod state; + +use pinocchio::{entrypoint, nostd_panic_handler}; + +entrypoint!(processor::process_instruction); +nostd_panic_handler!(); diff --git a/tokens/token-fundraiser/pinocchio/program/src/processor.rs b/tokens/token-fundraiser/pinocchio/program/src/processor.rs new file mode 100644 index 000000000..1e8bf0338 --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/program/src/processor.rs @@ -0,0 +1,39 @@ +use pinocchio::{error::ProgramError, AccountView, Address, ProgramResult}; +use pinocchio_log::log; + +use crate::instructions::{check_contributions, contribute, initialize, refund}; + +/// Dispatches an instruction based on its leading discriminator byte. +/// +/// Instruction data layout: `[discriminator: u8, ..args]` +/// - `0` -> Initialize (args: `[amount: u64 (LE), duration: u16 (LE), bump: u8]`) +/// - `1` -> Contribute (args: `[amount: u64 (LE), contributor_bump: u8]`) +/// - `2` -> CheckContributions (no args) +/// - `3` -> Refund (args: `[contributor_bump: u8]`) +pub fn process_instruction( + program_id: &Address, + accounts: &mut [AccountView], + instruction_data: &[u8], +) -> ProgramResult { + let (discriminator, args) = instruction_data.split_first().ok_or(ProgramError::InvalidInstructionData)?; + + match *discriminator { + 0 => { + log!("Instruction: Initialize"); + initialize(program_id, accounts, args) + } + 1 => { + log!("Instruction: Contribute"); + contribute(program_id, accounts, args) + } + 2 => { + log!("Instruction: CheckContributions"); + check_contributions(program_id, accounts, args) + } + 3 => { + log!("Instruction: Refund"); + refund(program_id, accounts, args) + } + _ => Err(ProgramError::InvalidInstructionData), + } +} diff --git a/tokens/token-fundraiser/pinocchio/program/src/state.rs b/tokens/token-fundraiser/pinocchio/program/src/state.rs new file mode 100644 index 000000000..9be9129a7 --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/program/src/state.rs @@ -0,0 +1,98 @@ +use pinocchio::error::ProgramError; + +/// A fundraising campaign, stored in the fundraiser PDA. +/// +/// The fundraiser PDA is derived from `[b"fundraiser", maker]` and owns the +/// vault token account that holds contributions until the campaign either hits +/// its target (the maker collects) or expires (contributors refund). +/// +/// Serialized byte layout (little-endian), matching the field order below: +/// `[maker: 32][mint_to_raise: 32][amount_to_raise: u64][current_amount: u64] +/// [time_started: i64][duration: u16][bump: u8]` +pub struct Fundraiser { + /// The wallet that opened the campaign and collects a successful raise. + pub maker: [u8; 32], + /// Mint contributions must be denominated in. + pub mint_to_raise: [u8; 32], + /// Target, in base units of `mint_to_raise`. + pub amount_to_raise: u64, + /// Running total contributed so far, in base units. + pub current_amount: u64, + /// Unix timestamp the campaign opened at. + pub time_started: i64, + /// Campaign length in days, counted from `time_started`. + pub duration: u16, + /// Canonical bump for the fundraiser PDA. + pub bump: u8, +} + +impl Fundraiser { + /// Seed prefix for the fundraiser PDA: `[SEED_PREFIX, maker]`. + pub const SEED_PREFIX: &'static [u8] = b"fundraiser"; + + /// Serialized size of a `Fundraiser` in bytes. + pub const LEN: usize = 32 + 32 + 8 + 8 + 8 + 2 + 1; + + /// Writes the fundraiser into `dst` using the layout documented above. + pub fn serialize(&self, dst: &mut [u8]) -> Result<(), ProgramError> { + let dst = dst.get_mut(..Self::LEN).ok_or(ProgramError::AccountDataTooSmall)?; + dst[0..32].copy_from_slice(&self.maker); + dst[32..64].copy_from_slice(&self.mint_to_raise); + dst[64..72].copy_from_slice(&self.amount_to_raise.to_le_bytes()); + dst[72..80].copy_from_slice(&self.current_amount.to_le_bytes()); + dst[80..88].copy_from_slice(&self.time_started.to_le_bytes()); + dst[88..90].copy_from_slice(&self.duration.to_le_bytes()); + dst[90] = self.bump; + Ok(()) + } + + /// Reads a fundraiser from `src`, which must be at least [`Fundraiser::LEN`] bytes. + pub fn deserialize(src: &[u8]) -> Result { + let src: &[u8; Self::LEN] = + src.get(..Self::LEN).and_then(|s| s.try_into().ok()).ok_or(ProgramError::InvalidAccountData)?; + Ok(Self { + maker: src[0..32].try_into().unwrap(), + mint_to_raise: src[32..64].try_into().unwrap(), + amount_to_raise: u64::from_le_bytes(src[64..72].try_into().unwrap()), + current_amount: u64::from_le_bytes(src[72..80].try_into().unwrap()), + time_started: i64::from_le_bytes(src[80..88].try_into().unwrap()), + duration: u16::from_le_bytes(src[88..90].try_into().unwrap()), + bump: src[90], + }) + } +} + +/// Per-contributor running total, stored in the contributor PDA derived from +/// `[b"contributor", fundraiser, contributor]`. +/// +/// Tracking each contributor separately is what makes the per-contributor cap +/// enforceable and lets an expired campaign refund exactly what each wallet put +/// in. +/// +/// Serialized byte layout: `[amount: u64]`. +pub struct Contributor { + /// Total contributed by this wallet, in base units of the campaign mint. + pub amount: u64, +} + +impl Contributor { + /// Seed prefix for the contributor PDA: `[SEED_PREFIX, fundraiser, contributor]`. + pub const SEED_PREFIX: &'static [u8] = b"contributor"; + + /// Serialized size of a `Contributor` in bytes. + pub const LEN: usize = 8; + + /// Writes the contributor total into `dst`. + pub fn serialize(&self, dst: &mut [u8]) -> Result<(), ProgramError> { + let dst = dst.get_mut(..Self::LEN).ok_or(ProgramError::AccountDataTooSmall)?; + dst[0..8].copy_from_slice(&self.amount.to_le_bytes()); + Ok(()) + } + + /// Reads a contributor total from `src`. + pub fn deserialize(src: &[u8]) -> Result { + let src: &[u8; Self::LEN] = + src.get(..Self::LEN).and_then(|s| s.try_into().ok()).ok_or(ProgramError::InvalidAccountData)?; + Ok(Self { amount: u64::from_le_bytes(*src) }) + } +} diff --git a/tokens/token-fundraiser/pinocchio/tests/account.ts b/tokens/token-fundraiser/pinocchio/tests/account.ts new file mode 100644 index 000000000..0a724bee5 --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/tests/account.ts @@ -0,0 +1,22 @@ +import { + getAddressDecoder, + getI64Decoder, + getStructDecoder, + getU16Decoder, + getU8Decoder, + getU64Decoder, +} from '@solana/kit'; + +// Account data layout, matching the program's `Fundraiser` struct. +export const fundraiserDecoder = getStructDecoder([ + ['maker', getAddressDecoder()], + ['mint_to_raise', getAddressDecoder()], + ['amount_to_raise', getU64Decoder()], + ['current_amount', getU64Decoder()], + ['time_started', getI64Decoder()], + ['duration', getU16Decoder()], + ['bump', getU8Decoder()], +]); + +// Account data layout, matching the program's `Contributor` struct. +export const contributorDecoder = getStructDecoder([['amount', getU64Decoder()]]); diff --git a/tokens/token-fundraiser/pinocchio/tests/instruction.ts b/tokens/token-fundraiser/pinocchio/tests/instruction.ts new file mode 100644 index 000000000..a33da958e --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/tests/instruction.ts @@ -0,0 +1,164 @@ +import { + AccountRole, + type Address, + getStructEncoder, + getU16Encoder, + getU8Encoder, + getU64Encoder, + type KeyPairSigner, +} from '@solana/kit'; +import { SYSTEM_PROGRAM_ADDRESS } from '@solana-program/system'; +import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS, TOKEN_PROGRAM_ADDRESS } from '@solana-program/token'; + +export enum FundraiserInstruction { + Initialize = 0, + Contribute = 1, + CheckContributions = 2, + Refund = 3, +} + +// PDA bumps are derived client-side and passed in the instruction data, which +// the program re-checks with `create_program_address`. This keeps the expensive +// `find_program_address` search off-chain. +const initializeEncoder = getStructEncoder([ + ['instruction', getU8Encoder()], + ['amount', getU64Encoder()], + ['duration', getU16Encoder()], + ['bump', getU8Encoder()], +]); + +const contributeEncoder = getStructEncoder([ + ['instruction', getU8Encoder()], + ['amount', getU64Encoder()], + ['contributor_bump', getU8Encoder()], +]); + +const checkContributionsEncoder = getStructEncoder([['instruction', getU8Encoder()]]); + +const refundEncoder = getStructEncoder([ + ['instruction', getU8Encoder()], + ['contributor_bump', getU8Encoder()], +]); + +export function buildInitialize(props: { + amount: bigint; + duration: number; + bump: number; + maker: KeyPairSigner; + mint: Address; + fundraiser: Address; + vault: Address; + programId: Address; +}) { + const data = initializeEncoder.encode({ + instruction: FundraiserInstruction.Initialize, + amount: props.amount, + duration: props.duration, + bump: props.bump, + }); + + return { + programAddress: props.programId, + accounts: [ + { address: props.maker.address, role: AccountRole.WRITABLE_SIGNER, signer: props.maker }, + { address: props.mint, role: AccountRole.READONLY }, + { address: props.fundraiser, role: AccountRole.WRITABLE }, + { address: props.vault, role: AccountRole.WRITABLE }, + { address: TOKEN_PROGRAM_ADDRESS, role: AccountRole.READONLY }, + { address: ASSOCIATED_TOKEN_PROGRAM_ADDRESS, role: AccountRole.READONLY }, + { address: SYSTEM_PROGRAM_ADDRESS, role: AccountRole.READONLY }, + ], + data, + }; +} + +export function buildContribute(props: { + amount: bigint; + contributorBump: number; + contributor: KeyPairSigner; + mint: Address; + fundraiser: Address; + contributorAccount: Address; + contributorAta: Address; + vault: Address; + programId: Address; +}) { + const data = contributeEncoder.encode({ + instruction: FundraiserInstruction.Contribute, + amount: props.amount, + contributor_bump: props.contributorBump, + }); + + return { + programAddress: props.programId, + accounts: [ + { address: props.contributor.address, role: AccountRole.WRITABLE_SIGNER, signer: props.contributor }, + { address: props.mint, role: AccountRole.READONLY }, + { address: props.fundraiser, role: AccountRole.WRITABLE }, + { address: props.contributorAccount, role: AccountRole.WRITABLE }, + { address: props.contributorAta, role: AccountRole.WRITABLE }, + { address: props.vault, role: AccountRole.WRITABLE }, + { address: TOKEN_PROGRAM_ADDRESS, role: AccountRole.READONLY }, + { address: SYSTEM_PROGRAM_ADDRESS, role: AccountRole.READONLY }, + ], + data, + }; +} + +export function buildCheckContributions(props: { + maker: KeyPairSigner; + mint: Address; + fundraiser: Address; + vault: Address; + makerAta: Address; + programId: Address; +}) { + const data = checkContributionsEncoder.encode({ + instruction: FundraiserInstruction.CheckContributions, + }); + + return { + programAddress: props.programId, + accounts: [ + { address: props.maker.address, role: AccountRole.WRITABLE_SIGNER, signer: props.maker }, + { address: props.mint, role: AccountRole.READONLY }, + { address: props.fundraiser, role: AccountRole.WRITABLE }, + { address: props.vault, role: AccountRole.WRITABLE }, + { address: props.makerAta, role: AccountRole.WRITABLE }, + { address: TOKEN_PROGRAM_ADDRESS, role: AccountRole.READONLY }, + { address: ASSOCIATED_TOKEN_PROGRAM_ADDRESS, role: AccountRole.READONLY }, + { address: SYSTEM_PROGRAM_ADDRESS, role: AccountRole.READONLY }, + ], + data, + }; +} + +export function buildRefund(props: { + contributorBump: number; + contributor: KeyPairSigner; + mint: Address; + fundraiser: Address; + contributorAccount: Address; + contributorAta: Address; + vault: Address; + programId: Address; +}) { + const data = refundEncoder.encode({ + instruction: FundraiserInstruction.Refund, + contributor_bump: props.contributorBump, + }); + + return { + programAddress: props.programId, + accounts: [ + { address: props.contributor.address, role: AccountRole.WRITABLE_SIGNER, signer: props.contributor }, + { address: props.mint, role: AccountRole.READONLY }, + { address: props.fundraiser, role: AccountRole.WRITABLE }, + { address: props.contributorAccount, role: AccountRole.WRITABLE }, + { address: props.contributorAta, role: AccountRole.WRITABLE }, + { address: props.vault, role: AccountRole.WRITABLE }, + { address: TOKEN_PROGRAM_ADDRESS, role: AccountRole.READONLY }, + ], + data, + }; +} diff --git a/tokens/token-fundraiser/pinocchio/tests/test.ts b/tokens/token-fundraiser/pinocchio/tests/test.ts new file mode 100644 index 000000000..c5cb997f9 --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/tests/test.ts @@ -0,0 +1,560 @@ +import { type Address, generateKeyPairSigner, type KeyPairSigner, lamports } from '@solana/kit'; +import { getCreateAccountInstruction } from '@solana-program/system'; +import { + getInitializeAccount3Instruction, + getTokenDecoder, + getTokenSize, + TOKEN_PROGRAM_ADDRESS, +} from '@solana-program/token'; +import { assert } from 'chai'; +import { LiteSVM } from 'litesvm'; +import { contributorDecoder, fundraiserDecoder } from './account'; +import { buildCheckContributions, buildContribute, buildInitialize, buildRefund } from './instruction'; +import { + createFundedHolder, + createMint, + DECIMALS, + expectFailure, + expectProgramError, + findAta, + findContributorPda, + findFundraiserPda, + FundraiserError, + SECONDS_PER_DAY, + sendInstructions, + warpTo, +} from './utils'; + +// 10 tokens at 6 decimals. The per-contributor cap is 10% of the target, so a +// campaign always needs at least ten distinct contributors to succeed. +const TARGET = 10_000_000n; +const CAP = TARGET / 10n; +const DURATION_DAYS = 1; + +// Everything each contributor needs: their wallet, their token account, and the +// contributor PDA that tracks how much they have put in. +interface Contributor { + wallet: KeyPairSigner; + ata: Address; + account: Address; + bump: number; +} + +// A campaign under test. +interface Campaign { + maker: KeyPairSigner; + makerAta: Address; + fundraiser: Address; + bump: number; + vault: Address; +} + +describe('Token fundraiser (Pinocchio)', () => { + let svm: LiteSVM; + let programId: Address; + let payer: KeyPairSigner; + let mint: Address; + + // The successful campaign, the campaign that expires unfunded, and the + // campaign that expires already funded. + let success: Campaign; + let expired: Campaign; + let funded: Campaign; + + let contributors: Contributor[]; + let expiredContributor: Contributor; + let fundedContributors: Contributor[]; + + const tokenBalance = (account: Address): bigint => { + const info = svm.getAccount(account); + if (!info.exists) throw new Error(`token account ${account} not found`); + return getTokenDecoder().decode(info.data).amount; + }; + + const readFundraiser = (account: Address) => { + const info = svm.getAccount(account); + if (!info.exists) throw new Error(`fundraiser ${account} not found`); + return fundraiserDecoder.decode(info.data); + }; + + const readContributor = (account: Address) => { + const info = svm.getAccount(account); + if (!info.exists) throw new Error(`contributor ${account} not found`); + return contributorDecoder.decode(info.data); + }; + + // Builds a campaign's addresses and registers a maker token account. + async function makeCampaign(): Promise { + const maker = await generateKeyPairSigner(); + svm.airdrop(maker.address, lamports(1_000_000_000n)); + const [fundraiser, bump] = await findFundraiserPda(programId, maker.address); + return { + maker, + makerAta: await findAta(mint, maker.address), + fundraiser, + bump, + vault: await findAta(mint, fundraiser), + }; + } + + // Creates a funded wallet plus its contributor PDA for `campaign`. + async function makeContributor(campaign: Campaign, balance = 5_000_000n): Promise { + const { holder, ata } = await createFundedHolder(svm, payer, mint, balance); + const [account, bump] = await findContributorPda(programId, campaign.fundraiser, holder.address); + return { wallet: holder, ata, account, bump }; + } + + const contributeIx = (campaign: Campaign, contributor: Contributor, amount: bigint) => + buildContribute({ + amount, + contributorBump: contributor.bump, + contributor: contributor.wallet, + mint, + fundraiser: campaign.fundraiser, + contributorAccount: contributor.account, + contributorAta: contributor.ata, + vault: campaign.vault, + programId, + }); + + const initializeIx = (campaign: Campaign, amount = TARGET, duration = DURATION_DAYS) => + buildInitialize({ + amount, + duration, + bump: campaign.bump, + maker: campaign.maker, + mint, + fundraiser: campaign.fundraiser, + vault: campaign.vault, + programId, + }); + + before(async () => { + programId = (await generateKeyPairSigner()).address; + + svm = new LiteSVM(); + svm.addProgramFromFile(programId, 'tests/fixtures/fundraiser_pinocchio_program.so'); + + payer = await generateKeyPairSigner(); + svm.airdrop(payer.address, lamports(100_000_000_000n)); + + const mintKeypair = await generateKeyPairSigner(); + await createMint(svm, payer, mintKeypair); + mint = mintKeypair.address; + + success = await makeCampaign(); + expired = await makeCampaign(); + funded = await makeCampaign(); + + // Ten contributors, so the campaign can reach its target under the cap. + contributors = []; + for (let i = 0; i < 10; i++) { + contributors.push(await makeContributor(success)); + } + expiredContributor = await makeContributor(expired); + fundedContributors = []; + for (let i = 0; i < 10; i++) { + fundedContributors.push(await makeContributor(funded)); + } + }); + + describe('Initialize', () => { + it('rejects a target below the minimum', async () => { + // The minimum is MIN_AMOUNT_TO_RAISE.pow(decimals) = 3^6 = 729. + const campaign = await makeCampaign(); + await expectProgramError(svm, payer, [initializeIx(campaign, 728n)], FundraiserError.InvalidAmount); + }); + + it('opens a campaign', async () => { + // LiteSVM's clock starts at unix timestamp 0, so the start time is + // checked against the clock rather than assumed to be non-zero. + const startedAt = svm.getClock().unixTimestamp; + + await sendInstructions(svm, payer, [initializeIx(success)]); + + const state = readFundraiser(success.fundraiser); + assert.strictEqual(state.maker, success.maker.address, 'maker not recorded'); + assert.strictEqual(state.mint_to_raise, mint, 'mint not recorded'); + assert.strictEqual(state.amount_to_raise, TARGET, 'target not recorded'); + assert.strictEqual(state.current_amount, 0n, 'a new campaign should have raised nothing'); + assert.strictEqual(state.duration, DURATION_DAYS, 'duration not recorded'); + assert.strictEqual(state.bump, success.bump, 'bump not recorded'); + assert.strictEqual(state.time_started, startedAt, 'start time not taken from the clock sysvar'); + + // The vault is created empty and owned by the fundraiser PDA. + const vault = svm.getAccount(success.vault); + assert.isTrue(vault.exists, 'vault not created'); + const vaultAccount = getTokenDecoder().decode(vault.data); + assert.strictEqual(vaultAccount.amount, 0n, 'a new vault should be empty'); + assert.strictEqual(vaultAccount.owner, success.fundraiser, 'the vault must be owned by the fundraiser PDA'); + assert.strictEqual(vaultAccount.mint, mint, 'vault holds the wrong mint'); + }); + + it('rejects a target on a mint whose decimals overflow the minimum calculation', async () => { + // Mint decimals are a u8 and SPL Token does not cap them, so a + // campaign can be opened against a mint with decimals = 41. The + // minimum target is 3^decimals, and 3^41 overflows u64: with + // overflow-checks on in the release profile, an unchecked `pow` + // aborts the program (ProgramFailedToComplete) instead of + // returning. `checked_pow` turns that into a clean error. + const oddMintKeypair = await generateKeyPairSigner(); + await createMint(svm, payer, oddMintKeypair, 41); + + const oddMaker = await generateKeyPairSigner(); + svm.airdrop(oddMaker.address, lamports(1_000_000_000n)); + const [oddFundraiser, oddBump] = await findFundraiserPda(programId, oddMaker.address); + const oddVault = await findAta(oddMintKeypair.address, oddFundraiser); + + await expectProgramError( + svm, + payer, + [ + buildInitialize({ + amount: 18_000_000_000_000_000_000n, + duration: DURATION_DAYS, + bump: oddBump, + maker: oddMaker, + mint: oddMintKeypair.address, + fundraiser: oddFundraiser, + vault: oddVault, + programId, + }), + ], + FundraiserError.InvalidAmount, + ); + }); + + it('rejects a fundraiser account that is not the derived PDA', async () => { + // A caller substituting their own account for the fundraiser PDA + // would otherwise get a program-owned account they control. + const campaign = await makeCampaign(); + const impostor = await generateKeyPairSigner(); + await expectFailure(svm, payer, [ + buildInitialize({ + amount: TARGET, + duration: DURATION_DAYS, + bump: campaign.bump, + maker: campaign.maker, + mint, + fundraiser: impostor.address, + vault: campaign.vault, + programId, + }), + ]); + }); + }); + + describe('Contribute', () => { + it('rejects a zero contribution', async () => { + await expectProgramError( + svm, + payer, + [contributeIx(success, contributors[0], 0n)], + FundraiserError.ContributionTooSmall, + ); + }); + + it('rejects a single contribution above the per-contributor cap', async () => { + await expectProgramError( + svm, + payer, + [contributeIx(success, contributors[0], CAP + 1n)], + FundraiserError.ContributionTooBig, + ); + }); + + it('rejects a substitute vault', async () => { + // A token account for the campaign mint owned by the fundraiser PDA + // is creatable by anyone - a token account's owner field never needs + // the owner to sign. Without the ATA derivation check, contributions + // could be routed into an attacker-chosen account. + const decoyVault = await generateKeyPairSigner(); + const tokenSize = BigInt(getTokenSize()); + await sendInstructions(svm, payer, [ + getCreateAccountInstruction({ + payer, + newAccount: decoyVault, + space: tokenSize, + lamports: svm.minimumBalanceForRentExemption(tokenSize), + programAddress: TOKEN_PROGRAM_ADDRESS, + }), + ]); + await sendInstructions(svm, payer, [ + getInitializeAccount3Instruction({ + account: decoyVault.address, + mint, + owner: success.fundraiser, + }), + ]); + + await expectProgramError( + svm, + payer, + [ + buildContribute({ + amount: CAP, + contributorBump: contributors[0].bump, + contributor: contributors[0].wallet, + mint, + fundraiser: success.fundraiser, + contributorAccount: contributors[0].account, + contributorAta: contributors[0].ata, + vault: decoyVault.address, + programId, + }), + ], + FundraiserError.InvalidAccount, + ); + }); + + it("rejects a contributor token account that is not the signer's own ATA", async () => { + await expectProgramError( + svm, + payer, + [ + buildContribute({ + amount: CAP, + contributorBump: contributors[0].bump, + contributor: contributors[0].wallet, + mint, + fundraiser: success.fundraiser, + contributorAccount: contributors[0].account, + contributorAta: contributors[1].ata, + vault: success.vault, + programId, + }), + ], + FundraiserError.InvalidAccount, + ); + }); + + it('rejects a contributor account that is not the derived PDA', async () => { + await expectFailure(svm, payer, [ + buildContribute({ + amount: CAP, + contributorBump: contributors[1].bump, + contributor: contributors[0].wallet, + mint, + fundraiser: success.fundraiser, + contributorAccount: contributors[1].account, + contributorAta: contributors[0].ata, + vault: success.vault, + programId, + }), + ]); + }); + + it('records a contribution and creates the contributor account', async () => { + const balanceBefore = tokenBalance(contributors[0].ata); + + await sendInstructions(svm, payer, [contributeIx(success, contributors[0], 400_000n)]); + + assert.strictEqual(tokenBalance(success.vault), 400_000n, 'vault did not receive the contribution'); + assert.strictEqual( + tokenBalance(contributors[0].ata), + balanceBefore - 400_000n, + 'contribution not debited from the contributor', + ); + assert.strictEqual( + readFundraiser(success.fundraiser).current_amount, + 400_000n, + 'campaign total not updated', + ); + assert.strictEqual( + readContributor(contributors[0].account).amount, + 400_000n, + 'contributor total not updated', + ); + }); + + it('accumulates repeat contributions from the same wallet', async () => { + await sendInstructions(svm, payer, [contributeIx(success, contributors[0], 600_000n)]); + + assert.strictEqual(tokenBalance(success.vault), CAP, 'vault total wrong after the second contribution'); + assert.strictEqual( + readContributor(contributors[0].account).amount, + CAP, + 'contributor total should accumulate across contributions', + ); + assert.strictEqual( + readFundraiser(success.fundraiser).current_amount, + CAP, + 'campaign total should accumulate across contributions', + ); + }); + + it('rejects contributions that would push a wallet past the cap', async () => { + // This wallet is already at the cap, so even one more base unit is + // rejected - the cap is on the running total, not per transaction. + await expectProgramError( + svm, + payer, + [contributeIx(success, contributors[0], 1n)], + FundraiserError.MaximumContributionsReached, + ); + }); + }); + + describe('CheckContributions', () => { + it('rejects a payout while the target is unmet', async () => { + await expectProgramError( + svm, + payer, + [ + buildCheckContributions({ + maker: success.maker, + mint, + fundraiser: success.fundraiser, + vault: success.vault, + makerAta: success.makerAta, + programId, + }), + ], + FundraiserError.TargetNotMet, + ); + }); + + it('reaches the target with the remaining contributors', async () => { + for (let i = 1; i < 10; i++) { + await sendInstructions(svm, payer, [contributeIx(success, contributors[i], CAP)]); + } + + assert.strictEqual(tokenBalance(success.vault), TARGET, 'vault should hold exactly the target'); + assert.strictEqual( + readFundraiser(success.fundraiser).current_amount, + TARGET, + 'campaign total should equal the target', + ); + }); + + it('rejects a non-maker signer', async () => { + // The impostor signs, but the campaign records who its maker is. + await expectProgramError( + svm, + payer, + [ + buildCheckContributions({ + maker: contributors[0].wallet, + mint, + fundraiser: success.fundraiser, + vault: success.vault, + makerAta: await findAta(mint, contributors[0].wallet.address), + programId, + }), + ], + FundraiserError.InvalidAccount, + ); + }); + + it('pays the maker and closes the campaign once the target is met', async () => { + await sendInstructions(svm, payer, [ + buildCheckContributions({ + maker: success.maker, + mint, + fundraiser: success.fundraiser, + vault: success.vault, + makerAta: success.makerAta, + programId, + }), + ]); + + assert.strictEqual(tokenBalance(success.makerAta), TARGET, 'the maker should receive the full raise'); + assert.isFalse(svm.getAccount(success.vault).exists, 'the vault should be closed'); + assert.isFalse(svm.getAccount(success.fundraiser).exists, 'the fundraiser account should be closed'); + }); + }); + + describe('Refund', () => { + let deadline: bigint; + + before(async () => { + // Both campaigns open before any clock warp, so their deadlines are + // in the future at this point. + await sendInstructions(svm, payer, [initializeIx(expired)]); + await sendInstructions(svm, payer, [initializeIx(funded)]); + + await sendInstructions(svm, payer, [contributeIx(expired, expiredContributor, CAP)]); + + // Fill the second campaign all the way to its target. + for (const contributor of fundedContributors) { + await sendInstructions(svm, payer, [contributeIx(funded, contributor, CAP)]); + } + + const state = readFundraiser(expired.fundraiser); + deadline = state.time_started + BigInt(state.duration) * SECONDS_PER_DAY; + }); + + const refundIx = (campaign: Campaign, contributor: Contributor, contributorAta = contributor.ata) => + buildRefund({ + contributorBump: contributor.bump, + contributor: contributor.wallet, + mint, + fundraiser: campaign.fundraiser, + contributorAccount: contributor.account, + contributorAta, + vault: campaign.vault, + programId, + }); + + it('rejects a refund before the campaign has ended', async () => { + await expectProgramError( + svm, + payer, + [refundIx(expired, expiredContributor)], + FundraiserError.FundraiserNotEnded, + ); + }); + + it('closes to contributions exactly at the deadline', async () => { + // Warps to the deadline itself, not past it, so this pins the + // off-by-one: the campaign is over the moment `duration` days have + // elapsed, not a second later. + warpTo(svm, deadline); + + await expectProgramError( + svm, + payer, + [contributeIx(expired, expiredContributor, 1n)], + FundraiserError.FundraiserEnded, + ); + }); + + it('rejects a refund redirected to another wallet token account', async () => { + // The contributor still signs, but the destination was swapped. Only + // the ATA derivation check catches this. + await expectProgramError( + svm, + payer, + [refundIx(expired, expiredContributor, contributors[0].ata)], + FundraiserError.InvalidAccount, + ); + }); + + it('refunds the contributor and closes their account', async () => { + const balanceBefore = tokenBalance(expiredContributor.ata); + + await sendInstructions(svm, payer, [refundIx(expired, expiredContributor)]); + + assert.strictEqual( + tokenBalance(expiredContributor.ata), + balanceBefore + CAP, + 'the contributor should get their full contribution back', + ); + assert.strictEqual(tokenBalance(expired.vault), 0n, 'the vault should be drained'); + assert.isFalse( + svm.getAccount(expiredContributor.account).exists, + 'the contributor account should be closed', + ); + assert.strictEqual( + readFundraiser(expired.fundraiser).current_amount, + 0n, + 'the refund should be deducted from the campaign total', + ); + }); + + it('rejects a refund from a campaign that reached its target', async () => { + // Expired, but fully funded: the raise belongs to the maker. + await expectProgramError(svm, payer, [refundIx(funded, fundedContributors[0])], FundraiserError.TargetMet); + }); + }); +}); diff --git a/tokens/token-fundraiser/pinocchio/tests/utils.ts b/tokens/token-fundraiser/pinocchio/tests/utils.ts new file mode 100644 index 000000000..072036ac0 --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/tests/utils.ts @@ -0,0 +1,177 @@ +import { + type Address, + appendTransactionMessageInstructions, + createTransactionMessage, + generateKeyPairSigner, + getAddressEncoder, + getProgramDerivedAddress, + type Instruction, + type KeyPairSigner, + lamports, + pipe, + setTransactionMessageFeePayerSigner, + signTransactionMessageWithSigners, +} from '@solana/kit'; +import { getCreateAccountInstruction } from '@solana-program/system'; +import { + findAssociatedTokenPda, + getCreateAssociatedTokenIdempotentInstruction, + getInitializeMint2Instruction, + getMintSize, + getMintToInstruction, + TOKEN_PROGRAM_ADDRESS, +} from '@solana-program/token'; +import { FailedTransactionMetadata, type LiteSVM } from 'litesvm'; + +const addressEncoder = getAddressEncoder(); + +/// Mirrors the `FundraiserError` enum in the program. `ProgramError::Custom(n)` +/// carries the variant's discriminant, so the tests can pin the exact reason a +/// transaction was rejected instead of just "it failed". +export enum FundraiserError { + TargetNotMet = 0, + TargetMet = 1, + ContributionTooBig = 2, + ContributionTooSmall = 3, + MaximumContributionsReached = 4, + FundraiserNotEnded = 5, + FundraiserEnded = 6, + InvalidAmount = 7, + InvalidAccount = 8, + ArithmeticOverflow = 9, +} + +export const DECIMALS = 6; +export const SECONDS_PER_DAY = 86400n; + +async function buildSignedTransaction(svm: LiteSVM, payer: KeyPairSigner, instructions: readonly Instruction[]) { + // Several tests send byte-identical transactions (the same rejected call + // retried, or a call that failed and then succeeds). litesvm rejects a + // repeated signature as AlreadyProcessed, so every send starts from a fresh + // blockhash. + svm.expireBlockhash(); + const transactionMessage = pipe( + createTransactionMessage({ version: 0 }), + m => setTransactionMessageFeePayerSigner(payer, m), + m => svm.setTransactionMessageLifetimeUsingLatestBlockhash(m), + m => appendTransactionMessageInstructions(instructions, m), + ); + return signTransactionMessageWithSigners(transactionMessage); +} + +/// Sends a transaction and throws if it failed. +export async function sendInstructions(svm: LiteSVM, payer: KeyPairSigner, instructions: readonly Instruction[]) { + const signedTx = await buildSignedTransaction(svm, payer, instructions); + const result = svm.sendTransaction(signedTx); + if (result instanceof FailedTransactionMetadata) { + throw new Error(`transaction failed: ${result.toString()}`); + } + return result; +} + +/// Sends a transaction expected to fail, and asserts it failed with `expected`. +/// +/// litesvm never throws on a failed transaction, so the failure has to be +/// detected with an `instanceof` check rather than a try/catch. +export async function expectProgramError( + svm: LiteSVM, + payer: KeyPairSigner, + instructions: readonly Instruction[], + expected: FundraiserError, +) { + const signedTx = await buildSignedTransaction(svm, payer, instructions); + const result = svm.sendTransaction(signedTx); + if (!(result instanceof FailedTransactionMetadata)) { + throw new Error(`expected the transaction to fail with Custom(${expected}), but it succeeded`); + } + // litesvm renders this as `InstructionErrorCustom { code: N }`; older + // formats print `Custom(N)`. Pull the code out rather than matching prose. + const message = result.err().toString(); + const code = message.match(/code:\s*(\d+)/)?.[1] ?? message.match(/Custom\((\d+)\)/)?.[1]; + if (code === undefined || Number(code) !== expected) { + throw new Error( + `expected the transaction to fail with ${FundraiserError[expected]} (${expected}), got: ${message}`, + ); + } +} + +/// Sends a transaction expected to fail for a reason the runtime raises rather +/// than the program (missing signature, bad seeds, wrong owner). +export async function expectFailure(svm: LiteSVM, payer: KeyPairSigner, instructions: readonly Instruction[]) { + const signedTx = await buildSignedTransaction(svm, payer, instructions); + const result = svm.sendTransaction(signedTx); + if (!(result instanceof FailedTransactionMetadata)) { + throw new Error('expected the transaction to fail, but it succeeded'); + } +} + +/// Creates a mint with `payer` as the mint authority. +export async function createMint(svm: LiteSVM, payer: KeyPairSigner, mintKeypair: KeyPairSigner, decimals = DECIMALS) { + await sendInstructions(svm, payer, [ + getCreateAccountInstruction({ + payer, + newAccount: mintKeypair, + lamports: svm.minimumBalanceForRentExemption(BigInt(getMintSize())), + space: getMintSize(), + programAddress: TOKEN_PROGRAM_ADDRESS, + }), + getInitializeMint2Instruction({ + mint: mintKeypair.address, + decimals, + mintAuthority: payer.address, + freezeAuthority: payer.address, + }), + ]); +} + +/// Creates a funded wallet holding `amount` base units of `mint`. +export async function createFundedHolder( + svm: LiteSVM, + payer: KeyPairSigner, + mint: Address, + amount: bigint, +): Promise<{ holder: KeyPairSigner; ata: Address }> { + const holder = await generateKeyPairSigner(); + svm.airdrop(holder.address, lamports(1_000_000_000n)); + + const [ata] = await findAssociatedTokenPda({ mint, owner: holder.address, tokenProgram: TOKEN_PROGRAM_ADDRESS }); + + await sendInstructions(svm, payer, [ + getCreateAssociatedTokenIdempotentInstruction({ payer, ata, owner: holder.address, mint }), + ]); + + if (amount > 0n) { + await sendInstructions(svm, payer, [getMintToInstruction({ mint, token: ata, mintAuthority: payer, amount })]); + } + + return { holder, ata }; +} + +/// Derives the fundraiser PDA: `[b"fundraiser", maker]`. +export function findFundraiserPda(programId: Address, maker: Address) { + return getProgramDerivedAddress({ + programAddress: programId, + seeds: ['fundraiser', addressEncoder.encode(maker)], + }); +} + +/// Derives the contributor PDA: `[b"contributor", fundraiser, contributor]`. +export function findContributorPda(programId: Address, fundraiser: Address, contributor: Address) { + return getProgramDerivedAddress({ + programAddress: programId, + seeds: ['contributor', addressEncoder.encode(fundraiser), addressEncoder.encode(contributor)], + }); +} + +/// Derives an associated token account address. +export async function findAta(mint: Address, owner: Address): Promise
{ + const [ata] = await findAssociatedTokenPda({ mint, owner, tokenProgram: TOKEN_PROGRAM_ADDRESS }); + return ata; +} + +/// Moves the validator clock to `timestamp`. +export function warpTo(svm: LiteSVM, timestamp: bigint) { + const clock = svm.getClock(); + clock.unixTimestamp = timestamp; + svm.setClock(clock); +} diff --git a/tokens/token-fundraiser/pinocchio/tsconfig.json b/tokens/token-fundraiser/pinocchio/tsconfig.json new file mode 100644 index 000000000..c02443141 --- /dev/null +++ b/tokens/token-fundraiser/pinocchio/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "types": ["mocha", "chai", "node"], + "typeRoots": ["./node_modules/@types"], + "lib": ["esnext"], + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "resolveJsonModule": true, + "allowImportingTsExtensions": true, + "noEmit": true, + "skipLibCheck": true + } +}