From 82406e2765cb503a43256b77ca25bfbab9651e30 Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Thu, 5 Nov 2026 23:13:34 +0000 Subject: [PATCH 1/2] Rename the Whitelist transfer hook to Allow List and remove its size limit The whitelist kept every allowed address in one account, capped at eleven entries and shared by every mint. Its setup instruction used `init_if_needed` and made the caller the list's authority each time, so anyone could take over the list by setting up the hook for a mint of their own. Allow List gives each mint its own allow list account (PDA seeds "allow_list", mint) holding the authority, and each allowed wallet its own account (PDA seeds "allowed_wallet", mint, wallet). `allow_wallet` creates a wallet's account and `disallow_wallet` closes it. Setup now requires the mint authority's signature and uses `init`. The hook's one extra account is derived per transfer from the mint and the destination token account's owner, and a transfer to a wallet without an account fails with `RecipientNotAllowed` instead of panicking. All three variants (Anchor v1, Anchor v2, Quasar) change together, with tests for allowing, disallowing, per-mint scoping, fifty allowed wallets, and the takeover attempts. The lending and managed-fund docs say "allow list" too. Claude-Session: https://claude.ai/code/session_01GZHin7uZN95tCycB2vWhog --- CHANGELOG.md | 32 ++ Cargo.lock | 20 +- Cargo.toml | 2 +- README.md | 12 +- finance/lending/anchor-v1/README.md | 2 +- finance/lending/anchor/README.md | 2 +- finance/managed-fund/anchor-v1/PRODUCT.md | 2 +- finance/managed-fund/anchor/PRODUCT.md | 2 +- scripts/generate-quasar-readmes.mjs | 11 +- .../anchor-v1/.gitignore | 0 .../anchor-v1/.prettierignore | 0 .../anchor-v1/Anchor.toml | 0 .../anchor-v1/Cargo.toml | 0 .../allow-list/anchor-v1/README.md | 61 +++ .../programs/transfer-hook/Cargo.toml | 2 +- .../programs/transfer-hook/Xargo.toml | 0 .../src/instructions/allow_wallet.rs | 31 ++ .../src/instructions/disallow_wallet.rs | 28 ++ .../initialize_extra_account_meta_list.rs | 26 +- .../transfer-hook/src/instructions/mod.rs | 6 +- .../src/instructions/transfer_hook.rs | 29 +- .../programs/transfer-hook/src/lib.rs | 54 ++- .../transfer-hook/tests/test_transfer_hook.rs | 404 +++++++++++++++++ .../anchor/.gitignore | 0 .../anchor/.prettierignore | 0 .../anchor/Anchor.toml | 0 .../anchor/Cargo.toml | 0 .../transfer-hook/allow-list/anchor/README.md | 61 +++ .../anchor/programs/transfer-hook/Cargo.toml | 2 +- .../anchor/programs/transfer-hook/Xargo.toml | 0 .../programs/transfer-hook/src/entrypoint.rs | 0 .../src/instructions/allow_wallet.rs | 31 ++ .../src/instructions/disallow_wallet.rs | 28 ++ .../initialize_extra_account_meta_list.rs | 23 +- .../transfer-hook/src/instructions/mod.rs | 6 +- .../src/instructions/transfer_hook.rs | 29 +- .../anchor/programs/transfer-hook/src/lib.rs | 54 ++- .../transfer-hook/tests/test_transfer_hook.rs | 403 +++++++++++++++++ .../quasar/Cargo.toml | 2 +- .../quasar/Quasar.toml | 2 +- .../transfer-hook/allow-list/quasar/README.md | 55 +++ .../allow-list/quasar/src/lib.rs | 406 ++++++++++++++++++ .../allow-list/quasar/src/tests.rs | 214 +++++++++ .../whitelist/anchor-v1/README.md | 11 - .../src/instructions/add_to_whitelist.rs | 40 -- .../transfer-hook/tests/test_transfer_hook.rs | 138 ------ .../transfer-hook/whitelist/anchor/README.md | 11 - .../src/instructions/add_to_whitelist.rs | 40 -- .../transfer-hook/tests/test_transfer_hook.rs | 136 ------ .../transfer-hook/whitelist/quasar/README.md | 34 -- .../transfer-hook/whitelist/quasar/src/lib.rs | 265 ------------ .../whitelist/quasar/src/tests.rs | 72 ---- 52 files changed, 1943 insertions(+), 846 deletions(-) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor-v1/.gitignore (100%) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor-v1/.prettierignore (100%) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor-v1/Anchor.toml (100%) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor-v1/Cargo.toml (100%) create mode 100644 tokens/token-extensions/transfer-hook/allow-list/anchor-v1/README.md rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor-v1/programs/transfer-hook/Cargo.toml (91%) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor-v1/programs/transfer-hook/Xargo.toml (100%) create mode 100644 tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/allow_wallet.rs create mode 100644 tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/disallow_wallet.rs rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor-v1/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs (65%) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor-v1/programs/transfer-hook/src/instructions/mod.rs (58%) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor-v1/programs/transfer-hook/src/instructions/transfer_hook.rs (56%) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor-v1/programs/transfer-hook/src/lib.rs (62%) create mode 100644 tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor/.gitignore (100%) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor/.prettierignore (100%) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor/Anchor.toml (100%) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor/Cargo.toml (100%) create mode 100644 tokens/token-extensions/transfer-hook/allow-list/anchor/README.md rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor/programs/transfer-hook/Cargo.toml (98%) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor/programs/transfer-hook/Xargo.toml (100%) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor/programs/transfer-hook/src/entrypoint.rs (100%) create mode 100644 tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/allow_wallet.rs create mode 100644 tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/disallow_wallet.rs rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs (71%) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor/programs/transfer-hook/src/instructions/mod.rs (58%) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs (56%) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/anchor/programs/transfer-hook/src/lib.rs (64%) create mode 100644 tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/tests/test_transfer_hook.rs rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/quasar/Cargo.toml (97%) rename tokens/token-extensions/transfer-hook/{whitelist => allow-list}/quasar/Quasar.toml (75%) create mode 100644 tokens/token-extensions/transfer-hook/allow-list/quasar/README.md create mode 100644 tokens/token-extensions/transfer-hook/allow-list/quasar/src/lib.rs create mode 100644 tokens/token-extensions/transfer-hook/allow-list/quasar/src/tests.rs delete mode 100644 tokens/token-extensions/transfer-hook/whitelist/anchor-v1/README.md delete mode 100644 tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/src/instructions/add_to_whitelist.rs delete mode 100644 tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs delete mode 100644 tokens/token-extensions/transfer-hook/whitelist/anchor/README.md delete mode 100644 tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/add_to_whitelist.rs delete mode 100644 tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/tests/test_transfer_hook.rs delete mode 100644 tokens/token-extensions/transfer-hook/whitelist/quasar/README.md delete mode 100644 tokens/token-extensions/transfer-hook/whitelist/quasar/src/lib.rs delete mode 100644 tokens/token-extensions/transfer-hook/whitelist/quasar/src/tests.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e769f666..0fdc384ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,38 @@ All notable changes to this repository are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## [2026-11-05] - Transfer Hook - Whitelist becomes Transfer Hook - Allow List, with no size limit + +The whitelist example kept every allowed address in one account, capped at eleven, +shared by every mint. Its setup instruction used `init_if_needed` and made whoever +called it the list's authority, so anyone could take over the list by setting up a +hook for a mint of their own. The example now lives at +`tokens/token-extensions/transfer-hook/allow-list/`, and is rebuilt around one +account per allowed wallet. + +### Changed + +- `whitelist/` is renamed `allow-list/`, in all three variants (Anchor v1, Anchor v2, + Quasar). The Anchor crate is `transfer-hook-allow-list` and the Quasar crate + `quasar-transfer-hook-allow-list`; the program ID is unchanged. +- Each mint has its own **allow list** account, at a PDA with seeds `"allow_list"` and + the mint, holding the list's `authority`. Each allowed wallet has an **allowed + wallet** account, at a PDA with seeds `"allowed_wallet"`, the mint and the wallet, + so the list has no size limit. +- `add_to_whitelist` is replaced by `allow_wallet`, and the new `disallow_wallet` + closes a wallet's account and returns its rent to the authority. +- `initialize_extra_account_meta_list` now requires the mint authority's signature, + and creates the allow list with `init`, so only the issuer can set up a mint's list. +- The hook checks the wallet that owns the destination token account, not the token + account's own address. Its one extra account is derived per transfer from the mint + and the destination token account's owner, and a transfer to a wallet without an + allowed wallet account fails with `RecipientNotAllowed` (Quasar: custom error 6000) + rather than a panic. +- Tests cover allowing, disallowing, a wallet allowed for one mint only, fifty + allowed wallets, and the attempts to create a mint's allow list without its mint + authority, or to allow a wallet without the list's authority. +- The lending and managed-fund docs say "allow list" for "whitelist". + ## [2026-10-28] - Anchor v2 examples on Anchor 2.0.0-rc.2, sending v1 transactions Anchor 2.0.0-rc.2's test harness, `anchor-v2-testing`, runs on LiteSVM 0.18, which diff --git a/Cargo.lock b/Cargo.lock index 21f6282ba..a73090744 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7565,7 +7565,7 @@ dependencies = [ ] [[package]] -name = "transfer-hook-counter" +name = "transfer-hook-allow-list" version = "0.1.0" dependencies = [ "anchor-lang", @@ -7582,7 +7582,7 @@ dependencies = [ ] [[package]] -name = "transfer-hook-hello-world" +name = "transfer-hook-counter" version = "0.1.0" dependencies = [ "anchor-lang", @@ -7599,7 +7599,7 @@ dependencies = [ ] [[package]] -name = "transfer-hook-transfer-cost" +name = "transfer-hook-hello-world" version = "0.1.0" dependencies = [ "anchor-lang", @@ -7609,14 +7609,14 @@ dependencies = [ "solana-fee", "solana-kite", "solana-program-runtime", - "spl-discriminator 0.5.2", - "spl-tlv-account-resolution 0.11.1", - "spl-transfer-hook-interface 2.1.0", + "spl-discriminator 0.4.1", + "spl-tlv-account-resolution 0.9.0", + "spl-transfer-hook-interface 0.9.0", "wincode", ] [[package]] -name = "transfer-hook-whitelist" +name = "transfer-hook-transfer-cost" version = "0.1.0" dependencies = [ "anchor-lang", @@ -7626,9 +7626,9 @@ dependencies = [ "solana-fee", "solana-kite", "solana-program-runtime", - "spl-discriminator 0.4.1", - "spl-tlv-account-resolution 0.9.0", - "spl-transfer-hook-interface 0.9.0", + "spl-discriminator 0.5.2", + "spl-tlv-account-resolution 0.11.1", + "spl-transfer-hook-interface 2.1.0", "wincode", ] diff --git a/Cargo.toml b/Cargo.toml index 4178f7f73..3ab5daef5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,11 +118,11 @@ members = [ "tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee", "tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook", "tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token", + "tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook", "tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook", "tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook", "tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook", "tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch", - "tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook", ] # A path dependency inside the workspace directory joins the workspace unless it # is excluded. Both of these are named `mock_price_feed`, so leaving them in diff --git a/README.md b/README.md index 8e9a3a17a..efc91f93f 100644 --- a/README.md +++ b/README.md @@ -345,6 +345,12 @@ Use token account owner data as seeds to derive extra accounts in a transfer hoo [⚓ Anchor v1](./tokens/token-extensions/transfer-hook/account-data-as-seed/anchor-v1) [⚓ Anchor v2](./tokens/token-extensions/transfer-hook/account-data-as-seed/anchor) [💫 Quasar](./tokens/token-extensions/transfer-hook/account-data-as-seed/quasar) +### Transfer Hook - Allow List + +Only let tokens reach wallets on the mint's allow list. Each allowed wallet is its own account, so the list has no size limit. + +[⚓ Anchor v1](./tokens/token-extensions/transfer-hook/allow-list/anchor-v1) [⚓ Anchor v2](./tokens/token-extensions/transfer-hook/allow-list/anchor) [💫 Quasar](./tokens/token-extensions/transfer-hook/allow-list/quasar) + ### Transfer Hook - Allow/Block List Restrict or allow token transfers using an onchain list managed by a list authority. @@ -363,12 +369,6 @@ Enable or disable token transfers with an onchain switch. [⚓ Anchor v1](./tokens/token-extensions/transfer-hook/transfer-switch/anchor-v1) [⚓ Anchor v2](./tokens/token-extensions/transfer-hook/transfer-switch/anchor) [💫 Quasar](./tokens/token-extensions/transfer-hook/transfer-switch/quasar) -### Transfer Hook - Whitelist - -Restrict transfers so only whitelisted accounts can receive tokens. - -[⚓ Anchor v1](./tokens/token-extensions/transfer-hook/whitelist/anchor-v1) [⚓ Anchor v2](./tokens/token-extensions/transfer-hook/whitelist/anchor) [💫 Quasar](./tokens/token-extensions/transfer-hook/whitelist/quasar) - ## Compression ### cNFT Burn diff --git a/finance/lending/anchor-v1/README.md b/finance/lending/anchor-v1/README.md index 479301f68..aa5b1df46 100644 --- a/finance/lending/anchor-v1/README.md +++ b/finance/lending/anchor-v1/README.md @@ -276,7 +276,7 @@ three limits protect the people already there: `token_interface`, so Token Extensions mints are accepted, but a transfer-fee extension would make the vault receive less than the recorded deposit and the accounting would overstate `available_liquidity`. Production lending programs - whitelist mints; a market owner here must only create reserves for tokens + keep an allow list of mints; a market owner here must only create reserves for tokens without transfer fees. - This is an example. Deploying any program that custodies funds calls for a professional security audit first. diff --git a/finance/lending/anchor/README.md b/finance/lending/anchor/README.md index eeb122348..6b12f52fb 100644 --- a/finance/lending/anchor/README.md +++ b/finance/lending/anchor/README.md @@ -276,7 +276,7 @@ three limits protect the people already there: `token_interface`, so Token Extensions mints are accepted, but a transfer-fee extension would make the vault receive less than the recorded deposit and the accounting would overstate `available_liquidity`. Production lending programs - whitelist mints; a market owner here must only create reserves for tokens + keep an allow list of mints; a market owner here must only create reserves for tokens without transfer fees. - This is an example. Deploying any program that custodies funds calls for a professional security audit first. diff --git a/finance/managed-fund/anchor-v1/PRODUCT.md b/finance/managed-fund/anchor-v1/PRODUCT.md index a9180eefb..d537124dd 100644 --- a/finance/managed-fund/anchor-v1/PRODUCT.md +++ b/finance/managed-fund/anchor-v1/PRODUCT.md @@ -34,7 +34,7 @@ A **multi-asset** fund built from single-asset vaults, transparently priced on-c Program instructions the frontend can surface: -- **Registry / curator:** `initialize_registry`, `approve_asset` (whitelist of assets managers may use). +- **Registry / curator:** `initialize_registry`, `approve_asset` (an allow list of assets managers may use). - **Manager:** `initialize_fund` (fee, slippage tolerance, and rebalance threshold, all fixed at creation), `add_asset`, `set_weight` (including set-to-zero to retire an asset), `collect_fees`. - **Anyone:** `rebalance(sell_index, buy_index)`. The program sizes the trade from oracle prices and target weights, and refuses unless the asset sold is over its target by at least the fund's threshold and the asset bought is under its own (`DriftBelowThreshold`, `NotUnderweight`). - **Depositor:** `deposit` (USDC → minted shares), `withdraw` (burn shares → in-kind slice of every vault). diff --git a/finance/managed-fund/anchor/PRODUCT.md b/finance/managed-fund/anchor/PRODUCT.md index a9180eefb..d537124dd 100644 --- a/finance/managed-fund/anchor/PRODUCT.md +++ b/finance/managed-fund/anchor/PRODUCT.md @@ -34,7 +34,7 @@ A **multi-asset** fund built from single-asset vaults, transparently priced on-c Program instructions the frontend can surface: -- **Registry / curator:** `initialize_registry`, `approve_asset` (whitelist of assets managers may use). +- **Registry / curator:** `initialize_registry`, `approve_asset` (an allow list of assets managers may use). - **Manager:** `initialize_fund` (fee, slippage tolerance, and rebalance threshold, all fixed at creation), `add_asset`, `set_weight` (including set-to-zero to retire an asset), `collect_fees`. - **Anyone:** `rebalance(sell_index, buy_index)`. The program sizes the trade from oracle prices and target weights, and refuses unless the asset sold is over its target by at least the fund's threshold and the asset bought is under its own (`DriftBelowThreshold`, `NotUnderweight`). - **Depositor:** `deposit` (USDC → minted shares), `withdraw` (burn shares → in-kind slice of every vault). diff --git a/scripts/generate-quasar-readmes.mjs b/scripts/generate-quasar-readmes.mjs index aae62dca0..fcf0a35dd 100644 --- a/scripts/generate-quasar-readmes.mjs +++ b/scripts/generate-quasar-readmes.mjs @@ -216,6 +216,12 @@ const examples = { purpose: "Allow/block list enforced by a transfer hook program.", concepts: ["Transfer hook", "List authority"], }, + "tokens/token-extensions/transfer-hook/allow-list/quasar": { + title: "Transfer Hook - Allow List", + purpose: + "Only wallets on the mint's allow list may receive its tokens. Each allowed wallet is its own [PDA](https://solana.com/docs/terminology#program-derived-address-pda), so the list has no size limit.", + concepts: ["Transfer hook", "One account per allowed wallet", "Mint-authority-only setup"], + }, "tokens/token-extensions/transfer-hook/counter/quasar": { title: "Transfer Hook - Counter", purpose: "Count transfers in hook-side state.", @@ -236,11 +242,6 @@ const examples = { purpose: "Globally enable or disable transfers.", concepts: ["Transfer hook", "Admin switch"], }, - "tokens/token-extensions/transfer-hook/whitelist/quasar": { - title: "Transfer Hook - Whitelist", - purpose: "Only whitelisted accounts may receive tokens.", - concepts: ["Transfer hook", "Whitelist PDA"], - }, }; const redirects = { diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/.gitignore b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/.gitignore similarity index 100% rename from tokens/token-extensions/transfer-hook/whitelist/anchor-v1/.gitignore rename to tokens/token-extensions/transfer-hook/allow-list/anchor-v1/.gitignore diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/.prettierignore b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/.prettierignore similarity index 100% rename from tokens/token-extensions/transfer-hook/whitelist/anchor-v1/.prettierignore rename to tokens/token-extensions/transfer-hook/allow-list/anchor-v1/.prettierignore diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/Anchor.toml b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/Anchor.toml similarity index 100% rename from tokens/token-extensions/transfer-hook/whitelist/anchor-v1/Anchor.toml rename to tokens/token-extensions/transfer-hook/allow-list/anchor-v1/Anchor.toml diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/Cargo.toml b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/Cargo.toml similarity index 100% rename from tokens/token-extensions/transfer-hook/whitelist/anchor-v1/Cargo.toml rename to tokens/token-extensions/transfer-hook/allow-list/anchor-v1/Cargo.toml diff --git a/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/README.md b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/README.md new file mode 100644 index 000000000..298e99a29 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/README.md @@ -0,0 +1,61 @@ +# Transfer Hook - Allow List (Anchor) + +> [!NOTE] +> This is the **Anchor v1** copy of this example, on Anchor 1.2.1, the current +> stable Anchor release. Every `anchor` command on this page needs the v1 CLI: +> `avm install 1.2.1 && avm use 1.2.1`. The Anchor v2 version of this example is in +> [`../anchor`](../anchor/). + +A [Token Extensions](https://solana.com/docs/terminology#token-extensions-program) transfer hook that only lets a mint's tokens reach wallets on that mint's allow list. Every transfer of the mint calls the hook, and the hook rejects the transfer unless the recipient wallet is allowed. + +See also: the [repository catalog](../../../../../README.md), and [Allow/Block List](../../allow-block-list-token/) for a list that can also block wallets or allow transfers below a threshold. + +## Major concepts + +- Transfer hook with one extra account, resolved per transfer +- One account per allowed wallet, so the list has no size limit +- Only the mint authority can set up a mint's allow list + +## Accounts + +- The **allow list** account lives at a PDA with seeds `"allow_list"` and the mint. It holds the `authority` that may allow and disallow wallets: the mint authority who created it. One per mint. +- An **allowed wallet** account lives at a PDA with seeds `"allowed_wallet"`, the mint and the wallet. Its existence is the permission. `allow_wallet` creates it and `disallow_wallet` closes it, returning the rent to the authority. + +Because each allowed wallet is its own account, allowing the ten-thousandth wallet costs the same as allowing the first. + +## How a transfer is checked + +`initialize_extra_account_meta_list` stores one extra account in the mint's `ExtraAccountMetaList`: the allowed wallet PDA, with seeds `"allowed_wallet"`, the mint (account 1 of the transfer) and the destination token account's owner (account 2, bytes 32 to 64). On every transfer Token Extensions derives that address for the actual recipient and passes the account to the hook. The hook re-derives the address from the destination token account and fails with `RecipientNotAllowed` unless an allowed wallet account exists there. + +The hook checks the wallet that owns the destination token account, not the token account itself, so allowing a wallet covers every token account it holds for that mint. + +## Instruction handlers + +- `initialize_extra_account_meta_list`: creates the mint's `ExtraAccountMetaList` and allow list. The signer must be the mint authority, and becomes the allow list's authority. +- `allow_wallet`: the allow list's authority adds a wallet. +- `disallow_wallet`: the allow list's authority removes a wallet. +- `transfer_hook`: called by Token Extensions on every transfer. + +## Setup + +From this directory (`tokens/token-extensions/transfer-hook/allow-list/anchor-v1/`): + +```bash +anchor build +``` + +Prerequisites: [Agave](https://docs.anza.xyz/) CLI (version in `Anchor.toml` `[toolchain]`), [Anchor](https://www.anchor-lang.com/docs). + +## Testing + +Tests run in-process with [LiteSVM](https://www.anchor-lang.com/docs/testing/litesvm). No local validator. + +```bash +anchor test +``` + +This runs `cargo test` as configured in `Anchor.toml`. The tests make real Token Extensions transfers through the hook. They check that a transfer reaches an allowed wallet; that a wallet not on the list, a disallowed wallet, or a wallet allowed only for another mint is refused; that fifty wallets can be allowed; that only the mint authority can create the allow list; and that only its authority can allow a wallet. + +## Usage + +Read the program `programs/` source and `Anchor.toml` for deployed program IDs. For deployment, use `anchor build && anchor deploy` against your target cluster. diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/Cargo.toml similarity index 91% rename from tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/Cargo.toml rename to tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/Cargo.toml index 632351aec..55103d8df 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/Cargo.toml @@ -20,7 +20,7 @@ custom-heap = [] custom-panic = [] [dependencies] -anchor-lang = { version = "1.2.1", features = ["init-if-needed"] } +anchor-lang = "1.2.1" anchor-spl = "1.2.1" spl-discriminator = "0.4.1" spl-tlv-account-resolution = "0.9.0" diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/Xargo.toml b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/Xargo.toml similarity index 100% rename from tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/Xargo.toml rename to tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/Xargo.toml diff --git a/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/allow_wallet.rs b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/allow_wallet.rs new file mode 100644 index 000000000..3054449c9 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/allow_wallet.rs @@ -0,0 +1,31 @@ +use anchor_lang::prelude::*; + +use crate::{AllowList, AllowedWallet, ALLOWED_WALLET_SEED, ALLOW_LIST_SEED}; + +#[derive(Accounts)] +pub struct AllowWalletAccountConstraints<'info> { + #[account(mut, address = allow_list.authority)] + pub authority: Signer<'info>, + /// CHECK: only its address is used, as a seed; the allow list's own seeds + /// tie it to this mint. + pub mint: UncheckedAccount<'info>, + #[account(seeds = [ALLOW_LIST_SEED, mint.key().as_ref()], bump = allow_list.bump)] + pub allow_list: Account<'info, AllowList>, + /// CHECK: the wallet being allowed to receive the token. Any address: a + /// person's wallet or a PDA owned by another program. + pub wallet: UncheckedAccount<'info>, + #[account( + init, + seeds = [ALLOWED_WALLET_SEED, mint.key().as_ref(), wallet.key().as_ref()], + bump, + payer = authority, + space = AllowedWallet::DISCRIMINATOR.len() + AllowedWallet::INIT_SPACE + )] + pub allowed_wallet: Account<'info, AllowedWallet>, + pub system_program: Program<'info, System>, +} + +pub fn handler(context: Context) -> Result<()> { + context.accounts.allowed_wallet.bump = context.bumps.allowed_wallet; + Ok(()) +} diff --git a/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/disallow_wallet.rs b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/disallow_wallet.rs new file mode 100644 index 000000000..597c4a433 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/disallow_wallet.rs @@ -0,0 +1,28 @@ +use anchor_lang::prelude::*; + +use crate::{AllowList, AllowedWallet, ALLOWED_WALLET_SEED, ALLOW_LIST_SEED}; + +#[derive(Accounts)] +pub struct DisallowWalletAccountConstraints<'info> { + #[account(mut, address = allow_list.authority)] + pub authority: Signer<'info>, + /// CHECK: only its address is used, as a seed; the allow list's own seeds + /// tie it to this mint. + pub mint: UncheckedAccount<'info>, + #[account(seeds = [ALLOW_LIST_SEED, mint.key().as_ref()], bump = allow_list.bump)] + pub allow_list: Account<'info, AllowList>, + /// CHECK: the wallet losing its permission; only its address is used. + pub wallet: UncheckedAccount<'info>, + /// Closing the account is the whole operation: rent goes back to the authority. + #[account( + mut, + close = authority, + seeds = [ALLOWED_WALLET_SEED, mint.key().as_ref(), wallet.key().as_ref()], + bump = allowed_wallet.bump + )] + pub allowed_wallet: Account<'info, AllowedWallet>, +} + +pub fn handler(_context: Context) -> Result<()> { + Ok(()) +} diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs similarity index 65% rename from tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs rename to tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs index ed20fbb39..b1fe32dfb 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs @@ -3,10 +3,14 @@ use anchor_spl::token_interface::Mint; use spl_tlv_account_resolution::state::ExtraAccountMetaList; use spl_transfer_hook_interface::instruction::ExecuteInstruction; -use crate::{handle_extra_account_metas, handle_extra_account_metas_count, WhiteList}; +use crate::{ + handle_extra_account_metas, handle_extra_account_metas_count, AllowList, ALLOW_LIST_SEED, +}; #[derive(Accounts)] pub struct InitializeExtraAccountMetaListAccountConstraints<'info> { + /// The mint's mint authority, who becomes the allow list's authority. Only + /// the issuer can set up a mint's allow list, so nobody can claim it first. #[account(mut)] payer: Signer<'info>, @@ -22,18 +26,22 @@ pub struct InitializeExtraAccountMetaListAccountConstraints<'info> { payer = payer )] pub extra_account_meta_list: UncheckedAccount<'info>, + #[account(mint::authority = payer)] pub mint: InterfaceAccount<'info, Mint>, + #[account( + init, + seeds = [ALLOW_LIST_SEED, mint.key().as_ref()], + bump, + payer = payer, + space = AllowList::DISCRIMINATOR.len() + AllowList::INIT_SPACE + )] + pub allow_list: Account<'info, AllowList>, pub system_program: Program<'info, System>, - #[account(init_if_needed, seeds = [b"white_list"], bump, payer = payer, space = WhiteList::DISCRIMINATOR.len() + WhiteList::INIT_SPACE)] - pub white_list: Account<'info, WhiteList>, } -pub fn handler( - mut context: Context, -) -> Result<()> { - // set authority field on white_list account as payer address - context.accounts.white_list.authority = context.accounts.payer.key(); - context.accounts.white_list.bump = context.bumps.white_list; +pub fn handler(context: Context) -> Result<()> { + context.accounts.allow_list.authority = context.accounts.payer.key(); + context.accounts.allow_list.bump = context.bumps.allow_list; let extra_account_metas = handle_extra_account_metas()?; diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/src/instructions/mod.rs b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/mod.rs similarity index 58% rename from tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/src/instructions/mod.rs rename to tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/mod.rs index 9608cbc21..eafecfadb 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/src/instructions/mod.rs +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/mod.rs @@ -1,7 +1,9 @@ -pub mod add_to_whitelist; +pub mod allow_wallet; +pub mod disallow_wallet; pub mod initialize_extra_account_meta_list; pub mod transfer_hook; -pub use add_to_whitelist::*; +pub use allow_wallet::*; +pub use disallow_wallet::*; pub use initialize_extra_account_meta_list::*; pub use transfer_hook::*; diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/src/instructions/transfer_hook.rs b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/transfer_hook.rs similarity index 56% rename from tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/src/instructions/transfer_hook.rs rename to tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/transfer_hook.rs index d0279a605..ebff84d89 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/src/instructions/transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/instructions/transfer_hook.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; use anchor_spl::token_interface::{Mint, TokenAccount}; -use crate::{check_is_transferring, WhiteList}; +use crate::{check_is_transferring, AllowListError, AllowedWallet, ALLOWED_WALLET_SEED}; // Order of accounts matters for this struct. // The first 4 accounts are the accounts required for token transfer (source, mint, destination, owner) @@ -19,24 +19,29 @@ pub struct TransferHookAccountConstraints<'info> { /// CHECK: ExtraAccountMetaList Account, #[account(seeds = [b"extra-account-metas", mint.key().as_ref()], bump)] pub extra_account_meta_list: UncheckedAccount<'info>, - #[account(seeds = [b"white_list"], bump)] - pub white_list: Account<'info, WhiteList>, + /// CHECK: the recipient's AllowedWallet PDA. It may not exist: a missing + /// account is how a wallet that is not allowed shows up, so the handler + /// reads it by hand instead of letting a typed account fail to load. + #[account(seeds = [ + ALLOWED_WALLET_SEED, + mint.key().as_ref(), + destination_token.owner.as_ref(), + ], bump)] + pub allowed_wallet: UncheckedAccount<'info>, } pub fn handler(context: Context, _amount: u64) -> Result<()> { // Fail this instruction if it is not called from within a transfer hook check_is_transferring(&context)?; - if !context - .accounts - .white_list - .white_list - .contains(&context.accounts.destination_token.key()) - { - panic!("Account not in white list!"); + // Only this program can put data at one of its own PDAs, so an account at + // this address that carries the AllowedWallet discriminator was created by + // `allow_wallet` and not since closed by `disallow_wallet`. + let data = context.accounts.allowed_wallet.try_borrow_data()?; + let discriminator = AllowedWallet::DISCRIMINATOR; + if data.len() < discriminator.len() || &data[..discriminator.len()] != discriminator { + return err!(AllowListError::RecipientNotAllowed); } - msg!("Account in white list, all good!"); - Ok(()) } diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/lib.rs similarity index 62% rename from tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/src/lib.rs rename to tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/lib.rs index c3c652dc0..c5529bd59 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/src/lib.rs @@ -18,10 +18,15 @@ use instructions::*; declare_id!("DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub"); +pub const ALLOW_LIST_SEED: &[u8] = b"allow_list"; +pub const ALLOWED_WALLET_SEED: &[u8] = b"allowed_wallet"; + #[error_code] -pub enum TransferError { +pub enum AllowListError { #[msg("The token is not currently transferring")] IsNotCurrentlyTransferring, + #[msg("The recipient wallet is not on this mint's allow list")] + RecipientNotAllowed, } #[program] @@ -43,8 +48,12 @@ pub mod transfer_hook { instructions::transfer_hook::handler(context, amount) } - pub fn add_to_whitelist(context: Context) -> Result<()> { - instructions::add_to_whitelist::handler(context) + pub fn allow_wallet(context: Context) -> Result<()> { + instructions::allow_wallet::handler(context) + } + + pub fn disallow_wallet(context: Context) -> Result<()> { + instructions::disallow_wallet::handler(context) } } @@ -60,37 +69,56 @@ pub fn check_is_transferring(context: &Context) .map_err(|_| ProgramError::InvalidAccountData)?; if !bool::from(account_extension.transferring) { - return err!(TransferError::IsNotCurrentlyTransferring); + return err!(AllowListError::IsNotCurrentlyTransferring); } Ok(()) } -// Define extra account metas to store on extra_account_meta_list account +// The one extra account Token Extensions passes to the hook: the recipient's +// `AllowedWallet` PDA, ["allowed_wallet", mint, recipient wallet]. Token +// Extensions derives it at transfer time from the mint (account 1) and the +// owner field of the destination token account (account 2, bytes 32..64). pub fn handle_extra_account_metas() -> Result> { // .map_err() needed because spl-tlv-account-resolution uses solana-program-error 2.x // while anchor-lang 1.0 uses 3.x - structurally identical but different semver types Ok(vec![ExtraAccountMeta::new_with_seeds( - &[Seed::Literal { - bytes: "white_list".as_bytes().to_vec(), - }], + &[ + Seed::Literal { + bytes: ALLOWED_WALLET_SEED.to_vec(), + }, + Seed::AccountKey { index: 1 }, + Seed::AccountData { + account_index: 2, + data_index: 32, + length: 32, + }, + ], false, // is_signer - true, // is_writable + false, // is_writable ) .map_err(|_| ProgramError::InvalidArgument)?]) } /// Returns the count of extra account metas (avoids the error conversion issue in #[account] attributes) pub fn handle_extra_account_metas_count() -> usize { - 1 // one extra account: the whitelist PDA + 1 // one extra account: the recipient's AllowedWallet PDA } +/// One per mint: who may add wallets to that mint's allow list. #[account] #[derive(InitSpace)] -pub struct WhiteList { +pub struct AllowList { pub authority: Pubkey, - #[max_len(11)] - pub white_list: Vec, + /// Canonical bump for this PDA. + pub bump: u8, +} + +/// One per allowed wallet per mint. Its existence is the permission: the hook +/// accepts a transfer only when the recipient's `AllowedWallet` account exists. +#[account] +#[derive(InitSpace)] +pub struct AllowedWallet { /// Canonical bump for this PDA. pub bump: u8, } diff --git a/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs new file mode 100644 index 000000000..6dc3ce75c --- /dev/null +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs @@ -0,0 +1,404 @@ +use { + anchor_lang::{ + solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, + InstructionData, ToAccountMetas, + }, + litesvm::LiteSVM, + solana_keypair::Keypair, + solana_kite::{ + create_wallet, send_transaction_from_instructions, + token_extensions::{ + create_token_extensions_account, create_token_extensions_mint, + mint_tokens_to_token_extensions_account, transfer_checked_token_extensions, + MintExtension, + }, + transfer_hook::{build_hook_accounts, get_hook_accounts_address, HookAccount}, + SolanaKiteError, + }, + solana_signer::Signer, + transfer_hook::{ALLOWED_WALLET_SEED, ALLOW_LIST_SEED}, +}; + +const DECIMALS: u8 = 9; +const ONE_TOKEN: u64 = 10u64.pow(DECIMALS as u32); + +// Anchor numbers custom errors from 6000 in declaration order. +const RECIPIENT_NOT_ALLOWED: &str = "Custom(6001)"; + +/// A mint with the transfer hook attached, its allow list set up, and a +/// sender holding 100 tokens. `issuer` is the mint authority and so the allow +/// list's authority. +struct Fixture { + svm: LiteSVM, + program_id: Pubkey, + issuer: Keypair, + mint: Pubkey, + allow_list: Pubkey, + sender_token: Pubkey, +} + +fn allowed_wallet_address(program_id: &Pubkey, mint: &Pubkey, wallet: &Pubkey) -> Pubkey { + Pubkey::find_program_address( + &[ALLOWED_WALLET_SEED, mint.as_ref(), wallet.as_ref()], + program_id, + ) + .0 +} + +fn create_hooked_mint(svm: &mut LiteSVM, program_id: Pubkey, issuer: &Keypair) -> Pubkey { + let mint = create_token_extensions_mint( + svm, + issuer, + DECIMALS, + None, + &[MintExtension::TransferHook { program_id }], + None, + ) + .unwrap(); + svm.expire_blockhash(); + mint +} + +fn initialize_instruction(program_id: Pubkey, payer: &Pubkey, mint: &Pubkey) -> Instruction { + Instruction::new_with_bytes( + program_id, + &transfer_hook::instruction::InitializeExtraAccountMetaList {}.data(), + transfer_hook::accounts::InitializeExtraAccountMetaListAccountConstraints { + payer: *payer, + extra_account_meta_list: get_hook_accounts_address(mint, &program_id), + mint: *mint, + allow_list: Pubkey::find_program_address( + &[ALLOW_LIST_SEED, mint.as_ref()], + &program_id, + ) + .0, + system_program: system_program::ID, + } + .to_account_metas(None), + ) +} + +fn setup() -> Fixture { + let program_id = transfer_hook::id(); + let mut svm = LiteSVM::new(); + let program_bytes = include_bytes!("../../../target/deploy/transfer_hook.so"); + svm.add_program(program_id, program_bytes).unwrap(); + + let issuer = create_wallet(&mut svm, 10_000_000_000).unwrap(); + let mint = create_hooked_mint(&mut svm, program_id, &issuer); + let allow_list = Pubkey::find_program_address(&[ALLOW_LIST_SEED, mint.as_ref()], &program_id).0; + + send( + &mut svm, + initialize_instruction(program_id, &issuer.pubkey(), &mint), + &issuer, + ) + .unwrap(); + + let sender_token = + create_token_extensions_account(&mut svm, &issuer.pubkey(), &mint, &issuer, None).unwrap(); + svm.expire_blockhash(); + mint_tokens_to_token_extensions_account( + &mut svm, + &mint, + &sender_token, + 100 * ONE_TOKEN, + &issuer, + None, + ) + .unwrap(); + svm.expire_blockhash(); + + Fixture { + svm, + program_id, + issuer, + mint, + allow_list, + sender_token, + } +} + +fn send( + svm: &mut LiteSVM, + instruction: Instruction, + signer: &Keypair, +) -> Result<(), SolanaKiteError> { + let result = send_transaction_from_instructions( + svm, + vec![instruction], + &[signer], + &signer.pubkey(), + None, + ); + svm.expire_blockhash(); + result +} + +impl Fixture { + fn allow_wallet_instruction(&self, authority: &Pubkey, wallet: &Pubkey) -> Instruction { + Instruction::new_with_bytes( + self.program_id, + &transfer_hook::instruction::AllowWallet {}.data(), + transfer_hook::accounts::AllowWalletAccountConstraints { + authority: *authority, + mint: self.mint, + allow_list: self.allow_list, + wallet: *wallet, + allowed_wallet: allowed_wallet_address(&self.program_id, &self.mint, wallet), + system_program: system_program::ID, + } + .to_account_metas(None), + ) + } + + fn allow_wallet(&mut self, wallet: &Pubkey) { + let instruction = self.allow_wallet_instruction(&self.issuer.pubkey(), wallet); + send(&mut self.svm, instruction, &self.issuer).unwrap(); + } + + fn disallow_wallet(&mut self, wallet: &Pubkey) { + let instruction = Instruction::new_with_bytes( + self.program_id, + &transfer_hook::instruction::DisallowWallet {}.data(), + transfer_hook::accounts::DisallowWalletAccountConstraints { + authority: self.issuer.pubkey(), + mint: self.mint, + allow_list: self.allow_list, + wallet: *wallet, + allowed_wallet: allowed_wallet_address(&self.program_id, &self.mint, wallet), + } + .to_account_metas(None), + ); + send(&mut self.svm, instruction, &self.issuer).unwrap(); + } + + /// A token account for a fresh wallet, returned with that wallet. + fn new_recipient(&mut self) -> (Keypair, Pubkey) { + let wallet = Keypair::new(); + let token_account = create_token_extensions_account( + &mut self.svm, + &wallet.pubkey(), + &self.mint, + &self.issuer, + None, + ) + .unwrap(); + self.svm.expire_blockhash(); + (wallet, token_account) + } + + fn transfer_to( + &mut self, + wallet: &Pubkey, + token_account: &Pubkey, + ) -> Result<(), SolanaKiteError> { + let extra_accounts = build_hook_accounts( + &self.mint, + &self.program_id, + &[HookAccount { + pubkey: allowed_wallet_address(&self.program_id, &self.mint, wallet), + is_signer: false, + is_writable: false, + }], + ); + let result = transfer_checked_token_extensions( + &mut self.svm, + &self.sender_token, + &self.mint, + token_account, + &self.issuer, + ONE_TOKEN, + DECIMALS, + &extra_accounts, + None, + ); + self.svm.expire_blockhash(); + result + } +} + +fn assert_rejected(result: Result<(), SolanaKiteError>, expected: &str) { + let error = format!( + "{:?}", + result.expect_err("transfer should have been rejected") + ); + assert!(error.contains(expected), "expected {expected}, got {error}"); +} + +#[test] +fn transfer_to_an_allowed_wallet_succeeds() { + let mut fixture = setup(); + let (wallet, token_account) = fixture.new_recipient(); + fixture.allow_wallet(&wallet.pubkey()); + + fixture + .transfer_to(&wallet.pubkey(), &token_account) + .unwrap(); +} + +#[test] +fn transfer_to_a_wallet_not_on_the_list_is_rejected() { + let mut fixture = setup(); + let (wallet, token_account) = fixture.new_recipient(); + + assert_rejected( + fixture.transfer_to(&wallet.pubkey(), &token_account), + RECIPIENT_NOT_ALLOWED, + ); +} + +#[test] +fn disallowing_a_wallet_stops_transfers_to_it() { + let mut fixture = setup(); + let (wallet, token_account) = fixture.new_recipient(); + fixture.allow_wallet(&wallet.pubkey()); + fixture + .transfer_to(&wallet.pubkey(), &token_account) + .unwrap(); + + fixture.disallow_wallet(&wallet.pubkey()); + + assert_rejected( + fixture.transfer_to(&wallet.pubkey(), &token_account), + RECIPIENT_NOT_ALLOWED, + ); + let entry = allowed_wallet_address(&fixture.program_id, &fixture.mint, &wallet.pubkey()); + assert!( + fixture + .svm + .get_account(&entry) + .map_or(true, |account| account.lamports == 0), + "disallow_wallet should close the AllowedWallet account" + ); +} + +/// The list has no size limit: each allowed wallet is its own account, so the +/// fiftieth costs the same as the first. The single-account design this +/// example replaced held eleven. +#[test] +fn the_list_holds_more_wallets_than_one_account_could() { + let mut fixture = setup(); + let mut recipients = Vec::new(); + for _ in 0..50 { + let (wallet, token_account) = fixture.new_recipient(); + fixture.allow_wallet(&wallet.pubkey()); + recipients.push((wallet, token_account)); + } + + for (wallet, token_account) in &recipients { + fixture + .transfer_to(&wallet.pubkey(), token_account) + .unwrap(); + } +} + +/// Allowing a wallet for one mint says nothing about another mint. +#[test] +fn an_allowed_wallet_is_allowed_for_one_mint_only() { + let mut fixture = setup(); + let (wallet, _) = fixture.new_recipient(); + fixture.allow_wallet(&wallet.pubkey()); + + let other_mint = create_hooked_mint(&mut fixture.svm, fixture.program_id, &fixture.issuer); + send( + &mut fixture.svm, + initialize_instruction(fixture.program_id, &fixture.issuer.pubkey(), &other_mint), + &fixture.issuer, + ) + .unwrap(); + let other_sender = create_token_extensions_account( + &mut fixture.svm, + &fixture.issuer.pubkey(), + &other_mint, + &fixture.issuer, + None, + ) + .unwrap(); + fixture.svm.expire_blockhash(); + mint_tokens_to_token_extensions_account( + &mut fixture.svm, + &other_mint, + &other_sender, + ONE_TOKEN, + &fixture.issuer, + None, + ) + .unwrap(); + fixture.svm.expire_blockhash(); + let other_recipient = create_token_extensions_account( + &mut fixture.svm, + &wallet.pubkey(), + &other_mint, + &fixture.issuer, + None, + ) + .unwrap(); + fixture.svm.expire_blockhash(); + + let extra_accounts = build_hook_accounts( + &other_mint, + &fixture.program_id, + &[HookAccount { + pubkey: allowed_wallet_address(&fixture.program_id, &other_mint, &wallet.pubkey()), + is_signer: false, + is_writable: false, + }], + ); + assert_rejected( + transfer_checked_token_extensions( + &mut fixture.svm, + &other_sender, + &other_mint, + &other_recipient, + &fixture.issuer, + ONE_TOKEN, + DECIMALS, + &extra_accounts, + None, + ), + RECIPIENT_NOT_ALLOWED, + ); +} + +/// Only the mint authority can set up a mint's allow list, so nobody can +/// claim the list for a mint they do not issue. +#[test] +fn only_the_mint_authority_can_create_the_allow_list() { + let program_id = transfer_hook::id(); + let mut svm = LiteSVM::new(); + let program_bytes = include_bytes!("../../../target/deploy/transfer_hook.so"); + svm.add_program(program_id, program_bytes).unwrap(); + let issuer = create_wallet(&mut svm, 10_000_000_000).unwrap(); + let attacker = create_wallet(&mut svm, 10_000_000_000).unwrap(); + let mint = create_hooked_mint(&mut svm, program_id, &issuer); + + let result = send( + &mut svm, + initialize_instruction(program_id, &attacker.pubkey(), &mint), + &attacker, + ); + assert!(result.is_err(), "a stranger must not create the allow list"); + + send( + &mut svm, + initialize_instruction(program_id, &issuer.pubkey(), &mint), + &issuer, + ) + .unwrap(); +} + +#[test] +fn only_the_authority_can_allow_a_wallet() { + let mut fixture = setup(); + let attacker = create_wallet(&mut fixture.svm, 10_000_000_000).unwrap(); + let (wallet, token_account) = fixture.new_recipient(); + + let instruction = fixture.allow_wallet_instruction(&attacker.pubkey(), &wallet.pubkey()); + assert!(send(&mut fixture.svm, instruction, &attacker).is_err()); + + assert_rejected( + fixture.transfer_to(&wallet.pubkey(), &token_account), + RECIPIENT_NOT_ALLOWED, + ); +} diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/.gitignore b/tokens/token-extensions/transfer-hook/allow-list/anchor/.gitignore similarity index 100% rename from tokens/token-extensions/transfer-hook/whitelist/anchor/.gitignore rename to tokens/token-extensions/transfer-hook/allow-list/anchor/.gitignore diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/.prettierignore b/tokens/token-extensions/transfer-hook/allow-list/anchor/.prettierignore similarity index 100% rename from tokens/token-extensions/transfer-hook/whitelist/anchor/.prettierignore rename to tokens/token-extensions/transfer-hook/allow-list/anchor/.prettierignore diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/Anchor.toml b/tokens/token-extensions/transfer-hook/allow-list/anchor/Anchor.toml similarity index 100% rename from tokens/token-extensions/transfer-hook/whitelist/anchor/Anchor.toml rename to tokens/token-extensions/transfer-hook/allow-list/anchor/Anchor.toml diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/Cargo.toml b/tokens/token-extensions/transfer-hook/allow-list/anchor/Cargo.toml similarity index 100% rename from tokens/token-extensions/transfer-hook/whitelist/anchor/Cargo.toml rename to tokens/token-extensions/transfer-hook/allow-list/anchor/Cargo.toml diff --git a/tokens/token-extensions/transfer-hook/allow-list/anchor/README.md b/tokens/token-extensions/transfer-hook/allow-list/anchor/README.md new file mode 100644 index 000000000..fff31a2c4 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor/README.md @@ -0,0 +1,61 @@ +# Transfer Hook - Allow List (Anchor) + +> [!NOTE] +> This is the **Anchor v2** copy of this example. Every `anchor` command on this page +> needs the v2 CLI: `cargo install anchor-cli --version 2.0.0-rc.2 --locked` (avm has +> no prebuilt binary for this pre-release). The Anchor v1 version of this example is in +> [`../anchor-v1`](../anchor-v1/). + +A [Token Extensions](https://solana.com/docs/terminology#token-extensions-program) transfer hook that only lets a mint's tokens reach wallets on that mint's allow list. Every transfer of the mint calls the hook, and the hook rejects the transfer unless the recipient wallet is allowed. + +See also: the [repository catalog](../../../../../README.md), and [Allow/Block List](../../allow-block-list-token/) for a list that can also block wallets or allow transfers below a threshold. + +## Major concepts + +- Transfer hook with one extra account, resolved per transfer +- One account per allowed wallet, so the list has no size limit +- Only the mint authority can set up a mint's allow list + +## Accounts + +- The **allow list** account lives at a PDA with seeds `"allow_list"` and the mint. It holds the `authority` that may allow and disallow wallets: the mint authority who created it. One per mint. +- An **allowed wallet** account lives at a PDA with seeds `"allowed_wallet"`, the mint and the wallet. Its existence is the permission. `allow_wallet` creates it and `disallow_wallet` closes it, returning the rent to the authority. + +Because each allowed wallet is its own account, allowing the ten-thousandth wallet costs the same as allowing the first. + +## How a transfer is checked + +`initialize_extra_account_meta_list` stores one extra account in the mint's `ExtraAccountMetaList`: the allowed wallet PDA, with seeds `"allowed_wallet"`, the mint (account 1 of the transfer) and the destination token account's owner (account 2, bytes 32 to 64). On every transfer Token Extensions derives that address for the actual recipient and passes the account to the hook. The hook re-derives the address from the destination token account and fails with `RecipientNotAllowed` unless an allowed wallet account exists there. + +The hook checks the wallet that owns the destination token account, not the token account itself, so allowing a wallet covers every token account it holds for that mint. + +## Instruction handlers + +- `initialize_extra_account_meta_list`: creates the mint's `ExtraAccountMetaList` and allow list. The signer must be the mint authority, and becomes the allow list's authority. +- `allow_wallet`: the allow list's authority adds a wallet. +- `disallow_wallet`: the allow list's authority removes a wallet. +- `transfer_hook`: called by Token Extensions on every transfer. + +## Setup + +From this directory (`tokens/token-extensions/transfer-hook/allow-list/anchor/`): + +```bash +anchor build +``` + +Prerequisites: [Agave](https://docs.anza.xyz/) CLI (version in `Anchor.toml` `[toolchain]`), [Anchor](https://www.anchor-lang.com/docs). + +## Testing + +Tests run in-process with [LiteSVM](https://www.anchor-lang.com/docs/testing/litesvm). No local validator. + +```bash +anchor test +``` + +This runs `cargo test` as configured in `Anchor.toml`. The tests make real Token Extensions transfers through the hook. They check that a transfer reaches an allowed wallet; that a wallet not on the list, a disallowed wallet, or a wallet allowed only for another mint is refused; that fifty wallets can be allowed; that only the mint authority can create the allow list; and that only its authority can allow a wallet. + +## Usage + +Read the program `programs/` source and `Anchor.toml` for deployed program IDs. For deployment, use `anchor build && anchor deploy` against your target cluster. diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/Cargo.toml b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/Cargo.toml similarity index 98% rename from tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/Cargo.toml rename to tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/Cargo.toml index dacf03a65..9de761439 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/Cargo.toml @@ -3,7 +3,7 @@ # workspace cannot hold two packages with one name. The package name carries the # variant; `[lib] name` stays `transfer_hook`, which is what Anchor.toml, the IDL # and the .so are keyed on. -name = "transfer-hook-whitelist" +name = "transfer-hook-allow-list" version = "0.1.0" description = "Created with Anchor" edition = "2021" diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/Xargo.toml b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/Xargo.toml similarity index 100% rename from tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/Xargo.toml rename to tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/Xargo.toml diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/entrypoint.rs b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/entrypoint.rs similarity index 100% rename from tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/entrypoint.rs rename to tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/entrypoint.rs diff --git a/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/allow_wallet.rs b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/allow_wallet.rs new file mode 100644 index 000000000..e96746885 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/allow_wallet.rs @@ -0,0 +1,31 @@ +use anchor_lang::prelude::*; + +use crate::{AllowList, AllowedWallet, ALLOWED_WALLET_SEED, ALLOW_LIST_SEED}; + +#[derive(Accounts)] +pub struct AllowWalletAccountConstraints { + #[account(mut, address = allow_list.authority)] + pub authority: Signer, + /// CHECK: only its address is used, as a seed; the allow list's own seeds + /// tie it to this mint. + pub mint: UncheckedAccount, + #[account(seeds = [ALLOW_LIST_SEED, mint.address().as_ref()], bump = allow_list.bump)] + pub allow_list: BorshAccount, + /// CHECK: the wallet being allowed to receive the token. Any address: a + /// person's wallet or a PDA owned by another program. + pub wallet: UncheckedAccount, + #[account( + init, + seeds = [ALLOWED_WALLET_SEED, mint.address().as_ref(), wallet.address().as_ref()], + bump, + payer = authority, + space = AllowedWallet::DISCRIMINATOR.len() + AllowedWallet::INIT_SPACE + )] + pub allowed_wallet: BorshAccount, + pub system_program: Program, +} + +pub fn handler(context: &mut Context) -> Result<()> { + context.accounts.allowed_wallet.bump = context.bumps.allowed_wallet; + Ok(()) +} diff --git a/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/disallow_wallet.rs b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/disallow_wallet.rs new file mode 100644 index 000000000..c60bdc282 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/disallow_wallet.rs @@ -0,0 +1,28 @@ +use anchor_lang::prelude::*; + +use crate::{AllowList, AllowedWallet, ALLOWED_WALLET_SEED, ALLOW_LIST_SEED}; + +#[derive(Accounts)] +pub struct DisallowWalletAccountConstraints { + #[account(mut, address = allow_list.authority)] + pub authority: Signer, + /// CHECK: only its address is used, as a seed; the allow list's own seeds + /// tie it to this mint. + pub mint: UncheckedAccount, + #[account(seeds = [ALLOW_LIST_SEED, mint.address().as_ref()], bump = allow_list.bump)] + pub allow_list: BorshAccount, + /// CHECK: the wallet losing its permission; only its address is used. + pub wallet: UncheckedAccount, + /// Closing the account is the whole operation: rent goes back to the authority. + #[account( + mut, + close = authority, + seeds = [ALLOWED_WALLET_SEED, mint.address().as_ref(), wallet.address().as_ref()], + bump = allowed_wallet.bump + )] + pub allowed_wallet: BorshAccount, +} + +pub fn handler(_context: &mut Context) -> Result<()> { + Ok(()) +} diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs similarity index 71% rename from tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs rename to tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs index c6ae7342a..19a411b13 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/initialize_extra_account_meta_list.rs @@ -1,12 +1,17 @@ use anchor_lang::prelude::*; +use anchor_spl::mint; use anchor_spl::token_interface::Mint; use spl_tlv_account_resolution::state::ExtraAccountMetaList; use spl_transfer_hook_interface::instruction::ExecuteInstruction; -use crate::{handle_extra_account_metas, handle_extra_account_metas_count, WhiteList}; +use crate::{ + handle_extra_account_metas, handle_extra_account_metas_count, AllowList, ALLOW_LIST_SEED, +}; #[derive(Accounts)] pub struct InitializeExtraAccountMetaListAccountConstraints { + /// The mint's mint authority, who becomes the allow list's authority. Only + /// the issuer can set up a mint's allow list, so nobody can claim it first. #[account(mut)] payer: Signer, @@ -22,18 +27,24 @@ pub struct InitializeExtraAccountMetaListAccountConstraints { payer = payer )] pub extra_account_meta_list: UncheckedAccount, + #[account(mint::authority = payer)] pub mint: InterfaceAccount, + #[account( + init, + seeds = [ALLOW_LIST_SEED, mint.address().as_ref()], + bump, + payer = payer, + space = AllowList::DISCRIMINATOR.len() + AllowList::INIT_SPACE + )] + pub allow_list: BorshAccount, pub system_program: Program, - #[account(init_if_needed, seeds = [b"white_list"], bump, payer = payer, space = WhiteList::DISCRIMINATOR.len() + WhiteList::INIT_SPACE)] - pub white_list: BorshAccount, } pub fn handler( context: &mut Context, ) -> Result<()> { - // set authority field on white_list account as payer address - context.accounts.white_list.authority = *context.accounts.payer.address(); - context.accounts.white_list.bump = context.bumps.white_list; + context.accounts.allow_list.authority = *context.accounts.payer.address(); + context.accounts.allow_list.bump = context.bumps.allow_list; let extra_account_metas = handle_extra_account_metas()?; diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/mod.rs b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/mod.rs similarity index 58% rename from tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/mod.rs rename to tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/mod.rs index 9608cbc21..eafecfadb 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/mod.rs +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/mod.rs @@ -1,7 +1,9 @@ -pub mod add_to_whitelist; +pub mod allow_wallet; +pub mod disallow_wallet; pub mod initialize_extra_account_meta_list; pub mod transfer_hook; -pub use add_to_whitelist::*; +pub use allow_wallet::*; +pub use disallow_wallet::*; pub use initialize_extra_account_meta_list::*; pub use transfer_hook::*; diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs similarity index 56% rename from tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs rename to tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs index 4c79d3707..172840424 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/instructions/transfer_hook.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; use anchor_spl::token; use anchor_spl::token_interface::{Mint, TokenAccount}; -use crate::{check_is_transferring, WhiteList}; +use crate::{check_is_transferring, AllowListError, AllowedWallet, ALLOWED_WALLET_SEED}; // Order of accounts matters for this struct. // The first 4 accounts are the accounts required for token transfer (source, mint, destination, owner) @@ -20,24 +20,29 @@ pub struct TransferHookAccountConstraints { /// CHECK: ExtraAccountMetaList Account, #[account(seeds = [b"extra-account-metas", mint.address().as_ref()], bump)] pub extra_account_meta_list: UncheckedAccount, - #[account(seeds = [b"white_list"], bump)] - pub white_list: BorshAccount, + /// CHECK: the recipient's AllowedWallet PDA. It may not exist: a missing + /// account is how a wallet that is not allowed shows up, so the handler + /// reads it by hand instead of letting a typed account fail to load. + #[account(seeds = [ + ALLOWED_WALLET_SEED, + mint.address().as_ref(), + destination_token.owner().as_ref(), + ], bump)] + pub allowed_wallet: UncheckedAccount, } pub fn handler(context: &mut Context, _amount: u64) -> Result<()> { // Fail this instruction if it is not called from within a transfer hook check_is_transferring(context)?; - if !context - .accounts - .white_list - .white_list - .contains(context.accounts.destination_token.address()) - { - panic!("Account not in white list!"); + // Only this program can put data at one of its own PDAs, so an account at + // this address that carries the AllowedWallet discriminator was created by + // `allow_wallet` and not since closed by `disallow_wallet`. + let data = context.accounts.allowed_wallet.account().try_borrow()?; + let discriminator = AllowedWallet::DISCRIMINATOR; + if data.len() < discriminator.len() || &data[..discriminator.len()] != discriminator { + return err!(AllowListError::RecipientNotAllowed); } - msg!("Account in white list, all good!"); - Ok(()) } diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/lib.rs similarity index 64% rename from tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs rename to tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/lib.rs index 79b431108..b1fc527d5 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/lib.rs +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/src/lib.rs @@ -12,10 +12,15 @@ use instructions::*; declare_id!("DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub"); +pub const ALLOW_LIST_SEED: &[u8] = b"allow_list"; +pub const ALLOWED_WALLET_SEED: &[u8] = b"allowed_wallet"; + #[error_code] -pub enum TransferError { +pub enum AllowListError { #[msg("The token is not currently transferring")] IsNotCurrentlyTransferring, + #[msg("The recipient wallet is not on this mint's allow list")] + RecipientNotAllowed, } pub mod entrypoint; @@ -44,8 +49,12 @@ pub mod transfer_hook { instructions::transfer_hook::handler(context, amount) } - pub fn add_to_whitelist(context: &mut Context) -> Result<()> { - instructions::add_to_whitelist::handler(context) + pub fn allow_wallet(context: &mut Context) -> Result<()> { + instructions::allow_wallet::handler(context) + } + + pub fn disallow_wallet(context: &mut Context) -> Result<()> { + instructions::disallow_wallet::handler(context) } } @@ -62,37 +71,56 @@ pub fn check_is_transferring(context: &Context) .map_err(|_| ProgramError::InvalidAccountData)?; if !bool::from(account_extension.transferring) { - return err!(TransferError::IsNotCurrentlyTransferring); + return err!(AllowListError::IsNotCurrentlyTransferring); } Ok(()) } -// Define extra account metas to store on extra_account_meta_list account +// The one extra account Token Extensions passes to the hook: the recipient's +// `AllowedWallet` PDA, ["allowed_wallet", mint, recipient wallet]. Token +// Extensions derives it at transfer time from the mint (account 1) and the +// owner field of the destination token account (account 2, bytes 32..64). pub fn handle_extra_account_metas() -> Result> { // .map_err() needed because spl-tlv-account-resolution uses solana-program-error 2.x // while anchor-lang v2 uses 3.x - structurally identical but different semver types Ok(vec![ExtraAccountMeta::new_with_seeds( - &[Seed::Literal { - bytes: "white_list".as_bytes().to_vec(), - }], + &[ + Seed::Literal { + bytes: ALLOWED_WALLET_SEED.to_vec(), + }, + Seed::AccountKey { index: 1 }, + Seed::AccountData { + account_index: 2, + data_index: 32, + length: 32, + }, + ], false, // is_signer - true, // is_writable + false, // is_writable ) .map_err(|_| ProgramError::InvalidArgument)?]) } /// Returns the count of extra account metas (avoids the error conversion issue in #[account] attributes) pub fn handle_extra_account_metas_count() -> usize { - 1 // one extra account: the whitelist PDA + 1 // one extra account: the recipient's AllowedWallet PDA } +/// One per mint: who may add wallets to that mint's allow list. #[account(borsh)] #[derive(InitSpace)] -pub struct WhiteList { +pub struct AllowList { pub authority: Address, - #[max_len(11)] - pub white_list: Vec
, + /// Canonical bump for this PDA. + pub bump: u8, +} + +/// One per allowed wallet per mint. Its existence is the permission: the hook +/// accepts a transfer only when the recipient's `AllowedWallet` account exists. +#[account(borsh)] +#[derive(InitSpace)] +pub struct AllowedWallet { /// Canonical bump for this PDA. pub bump: u8, } diff --git a/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/tests/test_transfer_hook.rs b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/tests/test_transfer_hook.rs new file mode 100644 index 000000000..6a06d6bb3 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/allow-list/anchor/programs/transfer-hook/tests/test_transfer_hook.rs @@ -0,0 +1,403 @@ +use { + anchor_lang::{ + solana_program::instruction::Instruction, system_program, Address, InstructionData, + ToAccountMetas, + }, + anchor_v2_testing::{litesvm::LiteSVM, Keypair, Signer}, + solana_kite::{ + create_wallet, send_transaction_from_instructions, + token_extensions::{ + create_token_extensions_account, create_token_extensions_mint, + mint_tokens_to_token_extensions_account, transfer_checked_token_extensions, + MintExtension, + }, + transfer_hook::{build_hook_accounts, get_hook_accounts_address, HookAccount}, + SolanaKiteError, + }, + transfer_hook::{ALLOWED_WALLET_SEED, ALLOW_LIST_SEED}, +}; + +const DECIMALS: u8 = 9; +const ONE_TOKEN: u64 = 10u64.pow(DECIMALS as u32); + +// Anchor numbers custom errors from 6000 in declaration order. +const RECIPIENT_NOT_ALLOWED: &str = "Custom(6001)"; + +/// A mint with the transfer hook attached, its allow list set up, and a +/// sender holding 100 tokens. `issuer` is the mint authority and so the allow +/// list's authority. +struct Fixture { + svm: LiteSVM, + program_id: Address, + issuer: Keypair, + mint: Address, + allow_list: Address, + sender_token: Address, +} + +fn allowed_wallet_address(program_id: &Address, mint: &Address, wallet: &Address) -> Address { + Address::find_program_address( + &[ALLOWED_WALLET_SEED, mint.as_ref(), wallet.as_ref()], + program_id, + ) + .0 +} + +fn create_hooked_mint(svm: &mut LiteSVM, program_id: Address, issuer: &Keypair) -> Address { + let mint = create_token_extensions_mint( + svm, + issuer, + DECIMALS, + None, + &[MintExtension::TransferHook { program_id }], + None, + ) + .unwrap(); + svm.expire_blockhash(); + mint +} + +fn initialize_instruction(program_id: Address, payer: &Address, mint: &Address) -> Instruction { + Instruction::new_with_bytes( + program_id, + &transfer_hook::instruction::InitializeExtraAccountMetaList {}.data(), + transfer_hook::accounts::InitializeExtraAccountMetaListAccountConstraints { + payer: *payer, + extra_account_meta_list: get_hook_accounts_address(mint, &program_id), + mint: *mint, + allow_list: Address::find_program_address( + &[ALLOW_LIST_SEED, mint.as_ref()], + &program_id, + ) + .0, + system_program: system_program::ID, + } + .to_account_metas(None), + ) +} + +fn setup() -> Fixture { + let program_id = transfer_hook::id(); + let mut svm = anchor_v2_testing::svm(); + let program_bytes = include_bytes!("../../../target/deploy/transfer_hook.so"); + svm.add_program(program_id, program_bytes).unwrap(); + + let issuer = create_wallet(&mut svm, 10_000_000_000).unwrap(); + let mint = create_hooked_mint(&mut svm, program_id, &issuer); + let allow_list = + Address::find_program_address(&[ALLOW_LIST_SEED, mint.as_ref()], &program_id).0; + + send( + &mut svm, + initialize_instruction(program_id, &issuer.pubkey(), &mint), + &issuer, + ) + .unwrap(); + + let sender_token = + create_token_extensions_account(&mut svm, &issuer.pubkey(), &mint, &issuer, None).unwrap(); + svm.expire_blockhash(); + mint_tokens_to_token_extensions_account( + &mut svm, + &mint, + &sender_token, + 100 * ONE_TOKEN, + &issuer, + None, + ) + .unwrap(); + svm.expire_blockhash(); + + Fixture { + svm, + program_id, + issuer, + mint, + allow_list, + sender_token, + } +} + +fn send( + svm: &mut LiteSVM, + instruction: Instruction, + signer: &Keypair, +) -> Result<(), SolanaKiteError> { + let result = send_transaction_from_instructions( + svm, + vec![instruction], + &[signer], + &signer.pubkey(), + None, + ); + svm.expire_blockhash(); + result +} + +impl Fixture { + fn allow_wallet_instruction(&self, authority: &Address, wallet: &Address) -> Instruction { + Instruction::new_with_bytes( + self.program_id, + &transfer_hook::instruction::AllowWallet {}.data(), + transfer_hook::accounts::AllowWalletAccountConstraints { + authority: *authority, + mint: self.mint, + allow_list: self.allow_list, + wallet: *wallet, + allowed_wallet: allowed_wallet_address(&self.program_id, &self.mint, wallet), + system_program: system_program::ID, + } + .to_account_metas(None), + ) + } + + fn allow_wallet(&mut self, wallet: &Address) { + let instruction = self.allow_wallet_instruction(&self.issuer.pubkey(), wallet); + send(&mut self.svm, instruction, &self.issuer).unwrap(); + } + + fn disallow_wallet(&mut self, wallet: &Address) { + let instruction = Instruction::new_with_bytes( + self.program_id, + &transfer_hook::instruction::DisallowWallet {}.data(), + transfer_hook::accounts::DisallowWalletAccountConstraints { + authority: self.issuer.pubkey(), + mint: self.mint, + allow_list: self.allow_list, + wallet: *wallet, + allowed_wallet: allowed_wallet_address(&self.program_id, &self.mint, wallet), + } + .to_account_metas(None), + ); + send(&mut self.svm, instruction, &self.issuer).unwrap(); + } + + /// A token account for a fresh wallet, returned with that wallet. + fn new_recipient(&mut self) -> (Keypair, Address) { + let wallet = Keypair::new(); + let token_account = create_token_extensions_account( + &mut self.svm, + &wallet.pubkey(), + &self.mint, + &self.issuer, + None, + ) + .unwrap(); + self.svm.expire_blockhash(); + (wallet, token_account) + } + + fn transfer_to( + &mut self, + wallet: &Address, + token_account: &Address, + ) -> Result<(), SolanaKiteError> { + let extra_accounts = build_hook_accounts( + &self.mint, + &self.program_id, + &[HookAccount { + pubkey: allowed_wallet_address(&self.program_id, &self.mint, wallet), + is_signer: false, + is_writable: false, + }], + ); + let result = transfer_checked_token_extensions( + &mut self.svm, + &self.sender_token, + &self.mint, + token_account, + &self.issuer, + ONE_TOKEN, + DECIMALS, + &extra_accounts, + None, + ); + self.svm.expire_blockhash(); + result + } +} + +fn assert_rejected(result: Result<(), SolanaKiteError>, expected: &str) { + let error = format!( + "{:?}", + result.expect_err("transfer should have been rejected") + ); + assert!(error.contains(expected), "expected {expected}, got {error}"); +} + +#[test] +fn transfer_to_an_allowed_wallet_succeeds() { + let mut fixture = setup(); + let (wallet, token_account) = fixture.new_recipient(); + fixture.allow_wallet(&wallet.pubkey()); + + fixture + .transfer_to(&wallet.pubkey(), &token_account) + .unwrap(); +} + +#[test] +fn transfer_to_a_wallet_not_on_the_list_is_rejected() { + let mut fixture = setup(); + let (wallet, token_account) = fixture.new_recipient(); + + assert_rejected( + fixture.transfer_to(&wallet.pubkey(), &token_account), + RECIPIENT_NOT_ALLOWED, + ); +} + +#[test] +fn disallowing_a_wallet_stops_transfers_to_it() { + let mut fixture = setup(); + let (wallet, token_account) = fixture.new_recipient(); + fixture.allow_wallet(&wallet.pubkey()); + fixture + .transfer_to(&wallet.pubkey(), &token_account) + .unwrap(); + + fixture.disallow_wallet(&wallet.pubkey()); + + assert_rejected( + fixture.transfer_to(&wallet.pubkey(), &token_account), + RECIPIENT_NOT_ALLOWED, + ); + let entry = allowed_wallet_address(&fixture.program_id, &fixture.mint, &wallet.pubkey()); + assert!( + fixture + .svm + .get_account(&entry) + .map_or(true, |account| account.lamports == 0), + "disallow_wallet should close the AllowedWallet account" + ); +} + +/// The list has no size limit: each allowed wallet is its own account, so the +/// fiftieth costs the same as the first. The single-account design this +/// example replaced held eleven. +#[test] +fn the_list_holds_more_wallets_than_one_account_could() { + let mut fixture = setup(); + let mut recipients = Vec::new(); + for _ in 0..50 { + let (wallet, token_account) = fixture.new_recipient(); + fixture.allow_wallet(&wallet.pubkey()); + recipients.push((wallet, token_account)); + } + + for (wallet, token_account) in &recipients { + fixture + .transfer_to(&wallet.pubkey(), token_account) + .unwrap(); + } +} + +/// Allowing a wallet for one mint says nothing about another mint. +#[test] +fn an_allowed_wallet_is_allowed_for_one_mint_only() { + let mut fixture = setup(); + let (wallet, _) = fixture.new_recipient(); + fixture.allow_wallet(&wallet.pubkey()); + + let other_mint = create_hooked_mint(&mut fixture.svm, fixture.program_id, &fixture.issuer); + send( + &mut fixture.svm, + initialize_instruction(fixture.program_id, &fixture.issuer.pubkey(), &other_mint), + &fixture.issuer, + ) + .unwrap(); + let other_sender = create_token_extensions_account( + &mut fixture.svm, + &fixture.issuer.pubkey(), + &other_mint, + &fixture.issuer, + None, + ) + .unwrap(); + fixture.svm.expire_blockhash(); + mint_tokens_to_token_extensions_account( + &mut fixture.svm, + &other_mint, + &other_sender, + ONE_TOKEN, + &fixture.issuer, + None, + ) + .unwrap(); + fixture.svm.expire_blockhash(); + let other_recipient = create_token_extensions_account( + &mut fixture.svm, + &wallet.pubkey(), + &other_mint, + &fixture.issuer, + None, + ) + .unwrap(); + fixture.svm.expire_blockhash(); + + let extra_accounts = build_hook_accounts( + &other_mint, + &fixture.program_id, + &[HookAccount { + pubkey: allowed_wallet_address(&fixture.program_id, &other_mint, &wallet.pubkey()), + is_signer: false, + is_writable: false, + }], + ); + assert_rejected( + transfer_checked_token_extensions( + &mut fixture.svm, + &other_sender, + &other_mint, + &other_recipient, + &fixture.issuer, + ONE_TOKEN, + DECIMALS, + &extra_accounts, + None, + ), + RECIPIENT_NOT_ALLOWED, + ); +} + +/// Only the mint authority can set up a mint's allow list, so nobody can +/// claim the list for a mint they do not issue. +#[test] +fn only_the_mint_authority_can_create_the_allow_list() { + let program_id = transfer_hook::id(); + let mut svm = anchor_v2_testing::svm(); + let program_bytes = include_bytes!("../../../target/deploy/transfer_hook.so"); + svm.add_program(program_id, program_bytes).unwrap(); + let issuer = create_wallet(&mut svm, 10_000_000_000).unwrap(); + let attacker = create_wallet(&mut svm, 10_000_000_000).unwrap(); + let mint = create_hooked_mint(&mut svm, program_id, &issuer); + + let result = send( + &mut svm, + initialize_instruction(program_id, &attacker.pubkey(), &mint), + &attacker, + ); + assert!(result.is_err(), "a stranger must not create the allow list"); + + send( + &mut svm, + initialize_instruction(program_id, &issuer.pubkey(), &mint), + &issuer, + ) + .unwrap(); +} + +#[test] +fn only_the_authority_can_allow_a_wallet() { + let mut fixture = setup(); + let attacker = create_wallet(&mut fixture.svm, 10_000_000_000).unwrap(); + let (wallet, token_account) = fixture.new_recipient(); + + let instruction = fixture.allow_wallet_instruction(&attacker.pubkey(), &wallet.pubkey()); + assert!(send(&mut fixture.svm, instruction, &attacker).is_err()); + + assert_rejected( + fixture.transfer_to(&wallet.pubkey(), &token_account), + RECIPIENT_NOT_ALLOWED, + ); +} diff --git a/tokens/token-extensions/transfer-hook/whitelist/quasar/Cargo.toml b/tokens/token-extensions/transfer-hook/allow-list/quasar/Cargo.toml similarity index 97% rename from tokens/token-extensions/transfer-hook/whitelist/quasar/Cargo.toml rename to tokens/token-extensions/transfer-hook/allow-list/quasar/Cargo.toml index 4ac00eabd..9082bf649 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/quasar/Cargo.toml +++ b/tokens/token-extensions/transfer-hook/allow-list/quasar/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "quasar-transfer-hook-whitelist" +name = "quasar-transfer-hook-allow-list" version = "0.1.0" edition = "2021" diff --git a/tokens/token-extensions/transfer-hook/whitelist/quasar/Quasar.toml b/tokens/token-extensions/transfer-hook/allow-list/quasar/Quasar.toml similarity index 75% rename from tokens/token-extensions/transfer-hook/whitelist/quasar/Quasar.toml rename to tokens/token-extensions/transfer-hook/allow-list/quasar/Quasar.toml index 4989df023..f76145e2c 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/quasar/Quasar.toml +++ b/tokens/token-extensions/transfer-hook/allow-list/quasar/Quasar.toml @@ -1,5 +1,5 @@ [project] -name = "quasar-transfer-hook-whitelist" +name = "quasar-transfer-hook-allow-list" [testing] command = { program = "cargo", args = ["test"] } diff --git a/tokens/token-extensions/transfer-hook/allow-list/quasar/README.md b/tokens/token-extensions/transfer-hook/allow-list/quasar/README.md new file mode 100644 index 000000000..4899ea370 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/allow-list/quasar/README.md @@ -0,0 +1,55 @@ +# Transfer Hook - Allow List (Quasar) + +A [Token Extensions](https://solana.com/docs/terminology#token-extensions-program) transfer hook that only lets a mint's tokens reach wallets on that mint's allow list. Every transfer of the mint calls the hook, and the hook rejects the transfer unless the recipient wallet is allowed. + +See also: the [repository catalog](../../../../../README.md), and [Allow/Block List](../../allow-block-list-token/) for a list that can also block wallets or allow transfers below a threshold. + +## Major concepts + +- Transfer hook with one extra account, resolved per transfer +- One account per allowed wallet, so the list has no size limit +- Only the mint authority can set up a mint's allow list + +## Accounts + +- The **allow list** account lives at a PDA with seeds `"allow_list"` and the mint. It holds the `authority` that may allow and disallow wallets: the mint authority who created it. One per mint. +- An **allowed wallet** account lives at a PDA with seeds `"allowed_wallet"`, the mint and the wallet. Its existence is the permission. `allow_wallet` creates it and `disallow_wallet` closes it, returning the rent to the authority. + +Because each allowed wallet is its own account, allowing the ten-thousandth wallet costs the same as allowing the first. + +## How a transfer is checked + +`initialize_extra_account_meta_list` stores one extra account in the mint's `ExtraAccountMetaList`: the allowed wallet PDA, with seeds `"allowed_wallet"`, the mint (account 1 of the transfer) and the destination token account's owner (account 2, bytes 32 to 64). On every transfer Token Extensions derives that address for the actual recipient and passes the account to the hook. The hook re-derives the address from the destination token account and fails with custom error 6000 (`ERROR_RECIPIENT_NOT_ALLOWED`) unless an allowed wallet account exists there. + +The hook checks the wallet that owns the destination token account, not the token account itself, so allowing a wallet covers every token account it holds for that mint. + +## Instruction handlers + +- `initialize_extra_account_meta_list`: creates the mint's `ExtraAccountMetaList` and allow list. The signer must be the mint authority, and becomes the allow list's authority. +- `allow_wallet`: the allow list's authority adds a wallet. +- `disallow_wallet`: the allow list's authority removes a wallet. +- `transfer_hook`: called by Token Extensions on every transfer. + +## Setup + +From `tokens/token-extensions/transfer-hook/allow-list/quasar/`: + +```bash +quasar build +``` + +Prerequisites: [Quasar](https://quasar-lang.com/docs) CLI and [Agave](https://docs.anza.xyz/) toolchain (see `Quasar.toml`). + +## Testing + +In-process tests via **Quasar SVM** (`quasar-svm` in `Quasar.toml`): + +```bash +cargo test +``` + +The harness runs each instruction directly, so the tests call `transfer_hook` with the accounts Token Extensions would pass during a transfer, rather than making a transfer. They check that the hook accepts an allowed wallet; that it refuses a wallet not on the list, a disallowed wallet, a wallet allowed only for another mint, and an allowed wallet account passed for a different recipient; that fifty wallets can be allowed; that only the mint authority can create the allow list; and that only its authority can allow or disallow a wallet. No local validator. + +## Usage + +Read `src/` and `Quasar.toml`. Compare with the [Anchor](../anchor/) variant in the same example. diff --git a/tokens/token-extensions/transfer-hook/allow-list/quasar/src/lib.rs b/tokens/token-extensions/transfer-hook/allow-list/quasar/src/lib.rs new file mode 100644 index 000000000..72727d41f --- /dev/null +++ b/tokens/token-extensions/transfer-hook/allow-list/quasar/src/lib.rs @@ -0,0 +1,406 @@ +#![cfg_attr(not(test), no_std)] + +use quasar_lang::sysvars::Sysvar; +use quasar_lang::{cpi::Seed, prelude::*}; + +#[cfg(test)] +mod tests; + +declare_id!("DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub"); + +/// SPL Transfer Hook Interface discriminators (SHA-256 prefix). +#[allow(dead_code)] +const EXECUTE_DISCRIMINATOR: [u8; 8] = [105, 37, 101, 197, 75, 251, 102, 26]; + +const EXTRA_ACCOUNT_METAS_SEED: &[u8] = b"extra-account-metas"; +const ALLOW_LIST_SEED: &[u8] = b"allow_list"; +const ALLOWED_WALLET_SEED: &[u8] = b"allowed_wallet"; + +/// AllowList layout: [32 bytes authority] [1 byte bump]. +const ALLOW_LIST_SIZE: u64 = 33; +/// AllowedWallet layout: [1 byte marker, 1 while the wallet is allowed] [1 byte bump]. +const ALLOWED_WALLET_SIZE: u64 = 2; + +/// Size of the ExtraAccountMetaList account holding one extra account: +/// 8 (discriminator) + 4 (length) + 4 (count) + 35 (one ExtraAccountMeta). +const META_LIST_SIZE: u64 = 51; + +// Base SPL layouts: a mint's `mint_authority` is a COption at bytes +// 0..36 (4-byte tag, then the key), and a token account's owner sits at 32..64. +const MINT_AUTHORITY_TAG: core::ops::Range = 0..4; +const MINT_AUTHORITY_KEY: core::ops::Range = 4..36; +const TOKEN_ACCOUNT_OWNER: core::ops::Range = 32..64; + +const ERROR_RECIPIENT_NOT_ALLOWED: u32 = 6000; +const ERROR_UNAUTHORIZED: u32 = 6001; + +/// Transfer hook that only lets a mint's tokens reach wallets on that mint's +/// allow list. Each allowed wallet is its own PDA, +/// ["allowed_wallet", mint, wallet], so the list has no size limit. +#[program] +mod quasar_transfer_hook_allow_list { + use super::*; + + /// Create the ExtraAccountMetaList PDA and the mint's AllowList PDA. Only + /// the mint authority can call it, and becomes the allow list's authority. + /// Discriminator = sha256("spl-transfer-hook-interface:initialize-extra-account-metas")[:8] + #[instruction(discriminator = [43, 34, 13, 49, 167, 88, 235, 235])] + pub fn initialize_extra_account_meta_list( + ctx: Ctx, + ) -> Result<(), ProgramError> { + handle_initialize(&mut ctx.accounts) + } + + /// Transfer hook handler - checks the recipient wallet is allowed. + /// Discriminator = sha256("spl-transfer-hook-interface:execute")[:8] + #[instruction(discriminator = [105, 37, 101, 197, 75, 251, 102, 26])] + pub fn transfer_hook( + ctx: Ctx, + _amount: u64, + ) -> Result<(), ProgramError> { + handle_transfer_hook(&ctx.accounts) + } + + /// Allow a wallet to receive the mint's tokens. Only the allow list's authority. + #[instruction(discriminator = [0, 0, 0, 0, 0, 0, 0, 2])] + pub fn allow_wallet(ctx: Ctx) -> Result<(), ProgramError> { + handle_allow_wallet(&mut ctx.accounts) + } + + /// Remove a wallet from the allow list, closing its PDA. Only the allow list's authority. + #[instruction(discriminator = [0, 0, 0, 0, 0, 0, 0, 3])] + pub fn disallow_wallet(ctx: Ctx) -> Result<(), ProgramError> { + handle_disallow_wallet(&mut ctx.accounts) + } +} + +fn recipient_not_allowed() -> ProgramError { + ProgramError::Custom(ERROR_RECIPIENT_NOT_ALLOWED) +} + +fn unauthorized() -> ProgramError { + ProgramError::Custom(ERROR_UNAUTHORIZED) +} + +/// Check `account` is the mint's AllowList PDA and `signer` its authority. +fn require_allow_list_authority( + allow_list: &UncheckedAccount, + mint: &Address, + signer: &Address, +) -> Result<(), ProgramError> { + let (allow_list_pda, _) = + quasar_lang::pda::try_find_program_address(&[ALLOW_LIST_SEED, mint.as_ref()], &crate::ID)?; + let view = allow_list.to_account_view(); + if view.address() != &allow_list_pda || !view.owned_by(&crate::ID) { + return Err(ProgramError::InvalidSeeds); + } + let data = view.try_borrow()?; + if data.len() < ALLOW_LIST_SIZE as usize { + return Err(ProgramError::UninitializedAccount); + } + if &data[0..32] != signer.as_ref() { + log("Only the allow list's authority can change it"); + return Err(unauthorized()); + } + Ok(()) +} + +// --------------------------------------------------------------------------- +// InitializeExtraAccountMetaList +// --------------------------------------------------------------------------- + +#[derive(Accounts)] +pub struct InitializeExtraAccountMetaListAccountConstraints { + #[account(mut)] + pub payer: Signer, + #[account(mut)] + pub extra_account_meta_list: UncheckedAccount, + pub mint: UncheckedAccount, + /// AllowList PDA: ["allow_list", mint] + #[account(mut)] + pub allow_list: UncheckedAccount, + pub system_program: Program, +} + +#[inline(always)] +pub fn handle_initialize( + accounts: &mut InitializeExtraAccountMetaListAccountConstraints, +) -> Result<(), ProgramError> { + let mint_address = *accounts.mint.to_account_view().address(); + let payer_address = *accounts.payer.to_account_view().address(); + + // Only the mint authority may set up the mint's allow list, so nobody can + // claim the list for a mint they do not issue. + { + let mint_data = accounts.mint.to_account_view().try_borrow()?; + if mint_data.len() < MINT_AUTHORITY_KEY.end + || mint_data[MINT_AUTHORITY_TAG] != 1u32.to_le_bytes() + || &mint_data[MINT_AUTHORITY_KEY] != payer_address.as_ref() + { + log("Only the mint authority can create the allow list"); + return Err(unauthorized()); + } + } + + // Create ExtraAccountMetaList PDA (1 extra account: the recipient's AllowedWallet) + let (meta_list_pda, meta_list_bump) = quasar_lang::pda::try_find_program_address( + &[EXTRA_ACCOUNT_METAS_SEED, mint_address.as_ref()], + &crate::ID, + )?; + if accounts.extra_account_meta_list.to_account_view().address() != &meta_list_pda { + return Err(ProgramError::InvalidSeeds); + } + let meta_list_bump_bytes = [meta_list_bump]; + let meta_list_seeds = [ + Seed::from(EXTRA_ACCOUNT_METAS_SEED), + Seed::from(mint_address.as_ref()), + Seed::from(&meta_list_bump_bytes as &[u8]), + ]; + accounts + .system_program + .create_account( + &accounts.payer, + &accounts.extra_account_meta_list, + Rent::get()?.try_minimum_balance(META_LIST_SIZE as usize)?, + META_LIST_SIZE, + &crate::ID, + ) + .invoke_signed(&meta_list_seeds)?; + + { + let view = unsafe { + &mut *(&mut accounts.extra_account_meta_list as *mut UncheckedAccount + as *mut AccountView) + }; + let mut data = view.try_borrow_mut()?; + data[0..8].copy_from_slice(&EXECUTE_DISCRIMINATOR); + data[8..12].copy_from_slice(&39u32.to_le_bytes()); + data[12..16].copy_from_slice(&1u32.to_le_bytes()); + + // ExtraAccountMeta: PDA of this program from three seeds, + // [Literal("allowed_wallet"), AccountKey(1) = mint, + // AccountData(2, 32, 32) = the destination token account's owner]. + data[16] = 1; // PDA from seeds + let mut config = [0u8; 32]; + let literal_len = ALLOWED_WALLET_SEED.len(); + config[0] = 1; // literal + config[1] = literal_len as u8; + config[2..2 + literal_len].copy_from_slice(ALLOWED_WALLET_SEED); + let mut cursor = 2 + literal_len; + config[cursor..cursor + 2].copy_from_slice(&[3, 1]); // account key, index 1 + cursor += 2; + config[cursor..cursor + 4].copy_from_slice(&[4, 2, 32, 32]); // account data, index 2, offset 32, length 32 + data[17..49].copy_from_slice(&config); + data[49] = 0; // not signer + data[50] = 0; // not writable + } + + // Create the mint's AllowList PDA, with the mint authority as its authority. + let (allow_list_pda, allow_list_bump) = quasar_lang::pda::try_find_program_address( + &[ALLOW_LIST_SEED, mint_address.as_ref()], + &crate::ID, + )?; + if accounts.allow_list.to_account_view().address() != &allow_list_pda { + return Err(ProgramError::InvalidSeeds); + } + let allow_list_bump_bytes = [allow_list_bump]; + let allow_list_seeds = [ + Seed::from(ALLOW_LIST_SEED), + Seed::from(mint_address.as_ref()), + Seed::from(&allow_list_bump_bytes as &[u8]), + ]; + accounts + .system_program + .create_account( + &accounts.payer, + &accounts.allow_list, + Rent::get()?.try_minimum_balance(ALLOW_LIST_SIZE as usize)?, + ALLOW_LIST_SIZE, + &crate::ID, + ) + .invoke_signed(&allow_list_seeds)?; + + let view = + unsafe { &mut *(&mut accounts.allow_list as *mut UncheckedAccount as *mut AccountView) }; + let mut data = view.try_borrow_mut()?; + data[0..32].copy_from_slice(payer_address.as_ref()); + data[32] = allow_list_bump; + + log("Allow list transfer hook initialized"); + Ok(()) +} + +// --------------------------------------------------------------------------- +// TransferHook +// --------------------------------------------------------------------------- + +#[derive(Accounts)] +pub struct TransferHookAccountConstraints { + pub source_token: UncheckedAccount, + pub mint: UncheckedAccount, + pub destination_token: UncheckedAccount, + pub owner: UncheckedAccount, + pub extra_account_meta_list: UncheckedAccount, + /// The recipient's AllowedWallet PDA. It may not exist: a missing account + /// is how a wallet that is not allowed shows up. + pub allowed_wallet: UncheckedAccount, +} + +#[inline(always)] +pub fn handle_transfer_hook(accounts: &TransferHookAccountConstraints) -> Result<(), ProgramError> { + // The recipient is the wallet that owns the destination token account. + let destination = accounts.destination_token.to_account_view().try_borrow()?; + if destination.len() < TOKEN_ACCOUNT_OWNER.end { + return Err(ProgramError::InvalidAccountData); + } + let recipient = &destination[TOKEN_ACCOUNT_OWNER]; + + let mint_address = accounts.mint.to_account_view().address(); + let (allowed_wallet_pda, _) = quasar_lang::pda::try_find_program_address( + &[ALLOWED_WALLET_SEED, mint_address.as_ref(), recipient], + &crate::ID, + )?; + let allowed_wallet = accounts.allowed_wallet.to_account_view(); + if allowed_wallet.address() != &allowed_wallet_pda { + return Err(ProgramError::InvalidSeeds); + } + + // Only this program can create an account at one of its own PDAs, so a + // live, program-owned account here was created by `allow_wallet` and not + // since closed by `disallow_wallet`. + let allowed = allowed_wallet.owned_by(&crate::ID) + && allowed_wallet.data_len() >= ALLOWED_WALLET_SIZE as usize + && allowed_wallet.try_borrow()?[0] == 1; + if !allowed { + log("Recipient wallet is not on the allow list"); + return Err(recipient_not_allowed()); + } + + Ok(()) +} + +// --------------------------------------------------------------------------- +// AllowWallet +// --------------------------------------------------------------------------- + +#[derive(Accounts)] +pub struct AllowWalletAccountConstraints { + #[account(mut)] + pub authority: Signer, + pub mint: UncheckedAccount, + pub allow_list: UncheckedAccount, + pub wallet: UncheckedAccount, + #[account(mut)] + pub allowed_wallet: UncheckedAccount, + pub system_program: Program, +} + +#[inline(always)] +pub fn handle_allow_wallet( + accounts: &mut AllowWalletAccountConstraints, +) -> Result<(), ProgramError> { + let mint_address = *accounts.mint.to_account_view().address(); + let wallet_address = *accounts.wallet.to_account_view().address(); + require_allow_list_authority( + &accounts.allow_list, + &mint_address, + accounts.authority.to_account_view().address(), + )?; + + let (allowed_wallet_pda, bump) = quasar_lang::pda::try_find_program_address( + &[ + ALLOWED_WALLET_SEED, + mint_address.as_ref(), + wallet_address.as_ref(), + ], + &crate::ID, + )?; + if accounts.allowed_wallet.to_account_view().address() != &allowed_wallet_pda { + return Err(ProgramError::InvalidSeeds); + } + let bump_bytes = [bump]; + let seeds = [ + Seed::from(ALLOWED_WALLET_SEED), + Seed::from(mint_address.as_ref()), + Seed::from(wallet_address.as_ref()), + Seed::from(&bump_bytes as &[u8]), + ]; + accounts + .system_program + .create_account( + &accounts.authority, + &accounts.allowed_wallet, + Rent::get()?.try_minimum_balance(ALLOWED_WALLET_SIZE as usize)?, + ALLOWED_WALLET_SIZE, + &crate::ID, + ) + .invoke_signed(&seeds)?; + + let view = unsafe { + &mut *(&mut accounts.allowed_wallet as *mut UncheckedAccount as *mut AccountView) + }; + let mut data = view.try_borrow_mut()?; + data[0] = 1; + data[1] = bump; + + log("Wallet allowed"); + Ok(()) +} + +// --------------------------------------------------------------------------- +// DisallowWallet +// --------------------------------------------------------------------------- + +#[derive(Accounts)] +pub struct DisallowWalletAccountConstraints { + #[account(mut)] + pub authority: Signer, + pub mint: UncheckedAccount, + pub allow_list: UncheckedAccount, + pub wallet: UncheckedAccount, + #[account(mut)] + pub allowed_wallet: UncheckedAccount, +} + +#[inline(always)] +pub fn handle_disallow_wallet( + accounts: &mut DisallowWalletAccountConstraints, +) -> Result<(), ProgramError> { + let mint_address = *accounts.mint.to_account_view().address(); + let wallet_address = *accounts.wallet.to_account_view().address(); + require_allow_list_authority( + &accounts.allow_list, + &mint_address, + accounts.authority.to_account_view().address(), + )?; + + let (allowed_wallet_pda, _) = quasar_lang::pda::try_find_program_address( + &[ + ALLOWED_WALLET_SEED, + mint_address.as_ref(), + wallet_address.as_ref(), + ], + &crate::ID, + )?; + let allowed_wallet = accounts.allowed_wallet.to_account_view(); + if allowed_wallet.address() != &allowed_wallet_pda || !allowed_wallet.owned_by(&crate::ID) { + return Err(ProgramError::InvalidSeeds); + } + + // Close the AllowedWallet account: rent back to the authority, data zeroed. + let refund = allowed_wallet.lamports(); + let authority = accounts.authority.to_account_view(); + set_lamports(allowed_wallet, 0); + set_lamports(authority, authority.lamports() + refund); + + let view = unsafe { + &mut *(&mut accounts.allowed_wallet as *mut UncheckedAccount as *mut AccountView) + }; + let mut data = view.try_borrow_mut()?; + for byte in data.iter_mut() { + *byte = 0; + } + + log("Wallet disallowed"); + Ok(()) +} diff --git a/tokens/token-extensions/transfer-hook/allow-list/quasar/src/tests.rs b/tokens/token-extensions/transfer-hook/allow-list/quasar/src/tests.rs new file mode 100644 index 000000000..31b0279e5 --- /dev/null +++ b/tokens/token-extensions/transfer-hook/allow-list/quasar/src/tests.rs @@ -0,0 +1,214 @@ +use { + crate::cpi::{ + AllowWalletInstruction, DisallowWalletInstruction, + InitializeExtraAccountMetaListInstruction, TransferHookInstruction, + }, + quasar_test::prelude::*, +}; + +// The harness runs each instruction directly, so these tests call the hook the +// way Token Extensions would during a transfer, with the accounts it would pass. + +// Deterministic addresses keep tests independent of discovery order. +const ISSUER: Pubkey = Pubkey::new_from_array([1; 32]); +const MINT: Pubkey = Pubkey::new_from_array([2; 32]); +const SOURCE_TOKEN: Pubkey = Pubkey::new_from_array([3; 32]); +const ATTACKER: Pubkey = Pubkey::new_from_array([4; 32]); +const OTHER_MINT: Pubkey = Pubkey::new_from_array([5; 32]); + +const RECIPIENT_NOT_ALLOWED: ProgramError = ProgramError::Custom(6000); +const UNAUTHORIZED: ProgramError = ProgramError::Custom(6001); + +fn pda(seeds: &[&[u8]]) -> Pubkey { + let program_id: Pubkey = crate::ID.into(); + Pubkey::find_program_address(seeds, &program_id).0 +} + +fn meta_list(mint: &Pubkey) -> Pubkey { + pda(&[b"extra-account-metas", mint.as_ref()]) +} + +fn allow_list(mint: &Pubkey) -> Pubkey { + pda(&[b"allow_list", mint.as_ref()]) +} + +fn allowed_wallet(mint: &Pubkey, wallet: &Pubkey) -> Pubkey { + pda(&[b"allowed_wallet", mint.as_ref(), wallet.as_ref()]) +} + +fn add_mint(test: &mut Test, mint: Pubkey) { + test.add( + Mint::new(ISSUER) + .at(mint) + .token_program(TokenProgram::Token2022), + ); +} + +fn initialize(payer: Pubkey, mint: Pubkey) -> InitializeExtraAccountMetaListInstruction { + InitializeExtraAccountMetaListInstruction { + payer, + extra_account_meta_list: meta_list(&mint), + mint, + allow_list: allow_list(&mint), + } +} + +fn allow(authority: Pubkey, mint: Pubkey, wallet: Pubkey) -> AllowWalletInstruction { + AllowWalletInstruction { + authority, + mint, + allow_list: allow_list(&mint), + wallet, + allowed_wallet: allowed_wallet(&mint, &wallet), + } +} + +fn disallow(authority: Pubkey, mint: Pubkey, wallet: Pubkey) -> DisallowWalletInstruction { + DisallowWalletInstruction { + authority, + mint, + allow_list: allow_list(&mint), + wallet, + allowed_wallet: allowed_wallet(&mint, &wallet), + } +} + +/// A recipient wallet with a token account for `mint`; returns (wallet, token account). +fn add_recipient(test: &mut Test, mint: Pubkey) -> (Pubkey, Pubkey) { + let wallet = test.add(Wallet::new()); + let token_account = + test.add(TokenAccount::new(mint, wallet).token_program(TokenProgram::Token2022)); + (wallet, token_account) +} + +fn hook(mint: Pubkey, wallet: Pubkey, destination_token: Pubkey) -> TransferHookInstruction { + TransferHookInstruction { + source_token: SOURCE_TOKEN, + mint, + destination_token, + owner: ISSUER, + extra_account_meta_list: meta_list(&mint), + allowed_wallet: allowed_wallet(&mint, &wallet), + _amount: 100, + } +} + +/// An issuer with a mint whose allow list is set up. +fn setup(test: &mut Test) { + test.add(Wallet::new().at(ISSUER)); + add_mint(test, MINT); + test.send(initialize(ISSUER, MINT)).succeeds(); +} + +#[quasar_test] +fn transfer_to_an_allowed_wallet_succeeds(test: &mut Test) { + setup(test); + let (wallet, token_account) = add_recipient(test, MINT); + test.send(allow(ISSUER, MINT, wallet)).succeeds(); + + test.send(hook(MINT, wallet, token_account)).succeeds(); +} + +#[quasar_test] +fn transfer_to_a_wallet_not_on_the_list_is_rejected(test: &mut Test) { + setup(test); + let (wallet, token_account) = add_recipient(test, MINT); + + test.send(hook(MINT, wallet, token_account)) + .fails(RECIPIENT_NOT_ALLOWED); +} + +#[quasar_test] +fn disallowing_a_wallet_stops_transfers_to_it(test: &mut Test) { + setup(test); + let (wallet, token_account) = add_recipient(test, MINT); + test.send(allow(ISSUER, MINT, wallet)).succeeds(); + test.send(hook(MINT, wallet, token_account)).succeeds(); + + test.send(disallow(ISSUER, MINT, wallet)).succeeds(); + + test.send(hook(MINT, wallet, token_account)) + .fails(RECIPIENT_NOT_ALLOWED); + assert_eq!(test.lamports(allowed_wallet(&MINT, &wallet)), 0); +} + +/// The list has no size limit: each allowed wallet is its own account, so the +/// fiftieth costs the same as the first. The single-account design this +/// example replaced held eleven. +#[quasar_test] +fn the_list_holds_more_wallets_than_one_account_could(test: &mut Test) { + setup(test); + let mut recipients = Vec::new(); + for _ in 0..50 { + let recipient = add_recipient(test, MINT); + test.send(allow(ISSUER, MINT, recipient.0)).succeeds(); + recipients.push(recipient); + } + + for (wallet, token_account) in recipients { + test.send(hook(MINT, wallet, token_account)).succeeds(); + } +} + +/// Allowing a wallet for one mint says nothing about another mint. +#[quasar_test] +fn an_allowed_wallet_is_allowed_for_one_mint_only(test: &mut Test) { + setup(test); + add_mint(test, OTHER_MINT); + test.send(initialize(ISSUER, OTHER_MINT)).succeeds(); + let (wallet, _) = add_recipient(test, MINT); + test.send(allow(ISSUER, MINT, wallet)).succeeds(); + let other_token_account = + test.add(TokenAccount::new(OTHER_MINT, wallet).token_program(TokenProgram::Token2022)); + + test.send(hook(OTHER_MINT, wallet, other_token_account)) + .fails(RECIPIENT_NOT_ALLOWED); +} + +/// The hook reads the recipient from the destination token account, so the +/// AllowedWallet of some other wallet does not unlock a transfer. +#[quasar_test] +fn another_wallets_entry_does_not_unlock_a_transfer(test: &mut Test) { + setup(test); + let (allowed, _) = add_recipient(test, MINT); + let (_, stranger_token_account) = add_recipient(test, MINT); + test.send(allow(ISSUER, MINT, allowed)).succeeds(); + + test.send(hook(MINT, allowed, stranger_token_account)) + .fails(ProgramError::InvalidSeeds); +} + +/// Only the mint authority can set up a mint's allow list, so nobody can +/// claim the list for a mint they do not issue. +#[quasar_test] +fn only_the_mint_authority_can_create_the_allow_list(test: &mut Test) { + test.add(Wallet::new().at(ISSUER)); + test.add(Wallet::new().at(ATTACKER)); + add_mint(test, MINT); + + test.send(initialize(ATTACKER, MINT)).fails(UNAUTHORIZED); + test.send(initialize(ISSUER, MINT)).succeeds(); +} + +#[quasar_test] +fn only_the_authority_can_allow_a_wallet(test: &mut Test) { + setup(test); + test.add(Wallet::new().at(ATTACKER)); + let (wallet, token_account) = add_recipient(test, MINT); + + test.send(allow(ATTACKER, MINT, wallet)).fails(UNAUTHORIZED); + test.send(hook(MINT, wallet, token_account)) + .fails(RECIPIENT_NOT_ALLOWED); +} + +#[quasar_test] +fn only_the_authority_can_disallow_a_wallet(test: &mut Test) { + setup(test); + test.add(Wallet::new().at(ATTACKER)); + let (wallet, token_account) = add_recipient(test, MINT); + test.send(allow(ISSUER, MINT, wallet)).succeeds(); + + test.send(disallow(ATTACKER, MINT, wallet)) + .fails(UNAUTHORIZED); + test.send(hook(MINT, wallet, token_account)).succeeds(); +} diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/README.md b/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/README.md deleted file mode 100644 index c716e8821..000000000 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Transfer Hook - Whitelist (Anchor) - -> [!NOTE] -> This is the **Anchor v1** copy of this example, on Anchor 1.2.1, the current -> stable Anchor release. Every `anchor` command on this page needs the v1 CLI: -> `avm install 1.2.1 && avm use 1.2.1`. The Anchor v2 version of this example is in -> [`../anchor`](../anchor/). - -A whitelist enforced by a [Token Extensions](https://solana.com/docs/terminology#token-extensions-program) transfer hook. The whitelist is stored inline on a single [account](https://solana.com/docs/terminology#account). - -This approach doesn't scale: the whitelist eventually runs out of account space. For larger lists, store entries in external [PDAs](https://solana.com/docs/terminology#program-derived-address-pda) (one PDA per whitelisted wallet) - see the [`block-list`](../../block-list/) example for that pattern. diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/src/instructions/add_to_whitelist.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/src/instructions/add_to_whitelist.rs deleted file mode 100644 index 40e4b72b1..000000000 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/src/instructions/add_to_whitelist.rs +++ /dev/null @@ -1,40 +0,0 @@ -use anchor_lang::prelude::*; - -use crate::WhiteList; - -#[derive(Accounts)] -pub struct AddToWhiteListAccountConstraints<'info> { - /// CHECK: New account to add to white list - #[account()] - pub new_account: UncheckedAccount<'info>, - #[account( - mut, - seeds = [b"white_list"], - bump - )] - pub white_list: Account<'info, WhiteList>, - #[account(mut)] - pub signer: Signer<'info>, -} - -pub fn handler(context: Context) -> Result<()> { - if context.accounts.white_list.authority != context.accounts.signer.key() { - panic!("Only the authority can add to the white list!"); - } - - context - .accounts - .white_list - .white_list - .push(context.accounts.new_account.key()); - msg!( - "New account white listed! {0}", - context.accounts.new_account.key().to_string() - ); - msg!( - "White list length! {0}", - context.accounts.white_list.white_list.len() - ); - - Ok(()) -} diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs deleted file mode 100644 index 66017e72c..000000000 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor-v1/programs/transfer-hook/tests/test_transfer_hook.rs +++ /dev/null @@ -1,138 +0,0 @@ -use { - anchor_lang::{ - solana_program::{instruction::Instruction, pubkey::Pubkey, system_program}, - InstructionData, ToAccountMetas, - }, - litesvm::LiteSVM, - solana_keypair::Keypair, - solana_kite::{ - create_wallet, send_transaction_from_instructions, - token_extensions::{ - create_token_extensions_account, create_token_extensions_mint, - mint_tokens_to_token_extensions_account, transfer_checked_token_extensions, - MintExtension, TOKEN_EXTENSIONS_PROGRAM_ID, - }, - transfer_hook::{build_hook_accounts, get_hook_accounts_address, HookAccount}, - }, - solana_signer::Signer, -}; - -fn setup() -> (LiteSVM, Pubkey, Keypair) { - let program_id = transfer_hook::id(); - let mut svm = LiteSVM::new(); - - let program_bytes = include_bytes!("../../../target/deploy/transfer_hook.so"); - svm.add_program(program_id, program_bytes).unwrap(); - - let payer = create_wallet(&mut svm, 10_000_000_000).unwrap(); - (svm, program_id, payer) -} - -#[test] -fn test_whitelist_transfer_hook() { - let (mut svm, program_id, payer) = setup(); - let recipient = Keypair::new(); - let decimals: u8 = 9; - - // Derive PDAs - let (white_list_pda, _) = Pubkey::find_program_address(&[b"white_list"], &program_id); - - // Step 1: Create mint with TransferHook extension - let mint = create_token_extensions_mint( - &mut svm, - &payer, - decimals, - None, - &[MintExtension::TransferHook { - program_id: program_id, - }], - None, - ) - .unwrap(); - svm.expire_blockhash(); - - let extra_account_meta_list = get_hook_accounts_address(&mint, &program_id); - - // Step 2: Create token accounts and mint tokens - let amount: u64 = 100 * 10u64.pow(decimals as u32); - let source_ata = - create_token_extensions_account(&mut svm, &payer.pubkey(), &mint, &payer, None).unwrap(); - svm.expire_blockhash(); - - let dest_ata = - create_token_extensions_account(&mut svm, &recipient.pubkey(), &mint, &payer, None) - .unwrap(); - svm.expire_blockhash(); - - mint_tokens_to_token_extensions_account(&mut svm, &mint, &source_ata, amount, &payer, None) - .unwrap(); - svm.expire_blockhash(); - - // Step 3: Initialize ExtraAccountMetaList (also creates whitelist) - let init_extra_ix = Instruction::new_with_bytes( - program_id, - &transfer_hook::instruction::InitializeExtraAccountMetaList {}.data(), - transfer_hook::accounts::InitializeExtraAccountMetaListAccountConstraints { - payer: payer.pubkey(), - extra_account_meta_list, - mint, - system_program: system_program::id(), - white_list: white_list_pda, - } - .to_account_metas(None), - ); - send_transaction_from_instructions( - &mut svm, - vec![init_extra_ix], - &[&payer], - &payer.pubkey(), - None, - ) - .unwrap(); - svm.expire_blockhash(); - - // Step 4: Add destination token account to whitelist - let add_to_whitelist_ix = Instruction::new_with_bytes( - program_id, - &transfer_hook::instruction::AddToWhitelist {}.data(), - transfer_hook::accounts::AddToWhiteListAccountConstraints { - new_account: dest_ata, - white_list: white_list_pda, - signer: payer.pubkey(), - } - .to_account_metas(None), - ); - send_transaction_from_instructions( - &mut svm, - vec![add_to_whitelist_ix], - &[&payer], - &payer.pubkey(), - None, - ) - .unwrap(); - svm.expire_blockhash(); - - // Step 5: Transfer - should succeed (destination is whitelisted) - let transfer_amount: u64 = 1 * 10u64.pow(decimals as u32); - let extra_accounts = build_hook_accounts( - &mint, - &program_id, - &[HookAccount { - pubkey: white_list_pda, - is_signer: false, - is_writable: true, - }], - ); - transfer_checked_token_extensions( - &mut svm, - &source_ata, - &mint, - &dest_ata, - &payer, - transfer_amount, - decimals, - &extra_accounts, - None, - ) - .unwrap(); -} diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/README.md b/tokens/token-extensions/transfer-hook/whitelist/anchor/README.md deleted file mode 100644 index 47f452ebc..000000000 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Transfer Hook - Whitelist (Anchor) - -> [!NOTE] -> This is the **Anchor v2** copy of this example. Every `anchor` command on this page -> needs the v2 CLI: `cargo install anchor-cli --version 2.0.0-rc.2 --locked` (avm has -> no prebuilt binary for this pre-release). The Anchor v1 version of this example is in -> [`../anchor-v1`](../anchor-v1/). - -A whitelist enforced by a [Token Extensions](https://solana.com/docs/terminology#token-extensions-program) transfer hook. The whitelist is stored inline on a single [account](https://solana.com/docs/terminology#account). - -This approach doesn't scale: the whitelist eventually runs out of account space. For larger lists, store entries in external [PDAs](https://solana.com/docs/terminology#program-derived-address-pda) (one PDA per whitelisted wallet) - see the [`block-list`](../../block-list/) example for that pattern. diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/add_to_whitelist.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/add_to_whitelist.rs deleted file mode 100644 index 4b7d6803b..000000000 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/src/instructions/add_to_whitelist.rs +++ /dev/null @@ -1,40 +0,0 @@ -use anchor_lang::prelude::*; - -use crate::WhiteList; - -#[derive(Accounts)] -pub struct AddToWhiteListAccountConstraints { - /// CHECK: New account to add to white list - #[account()] - pub new_account: UncheckedAccount, - #[account( - mut, - seeds = [b"white_list"], - bump - )] - pub white_list: BorshAccount, - #[account(mut)] - pub signer: Signer, -} - -pub fn handler(context: &mut Context) -> Result<()> { - if context.accounts.white_list.authority != *context.accounts.signer.address() { - panic!("Only the authority can add to the white list!"); - } - - context - .accounts - .white_list - .white_list - .push(*context.accounts.new_account.address()); - msg!( - "New account white listed! {0}", - context.accounts.new_account.address().to_string() - ); - msg!( - "White list length! {0}", - context.accounts.white_list.white_list.len() - ); - - Ok(()) -} diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/tests/test_transfer_hook.rs b/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/tests/test_transfer_hook.rs deleted file mode 100644 index 1513ca3a6..000000000 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook/tests/test_transfer_hook.rs +++ /dev/null @@ -1,136 +0,0 @@ -use { - anchor_lang::{ - solana_program::instruction::Instruction, system_program, Address, InstructionData, - ToAccountMetas, - }, - anchor_v2_testing::{litesvm::LiteSVM, Keypair, Signer}, - solana_kite::{ - create_wallet, send_transaction_from_instructions, - token_extensions::{ - create_token_extensions_account, create_token_extensions_mint, - mint_tokens_to_token_extensions_account, transfer_checked_token_extensions, - MintExtension, TOKEN_EXTENSIONS_PROGRAM_ID, - }, - transfer_hook::{build_hook_accounts, get_hook_accounts_address, HookAccount}, - }, -}; - -fn setup() -> (LiteSVM, Address, Keypair) { - let program_id = transfer_hook::id(); - let mut svm = anchor_v2_testing::svm(); - - let program_bytes = include_bytes!("../../../target/deploy/transfer_hook.so"); - svm.add_program(program_id, program_bytes).unwrap(); - - let payer = create_wallet(&mut svm, 10_000_000_000).unwrap(); - (svm, program_id, payer) -} - -#[test] -fn test_whitelist_transfer_hook() { - let (mut svm, program_id, payer) = setup(); - let recipient = Keypair::new(); - let decimals: u8 = 9; - - // Derive PDAs - let (white_list_pda, _) = Address::find_program_address(&[b"white_list"], &program_id); - - // Step 1: Create mint with TransferHook extension - let mint = create_token_extensions_mint( - &mut svm, - &payer, - decimals, - None, - &[MintExtension::TransferHook { - program_id: program_id, - }], - None, - ) - .unwrap(); - svm.expire_blockhash(); - - let extra_account_meta_list = get_hook_accounts_address(&mint, &program_id); - - // Step 2: Create token accounts and mint tokens - let amount: u64 = 100 * 10u64.pow(decimals as u32); - let source_ata = - create_token_extensions_account(&mut svm, &payer.pubkey(), &mint, &payer, None).unwrap(); - svm.expire_blockhash(); - - let dest_ata = - create_token_extensions_account(&mut svm, &recipient.pubkey(), &mint, &payer, None) - .unwrap(); - svm.expire_blockhash(); - - mint_tokens_to_token_extensions_account(&mut svm, &mint, &source_ata, amount, &payer, None) - .unwrap(); - svm.expire_blockhash(); - - // Step 3: Initialize ExtraAccountMetaList (also creates whitelist) - let init_extra_ix = Instruction::new_with_bytes( - program_id, - &transfer_hook::instruction::InitializeExtraAccountMetaList {}.data(), - transfer_hook::accounts::InitializeExtraAccountMetaListAccountConstraints { - payer: payer.pubkey(), - extra_account_meta_list, - mint, - system_program: system_program::ID, - white_list: white_list_pda, - } - .to_account_metas(None), - ); - send_transaction_from_instructions( - &mut svm, - vec![init_extra_ix], - &[&payer], - &payer.pubkey(), - None, - ) - .unwrap(); - svm.expire_blockhash(); - - // Step 4: Add destination token account to whitelist - let add_to_whitelist_ix = Instruction::new_with_bytes( - program_id, - &transfer_hook::instruction::AddToWhitelist {}.data(), - transfer_hook::accounts::AddToWhiteListAccountConstraints { - new_account: dest_ata, - white_list: white_list_pda, - signer: payer.pubkey(), - } - .to_account_metas(None), - ); - send_transaction_from_instructions( - &mut svm, - vec![add_to_whitelist_ix], - &[&payer], - &payer.pubkey(), - None, - ) - .unwrap(); - svm.expire_blockhash(); - - // Step 5: Transfer - should succeed (destination is whitelisted) - let transfer_amount: u64 = 1 * 10u64.pow(decimals as u32); - let extra_accounts = build_hook_accounts( - &mint, - &program_id, - &[HookAccount { - pubkey: white_list_pda, - is_signer: false, - is_writable: true, - }], - ); - transfer_checked_token_extensions( - &mut svm, - &source_ata, - &mint, - &dest_ata, - &payer, - transfer_amount, - decimals, - &extra_accounts, - None, - ) - .unwrap(); -} diff --git a/tokens/token-extensions/transfer-hook/whitelist/quasar/README.md b/tokens/token-extensions/transfer-hook/whitelist/quasar/README.md deleted file mode 100644 index 8ad529b9b..000000000 --- a/tokens/token-extensions/transfer-hook/whitelist/quasar/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Transfer Hook - Whitelist (Quasar) - -Only whitelisted accounts may receive tokens. - -See also: the [repository catalog](../../../../../README.md). - -## Major concepts - -- Transfer hook -- Whitelist PDA - -## Setup - -From `tokens/token-extensions/transfer-hook/whitelist/quasar/`: - -```bash -quasar build -``` - -Prerequisites: [Quasar](https://quasar-lang.com/docs) CLI and [Agave](https://docs.anza.xyz/) toolchain (see `Quasar.toml`). - -## Testing - -In-process tests via **Quasar SVM** (`quasar-svm` in `Quasar.toml`): - -```bash -cargo test -``` - -Tests invoke instruction handlers and assert onchain state. No local validator. - -## Usage - -Read `src/` and `Quasar.toml`. Compare with the [Anchor](../anchor/) variant in the same example where present. diff --git a/tokens/token-extensions/transfer-hook/whitelist/quasar/src/lib.rs b/tokens/token-extensions/transfer-hook/whitelist/quasar/src/lib.rs deleted file mode 100644 index f591ac971..000000000 --- a/tokens/token-extensions/transfer-hook/whitelist/quasar/src/lib.rs +++ /dev/null @@ -1,265 +0,0 @@ -#![cfg_attr(not(test), no_std)] - -use quasar_lang::sysvars::Sysvar; -use quasar_lang::{cpi::Seed, prelude::*}; - -#[cfg(test)] -mod tests; - -declare_id!("DrWbQtYJGtsoRwzKqAbHKHKsCJJfpysudF39GBVFSxub"); - -/// SPL Transfer Hook Interface discriminators (SHA-256 prefix). -#[allow(dead_code)] -const EXECUTE_DISCRIMINATOR: [u8; 8] = [105, 37, 101, 197, 75, 251, 102, 26]; - -/// Transfer hook that only allows transfers to whitelisted destination token -/// accounts. The whitelist is stored in a PDA seeded by ["white_list"]. -#[program] -mod quasar_transfer_hook_whitelist { - use super::*; - - /// Create the ExtraAccountMetaList PDA and the whitelist PDA. - /// Discriminator = sha256("spl-transfer-hook-interface:initialize-extra-account-metas")[:8] - #[instruction(discriminator = [43, 34, 13, 49, 167, 88, 235, 235])] - pub fn initialize_extra_account_meta_list( - ctx: Ctx, - ) -> Result<(), ProgramError> { - handle_initialize(&mut ctx.accounts) - } - - /// Transfer hook handler - checks if the destination is in the whitelist. - /// Discriminator = sha256("spl-transfer-hook-interface:execute")[:8] - #[instruction(discriminator = [105, 37, 101, 197, 75, 251, 102, 26])] - pub fn transfer_hook( - ctx: Ctx, - _amount: u64, - ) -> Result<(), ProgramError> { - handle_transfer_hook(&ctx.accounts) - } - - /// Add an address to the whitelist. Only callable by the authority. - #[instruction(discriminator = [0, 0, 0, 0, 0, 0, 0, 2])] - pub fn add_to_whitelist( - ctx: Ctx, - ) -> Result<(), ProgramError> { - handle_add_to_whitelist(&mut ctx.accounts) - } -} - -// --------------------------------------------------------------------------- -// InitializeExtraAccountMetaList -// --------------------------------------------------------------------------- - -#[derive(Accounts)] -pub struct InitializeExtraAccountMetaListAccountConstraints { - #[account(mut)] - pub payer: Signer, - #[account(mut)] - pub extra_account_meta_list: UncheckedAccount, - pub mint: UncheckedAccount, - /// Whitelist PDA: ["white_list"] - #[account(mut)] - pub white_list: UncheckedAccount, - pub system_program: Program, -} - -#[inline(always)] -pub fn handle_initialize( - accounts: &mut InitializeExtraAccountMetaListAccountConstraints, -) -> Result<(), ProgramError> { - // Create ExtraAccountMetaList PDA (1 extra account: whitelist) - let meta_list_size: u64 = 51; // 8 + 4 + 4 + 35 - let lamports = Rent::get()?.try_minimum_balance(meta_list_size as usize)?; - - let mint_address = accounts.mint.to_account_view().address(); - let (expected_pda, bump) = quasar_lang::pda::try_find_program_address( - &[b"extra-account-metas", mint_address.as_ref()], - &crate::ID, - )?; - - if accounts.extra_account_meta_list.to_account_view().address() != &expected_pda { - return Err(ProgramError::InvalidSeeds); - } - - let bump_bytes = [bump]; - let seeds = [ - Seed::from(b"extra-account-metas" as &[u8]), - Seed::from(mint_address.as_ref()), - Seed::from(&bump_bytes as &[u8]), - ]; - - accounts - .system_program - .create_account( - &accounts.payer, - &accounts.extra_account_meta_list, - lamports, - meta_list_size, - &crate::ID, - ) - .invoke_signed(&seeds)?; - - // Write TLV data - let view = unsafe { - &mut *(&mut accounts.extra_account_meta_list as *mut UncheckedAccount as *mut AccountView) - }; - let mut data = view.try_borrow_mut()?; - data[0..8].copy_from_slice(&EXECUTE_DISCRIMINATOR); - data[8..12].copy_from_slice(&39u32.to_le_bytes()); - data[12..16].copy_from_slice(&1u32.to_le_bytes()); - - // ExtraAccountMeta for whitelist PDA: seeds = [Literal("white_list")] - data[16] = 1; // PDA from seeds - let mut config = [0u8; 32]; - config[0] = 1; // 1 seed - config[1] = 0; // literal - config[2] = 10; // length - config[3..13].copy_from_slice(b"white_list"); - data[17..49].copy_from_slice(&config); - data[49] = 0; // not signer - data[50] = 1; // writable - - // Create whitelist PDA - // Layout: [32 bytes authority] [4 bytes count] [N * 32 bytes addresses] - // Allocate 400 bytes (enough for ~11 addresses) - let wl_size: u64 = 400; - let wl_lamports = Rent::get()?.try_minimum_balance(wl_size as usize)?; - - let (wl_pda, wl_bump) = - quasar_lang::pda::try_find_program_address(&[b"white_list"], &crate::ID)?; - if accounts.white_list.to_account_view().address() != &wl_pda { - return Err(ProgramError::InvalidSeeds); - } - - let wl_bump_bytes = [wl_bump]; - let wl_seeds = [ - Seed::from(b"white_list" as &[u8]), - Seed::from(&wl_bump_bytes as &[u8]), - ]; - - accounts - .system_program - .create_account( - &accounts.payer, - &accounts.white_list, - wl_lamports, - wl_size, - &crate::ID, - ) - .invoke_signed(&wl_seeds)?; - - // Write authority (payer) to whitelist account - let wl_view = - unsafe { &mut *(&mut accounts.white_list as *mut UncheckedAccount as *mut AccountView) }; - let mut wl_data = wl_view.try_borrow_mut()?; - wl_data[0..32].copy_from_slice(accounts.payer.to_account_view().address().as_ref()); - // count = 0 (already zeroed) - - log("Whitelist transfer hook initialized"); - Ok(()) -} - -// --------------------------------------------------------------------------- -// TransferHook -// --------------------------------------------------------------------------- - -#[derive(Accounts)] -pub struct TransferHookAccountConstraints { - pub source_token: UncheckedAccount, - pub mint: UncheckedAccount, - pub destination_token: UncheckedAccount, - pub owner: UncheckedAccount, - pub extra_account_meta_list: UncheckedAccount, - pub white_list: UncheckedAccount, -} - -#[inline(always)] -pub fn handle_transfer_hook(accounts: &TransferHookAccountConstraints) -> Result<(), ProgramError> { - let wl_view = accounts.white_list.to_account_view(); - let data = wl_view.try_borrow()?; - - if data.len() < 36 { - return Err(ProgramError::AccountDataTooSmall); - } - - // Read count at offset 32 - let mut count_bytes = [0u8; 4]; - count_bytes.copy_from_slice(&data[32..36]); - let count = u32::from_le_bytes(count_bytes) as usize; - - // Check if destination is in the whitelist - let dest_address = accounts.destination_token.to_account_view().address(); - let mut found = false; - for i in 0..count { - let offset = 36 + i * 32; - if offset + 32 > data.len() { - break; - } - if &data[offset..offset + 32] == dest_address.as_ref() { - found = true; - break; - } - } - - if !found { - log("Destination not in whitelist!"); - return Err(ProgramError::InvalidArgument); - } - - log("Transfer allowed: destination is whitelisted"); - Ok(()) -} - -// --------------------------------------------------------------------------- -// AddToWhitelist -// --------------------------------------------------------------------------- - -#[derive(Accounts)] -pub struct AddToWhitelistAccountConstraints { - pub signer: Signer, - pub new_account: UncheckedAccount, - #[account(mut)] - pub white_list: UncheckedAccount, -} - -#[inline(always)] -pub fn handle_add_to_whitelist( - accounts: &mut AddToWhitelistAccountConstraints, -) -> Result<(), ProgramError> { - let view = - unsafe { &mut *(&mut accounts.white_list as *mut UncheckedAccount as *mut AccountView) }; - let mut data = view.try_borrow_mut()?; - - if data.len() < 36 { - return Err(ProgramError::AccountDataTooSmall); - } - - // Verify signer is the authority - let signer_address = accounts.signer.to_account_view().address(); - if &data[0..32] != signer_address.as_ref() { - log("Only the authority can add to the whitelist"); - return Err(ProgramError::IllegalOwner); - } - - // Read current count - let mut count_bytes = [0u8; 4]; - count_bytes.copy_from_slice(&data[32..36]); - let count = u32::from_le_bytes(count_bytes) as usize; - - // Write new address - let offset = 36 + count * 32; - if offset + 32 > data.len() { - log("Whitelist is full"); - return Err(ProgramError::AccountDataTooSmall); - } - - let new_address = accounts.new_account.to_account_view().address(); - data[offset..offset + 32].copy_from_slice(new_address.as_ref()); - - // Update count - let new_count = (count + 1) as u32; - data[32..36].copy_from_slice(&new_count.to_le_bytes()); - - log("Address added to whitelist"); - Ok(()) -} diff --git a/tokens/token-extensions/transfer-hook/whitelist/quasar/src/tests.rs b/tokens/token-extensions/transfer-hook/whitelist/quasar/src/tests.rs deleted file mode 100644 index 689a4c412..000000000 --- a/tokens/token-extensions/transfer-hook/whitelist/quasar/src/tests.rs +++ /dev/null @@ -1,72 +0,0 @@ -use { - crate::cpi::{ - AddToWhitelistInstruction, InitializeExtraAccountMetaListInstruction, - TransferHookInstruction, - }, - quasar_test::prelude::*, -}; - -// Deterministic addresses keep tests independent of discovery order. -const PAYER: Pubkey = Pubkey::new_from_array([1; 32]); -const MINT: Pubkey = Pubkey::new_from_array([2; 32]); -const SOURCE_TOKEN: Pubkey = Pubkey::new_from_array([3; 32]); -const DESTINATION_TOKEN: Pubkey = Pubkey::new_from_array([4; 32]); -const OWNER: Pubkey = Pubkey::new_from_array([5; 32]); -const BAD_DEST: Pubkey = Pubkey::new_from_array([6; 32]); - -/// (extra_account_meta_list, white_list) PDAs. The program derives these with -/// raw seed literals, so the test mirrors the derivation directly. -fn pdas() -> (Pubkey, Pubkey) { - let program_id: Pubkey = crate::ID.into(); - let (meta_list, _) = - Pubkey::find_program_address(&[b"extra-account-metas", MINT.as_ref()], &program_id); - let (white_list, _) = Pubkey::find_program_address(&[b"white_list"], &program_id); - (meta_list, white_list) -} - -fn hook_instruction( - destination_token: Pubkey, - meta_list: Pubkey, - white_list: Pubkey, -) -> TransferHookInstruction { - TransferHookInstruction { - source_token: SOURCE_TOKEN, - mint: MINT, - destination_token, - owner: OWNER, - extra_account_meta_list: meta_list, - white_list, - _amount: 100, - } -} - -#[quasar_test] -fn whitelist_gates_transfers_by_destination(test: &mut Test) { - test.add(Wallet::new().at(PAYER)); - let (meta_list, white_list) = pdas(); - - // 1. Initialize the meta list and whitelist (payer becomes authority). - test.send(InitializeExtraAccountMetaListInstruction { - payer: PAYER, - extra_account_meta_list: meta_list, - mint: MINT, - white_list, - }) - .succeeds(); - - // 2. Add the destination token account to the whitelist. - test.send(AddToWhitelistInstruction { - signer: PAYER, - new_account: DESTINATION_TOKEN, - white_list, - }) - .succeeds(); - - // 3. Transfer hook with a whitelisted destination succeeds. - test.send(hook_instruction(DESTINATION_TOKEN, meta_list, white_list)) - .succeeds(); - - // 4. Transfer hook with a non-whitelisted destination is rejected. - test.send(hook_instruction(BAD_DEST, meta_list, white_list)) - .fails(ProgramError::InvalidArgument); -} From 92dd72c00fe389f21a656c9b01feb79877dd7c0c Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Fri, 6 Nov 2026 16:07:13 +0000 Subject: [PATCH 2/2] Remove the Block List transfer hook and leave notices in the old paths Block List was a Pinocchio-only example with no catalog entry. Its one account per blocked wallet is what Allow/Block List does in Block mode, in Anchor v1, Anchor v2 and Quasar, so it duplicated that example. `whitelist/` and `block-list/` each keep a README.md saying where the example went, so old links still land somewhere useful. Claude-Session: https://claude.ai/code/session_01GZHin7uZN95tCycB2vWhog --- CHANGELOG.md | 8 + .../transfer-hook/block-list/README.md | 5 + .../block-list/pinocchio/.gitignore | 7 - .../block-list/pinocchio/Cargo.toml | 7 - .../block-list/pinocchio/cli/Cargo.toml | 26 - .../block-list/pinocchio/cli/src/main.rs | 488 ---------- .../block-list/pinocchio/codama.ts | 304 ------- .../block-list/pinocchio/package.json | 21 - .../block-list/pinocchio/pnpm-lock.yaml | 832 ------------------ .../block-list/pinocchio/program/Cargo.toml | 36 - .../block-list/pinocchio/program/src/error.rs | 24 - .../program/src/instructions/block_wallet.rs | 100 --- .../program/src/instructions/init.rs | 87 -- .../pinocchio/program/src/instructions/mod.rs | 11 - .../src/instructions/setup_extra_metas.rs | 162 ---- .../program/src/instructions/tx_hook.rs | 115 --- .../src/instructions/unblock_wallet.rs | 71 -- .../block-list/pinocchio/program/src/lib.rs | 45 - .../pinocchio/program/src/state/config.rs | 28 - .../pinocchio/program/src/state/mod.rs | 63 -- .../program/src/state/wallet_block.rs | 21 - .../program/src/token_extensions_utils.rs | 86 -- .../pinocchio/program/tests/test.rs | 421 --------- .../block-list/pinocchio/sdk/rust/Cargo.toml | 16 - .../sdk/rust/src/client/accounts/config.rs | 163 ---- .../rust/src/client/accounts/extra_metas.rs | 155 ---- .../sdk/rust/src/client/accounts/mod.rs | 14 - .../rust/src/client/accounts/wallet_block.rs | 163 ---- .../sdk/rust/src/client/errors/mod.rs | 6 - .../src/client/instructions/block_wallet.rs | 447 ---------- .../sdk/rust/src/client/instructions/init.rs | 370 -------- .../sdk/rust/src/client/instructions/mod.rs | 16 - .../client/instructions/setup_extra_metas.rs | 483 ---------- .../src/client/instructions/unblock_wallet.rs | 410 --------- .../pinocchio/sdk/rust/src/client/mod.rs | 14 - .../pinocchio/sdk/rust/src/client/programs.rs | 11 - .../pinocchio/sdk/rust/src/client/shared.rs | 21 - .../block-list/pinocchio/sdk/rust/src/lib.rs | 2 - .../pinocchio/sdk/ts/src/accounts/config.ts | 145 --- .../sdk/ts/src/accounts/extraMetas.ts | 115 --- .../pinocchio/sdk/ts/src/accounts/index.ts | 11 - .../sdk/ts/src/accounts/walletBlock.ts | 128 --- .../block-list/pinocchio/sdk/ts/src/index.ts | 12 - .../sdk/ts/src/instructions/blockWallet.ts | 287 ------ .../sdk/ts/src/instructions/index.ts | 12 - .../pinocchio/sdk/ts/src/instructions/init.ts | 214 ----- .../ts/src/instructions/setupExtraMetas.ts | 313 ------- .../sdk/ts/src/instructions/unblockWallet.ts | 257 ------ .../pinocchio/sdk/ts/src/pdas/config.ts | 21 - .../pinocchio/sdk/ts/src/pdas/extraMetas.ts | 32 - .../pinocchio/sdk/ts/src/pdas/index.ts | 11 - .../pinocchio/sdk/ts/src/pdas/walletBlock.ts | 32 - .../sdk/ts/src/programs/blockList.ts | 75 -- .../pinocchio/sdk/ts/src/programs/index.ts | 9 - .../pinocchio/sdk/ts/src/shared/index.ts | 128 --- .../transfer-hook/block-list/readme.md | 152 ---- .../transfer-hook/whitelist/README.md | 9 + 57 files changed, 22 insertions(+), 7200 deletions(-) create mode 100644 tokens/token-extensions/transfer-hook/block-list/README.md delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/.gitignore delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/Cargo.toml delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/cli/Cargo.toml delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/cli/src/main.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/codama.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/package.json delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/pnpm-lock.yaml delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/program/Cargo.toml delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/error.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/block_wallet.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/init.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/mod.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/setup_extra_metas.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/tx_hook.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/unblock_wallet.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/lib.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/state/config.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/state/mod.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/state/wallet_block.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/token_extensions_utils.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/program/tests/test.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/Cargo.toml delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/accounts/config.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/accounts/extra_metas.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/accounts/mod.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/accounts/wallet_block.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/errors/mod.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/block_wallet.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/init.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/mod.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/setup_extra_metas.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/unblock_wallet.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/mod.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/programs.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/shared.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/lib.rs delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/accounts/config.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/accounts/extraMetas.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/accounts/index.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/accounts/walletBlock.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/index.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/blockWallet.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/index.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/init.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/setupExtraMetas.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/unblockWallet.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/pdas/config.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/pdas/extraMetas.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/pdas/index.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/pdas/walletBlock.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/programs/blockList.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/programs/index.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/shared/index.ts delete mode 100644 tokens/token-extensions/transfer-hook/block-list/readme.md create mode 100644 tokens/token-extensions/transfer-hook/whitelist/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fdc384ae..4cf224e0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,14 @@ account per allowed wallet. authority, or to allow a wallet without the list's authority. - The lending and managed-fund docs say "allow list" for "whitelist". +### Removed + +- `tokens/token-extensions/transfer-hook/block-list/`, a Pinocchio-only example with no + entry in the catalog. Allow/Block List covers the same one-account-per-blocked-wallet + design in its Block mode, in Anchor v1, Anchor v2 and Quasar. +- `whitelist/` and `block-list/` each keep a `README.md` pointing to where the example + went. + ## [2026-10-28] - Anchor v2 examples on Anchor 2.0.0-rc.2, sending v1 transactions Anchor 2.0.0-rc.2's test harness, `anchor-v2-testing`, runs on LiteSVM 0.18, which diff --git a/tokens/token-extensions/transfer-hook/block-list/README.md b/tokens/token-extensions/transfer-hook/block-list/README.md new file mode 100644 index 000000000..dd249d54e --- /dev/null +++ b/tokens/token-extensions/transfer-hook/block-list/README.md @@ -0,0 +1,5 @@ +# Transfer Hook - Block List has been replaced + +This Pinocchio example has been removed. [Transfer Hook - Allow/Block List](../allow-block-list-token/) does what it did, one account per blocked wallet, and can also allow wallets or allow transfers below a threshold. It ships in Anchor v1, Anchor v2 and Quasar. + +For a list that only allows wallets, see [Transfer Hook - Allow List](../allow-list/). diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/.gitignore b/tokens/token-extensions/transfer-hook/block-list/pinocchio/.gitignore deleted file mode 100644 index 22bbf9eb1..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -target/ -test-ledger/ -node_modules/ -# The compiled program is built into tests/fixtures by `pnpm build-and-test` -# (and by CI on every run) so we don't track it. -tests/fixtures/*.so -tests/fixtures/*-keypair.json diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/Cargo.toml b/tokens/token-extensions/transfer-hook/block-list/pinocchio/Cargo.toml deleted file mode 100644 index 11ccfb996..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[workspace] -members = ["program", "cli", "sdk/rust"] -resolver = "2" - - -[workspace.dependencies] -block-list-client = { path = "sdk/rust"} \ No newline at end of file diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/cli/Cargo.toml b/tokens/token-extensions/transfer-hook/block-list/pinocchio/cli/Cargo.toml deleted file mode 100644 index 46c44717e..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/cli/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "block-list-cli" -version = "0.1.0" -edition = "2021" - -[dependencies] -clap = { version = "3", features = ["cargo"] } -futures-util = "0.3.31" -solana-clap-v3-utils = "3.0.0" -solana-cli-config = "3.0.0" -solana-commitment-config = "3.0.0" -solana-client = "3.0.0" -solana-logger = "3.0.0" -solana-remote-wallet = "3.0.0" -solana-sdk = "3.0.0" -spl-token-client = { version = "0.18.0" } -tokio = { version = "1", features = ["full"] } -block-list-client = { workspace = true } -spl-tlv-account-resolution = "0.11.0" -spl-transfer-hook-interface = { version = "2.0.0" } - - - -[[bin]] -name = "block-list-cli" -path = "src/main.rs" \ No newline at end of file diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/cli/src/main.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/cli/src/main.rs deleted file mode 100644 index c72edb001..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/cli/src/main.rs +++ /dev/null @@ -1,488 +0,0 @@ -use { - clap::{ - builder::BoolishValueParser, crate_description, crate_name, crate_version, Arg, Command, - }, - solana_clap_v3_utils::{ - input_parsers::{ - parse_url_or_moniker, - signer::{SignerSource, SignerSourceParserBuilder}, - }, - input_validators::normalize_to_url_if_moniker, - keypair::signer_from_path, - }, - solana_client::nonblocking::rpc_client::RpcClient, - solana_commitment_config::CommitmentConfig, - solana_remote_wallet::remote_wallet::RemoteWalletManager, - solana_sdk::{ - message::Message, - pubkey::Pubkey, - signature::{Signature, Signer}, - transaction::Transaction, - }, - spl_tlv_account_resolution::{ - account::ExtraAccountMeta, seeds::Seed, state::ExtraAccountMetaList, - }, - spl_transfer_hook_interface::instruction::ExecuteInstruction, - std::{error::Error, process::exit, rc::Rc, sync::Arc}, -}; - -struct Config { - commitment_config: CommitmentConfig, - payer: Arc, - json_rpc_url: String, - verbose: bool, -} - -pub fn get_extra_account_metas_with_source_wallet_block() -> Vec { - vec![ - // [5] wallet_block for source token account wallet - ExtraAccountMeta::new_with_seeds( - &[ - Seed::Literal { - bytes: b"wallet_block".to_vec(), - }, - Seed::AccountData { - account_index: 0, - data_index: 32, - length: 32, - }, - ], - false, - false, - ) - .unwrap(), - ] -} -pub fn get_extra_account_metas_with_both_wallet_blocks() -> Vec { - vec![ - // [5] wallet_block for source token account wallet - ExtraAccountMeta::new_with_seeds( - &[ - Seed::Literal { - bytes: b"wallet_block".to_vec(), - }, - Seed::AccountData { - account_index: 0, - data_index: 32, - length: 32, - }, - ], - false, - false, - ) - .unwrap(), - // [6] wallet_block for destination token account wallet - ExtraAccountMeta::new_with_seeds( - &[ - Seed::Literal { - bytes: b"wallet_block".to_vec(), - }, - Seed::AccountData { - account_index: 2, - data_index: 32, - length: 32, - }, - ], - false, - false, - ) - .unwrap(), - ] -} - -fn create_empty_extra_metas() -> Vec { - let size = ExtraAccountMetaList::size_of(0).unwrap(); - let metas: Vec = vec![]; - let mut data = vec![0; size]; - ExtraAccountMetaList::init::(&mut data, &metas).unwrap(); - data -} - -fn create_extra_metas_with_source_wallet_block() -> Vec { - let metas: Vec = get_extra_account_metas_with_source_wallet_block(); - let size = ExtraAccountMetaList::size_of(metas.len()).unwrap(); - let mut data = vec![0; size]; - ExtraAccountMetaList::init::(&mut data, &metas).unwrap(); - data -} -fn create_extra_metas_with_both_wallet_blocks() -> Vec { - let metas: Vec = get_extra_account_metas_with_both_wallet_blocks(); - let size = ExtraAccountMetaList::size_of(metas.len()).unwrap(); - let mut data = vec![0; size]; - ExtraAccountMetaList::init::(&mut data, &metas).unwrap(); - data -} - -fn get_extra_metas_account_data() { - let data_empty = create_empty_extra_metas(); - let data_source_wallet_block = create_extra_metas_with_source_wallet_block(); - let data_both_wallet_blocks = create_extra_metas_with_both_wallet_blocks(); - - println!("data empty: {:?}", data_empty); - println!("data source wallet block: {:?}", data_source_wallet_block); - println!("data both wallet blocks: {:?}", data_both_wallet_blocks); -} - -async fn get_config(rpc_client: &Arc) { - let config = block_list_client::accounts::Config::find_pda().0; - let data = rpc_client.get_account_data(&config).await.unwrap(); - println!("config: {:?}", data); - - let config = block_list_client::accounts::Config::from_bytes(&data).unwrap(); - println!("config: {:?}", config); -} - -async fn get_extra_metas(rpc_client: &Arc, mint_address: &Pubkey) { - let extra_metas = block_list_client::accounts::ExtraMetas::find_pda(mint_address).0; - let data = rpc_client.get_account_data(&extra_metas).await.unwrap(); - println!("extra_metas: {:?}", data); -} - -async fn process_setup_extra_metas( - rpc_client: &Arc, - payer: &Arc, - mint_address: &Pubkey, - check_both_wallets: bool, -) -> Result> { - let ix = block_list_client::instructions::SetupExtraMetasBuilder::new() - .authority(payer.pubkey()) - .config(block_list_client::accounts::Config::find_pda().0) - .mint(*mint_address) - .extra_metas(block_list_client::accounts::ExtraMetas::find_pda(mint_address).0) - .check_both_wallets(check_both_wallets) - .instruction(); - - let mut transaction = Transaction::new_unsigned(Message::new(&[ix], Some(&payer.pubkey()))); - - let blockhash = rpc_client - .get_latest_blockhash() - .await - .map_err(|err| format!("error: unable to get latest blockhash: {}", err))?; - - transaction - .try_sign(&[payer], blockhash) - .map_err(|err| format!("error: failed to sign transaction: {}", err))?; - - let signature = rpc_client - .send_and_confirm_transaction_with_spinner(&transaction) - .await - .map_err(|err| format!("error: send transaction: {}", err))?; - - Ok(signature) -} - -async fn process_init( - rpc_client: &Arc, - payer: &Arc, -) -> Result> { - let ix = block_list_client::instructions::InitBuilder::new() - .authority(payer.pubkey()) - .config(block_list_client::accounts::Config::find_pda().0) - .instruction(); - - let mut transaction = Transaction::new_unsigned(Message::new(&[ix], Some(&payer.pubkey()))); - - let blockhash = rpc_client - .get_latest_blockhash() - .await - .map_err(|err| format!("error: unable to get latest blockhash: {}", err))?; - - transaction - .try_sign(&[payer], blockhash) - .map_err(|err| format!("error: failed to sign transaction: {}", err))?; - - let signature = rpc_client - .send_and_confirm_transaction_with_spinner(&transaction) - .await - .map_err(|err| format!("error: send transaction: {}", err))?; - - Ok(signature) -} - -async fn process_block_wallet( - rpc_client: &Arc, - payer: &Arc, - wallet_address: &Pubkey, -) -> Result> { - let ix = block_list_client::instructions::BlockWalletBuilder::new() - .authority(payer.pubkey()) - .config(block_list_client::accounts::Config::find_pda().0) - .wallet(*wallet_address) - .wallet_block(block_list_client::accounts::WalletBlock::find_pda(wallet_address).0) - .instruction(); - - let mut transaction = Transaction::new_unsigned(Message::new(&[ix], Some(&payer.pubkey()))); - - let blockhash = rpc_client - .get_latest_blockhash() - .await - .map_err(|err| format!("error: unable to get latest blockhash: {}", err))?; - - transaction - .try_sign(&[payer], blockhash) - .map_err(|err| format!("error: failed to sign transaction: {}", err))?; - - let signature = rpc_client - .send_and_confirm_transaction_with_spinner(&transaction) - .await - .map_err(|err| format!("error: send transaction: {}", err))?; - - Ok(signature) -} - -async fn process_unblock_wallet( - rpc_client: &Arc, - payer: &Arc, - wallet_address: &Pubkey, -) -> Result> { - let ix = block_list_client::instructions::UnblockWalletBuilder::new() - .authority(payer.pubkey()) - .config(block_list_client::accounts::Config::find_pda().0) - .wallet_block(block_list_client::accounts::WalletBlock::find_pda(wallet_address).0) - .instruction(); - - let mut transaction = Transaction::new_unsigned(Message::new(&[ix], Some(&payer.pubkey()))); - - let blockhash = rpc_client - .get_latest_blockhash() - .await - .map_err(|err| format!("error: unable to get latest blockhash: {}", err))?; - - transaction - .try_sign(&[payer], blockhash) - .map_err(|err| format!("error: failed to sign transaction: {}", err))?; - - let signature = rpc_client - .send_and_confirm_transaction_with_spinner(&transaction) - .await - .map_err(|err| format!("error: send transaction: {}", err))?; - - Ok(signature) -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - let app_matches = Command::new(crate_name!()) - .about(crate_description!()) - .version(crate_version!()) - .subcommand_required(true) - .arg_required_else_help(true) - .arg({ - let arg = Arg::new("config_file") - .short('C') - .long("config") - .value_name("PATH") - .takes_value(true) - .global(true) - .help("Configuration file to use"); - if let Some(ref config_file) = *solana_cli_config::CONFIG_FILE { - arg.default_value(config_file) - } else { - arg - } - }) - .arg( - Arg::new("payer") - .long("payer") - .value_name("KEYPAIR") - .value_parser(SignerSourceParserBuilder::default().allow_all().build()) - .takes_value(true) - .global(true) - .help("Filepath or URL to a keypair [default: client keypair]"), - ) - .arg( - Arg::new("verbose") - .long("verbose") - .short('v') - .takes_value(false) - .global(true) - .help("Show additional information"), - ) - .arg( - Arg::new("json_rpc_url") - .short('u') - .long("url") - .value_name("URL") - .takes_value(true) - .global(true) - .value_parser(parse_url_or_moniker) - .help("JSON RPC URL for the cluster [default: value from configuration file]"), - ) - .subcommand(Command::new("init").about("Initializes the blocklist")) - .subcommand( - Command::new("block-wallet").about("Blocks a wallet").arg( - Arg::new("wallet_address") - .value_name("WALLET_ADDRESS") - .value_parser(SignerSourceParserBuilder::default().allow_pubkey().build()) - .takes_value(true) - .index(1) - .help("Specify the wallet address to block"), - ), - ) - .subcommand( - Command::new("unblock-wallet") - .about("Unblocks a wallet") - .arg( - Arg::new("wallet_address") - .value_name("WALLET_ADDRESS") - .value_parser(SignerSourceParserBuilder::default().allow_pubkey().build()) - .takes_value(true) - .index(1) - .help("Specify the wallet address to unblock"), - ), - ) - .subcommand( - Command::new("get-extra-metas-account-data").about("Gets the extra metas account data"), - ) - .subcommand(Command::new("get-config").about("Gets the config account data")) - .subcommand( - Command::new("get-extra-metas") - .about("Gets the extra metas account data") - .arg( - Arg::new("mint_address") - .value_name("MINT_ADDRESS") - .value_parser(SignerSourceParserBuilder::default().allow_pubkey().build()) - .takes_value(true) - .index(1) - .help("Specify the mint address"), - ), - ) - .subcommand( - Command::new("setup-extra-metas") - .about("Setup the extra metas account") - .arg( - Arg::new("mint_address") - .value_name("MINT_ADDRESS") - .value_parser(SignerSourceParserBuilder::default().allow_pubkey().build()) - .takes_value(true) - .index(1) - .help("Specify the mint address"), - ) - .arg( - Arg::new("check-both-wallets") - .long("check-both-wallets") - .short('b') - .help("Specify if both wallets should be checked"), - ), - ) - .get_matches(); - - let (command, matches) = app_matches.subcommand().unwrap(); - let mut wallet_manager: Option> = None; - - let config = { - let cli_config = if let Some(config_file) = matches.try_get_one::("config_file")? { - solana_cli_config::Config::load(config_file).unwrap_or_default() - } else { - solana_cli_config::Config::default() - }; - - let payer = if let Ok(Some((signer, _))) = - SignerSource::try_get_signer(matches, "payer", &mut wallet_manager) - { - Box::new(signer) - } else { - signer_from_path( - matches, - &cli_config.keypair_path, - "payer", - &mut wallet_manager, - )? - }; - - let json_rpc_url = normalize_to_url_if_moniker( - matches - .get_one::("json_rpc_url") - .unwrap_or(&cli_config.json_rpc_url), - ); - - Config { - commitment_config: CommitmentConfig::confirmed(), - payer: Arc::from(payer), - json_rpc_url, - verbose: matches.try_contains_id("verbose")?, - } - }; - solana_logger::setup_with_default("solana=info"); - - if config.verbose { - println!("JSON RPC URL: {}", config.json_rpc_url); - } - let rpc_client = Arc::new(RpcClient::new_with_commitment( - config.json_rpc_url.clone(), - config.commitment_config, - )); - - match (command, matches) { - ("init", _arg_matches) => { - let response = process_init(&rpc_client, &config.payer) - .await - .unwrap_or_else(|err| { - eprintln!("error: init: {}", err); - exit(1); - }); - println!("{}", response); - } - ("block-wallet", arg_matches) => { - let wallet_address = - SignerSource::try_get_pubkey(arg_matches, "wallet_address", &mut wallet_manager) - .unwrap() - .unwrap(); - let response = process_block_wallet(&rpc_client, &config.payer, &wallet_address) - .await - .unwrap_or_else(|err| { - eprintln!("error: init: {}", err); - exit(1); - }); - println!("{}", response); - } - ("unblock-wallet", arg_matches) => { - let wallet_address = - SignerSource::try_get_pubkey(arg_matches, "wallet_address", &mut wallet_manager) - .unwrap() - .unwrap(); - let response = process_unblock_wallet(&rpc_client, &config.payer, &wallet_address) - .await - .unwrap_or_else(|err| { - eprintln!("error: init: {}", err); - exit(1); - }); - println!("{}", response); - } - ("get-extra-metas-account-data", _arg_matches) => { - get_extra_metas_account_data(); - } - ("get-config", _arg_matches) => { - get_config(&rpc_client).await; - } - ("get-extra-metas", arg_matches) => { - let mint_address = - SignerSource::try_get_pubkey(arg_matches, "mint_address", &mut wallet_manager) - .unwrap() - .unwrap(); - get_extra_metas(&rpc_client, &mint_address).await; - } - ("setup-extra-metas", arg_matches) => { - let mint_address = - SignerSource::try_get_pubkey(arg_matches, "mint_address", &mut wallet_manager) - .unwrap() - .unwrap(); - let check_both_wallets = arg_matches.contains_id("check-both-wallets"); - let response = process_setup_extra_metas( - &rpc_client, - &config.payer, - &mint_address, - check_both_wallets, - ) - .await - .unwrap_or_else(|err| { - eprintln!("error: setup_extra_metas: {}", err); - exit(1); - }); - println!("{}", response); - } - _ => unreachable!(), - }; - - Ok(()) -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/codama.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/codama.ts deleted file mode 100644 index 643c667a5..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/codama.ts +++ /dev/null @@ -1,304 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; -import { renderJavaScriptVisitor, renderRustVisitor } from "@codama/renderers"; -import { - accountNode, - booleanTypeNode, - booleanValueNode, - constantDiscriminatorNode, - constantPdaSeedNodeFromString, - constantValueNode, - createFromRoot, - instructionAccountNode, - instructionArgumentNode, - instructionNode, - numberTypeNode, - numberValueNode, - pdaLinkNode, - pdaNode, - pdaValueNode, - programNode, - publicKeyTypeNode, - publicKeyValueNode, - rootNode, - structFieldTypeNode, - structTypeNode, - variablePdaSeedNode, -} from "codama"; - -const _rustClientsDir = path.join(__dirname, "..", "sdk", "rust"); -const typescriptClientsDir = path.join(__dirname, "..", "sdk", "ts"); - -const root = rootNode( - programNode({ - name: "block-list", - publicKey: "BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf", - version: "1.0.0", - accounts: [ - accountNode({ - name: "config", - discriminators: [constantDiscriminatorNode(constantValueNode(numberTypeNode("u8"), numberValueNode(0)))], - size: 41, - pda: pdaLinkNode("config"), - docs: ["The config PDA account"], - data: structTypeNode([ - structFieldTypeNode({ - name: "discriminator", - type: numberTypeNode("u8"), - defaultValueStrategy: "omitted", - }), - structFieldTypeNode({ - name: "authority", - type: publicKeyTypeNode(), - }), - structFieldTypeNode({ - name: "blocked_wallets_count", - type: numberTypeNode("u64"), - }), - ]), - }), - accountNode({ - name: "walletBlock", - discriminators: [constantDiscriminatorNode(constantValueNode(numberTypeNode("u8"), numberValueNode(1)))], - size: 33, - pda: pdaLinkNode("walletBlock"), - docs: ["The config PDA account"], - data: structTypeNode([ - structFieldTypeNode({ - name: "authority", - type: publicKeyTypeNode(), - }), - ]), - }), - accountNode({ - name: "extraMetas", - pda: pdaLinkNode("extraMetas"), - docs: ["The extra metas PDA account"], - }), - ], - instructions: [ - instructionNode({ - name: "init", - arguments: [ - instructionArgumentNode({ - name: "discriminator", - type: numberTypeNode("u8"), - defaultValue: numberValueNode(0xf1), - defaultValueStrategy: "omitted", - }), - ], - accounts: [ - instructionAccountNode({ - name: "authority", - isSigner: true, - isWritable: true, - }), - instructionAccountNode({ - name: "config", - isSigner: false, - isWritable: true, - defaultValue: pdaValueNode(pdaLinkNode("config")), - }), - instructionAccountNode({ - name: "systemProgram", - defaultValue: publicKeyValueNode("11111111111111111111111111111111", "systemProgram"), - isSigner: false, - isWritable: false, - }), - ], - discriminators: [constantDiscriminatorNode(constantValueNode(numberTypeNode("u8"), numberValueNode(0xf1)))], - docs: ["Initialize the config PDA account"], - }), - instructionNode({ - name: "blockWallet", - arguments: [ - instructionArgumentNode({ - name: "discriminator", - type: numberTypeNode("u8"), - defaultValue: numberValueNode(0xf2), - defaultValueStrategy: "omitted", - }), - ], - accounts: [ - instructionAccountNode({ - name: "authority", - isSigner: true, - isWritable: true, - }), - instructionAccountNode({ - name: "config", - isSigner: false, - isWritable: true, - defaultValue: pdaValueNode(pdaLinkNode("config")), - }), - instructionAccountNode({ - name: "wallet", - isSigner: false, - isWritable: false, - }), - instructionAccountNode({ - name: "walletBlock", - isSigner: false, - isWritable: true, - }), - instructionAccountNode({ - name: "systemProgram", - defaultValue: publicKeyValueNode("11111111111111111111111111111111", "systemProgram"), - isSigner: false, - isWritable: false, - }), - ], - discriminators: [constantDiscriminatorNode(constantValueNode(numberTypeNode("u8"), numberValueNode(0xf2)))], - docs: ["Block a wallet"], - }), - instructionNode({ - name: "unblockWallet", - arguments: [ - instructionArgumentNode({ - name: "discriminator", - type: numberTypeNode("u8"), - defaultValue: numberValueNode(0xf3), - defaultValueStrategy: "omitted", - }), - ], - accounts: [ - instructionAccountNode({ - name: "authority", - isSigner: true, - isWritable: true, - }), - instructionAccountNode({ - name: "config", - isSigner: false, - isWritable: true, - defaultValue: pdaValueNode(pdaLinkNode("config")), - }), - instructionAccountNode({ - name: "walletBlock", - isSigner: false, - isWritable: true, - }), - instructionAccountNode({ - name: "systemProgram", - defaultValue: publicKeyValueNode("11111111111111111111111111111111", "systemProgram"), - isSigner: false, - isWritable: false, - }), - ], - discriminators: [constantDiscriminatorNode(constantValueNode(numberTypeNode("u8"), numberValueNode(0xf3)))], - docs: ["Unblock a wallet"], - }), - instructionNode({ - name: "setupExtraMetas", - arguments: [ - instructionArgumentNode({ - name: "discriminator", - type: numberTypeNode("u8"), - defaultValue: numberValueNode(0x6a), - defaultValueStrategy: "omitted", - }), - instructionArgumentNode({ - name: "checkBothWallets", - type: booleanTypeNode(), - defaultValue: booleanValueNode(false), - defaultValueStrategy: "optional", - }), - ], - accounts: [ - instructionAccountNode({ - name: "authority", - isSigner: true, - isWritable: true, - }), - instructionAccountNode({ - name: "config", - isSigner: false, - isWritable: false, - defaultValue: pdaValueNode(pdaLinkNode("config")), - }), - instructionAccountNode({ - name: "mint", - isSigner: false, - isWritable: false, - }), - instructionAccountNode({ - name: "extraMetas", - isSigner: false, - isWritable: true, - defaultValue: pdaValueNode(pdaLinkNode("extraMetas")), - }), - instructionAccountNode({ - name: "systemProgram", - defaultValue: publicKeyValueNode("11111111111111111111111111111111", "systemProgram"), - isSigner: false, - isWritable: false, - }), - ], - discriminators: [constantDiscriminatorNode(constantValueNode(numberTypeNode("u8"), numberValueNode(0x6a)))], - docs: ["Unblock a wallet"], - }), - ], - pdas: [ - pdaNode({ - name: "config", - seeds: [constantPdaSeedNodeFromString("utf8", "config")], - docs: ["The config PDA account"], - }), - pdaNode({ - name: "walletBlock", - seeds: [ - constantPdaSeedNodeFromString("utf8", "wallet_block"), - variablePdaSeedNode("wallet", publicKeyTypeNode()), - ], - docs: ["The wallet block PDA account"], - }), - pdaNode({ - name: "extraMetas", - seeds: [ - constantPdaSeedNodeFromString("utf8", "extra-account-metas"), - variablePdaSeedNode("mint", publicKeyTypeNode()), - ], - docs: ["The extra metas PDA account"], - }), - ], - }), -); - -function preserveConfigFiles() { - const filesToPreserve = ["package.json", "tsconfig.json", ".npmignore", "pnpm-lock.yaml", "Cargo.toml"]; - const preservedFiles = new Map(); - - for (const filename of filesToPreserve) { - const filePath = path.join(typescriptClientsDir, filename); - const tempPath = path.join(typescriptClientsDir, `${filename}.temp`); - - if (fs.existsSync(filePath)) { - fs.copyFileSync(filePath, tempPath); - preservedFiles.set(filename, tempPath); - } - } - - return { - restore: () => { - for (const [filename, tempPath] of preservedFiles) { - const filePath = path.join(typescriptClientsDir, filename); - if (fs.existsSync(tempPath)) { - fs.copyFileSync(tempPath, filePath); - fs.unlinkSync(tempPath); - } - } - }, - }; -} - -const codama = createFromRoot(root); - -const _configPreserver = preserveConfigFiles(); - -codama.accept(renderJavaScriptVisitor("sdk/ts/src", { formatCode: true })); -codama.accept( - renderRustVisitor("sdk/rust/src/client", { - crateFolder: "sdk/rust/", - formatCode: true, - }), -); diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/package.json b/tokens/token-extensions/transfer-hook/block-list/pinocchio/package.json deleted file mode 100644 index 85bc52c7c..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "block-list", - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=20.18.0" - }, - "scripts": { - "generate-sdks": "pnpx tsx codama.ts", - "build": "cargo build-sbf --manifest-path=./program/Cargo.toml", - "build-and-test": "cargo build-sbf --manifest-path=./program/Cargo.toml && cargo test --manifest-path=./program/Cargo.toml" - }, - "dependencies": { - "@codama/renderers": "^1.0.19", - "codama": "^1.2.11", - "tsx": "^4.19.3" - }, - "devDependencies": { - "@types/node": "^22.15.29" - } -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/pnpm-lock.yaml b/tokens/token-extensions/transfer-hook/block-list/pinocchio/pnpm-lock.yaml deleted file mode 100644 index f9f469335..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/pnpm-lock.yaml +++ /dev/null @@ -1,832 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@codama/renderers': - specifier: ^1.0.19 - version: 1.0.34(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - codama: - specifier: ^1.2.11 - version: 1.6.0 - tsx: - specifier: ^4.19.3 - version: 4.22.0 - devDependencies: - '@types/node': - specifier: ^22.15.29 - version: 22.19.19 - -packages: - - '@codama/cli@1.5.1': - resolution: {integrity: sha512-Cn9SokOi0IpixbdW1Aus61Qt0GCJhWE/+q1OdcvRBAQ4V0NacCpdf7N9aF9HR/H7AD+LWJa3JtK7pEs69ywM6Q==} - hasBin: true - - '@codama/errors@1.3.6': - resolution: {integrity: sha512-pWP42vregNgFGKY/hF4ifyfSL8FfYNnd0fKXjxP3IUX9HS7+T1CcfgfJ7St9YI6C77LpFPWmx+68Af4DGnAhKA==} - hasBin: true - - '@codama/errors@1.6.0': - resolution: {integrity: sha512-Evj9wO5lqvxvbjxG856ITY5lhRN7SqoYfRX4tMMBjs8J/kT+pKQ8qL0hz9OynOOv/5mWn9Q/sPCNzQ6CUscibQ==} - hasBin: true - - '@codama/node-types@1.3.6': - resolution: {integrity: sha512-n9BpPh/Kl6Z6Bbt1MtsKykAtdPKljoBm4T8ea1IL9ABdFs+wnvgZBxnIAhgM0hC82AtvQPNGezXzLTER1JZpyg==} - - '@codama/node-types@1.6.0': - resolution: {integrity: sha512-atIJW2/3MjPYey0bNlE86W9Gvq9aq8bud7zT7PMyyhj98mbmLqPwT4wclPdbFua0fROLkq17z3bXaaJy5FqSEw==} - - '@codama/nodes@1.3.6': - resolution: {integrity: sha512-6fgnfmx0v5fR153Lkwe4Ghl2ei/7ZEqFfGZxRPzzzYwPAE/Q5Am+xZ0mULDyI1wW37HftCtAkTuXnIv2+6l+SQ==} - - '@codama/nodes@1.6.0': - resolution: {integrity: sha512-F6Hy3REfl+Ih5R3jldPqEMjFqaPj871iBWX/LV0EtNK0xn7E4DG/3XCK4wlbHrOT9Z1NsiA70e0M1uChzmIrsw==} - - '@codama/renderers-core@1.2.1': - resolution: {integrity: sha512-L7qAQvgTLmgzBKZYLO9XZKCvdT5vAEConzzCEy6n/CW4n48bxJDMTb+J6deaId//CpHOCPpxsSSoEjcxCOvqjA==} - - '@codama/renderers-js-umi@1.1.22': - resolution: {integrity: sha512-Ur/A/meVvMvUzb9zl1yZXLilAC+a/L5ezEEZVyyC2ahd8e34YgJPmB9YM2P51jPrcm+6oAcizA4/q0Fhd+yN/Q==} - - '@codama/renderers-js@1.4.1': - resolution: {integrity: sha512-/yRd7u6GNdmJ7gaB3RQGg7Z3Jn9UaVn5m7LDvC465wXWUPIWw9h4y3JUW4gycJAhMYohGKoi7/E9moC05lXqzg==} - - '@codama/renderers-rust@1.2.5': - resolution: {integrity: sha512-tCsGpi98+JLvgIcAjorP/edFqeWUyCEpoVOAQZvfIl/yTzS5JpYd0UyzEq9Z3MpvEoG3d8o2jJx6C1NyXi+MzQ==} - - '@codama/renderers@1.0.34': - resolution: {integrity: sha512-sYI6/cRS6C99SS/8zb7oUsdQbcyLxhBS3drk2wBU023UJhXYQobYSaxwaqGZZf+20kzVAKERFa3hglfWXx4MmQ==} - - '@codama/validators@1.3.6': - resolution: {integrity: sha512-14VcLfo8bHERxKRsnKR8m3fVWSgoDM8v7M8wrgO0bEXElTOwMFGSyL5wbs4RqWQ/LFO7uB/IGSZo//jkOUvT9A==} - - '@codama/validators@1.6.0': - resolution: {integrity: sha512-QlLIQt6EpZ7sQvVOz8NFKtrzWLAwYzle0tet2Q0DDU8+4LO654lj+oAwjXzY3eAfTesqBqOgMCPtQe0EpGWk3g==} - - '@codama/visitors-core@1.3.6': - resolution: {integrity: sha512-qdG27oyCYYG53vD9V/M60/TS6IxlMSf8b/5KLb/UgCykNRFLxXhDZXkwnMKKtF3LgHFxaayb0Zq+W1Z0lmjaig==} - - '@codama/visitors-core@1.6.0': - resolution: {integrity: sha512-YG0rExvLbBCDAzXnZX6Imu4KwDoZrZz9NF232/nzs9Dr8uQuEWJ81x4VR9UxIcANHcF0+XwJzHamSwhZroAtjQ==} - - '@codama/visitors@1.6.0': - resolution: {integrity: sha512-11/adC2WiH3+iMWluXkb+ae46sjoDm2xztI+CBEeIcBQd6mm4iuJTTRS0yrGfDwAJE1XzI/nc2MrR0Pvn+Rvvw==} - - '@esbuild/aix-ppc64@0.28.0': - resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.28.0': - resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.28.0': - resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.28.0': - resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.28.0': - resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.28.0': - resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.28.0': - resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.28.0': - resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.28.0': - resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.28.0': - resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.28.0': - resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.28.0': - resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.28.0': - resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.28.0': - resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.28.0': - resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.28.0': - resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.28.0': - resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.28.0': - resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.28.0': - resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.28.0': - resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.28.0': - resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.28.0': - resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.28.0': - resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.28.0': - resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.28.0': - resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.28.0': - resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@solana/codecs-core@3.0.3': - resolution: {integrity: sha512-emKykJ3h1DmnDOY29Uv9eJXP8E/FHzvlUBJ6te+5EbKdFjj7vdlKYPfDxOI6iGdXTY+YC/ELtbNBh6QwF2uEDQ==} - engines: {node: '>=20.18.0'} - peerDependencies: - typescript: '>=5.3.3' - - '@solana/codecs-numbers@3.0.3': - resolution: {integrity: sha512-pfXkH9J0glrM8qj6389GAn30+cJOxzXLR2FsPOHCUMXrqLhGjMMZAWhsQkpOQ37SGc/7EiQsT/gmyGC7gxHqJQ==} - engines: {node: '>=20.18.0'} - peerDependencies: - typescript: '>=5.3.3' - - '@solana/codecs-strings@3.0.3': - resolution: {integrity: sha512-VHBXnnTVtcQ1j+7Vrz+qSYo38no+jiHRdGnhFspRXEHNJbllzwKqgBE7YN3qoIXH+MKxgJUcwO5KHmdzf8Wn2A==} - engines: {node: '>=20.18.0'} - peerDependencies: - fastestsmallesttextencoderdecoder: ^1.0.22 - typescript: '>=5.3.3' - - '@solana/errors@3.0.3': - resolution: {integrity: sha512-1l84xJlHNva6io62PcYfUamwWlc0eM95nHgCrKX0g0cLoC6D6QHYPCEbEVkR+C5UtP9JDgyQM8MFiv+Ei5tO9Q==} - engines: {node: '>=20.18.0'} - hasBin: true - peerDependencies: - typescript: '>=5.3.3' - - '@types/node@22.19.19': - resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} - - a-sync-waterfall@1.0.1: - resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==} - - asap@2.0.6: - resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bind@1.0.9: - resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - codama@1.6.0: - resolution: {integrity: sha512-JKydzwNYJkGjkZ98ipehd3hJksLQU6nYS7x0GPjOwD0wih+xP8q7WCKgleN8LM2sRuC75rfpr3uXLXSpQpBYKA==} - hasBin: true - - commander@14.0.0: - resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==} - engines: {node: '>=20'} - - commander@14.0.3: - resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} - engines: {node: '>=20'} - - commander@5.1.0: - resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} - engines: {node: '>= 6'} - - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - - esbuild@0.28.0: - resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} - engines: {node: '>=18'} - hasBin: true - - fastestsmallesttextencoderdecoder@1.0.22: - resolution: {integrity: sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - hasown@2.0.3: - resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} - engines: {node: '>= 0.4'} - - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - - json-stable-stringify@1.3.0: - resolution: {integrity: sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==} - engines: {node: '>= 0.4'} - - jsonify@0.0.1: - resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==} - - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - nunjucks@3.2.4: - resolution: {integrity: sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==} - engines: {node: '>= 6.9.0'} - hasBin: true - peerDependencies: - chokidar: ^3.3.0 - peerDependenciesMeta: - chokidar: - optional: true - - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - prettier@3.8.3: - resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} - engines: {node: '>=14'} - hasBin: true - - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - tsx@4.22.0: - resolution: {integrity: sha512-8ccZMPD69s1AbKXx0C5ddTNZfNjwV04iIKgjZmKfKxMynEtSYcK0Lh7iQFh53fI5Yu4pb9usgAiqyPmEONaALg==} - engines: {node: '>=18.0.0'} - hasBin: true - - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} - hasBin: true - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - -snapshots: - - '@codama/cli@1.5.1': - dependencies: - '@codama/nodes': 1.6.0 - '@codama/visitors': 1.6.0 - '@codama/visitors-core': 1.6.0 - commander: 14.0.3 - picocolors: 1.1.1 - prompts: 2.4.2 - - '@codama/errors@1.3.6': - dependencies: - '@codama/node-types': 1.3.6 - commander: 14.0.3 - picocolors: 1.1.1 - - '@codama/errors@1.6.0': - dependencies: - '@codama/node-types': 1.6.0 - commander: 14.0.3 - picocolors: 1.1.1 - - '@codama/node-types@1.3.6': {} - - '@codama/node-types@1.6.0': {} - - '@codama/nodes@1.3.6': - dependencies: - '@codama/errors': 1.3.6 - '@codama/node-types': 1.3.6 - - '@codama/nodes@1.6.0': - dependencies: - '@codama/errors': 1.6.0 - '@codama/node-types': 1.6.0 - - '@codama/renderers-core@1.2.1': - dependencies: - '@codama/errors': 1.3.6 - '@codama/nodes': 1.3.6 - '@codama/visitors-core': 1.3.6 - - '@codama/renderers-js-umi@1.1.22(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': - dependencies: - '@codama/errors': 1.3.6 - '@codama/nodes': 1.3.6 - '@codama/renderers-core': 1.2.1 - '@codama/validators': 1.3.6 - '@codama/visitors-core': 1.3.6 - '@solana/codecs-strings': 3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - nunjucks: 3.2.4 - prettier: 3.8.3 - transitivePeerDependencies: - - chokidar - - fastestsmallesttextencoderdecoder - - typescript - - '@codama/renderers-js@1.4.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': - dependencies: - '@codama/errors': 1.3.6 - '@codama/nodes': 1.3.6 - '@codama/renderers-core': 1.2.1 - '@codama/visitors-core': 1.3.6 - '@solana/codecs-strings': 3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - prettier: 3.8.3 - transitivePeerDependencies: - - fastestsmallesttextencoderdecoder - - typescript - - '@codama/renderers-rust@1.2.5(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': - dependencies: - '@codama/errors': 1.3.6 - '@codama/nodes': 1.3.6 - '@codama/renderers-core': 1.2.1 - '@codama/visitors-core': 1.3.6 - '@solana/codecs-strings': 3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - nunjucks: 3.2.4 - transitivePeerDependencies: - - chokidar - - fastestsmallesttextencoderdecoder - - typescript - - '@codama/renderers@1.0.34(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': - dependencies: - '@codama/renderers-js': 1.4.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@codama/renderers-js-umi': 1.1.22(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - '@codama/renderers-rust': 1.2.5(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3) - transitivePeerDependencies: - - chokidar - - fastestsmallesttextencoderdecoder - - typescript - - '@codama/validators@1.3.6': - dependencies: - '@codama/errors': 1.3.6 - '@codama/nodes': 1.3.6 - '@codama/visitors-core': 1.3.6 - - '@codama/validators@1.6.0': - dependencies: - '@codama/errors': 1.6.0 - '@codama/nodes': 1.6.0 - '@codama/visitors-core': 1.6.0 - - '@codama/visitors-core@1.3.6': - dependencies: - '@codama/errors': 1.3.6 - '@codama/nodes': 1.3.6 - json-stable-stringify: 1.3.0 - - '@codama/visitors-core@1.6.0': - dependencies: - '@codama/errors': 1.6.0 - '@codama/nodes': 1.6.0 - json-stable-stringify: 1.3.0 - - '@codama/visitors@1.6.0': - dependencies: - '@codama/errors': 1.6.0 - '@codama/nodes': 1.6.0 - '@codama/visitors-core': 1.6.0 - - '@esbuild/aix-ppc64@0.28.0': - optional: true - - '@esbuild/android-arm64@0.28.0': - optional: true - - '@esbuild/android-arm@0.28.0': - optional: true - - '@esbuild/android-x64@0.28.0': - optional: true - - '@esbuild/darwin-arm64@0.28.0': - optional: true - - '@esbuild/darwin-x64@0.28.0': - optional: true - - '@esbuild/freebsd-arm64@0.28.0': - optional: true - - '@esbuild/freebsd-x64@0.28.0': - optional: true - - '@esbuild/linux-arm64@0.28.0': - optional: true - - '@esbuild/linux-arm@0.28.0': - optional: true - - '@esbuild/linux-ia32@0.28.0': - optional: true - - '@esbuild/linux-loong64@0.28.0': - optional: true - - '@esbuild/linux-mips64el@0.28.0': - optional: true - - '@esbuild/linux-ppc64@0.28.0': - optional: true - - '@esbuild/linux-riscv64@0.28.0': - optional: true - - '@esbuild/linux-s390x@0.28.0': - optional: true - - '@esbuild/linux-x64@0.28.0': - optional: true - - '@esbuild/netbsd-arm64@0.28.0': - optional: true - - '@esbuild/netbsd-x64@0.28.0': - optional: true - - '@esbuild/openbsd-arm64@0.28.0': - optional: true - - '@esbuild/openbsd-x64@0.28.0': - optional: true - - '@esbuild/openharmony-arm64@0.28.0': - optional: true - - '@esbuild/sunos-x64@0.28.0': - optional: true - - '@esbuild/win32-arm64@0.28.0': - optional: true - - '@esbuild/win32-ia32@0.28.0': - optional: true - - '@esbuild/win32-x64@0.28.0': - optional: true - - '@solana/codecs-core@3.0.3(typescript@6.0.3)': - dependencies: - '@solana/errors': 3.0.3(typescript@6.0.3) - typescript: 6.0.3 - - '@solana/codecs-numbers@3.0.3(typescript@6.0.3)': - dependencies: - '@solana/codecs-core': 3.0.3(typescript@6.0.3) - '@solana/errors': 3.0.3(typescript@6.0.3) - typescript: 6.0.3 - - '@solana/codecs-strings@3.0.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@6.0.3)': - dependencies: - '@solana/codecs-core': 3.0.3(typescript@6.0.3) - '@solana/codecs-numbers': 3.0.3(typescript@6.0.3) - '@solana/errors': 3.0.3(typescript@6.0.3) - fastestsmallesttextencoderdecoder: 1.0.22 - typescript: 6.0.3 - - '@solana/errors@3.0.3(typescript@6.0.3)': - dependencies: - chalk: 5.6.2 - commander: 14.0.0 - typescript: 6.0.3 - - '@types/node@22.19.19': - dependencies: - undici-types: 6.21.0 - - a-sync-waterfall@1.0.1: {} - - asap@2.0.6: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bind@1.0.9: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - get-intrinsic: 1.3.0 - set-function-length: 1.2.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - - chalk@5.6.2: {} - - codama@1.6.0: - dependencies: - '@codama/cli': 1.5.1 - '@codama/errors': 1.6.0 - '@codama/nodes': 1.6.0 - '@codama/validators': 1.6.0 - '@codama/visitors': 1.6.0 - - commander@14.0.0: {} - - commander@14.0.3: {} - - commander@5.1.0: {} - - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - - esbuild@0.28.0: - optionalDependencies: - '@esbuild/aix-ppc64': 0.28.0 - '@esbuild/android-arm': 0.28.0 - '@esbuild/android-arm64': 0.28.0 - '@esbuild/android-x64': 0.28.0 - '@esbuild/darwin-arm64': 0.28.0 - '@esbuild/darwin-x64': 0.28.0 - '@esbuild/freebsd-arm64': 0.28.0 - '@esbuild/freebsd-x64': 0.28.0 - '@esbuild/linux-arm': 0.28.0 - '@esbuild/linux-arm64': 0.28.0 - '@esbuild/linux-ia32': 0.28.0 - '@esbuild/linux-loong64': 0.28.0 - '@esbuild/linux-mips64el': 0.28.0 - '@esbuild/linux-ppc64': 0.28.0 - '@esbuild/linux-riscv64': 0.28.0 - '@esbuild/linux-s390x': 0.28.0 - '@esbuild/linux-x64': 0.28.0 - '@esbuild/netbsd-arm64': 0.28.0 - '@esbuild/netbsd-x64': 0.28.0 - '@esbuild/openbsd-arm64': 0.28.0 - '@esbuild/openbsd-x64': 0.28.0 - '@esbuild/openharmony-arm64': 0.28.0 - '@esbuild/sunos-x64': 0.28.0 - '@esbuild/win32-arm64': 0.28.0 - '@esbuild/win32-ia32': 0.28.0 - '@esbuild/win32-x64': 0.28.0 - - fastestsmallesttextencoderdecoder@1.0.22: {} - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.3 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - gopd@1.2.0: {} - - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 - - has-symbols@1.1.0: {} - - hasown@2.0.3: - dependencies: - function-bind: 1.1.2 - - isarray@2.0.5: {} - - json-stable-stringify@1.3.0: - dependencies: - call-bind: 1.0.9 - call-bound: 1.0.4 - isarray: 2.0.5 - jsonify: 0.0.1 - object-keys: 1.1.1 - - jsonify@0.0.1: {} - - kleur@3.0.3: {} - - math-intrinsics@1.1.0: {} - - nunjucks@3.2.4: - dependencies: - a-sync-waterfall: 1.0.1 - asap: 2.0.6 - commander: 5.1.0 - - object-keys@1.1.1: {} - - picocolors@1.1.1: {} - - prettier@3.8.3: {} - - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - - set-function-length@1.2.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - - sisteransi@1.0.5: {} - - tsx@4.22.0: - dependencies: - esbuild: 0.28.0 - optionalDependencies: - fsevents: 2.3.3 - - typescript@6.0.3: {} - - undici-types@6.21.0: {} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/Cargo.toml b/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/Cargo.toml deleted file mode 100644 index 4fd129f36..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "block-list" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["lib", "cdylib"] -name = "block_list" - -# pinocchio 0.9.3 - 0.8.x's `nostd_panic_handler!` macro emits `#[no_mangle]` -# on a lang item, which rustc 1.89 (current platform-tools v1.52) rejects. -# 0.9 fixes that without breaking the surface used here. 0.10 renamed -# `Pubkey` → `Address`, which would require porting the rest of the code. -[dependencies] -bytemuck = "1.23.0" -pinocchio = "0.9.3" -pinocchio-pubkey = "0.3.0" -pinocchio-system = "0.3.0" -pinocchio-log = "0.5.1" - -# The generated Rust client (sdk/rust) is built on solana-program 2.x types, -# so the test stack pins litesvm and SPL crate versions from the same -# generation. Bump these together when the client is regenerated against a -# newer solana-program. -[dev-dependencies] -block-list-client = { workspace = true } -litesvm = "0.13.1" -solana-pubkey = "3.0.0" -solana-keypair = "3.0.1" -solana-signer = "3.0.0" -solana-instruction = "3.0.0" -solana-transaction = "3.0.1" -solana-system-interface = { version = "2.0.0", features = ["bincode"] } -solana-compute-budget-interface = "3.0.0" -spl-token-2022 = "10.0.0" -spl-associated-token-account = "8.0.0" diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/error.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/error.rs deleted file mode 100644 index 26bbf1d04..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/error.rs +++ /dev/null @@ -1,24 +0,0 @@ -use pinocchio::program_error::ProgramError; - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum BlockListError { - InvalidInstruction, - - InvalidAuthority, - AccountBlocked, - NotEnoughAccounts, - InvalidAccountData, - UninitializedAccount, - InvalidSystemProgram, - InvalidConfigAccount, - AccountNotWritable, - InvalidMint, - InvalidExtraMetasAccount, - ImmutableOwnerExtensionMissing, -} - -impl From for ProgramError { - fn from(e: BlockListError) -> Self { - ProgramError::Custom(e as u32) - } -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/block_wallet.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/block_wallet.rs deleted file mode 100644 index e265db9cf..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/block_wallet.rs +++ /dev/null @@ -1,100 +0,0 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::Signer, - program_error::ProgramError, - pubkey::find_program_address, - seeds, - sysvars::{rent::Rent, Sysvar}, - ProgramResult, -}; - -use crate::{ - load, load_mut_unchecked, BlockListError, Config, Discriminator, Transmutable, WalletBlock, -}; - -pub struct BlockWallet<'a> { - pub authority: &'a AccountInfo, - pub config: &'a AccountInfo, - pub wallet: &'a AccountInfo, - pub wallet_block: &'a AccountInfo, - pub system_program: &'a AccountInfo, - pub wallet_block_bump: u8, -} - -impl<'a> BlockWallet<'a> { - pub fn process(&self) -> ProgramResult { - let lamports = Rent::get()?.minimum_balance(WalletBlock::LEN); - - let bump_seed = [self.wallet_block_bump]; - let seeds = seeds!(WalletBlock::SEED_PREFIX, self.wallet.key(), &bump_seed); - let signer = Signer::from(&seeds); - - pinocchio_system::instructions::CreateAccount { - from: self.authority, - to: self.wallet_block, - lamports, - space: WalletBlock::LEN as u64, - owner: &crate::ID, - } - .invoke_signed(&[signer])?; - - let mut data = self.wallet_block.try_borrow_mut_data()?; - let wallet_block = unsafe { load_mut_unchecked::(&mut data)? }; - wallet_block.discriminator = WalletBlock::DISCRIMINATOR; - wallet_block.address = *self.wallet.key(); - - let config = - unsafe { load_mut_unchecked::(self.config.borrow_mut_data_unchecked())? }; - config.blocked_wallets_count = config - .blocked_wallets_count - .checked_add(1) - .ok_or(ProgramError::ArithmeticOverflow)?; - - Ok(()) - } -} - -impl<'a> Discriminator for BlockWallet<'a> { - const DISCRIMINATOR: u8 = 0xF2; -} - -impl<'a> TryFrom<&'a [AccountInfo]> for BlockWallet<'a> { - type Error = BlockListError; - - fn try_from(accounts: &'a [AccountInfo]) -> Result { - let [authority, config, wallet, wallet_block, system_program] = accounts else { - return Err(BlockListError::NotEnoughAccounts); - }; - - let cfg = unsafe { load::(config.borrow_data_unchecked())? }; - - if !config.is_owned_by(&crate::ID) { - return Err(BlockListError::InvalidConfigAccount); - } - - if !authority.is_signer() || cfg.authority.ne(authority.key()) { - return Err(BlockListError::InvalidAuthority); - } - - if !config.is_writable() && !wallet_block.is_writable() { - return Err(BlockListError::AccountNotWritable); - } - - let (_, wallet_block_bump) = - find_program_address(&[WalletBlock::SEED_PREFIX, wallet.key()], &crate::ID); - - // check if system program is valid - if system_program.key().ne(&pinocchio_system::ID) { - return Err(BlockListError::InvalidSystemProgram); - } - - Ok(Self { - authority, - config, - wallet, - wallet_block, - system_program, - wallet_block_bump, - }) - } -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/init.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/init.rs deleted file mode 100644 index c57a5da9c..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/init.rs +++ /dev/null @@ -1,87 +0,0 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::Signer, - pubkey::find_program_address, - seeds, - sysvars::{rent::Rent, Sysvar}, - ProgramResult, -}; - -use crate::{load_mut_unchecked, BlockListError, Config, Discriminator, Transmutable}; - -pub struct Init<'a> { - pub authority: &'a AccountInfo, - pub config: &'a AccountInfo, - pub system_program: &'a AccountInfo, - pub config_bump: u8, -} - -impl<'a> Discriminator for Init<'a> { - const DISCRIMINATOR: u8 = 0xF1; -} - -impl<'a> TryFrom<&'a [AccountInfo]> for Init<'a> { - type Error = BlockListError; - - fn try_from(accounts: &'a [AccountInfo]) -> Result { - let [authority, config, system_program] = accounts else { - return Err(BlockListError::NotEnoughAccounts); - }; - - if !authority.is_signer() { - return Err(BlockListError::InvalidAuthority); - } - - /* do we really need to check this? its going to fail silently if not writable - if !config.is_writable { - return Err(BlockListError::InvalidInstruction); - }*/ - - // derive config account - let (_, config_bump) = find_program_address(&[Config::SEED_PREFIX], &crate::ID); - // no need to check if address is valid - // cpi call with config as signer, runtime will check if the right account has been signer escalated - - //if config_account.ne(config.key()) { - // return Err(BlockListError::InvalidConfigAccount); - //} - - // check if system program is valid - if system_program.key().ne(&pinocchio_system::ID) { - return Err(BlockListError::InvalidSystemProgram); - } - - Ok(Self { - authority, - config, - system_program, - config_bump, - }) - } -} - -impl<'a> Init<'a> { - pub fn process(&self) -> ProgramResult { - let lamports = Rent::get()?.minimum_balance(Config::LEN); - - let bump_seed = [self.config_bump]; - let seeds = seeds!(Config::SEED_PREFIX, &bump_seed); - let signer = Signer::from(&seeds); - - pinocchio_system::instructions::CreateAccount { - from: self.authority, - to: self.config, - lamports, - space: Config::LEN as u64, - owner: &crate::ID, - } - .invoke_signed(&[signer])?; - - let mut data = self.config.try_borrow_mut_data()?; - let config = unsafe { load_mut_unchecked::(&mut data)? }; - config.discriminator = Config::DISCRIMINATOR; - config.authority = *self.authority.key(); - - Ok(()) - } -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/mod.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/mod.rs deleted file mode 100644 index 77770ff25..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod block_wallet; -pub mod init; -pub mod setup_extra_metas; -pub mod tx_hook; -pub mod unblock_wallet; - -pub use block_wallet::*; -pub use init::*; -pub use setup_extra_metas::*; -pub use tx_hook::*; -pub use unblock_wallet::*; diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/setup_extra_metas.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/setup_extra_metas.rs deleted file mode 100644 index c3966ea77..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/setup_extra_metas.rs +++ /dev/null @@ -1,162 +0,0 @@ -use pinocchio::{ - account_info::AccountInfo, - instruction::Signer, - memory::sol_memcpy, - pubkey::find_program_address, - seeds, - sysvars::{rent::Rent, Sysvar}, - ProgramResult, -}; - -use crate::{ - load, - token_extensions_utils::{ - get_transfer_hook_authority, is_token_extensions_mint, EXTRA_METAS_SEED, - }, - BlockListError, Config, Discriminator, -}; - -pub struct SetupExtraMetas<'a> { - pub authority: &'a AccountInfo, - pub config: &'a AccountInfo, - pub mint: &'a AccountInfo, - pub extra_metas: &'a AccountInfo, - pub system_program: &'a AccountInfo, - pub extra_metas_bump: u8, -} - -impl<'a> Discriminator for SetupExtraMetas<'a> { - const DISCRIMINATOR: u8 = 0x6A; -} - -impl<'a> TryFrom<&'a [AccountInfo]> for SetupExtraMetas<'a> { - type Error = BlockListError; - - fn try_from(accounts: &'a [AccountInfo]) -> Result { - let [authority, config, mint, extra_metas, system_program] = accounts else { - return Err(BlockListError::NotEnoughAccounts); - }; - - if !authority.is_signer() { - return Err(BlockListError::InvalidAuthority); - } - - if !is_token_extensions_mint(mint) { - return Err(BlockListError::InvalidMint); - } - - let transfer_hook_authority = - get_transfer_hook_authority(unsafe { mint.borrow_data_unchecked() }); - if transfer_hook_authority.is_none() - || !transfer_hook_authority.unwrap().eq(authority.key()) - { - return Err(BlockListError::InvalidAuthority); - } - - // derive extra_metas account - let (extra_metas_address, extra_metas_bump) = - find_program_address(&[EXTRA_METAS_SEED, mint.key()], &crate::ID); - - if extra_metas_address.ne(extra_metas.key()) { - return Err(BlockListError::InvalidExtraMetasAccount); - } - - // check if system program is valid - if system_program.key().ne(&pinocchio_system::ID) { - return Err(BlockListError::InvalidSystemProgram); - } - - Ok(Self { - authority, - config, - mint, - extra_metas, - system_program, - extra_metas_bump, - }) - } -} - -impl<'a> SetupExtraMetas<'a> { - pub fn process(&self, remaining_data: &[u8]) -> ProgramResult { - let config = unsafe { load::(&self.config.borrow_data_unchecked())? }; - - let data = if config.blocked_wallets_count == 0 { - EXTRA_METAS_EMPTY_DEPENDENCIES - } else if remaining_data.len() == 1 && remaining_data[0] == 1 { - EXTRA_METAS_BOTH_DEPENDENCIES - } else { - EXTRA_METAS_SOURCE_DEPENDENCY - }; - - let min_lamports = Rent::get()?.minimum_balance(data.len()); - - if self.extra_metas.is_owned_by(&crate::ID) { - let current_lamports = self.extra_metas.lamports(); - let auth_lamports = self.authority.lamports(); - - // just resize - self.extra_metas.resize(data.len())?; - - if current_lamports < min_lamports { - // transfer to extra - let diff = min_lamports - current_lamports; - pinocchio_system::instructions::Transfer { - from: self.authority, - to: self.extra_metas, - lamports: diff, - } - .invoke()?; - } else if current_lamports > min_lamports { - // transfer from extra - let diff = current_lamports - min_lamports; - unsafe { - *self.extra_metas.borrow_mut_lamports_unchecked() = min_lamports; - *self.authority.borrow_mut_lamports_unchecked() = - auth_lamports.checked_add(diff).unwrap(); - } - } - } else { - // create new account - - let bump_seed = [self.extra_metas_bump]; - let seeds = seeds!(EXTRA_METAS_SEED, self.mint.key(), &bump_seed); - let signer = Signer::from(&seeds); - - pinocchio_system::instructions::CreateAccount { - from: self.authority, - to: self.extra_metas, - lamports: min_lamports, - space: data.len() as u64, - owner: &crate::ID, - } - .invoke_signed(&[signer])?; - } - - // overwrite state depending on config - - let extra_metas_data = unsafe { self.extra_metas.borrow_mut_data_unchecked() }; - - unsafe { - sol_memcpy(extra_metas_data, data, data.len()); - } - - Ok(()) - } -} - -/// HOW TO GET THESE MAGIC VALUES -/// run the CLI using `block-list-cli get-extra-metas-account-data` -/// it will generate the 3 arrays without needing to add more dependencies (bloat) to the program -pub const EXTRA_METAS_EMPTY_DEPENDENCIES: &[u8] = - &[105, 37, 101, 197, 75, 251, 102, 26, 4, 0, 0, 0, 0, 0, 0, 0]; -pub const EXTRA_METAS_SOURCE_DEPENDENCY: &[u8] = &[ - 105, 37, 101, 197, 75, 251, 102, 26, 39, 0, 0, 0, 1, 0, 0, 0, 1, 1, 12, 119, 97, 108, 108, 101, - 116, 95, 98, 108, 111, 99, 107, 4, 0, 32, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -]; -pub const EXTRA_METAS_BOTH_DEPENDENCIES: &[u8] = &[ - 105, 37, 101, 197, 75, 251, 102, 26, 74, 0, 0, 0, 2, 0, 0, 0, 1, 1, 12, 119, 97, 108, 108, 101, - 116, 95, 98, 108, 111, 99, 107, 4, 0, 32, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 12, 119, 97, 108, 108, 101, 116, 95, 98, 108, 111, 99, 107, 4, 2, 32, 32, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -]; diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/tx_hook.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/tx_hook.rs deleted file mode 100644 index 92b833ae8..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/tx_hook.rs +++ /dev/null @@ -1,115 +0,0 @@ -use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult}; - -use crate::{ - load, token_extensions_utils::has_immutable_owner_extension, BlockListError, WalletBlock, -}; - -/// -/// SECURITY ASSUMPTIONS OVER TX-HOOK -/// -/// 1- its called by the token-2022 program -/// 2- if some other program is calling it, we don't care as we don't write state here -/// 2- its inputs are already sanitized by the token-2022 program -/// 3- if some other program is calling it with invalid inputs, we don't care as we only read state and return ok/nok -/// 4- there may be 3 different extra metas setup -/// 4.1- no extra accounts -/// 4.2- only source wallet block -/// 4.3- both source and destination wallet blocks -/// 5- given all the above we can skip a lot of type and owner checks - -pub struct TxHook<'a> { - pub source: &'a AccountInfo, - pub mint: &'a AccountInfo, - pub destination: &'a AccountInfo, - pub authority: &'a AccountInfo, - pub source_wallet_block: Option<&'a AccountInfo>, - pub destination_wallet_block: Option<&'a AccountInfo>, - //pub remaining_accounts: &'a [AccountInfo], -} - -impl<'a> TxHook<'a> { - pub const DISCRIMINATOR: u8 = 0x69; - - pub fn process(&self) -> ProgramResult { - // check if there is a wallet block for the source account - if let Some(source_wallet_block) = self.source_wallet_block { - let source_data = unsafe { self.source.borrow_data_unchecked() }; - // without the immutable owner extension, TA owners could bypass wallet blocks - // by changing the owner to a different wallet controlled by the same entity - if !has_immutable_owner_extension(source_data) { - return Err(BlockListError::ImmutableOwnerExtensionMissing.into()); - } - - if !source_wallet_block.data_is_empty() { - let _ = - unsafe { load::(source_wallet_block.borrow_data_unchecked())? }; - - // its a potential blocked wallet - // lets check if authority is not the owner nor the delegate - // this implies its the permanent delegate - // alternatively we can decode the mint and get the permanent delegate - - let owner = unsafe { &*(source_data[32..64].as_ptr() as *const Pubkey) }; - let delegate = unsafe { &*(source_data[76..108].as_ptr() as *const Pubkey) }; - - if owner.eq(self.authority.key()) || delegate.eq(self.authority.key()) { - return Err(BlockListError::AccountBlocked.into()); - } - } - } - - // check if there is a wallet block for the destination account - if let Some(destination_wallet_block) = self.destination_wallet_block { - if !has_immutable_owner_extension(unsafe { self.destination.borrow_data_unchecked() }) { - return Err(BlockListError::ImmutableOwnerExtensionMissing.into()); - } - - if !destination_wallet_block.data_is_empty() { - let _ = unsafe { - load::(destination_wallet_block.borrow_data_unchecked())? - }; - return Err(BlockListError::AccountBlocked.into()); - } - } - - Ok(()) - } -} - -impl<'a> TryFrom<&'a [AccountInfo]> for TxHook<'a> { - type Error = BlockListError; - - fn try_from(accounts: &'a [AccountInfo]) -> Result { - /* - TX HOOK GETS CALLED WITH: - 1- source TA - 2- mint - 3- destination TA - 4- authority (either src owner or src delegate) - 5- extra account metas - 6- (optional) source wallet block - 7- (optional) destination wallet block - */ - - let [source, mint, destination, authority, remaining_accounts @ ..] = accounts else { - return Err(BlockListError::NotEnoughAccounts); - }; - - let (source_wallet_block, destination_wallet_block) = if remaining_accounts.len() == 2 { - (Some(&remaining_accounts[1]), None) - } else if remaining_accounts.len() == 3 { - (Some(&remaining_accounts[1]), Some(&remaining_accounts[2])) - } else { - (None, None) - }; - - Ok(Self { - source, - destination, - mint, - authority, - source_wallet_block, - destination_wallet_block, - }) - } -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/unblock_wallet.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/unblock_wallet.rs deleted file mode 100644 index 7f2d679ff..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/instructions/unblock_wallet.rs +++ /dev/null @@ -1,71 +0,0 @@ -use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; - -use crate::{load, load_mut_unchecked, BlockListError, Config, Discriminator, WalletBlock}; - -pub struct UnblockWallet<'a> { - pub authority: &'a AccountInfo, - pub config: &'a AccountInfo, - pub wallet_block: &'a AccountInfo, - pub system_program: &'a AccountInfo, -} - -impl<'a> UnblockWallet<'a> { - pub fn process(&self) -> ProgramResult { - let destination_lamports = self.authority.lamports(); - - unsafe { - *self.authority.borrow_mut_lamports_unchecked() = destination_lamports - .checked_add(self.wallet_block.lamports()) - .ok_or(ProgramError::ArithmeticOverflow)?; - self.wallet_block.close_unchecked(); - } - - let config = - unsafe { load_mut_unchecked::(self.config.borrow_mut_data_unchecked())? }; - config.blocked_wallets_count = config - .blocked_wallets_count - .checked_sub(1) - .ok_or(ProgramError::ArithmeticOverflow)?; - - Ok(()) - } -} - -impl<'a> Discriminator for UnblockWallet<'a> { - const DISCRIMINATOR: u8 = 0xF3; -} - -impl<'a> TryFrom<&'a [AccountInfo]> for UnblockWallet<'a> { - type Error = BlockListError; - - fn try_from(accounts: &'a [AccountInfo]) -> Result { - let [authority, config, wallet_block, system_program] = accounts else { - return Err(BlockListError::NotEnoughAccounts); - }; - - let cfg = unsafe { load::(config.borrow_data_unchecked())? }; - - if !config.is_owned_by(&crate::ID) { - return Err(BlockListError::InvalidConfigAccount); - } - - if !authority.is_signer() || cfg.authority.ne(authority.key()) { - return Err(BlockListError::InvalidAuthority); - } - - if !config.is_writable() && !wallet_block.is_writable() { - return Err(BlockListError::AccountNotWritable); - } - - if unsafe { load::(wallet_block.borrow_data_unchecked()).is_err() } { - return Err(BlockListError::InvalidAccountData); - } - - Ok(Self { - authority, - config, - wallet_block, - system_program, - }) - } -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/lib.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/lib.rs deleted file mode 100644 index 6b07f8fef..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/lib.rs +++ /dev/null @@ -1,45 +0,0 @@ -#![no_std] - -use pinocchio::{ - account_info::AccountInfo, no_allocator, nostd_panic_handler, program_entrypoint, - program_error::ProgramError, pubkey::Pubkey, ProgramResult, -}; -use pinocchio_pubkey::declare_id; - -program_entrypoint!(process_instruction); -// Do not allocate memory. -no_allocator!(); -// Use the no_std panic handler. -nostd_panic_handler!(); - -pub mod instructions; -pub use instructions::*; -pub mod error; -pub use error::*; -pub mod state; -pub use state::*; -mod token_extensions_utils; - -declare_id!("BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf"); - -#[inline(always)] -fn process_instruction( - _program_id: &Pubkey, - accounts: &[AccountInfo], - instruction_data: &[u8], -) -> ProgramResult { - let [disc, remaining_data @ ..] = instruction_data else { - return Err(BlockListError::InvalidInstruction.into()); - }; - - match *disc { - TxHook::DISCRIMINATOR => TxHook::try_from(accounts)?.process(), - Init::DISCRIMINATOR => Init::try_from(accounts)?.process(), - BlockWallet::DISCRIMINATOR => BlockWallet::try_from(accounts)?.process(), - UnblockWallet::DISCRIMINATOR => UnblockWallet::try_from(accounts)?.process(), - SetupExtraMetas::DISCRIMINATOR => { - SetupExtraMetas::try_from(accounts)?.process(remaining_data) - } - _ => Err(ProgramError::InvalidInstructionData), - } -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/state/config.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/state/config.rs deleted file mode 100644 index a9b44b0fd..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/state/config.rs +++ /dev/null @@ -1,28 +0,0 @@ -use pinocchio::pubkey::Pubkey; - -use super::{Discriminator, Transmutable}; - -// `#[repr(C, packed)]` keeps the onchain layout exactly 41 bytes wide. -// With plain `#[repr(C)]` the u64 field gets 7 bytes of alignment padding -// inserted after the 33-byte (u8 + Pubkey) prefix, making the struct 48 bytes -// while `LEN = 41`. The program would then read 7 bytes past the end of the -// account buffer and fault with `AccountDataTooSmall`. Packed avoids that and -// matches the byte layout the SDK and tests expect. -#[repr(C, packed)] -pub struct Config { - pub discriminator: u8, - pub authority: Pubkey, - pub blocked_wallets_count: u64, -} - -impl Config { - pub const SEED_PREFIX: &'static [u8] = b"config"; -} - -impl Transmutable for Config { - const LEN: usize = 1 + 32 + 8; -} - -impl Discriminator for Config { - const DISCRIMINATOR: u8 = 0x01; -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/state/mod.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/state/mod.rs deleted file mode 100644 index 0e75b4f46..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/state/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -pub mod config; -pub mod wallet_block; -pub use config::*; -pub use wallet_block::*; - -use crate::BlockListError; - -pub trait Transmutable { - const LEN: usize; -} - -pub trait Discriminator { - const DISCRIMINATOR: u8; -} - -/// Return a reference for an initialized `T` from the given bytes. -/// -/// # Safety -/// -/// The caller must ensure that `bytes` contains a valid representation of `T`. -#[inline(always)] -pub unsafe fn load(bytes: &[u8]) -> Result<&T, BlockListError> { - load_unchecked(bytes).and_then(|t: &T| { - // checks if the data is initialized - if bytes[0] == T::DISCRIMINATOR { - Ok(t) - } else { - Err(BlockListError::InvalidAccountData) - } - }) -} - -/// Return a `T` reference from the given bytes. -/// -/// This function does not check if the data is initialized. -/// -/// # Safety -/// -/// The caller must ensure that `bytes` contains a valid representation of `T`. -#[inline(always)] -pub unsafe fn load_unchecked(bytes: &[u8]) -> Result<&T, BlockListError> { - if bytes.len() != T::LEN { - return Err(BlockListError::InvalidAccountData); - } - Ok(&*(bytes.as_ptr() as *const T)) -} - -/// Return a mutable `T` reference from the given bytes. -/// -/// This function does not check if the data is initialized. -/// -/// # Safety -/// -/// The caller must ensure that `bytes` contains a valid representation of `T`. -#[inline(always)] -pub unsafe fn load_mut_unchecked( - bytes: &mut [u8], -) -> Result<&mut T, BlockListError> { - if bytes.len() != T::LEN { - return Err(BlockListError::InvalidAccountData); - } - Ok(&mut *(bytes.as_mut_ptr() as *mut T)) -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/state/wallet_block.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/state/wallet_block.rs deleted file mode 100644 index 2ae0bcabe..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/state/wallet_block.rs +++ /dev/null @@ -1,21 +0,0 @@ -use pinocchio::pubkey::Pubkey; - -use super::{Discriminator, Transmutable}; - -#[repr(C)] -pub struct WalletBlock { - pub discriminator: u8, - pub address: Pubkey, -} - -impl WalletBlock { - pub const SEED_PREFIX: &'static [u8] = b"wallet_block"; -} - -impl Transmutable for WalletBlock { - const LEN: usize = 1 + 32; -} - -impl Discriminator for WalletBlock { - const DISCRIMINATOR: u8 = 0x02; -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/token_extensions_utils.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/token_extensions_utils.rs deleted file mode 100644 index aa3aedcb7..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/src/token_extensions_utils.rs +++ /dev/null @@ -1,86 +0,0 @@ -use pinocchio::{account_info::AccountInfo, pubkey::Pubkey}; -use pinocchio_pubkey::from_str; - -pub const TOKEN_EXTENSIONS_PROGRAM_ID: Pubkey = - from_str("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"); - -pub const EXTRA_METAS_SEED: &[u8] = b"extra-account-metas"; - -const MINT_LEN: usize = 82; -const EXTENSIONS_PADDING: usize = 83; -const EXTENSION_START_OFFSET: usize = 1; -const EXTENSION_LENGTH_LEN: usize = 2; -const EXTENSION_TYPE_LEN: usize = 2; -const TRANSFER_HOOK_EXTENSION_TYPE: u16 = 14; -const IMMUTABLE_OWNER_EXTENSION_TYPE: u16 = 7; -const TYPE_BYTE_OFFSET: usize = MINT_LEN + EXTENSIONS_PADDING; -const EXTENSION_DATA_OFFSET: usize = TYPE_BYTE_OFFSET + EXTENSION_START_OFFSET; -const MINT_TYPE_BYTE: u8 = 1; - -pub fn get_transfer_hook_authority(acc_data_bytes: &[u8]) -> Option<&Pubkey> { - let data = get_extension_data_(acc_data_bytes, TRANSFER_HOOK_EXTENSION_TYPE)?; - if data.len() < core::mem::size_of::() { - return None; - } - // The authority is the first 32 bytes of the TransferHook extension data. - // `Pubkey` is `[u8; 32]` so this cast is a plain reinterpret of the bytes - // with no alignment requirement. - Some(unsafe { &*(data.as_ptr() as *const Pubkey) }) -} - -fn get_extension_data_(acc_data_bytes: &[u8], extension_type: u16) -> Option<&[u8]> { - // Account data may be shorter than the extension region when the account - // isn't actually a Token Extensions account; bail safely instead of panicking on - // the slice operation. - if acc_data_bytes.len() <= EXTENSION_DATA_OFFSET { - return None; - } - let ext_bytes = &acc_data_bytes[EXTENSION_DATA_OFFSET..]; - let mut start = 0; - let end = ext_bytes.len(); - while start + EXTENSION_TYPE_LEN + EXTENSION_LENGTH_LEN <= end { - let ext_type_idx = start; - let ext_len_idx = ext_type_idx + EXTENSION_TYPE_LEN; - let ext_data_idx = ext_len_idx + EXTENSION_LENGTH_LEN; - - // Use unaligned reads. The extension TLV header is not guaranteed to - // be 2-byte aligned inside the runtime-provided account buffer, and - // SBF (like x86_64) tolerates unaligned reads but doing it via a raw - // `&*(ptr as *const u16)` is undefined behaviour and can produce - // garbage when the optimiser folds the read with surrounding ops. - let ext_type = u16::from_le_bytes([ext_bytes[ext_type_idx], ext_bytes[ext_type_idx + 1]]); - let ext_len = - u16::from_le_bytes([ext_bytes[ext_len_idx], ext_bytes[ext_len_idx + 1]]) as usize; - - if ext_data_idx + ext_len > end { - return None; - } - - if ext_type == extension_type { - return Some(&ext_bytes[ext_data_idx..ext_data_idx + ext_len]); - } - - start = ext_data_idx + ext_len; - } - None -} - -pub fn has_immutable_owner_extension(acc_data_bytes: &[u8]) -> bool { - let extension_data = get_extension_data_(acc_data_bytes, IMMUTABLE_OWNER_EXTENSION_TYPE); - extension_data.is_some() -} - -pub fn is_token_extensions_mint(mint: &AccountInfo) -> bool { - // Order of checks matters: read the type byte ONLY after we have proven - // the buffer is long enough. The previous implementation indexed first - // and length-checked second, which faulted (out-of-bounds) on any account - // shorter than 166 bytes - every mint that isn't a Token Extensions mint hits this. - if !mint.is_owned_by(&TOKEN_EXTENSIONS_PROGRAM_ID) { - return false; - } - let data = unsafe { mint.borrow_data_unchecked() }; - if data.len() <= TYPE_BYTE_OFFSET { - return false; - } - data[TYPE_BYTE_OFFSET] == MINT_TYPE_BYTE -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/tests/test.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/tests/test.rs deleted file mode 100644 index f7e62fae3..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/program/tests/test.rs +++ /dev/null @@ -1,421 +0,0 @@ -use block_list_client::client::{ - instructions::{ - BlockWallet, Init, SetupExtraMetas, SetupExtraMetasInstructionArgs, UnblockWallet, - }, - programs::BLOCK_LIST_ID, -}; -use litesvm::LiteSVM; -use solana_compute_budget_interface::ComputeBudgetInstruction; -use solana_instruction::{AccountMeta, Instruction}; -use solana_keypair::Keypair; -use solana_pubkey::Pubkey; -use solana_signer::Signer; -use solana_system_interface::instruction as system_instruction; -use solana_transaction::Transaction; -use spl_associated_token_account::{ - get_associated_token_address_with_program_id, instruction::create_associated_token_account, -}; -use spl_token_2022::{ - extension::ExtensionType, - instruction::{initialize_mint2, mint_to_checked, transfer_checked}, - state::Mint, -}; - -// The .so is built into this project's workspace target/deploy by -// `cargo build-sbf --manifest-path=./program/Cargo.toml` (run from the -// project root). Rebuild after every program change: the binary is embedded -// at test-compile time, so a stale .so silently tests old code. -const PROGRAM_SO: &[u8] = include_bytes!("../../target/deploy/block_list.so"); - -const DECIMALS: u8 = 6; -const MINT_AMOUNT: u64 = 1_000 * 10u64.pow(DECIMALS as u32); -const TRANSFER_AMOUNT: u64 = 10 * 10u64.pow(DECIMALS as u32); - -// Hook PDA derivations, mirroring program/src (seeds "config", "wallet_block", -// and the transfer-hook interface's "extra-account-metas"). -fn find_config_pda() -> Pubkey { - Pubkey::find_program_address(&[b"config"], &BLOCK_LIST_ID).0 -} - -fn find_wallet_block_pda(wallet: &Pubkey) -> Pubkey { - Pubkey::find_program_address(&[b"wallet_block", wallet.as_ref()], &BLOCK_LIST_ID).0 -} - -fn find_extra_metas_pda(mint: &Pubkey) -> Pubkey { - Pubkey::find_program_address(&[b"extra-account-metas", mint.as_ref()], &BLOCK_LIST_ID).0 -} - -enum ExtraMode { - Empty, - SourceOnly, -} - -#[allow(clippy::too_many_arguments)] -fn build_transfer_with_hook_accounts( - source: &Pubkey, - mint: &Pubkey, - destination: &Pubkey, - owner: &Pubkey, - source_owner: &Pubkey, - extra_mode: ExtraMode, -) -> Instruction { - let mut instruction = transfer_checked( - &spl_token_2022::id(), - source, - mint, - destination, - owner, - &[], - TRANSFER_AMOUNT, - DECIMALS, - ) - .unwrap(); - - // Token Extensions invokes the hook with these trailing accounts in this - // order: - // validation_pda (extra-account-metas) - // resolved wallet_block for the source TA (when listed in the metas) - // The hook program id is appended last so the Token Extensions transfer - // instruction handler can CPI into it (it strips that entry from the hook - // accounts list). - instruction - .accounts - .push(AccountMeta::new_readonly(find_extra_metas_pda(mint), false)); - if let ExtraMode::SourceOnly = extra_mode { - instruction.accounts.push(AccountMeta::new_readonly( - find_wallet_block_pda(source_owner), - false, - )); - } - instruction - .accounts - .push(AccountMeta::new_readonly(BLOCK_LIST_ID, false)); - instruction -} - -fn send_expecting_success( - svm: &mut LiteSVM, - instructions: &[Instruction], - payer: &Keypair, - signers: &[&Keypair], - label: &str, -) { - // Identical transactions across steps (same signers, instruction, and - // blockhash) collide on signature and are rejected as AlreadyProcessed. - // Expiring first gives each send a fresh blockhash and unique signature. - svm.expire_blockhash(); - let transaction = Transaction::new_signed_with_payer( - instructions, - Some(&payer.pubkey()), - signers, - svm.latest_blockhash(), - ); - if let Err(failure) = svm.send_transaction(transaction) { - panic!( - "{label} failed: {:?}\nlogs:\n{}", - failure.err, - failure.meta.logs.join("\n") - ); - } -} - -fn send_expecting_failure( - svm: &mut LiteSVM, - instructions: &[Instruction], - payer: &Keypair, - signers: &[&Keypair], - label: &str, -) -> Vec { - svm.expire_blockhash(); - let transaction = Transaction::new_signed_with_payer( - instructions, - Some(&payer.pubkey()), - signers, - svm.latest_blockhash(), - ); - match svm.send_transaction(transaction) { - Ok(_) => panic!("{label} unexpectedly succeeded"), - Err(failure) => failure.meta.logs, - } -} - -fn read_blocked_wallets_count(svm: &LiteSVM) -> u64 { - let config = svm.get_account(&find_config_pda()).unwrap(); - // Config layout: discriminator(1) | authority(32) | blocked_wallets_count(8). - u64::from_le_bytes(config.data[33..41].try_into().unwrap()) -} - -#[test] -fn block_list_transfer_hook_lifecycle() { - let mut svm = LiteSVM::new(); - svm.add_program(BLOCK_LIST_ID, PROGRAM_SO); - - let payer = Keypair::new(); - let wallet_a = Keypair::new(); - let wallet_b = Keypair::new(); - let mint_keypair = Keypair::new(); - svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap(); - svm.airdrop(&wallet_a.pubkey(), 100_000_000).unwrap(); - - // init: creates the config PDA owned by the hook program. - let init_instruction = Init { - authority: payer.pubkey(), - config: find_config_pda(), - system_program: solana_system_interface::program::ID, - } - .instruction(); - send_expecting_success(&mut svm, &[init_instruction], &payer, &[&payer], "init"); - - let config = svm.get_account(&find_config_pda()).unwrap(); - assert_eq!(config.data.len(), 41, "config account size"); - assert_eq!(config.data[0], 0x01, "config discriminator"); - assert_eq!( - Pubkey::try_from(&config.data[1..33]).unwrap(), - payer.pubkey(), - "config authority" - ); - assert_eq!(read_blocked_wallets_count(&svm), 0); - - // Create a Token Extensions mint with the TransferHook extension pointing - // at the block-list program, then write the (empty) extra-metas account. - let mint_len = - ExtensionType::try_calculate_account_len::(&[ExtensionType::TransferHook]).unwrap(); - let mint_rent = svm.minimum_balance_for_rent_exemption(mint_len); - let create_mint_account_instruction = system_instruction::create_account( - &payer.pubkey(), - &mint_keypair.pubkey(), - mint_rent, - mint_len as u64, - &spl_token_2022::id(), - ); - let init_hook_instruction = spl_token_2022::extension::transfer_hook::instruction::initialize( - &spl_token_2022::id(), - &mint_keypair.pubkey(), - Some(payer.pubkey()), - Some(BLOCK_LIST_ID), - ) - .unwrap(); - let init_mint_instruction = initialize_mint2( - &spl_token_2022::id(), - &mint_keypair.pubkey(), - &payer.pubkey(), - None, - DECIMALS, - ) - .unwrap(); - send_expecting_success( - &mut svm, - &[ - create_mint_account_instruction, - init_hook_instruction, - init_mint_instruction, - ], - &payer, - &[&payer, &mint_keypair], - "create-mint", - ); - - let setup_extra_metas_instruction = SetupExtraMetas { - authority: payer.pubkey(), - config: find_config_pda(), - mint: mint_keypair.pubkey(), - extra_metas: find_extra_metas_pda(&mint_keypair.pubkey()), - system_program: solana_system_interface::program::ID, - } - .instruction(SetupExtraMetasInstructionArgs { - check_both_wallets: false, - }); - send_expecting_success( - &mut svm, - &[setup_extra_metas_instruction.clone()], - &payer, - &[&payer], - "setup_extra_metas (empty)", - ); - let extra_metas = svm - .get_account(&find_extra_metas_pda(&mint_keypair.pubkey())) - .unwrap(); - // Empty ExtraAccountMetaList = 8 byte TLV header + 4 bytes length + 4 bytes count. - assert_eq!(extra_metas.data.len(), 16, "empty extra-metas data length"); - - // Create both ATAs and mint to wallet A. - let ata_a = get_associated_token_address_with_program_id( - &wallet_a.pubkey(), - &mint_keypair.pubkey(), - &spl_token_2022::id(), - ); - let ata_b = get_associated_token_address_with_program_id( - &wallet_b.pubkey(), - &mint_keypair.pubkey(), - &spl_token_2022::id(), - ); - let create_ata_a = create_associated_token_account( - &payer.pubkey(), - &wallet_a.pubkey(), - &mint_keypair.pubkey(), - &spl_token_2022::id(), - ); - let create_ata_b = create_associated_token_account( - &payer.pubkey(), - &wallet_b.pubkey(), - &mint_keypair.pubkey(), - &spl_token_2022::id(), - ); - let mint_to_a = mint_to_checked( - &spl_token_2022::id(), - &mint_keypair.pubkey(), - &ata_a, - &payer.pubkey(), - &[], - MINT_AMOUNT, - DECIMALS, - ) - .unwrap(); - send_expecting_success( - &mut svm, - &[create_ata_a, create_ata_b, mint_to_a], - &payer, - &[&payer], - "create-atas+mint", - ); - let ata_a_data_len = svm.get_account(&ata_a).unwrap().data.len(); - assert!( - ata_a_data_len > 165, - "ATA has extension data (immutable owner)" - ); - - // Transfer succeeds while the source wallet is not blocked. - let transfer_unblocked = build_transfer_with_hook_accounts( - &ata_a, - &mint_keypair.pubkey(), - &ata_b, - &wallet_a.pubkey(), - &wallet_a.pubkey(), - ExtraMode::Empty, - ); - send_expecting_success( - &mut svm, - &[ - ComputeBudgetInstruction::set_compute_unit_limit(400_000), - transfer_unblocked, - ], - &wallet_a, - &[&wallet_a], - "transfer (unblocked)", - ); - - // block_wallet: creates wallet A's wallet_block PDA and bumps the count. - let block_instruction = BlockWallet { - authority: payer.pubkey(), - config: find_config_pda(), - wallet: wallet_a.pubkey(), - wallet_block: find_wallet_block_pda(&wallet_a.pubkey()), - system_program: solana_system_interface::program::ID, - } - .instruction(); - send_expecting_success( - &mut svm, - &[block_instruction], - &payer, - &[&payer], - "block_wallet A", - ); - let wallet_block = svm - .get_account(&find_wallet_block_pda(&wallet_a.pubkey())) - .unwrap(); - assert_eq!(wallet_block.data[0], 0x02, "wallet_block discriminator"); - assert_eq!(read_blocked_wallets_count(&svm), 1); - - // With a nonzero blocked count, setup_extra_metas writes the source - // wallet_block dependency into the metas: 16-byte header + one 35-byte - // ExtraAccountMeta entry. - send_expecting_success( - &mut svm, - &[setup_extra_metas_instruction], - &payer, - &[&payer], - "setup_extra_metas (source dep)", - ); - let extra_metas = svm - .get_account(&find_extra_metas_pda(&mint_keypair.pubkey())) - .unwrap(); - assert_eq!( - extra_metas.data.len(), - 51, - "source-dependency extra-metas data length" - ); - - // Transfer from the blocked source wallet fails with - // BlockListError::AccountBlocked (variant index 2 -> custom code 0x2). - let transfer_blocked = build_transfer_with_hook_accounts( - &ata_a, - &mint_keypair.pubkey(), - &ata_b, - &wallet_a.pubkey(), - &wallet_a.pubkey(), - ExtraMode::SourceOnly, - ); - let logs = send_expecting_failure( - &mut svm, - &[ - ComputeBudgetInstruction::set_compute_unit_limit(400_000), - transfer_blocked, - ], - &wallet_a, - &[&wallet_a], - "transfer-from-blocked", - ); - let joined_logs = logs.join("\n"); - assert!( - joined_logs.contains("custom program error: 0x2"), - "expected AccountBlocked (custom 0x2) error in logs, got:\n{joined_logs}" - ); - - // unblock_wallet: closes the wallet_block PDA and decrements the count. - let unblock_instruction = UnblockWallet { - authority: payer.pubkey(), - config: find_config_pda(), - wallet_block: find_wallet_block_pda(&wallet_a.pubkey()), - system_program: solana_system_interface::program::ID, - } - .instruction(); - send_expecting_success( - &mut svm, - &[unblock_instruction], - &payer, - &[&payer], - "unblock_wallet A", - ); - // After close the runtime reports either no account or a drained shell - // (zero lamports, empty data) depending on whether the slot advanced. - let closed_wallet_block = svm.get_account(&find_wallet_block_pda(&wallet_a.pubkey())); - assert!( - closed_wallet_block - .map(|account| account.lamports == 0 && account.data.is_empty()) - .unwrap_or(true), - "wallet_block PDA closed" - ); - assert_eq!(read_blocked_wallets_count(&svm), 0); - - // Re-issue the transfer with the (now-closed) wallet_block PDA still in - // the extra metas. The closed account is empty, so the hook no longer - // blocks the transfer. - let transfer_after_unblock = build_transfer_with_hook_accounts( - &ata_a, - &mint_keypair.pubkey(), - &ata_b, - &wallet_a.pubkey(), - &wallet_a.pubkey(), - ExtraMode::SourceOnly, - ); - send_expecting_success( - &mut svm, - &[ - ComputeBudgetInstruction::set_compute_unit_limit(400_000), - transfer_after_unblock, - ], - &wallet_a, - &[&wallet_a], - "transfer (after unblock)", - ); -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/Cargo.toml b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/Cargo.toml deleted file mode 100644 index c1b697d8c..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "block-list-client" -version = "0.1.0" -edition = "2021" - -[lib] -name = "block_list_client" - -[dependencies] -solana-pubkey = { version = "3.0.0", features = ["borsh", "curve25519"] } -solana-instruction = "3.0.0" -solana-account-info = "3.0.0" -solana-program-error = "3.0.0" -solana-cpi = "3.0.0" -kaigan = ">=0.2.6" -borsh = "1.6.1" \ No newline at end of file diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/accounts/config.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/accounts/config.rs deleted file mode 100644 index 53625d3ec..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/accounts/config.rs +++ /dev/null @@ -1,163 +0,0 @@ -//! This code was AUTOGENERATED using the codama library. -//! Please DO NOT EDIT THIS FILE, instead use visitors -//! to add features, then rerun codama to update it. -//! -//! -//! - -use borsh::BorshDeserialize; -use borsh::BorshSerialize; -use solana_pubkey::Pubkey; - -/// The config PDA account - -#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Config { - pub discriminator: u8, - #[cfg_attr( - feature = "serde", - serde(with = "serde_with::As::") - )] - pub authority: Pubkey, - pub blocked_wallets_count: u64, -} - -impl Config { - pub const LEN: usize = 41; - - /// Prefix values used to generate a PDA for this account. - /// - /// Values are positional and appear in the following order: - /// - /// 0. `Config::PREFIX` - pub const PREFIX: &'static [u8] = "config".as_bytes(); - - pub fn create_pda( - bump: u8, - ) -> Result { - solana_pubkey::Pubkey::create_program_address( - &["config".as_bytes(), &[bump]], - &crate::BLOCK_LIST_ID, - ) - } - - pub fn find_pda() -> (solana_pubkey::Pubkey, u8) { - solana_pubkey::Pubkey::find_program_address( - &["config".as_bytes()], - &crate::BLOCK_LIST_ID, - ) - } - - #[inline(always)] - pub fn from_bytes(data: &[u8]) -> Result { - let mut data = data; - Self::deserialize(&mut data) - } -} - -impl<'a> TryFrom<&solana_account_info::AccountInfo<'a>> for Config { - type Error = std::io::Error; - - fn try_from( - account_info: &solana_account_info::AccountInfo<'a>, - ) -> Result { - let mut data: &[u8] = &(*account_info.data).borrow(); - Self::deserialize(&mut data) - } -} - -#[cfg(feature = "fetch")] -pub fn fetch_config( - rpc: &solana_client::rpc_client::RpcClient, - address: &solana_pubkey::Pubkey, -) -> Result, std::io::Error> { - let accounts = fetch_all_config(rpc, &[*address])?; - Ok(accounts[0].clone()) -} - -#[cfg(feature = "fetch")] -pub fn fetch_all_config( - rpc: &solana_client::rpc_client::RpcClient, - addresses: &[solana_pubkey::Pubkey], -) -> Result>, std::io::Error> { - let accounts = rpc - .get_multiple_accounts(addresses) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; - let mut decoded_accounts: Vec> = Vec::new(); - for i in 0..addresses.len() { - let address = addresses[i]; - let account = accounts[i].as_ref().ok_or(std::io::Error::new( - std::io::ErrorKind::Other, - format!("Account not found: {}", address), - ))?; - let data = Config::from_bytes(&account.data)?; - decoded_accounts.push(crate::shared::DecodedAccount { - address, - account: account.clone(), - data, - }); - } - Ok(decoded_accounts) -} - -#[cfg(feature = "fetch")] -pub fn fetch_maybe_config( - rpc: &solana_client::rpc_client::RpcClient, - address: &solana_pubkey::Pubkey, -) -> Result, std::io::Error> { - let accounts = fetch_all_maybe_config(rpc, &[*address])?; - Ok(accounts[0].clone()) -} - -#[cfg(feature = "fetch")] -pub fn fetch_all_maybe_config( - rpc: &solana_client::rpc_client::RpcClient, - addresses: &[solana_pubkey::Pubkey], -) -> Result>, std::io::Error> { - let accounts = rpc - .get_multiple_accounts(addresses) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; - let mut decoded_accounts: Vec> = Vec::new(); - for i in 0..addresses.len() { - let address = addresses[i]; - if let Some(account) = accounts[i].as_ref() { - let data = Config::from_bytes(&account.data)?; - decoded_accounts.push(crate::shared::MaybeAccount::Exists( - crate::shared::DecodedAccount { - address, - account: account.clone(), - data, - }, - )); - } else { - decoded_accounts.push(crate::shared::MaybeAccount::NotFound(address)); - } - } - Ok(decoded_accounts) -} - -#[cfg(feature = "anchor")] -impl anchor_lang::AccountDeserialize for Config { - fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { - Ok(Self::deserialize(buf)?) - } -} - -#[cfg(feature = "anchor")] -impl anchor_lang::AccountSerialize for Config {} - -#[cfg(feature = "anchor")] -impl anchor_lang::Owner for Config { - fn owner() -> Pubkey { - crate::BLOCK_LIST_ID - } -} - -#[cfg(feature = "anchor-idl-build")] -impl anchor_lang::IdlBuild for Config {} - -#[cfg(feature = "anchor-idl-build")] -impl anchor_lang::Discriminator for Config { - const DISCRIMINATOR: [u8; 8] = [0; 8]; -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/accounts/extra_metas.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/accounts/extra_metas.rs deleted file mode 100644 index ab0b39012..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/accounts/extra_metas.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! This code was AUTOGENERATED using the codama library. -//! Please DO NOT EDIT THIS FILE, instead use visitors -//! to add features, then rerun codama to update it. -//! -//! -//! - -use borsh::BorshDeserialize; -use borsh::BorshSerialize; -use solana_pubkey::Pubkey; - -/// The extra metas PDA account - -#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ExtraMetas {} - -impl ExtraMetas { - /// Prefix values used to generate a PDA for this account. - /// - /// Values are positional and appear in the following order: - /// - /// 0. `ExtraMetas::PREFIX` - /// 1. mint (`Pubkey`) - pub const PREFIX: &'static [u8] = "extra-account-metas".as_bytes(); - - pub fn create_pda( - mint: Pubkey, - bump: u8, - ) -> Result { - solana_pubkey::Pubkey::create_program_address( - &["extra-account-metas".as_bytes(), mint.as_ref(), &[bump]], - &crate::BLOCK_LIST_ID, - ) - } - - pub fn find_pda(mint: &Pubkey) -> (solana_pubkey::Pubkey, u8) { - solana_pubkey::Pubkey::find_program_address( - &["extra-account-metas".as_bytes(), mint.as_ref()], - &crate::BLOCK_LIST_ID, - ) - } - - #[inline(always)] - pub fn from_bytes(data: &[u8]) -> Result { - let mut data = data; - Self::deserialize(&mut data) - } -} - -impl<'a> TryFrom<&solana_account_info::AccountInfo<'a>> for ExtraMetas { - type Error = std::io::Error; - - fn try_from( - account_info: &solana_account_info::AccountInfo<'a>, - ) -> Result { - let mut data: &[u8] = &(*account_info.data).borrow(); - Self::deserialize(&mut data) - } -} - -#[cfg(feature = "fetch")] -pub fn fetch_extra_metas( - rpc: &solana_client::rpc_client::RpcClient, - address: &solana_pubkey::Pubkey, -) -> Result, std::io::Error> { - let accounts = fetch_all_extra_metas(rpc, &[*address])?; - Ok(accounts[0].clone()) -} - -#[cfg(feature = "fetch")] -pub fn fetch_all_extra_metas( - rpc: &solana_client::rpc_client::RpcClient, - addresses: &[solana_pubkey::Pubkey], -) -> Result>, std::io::Error> { - let accounts = rpc - .get_multiple_accounts(addresses) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; - let mut decoded_accounts: Vec> = Vec::new(); - for i in 0..addresses.len() { - let address = addresses[i]; - let account = accounts[i].as_ref().ok_or(std::io::Error::new( - std::io::ErrorKind::Other, - format!("Account not found: {}", address), - ))?; - let data = ExtraMetas::from_bytes(&account.data)?; - decoded_accounts.push(crate::shared::DecodedAccount { - address, - account: account.clone(), - data, - }); - } - Ok(decoded_accounts) -} - -#[cfg(feature = "fetch")] -pub fn fetch_maybe_extra_metas( - rpc: &solana_client::rpc_client::RpcClient, - address: &solana_pubkey::Pubkey, -) -> Result, std::io::Error> { - let accounts = fetch_all_maybe_extra_metas(rpc, &[*address])?; - Ok(accounts[0].clone()) -} - -#[cfg(feature = "fetch")] -pub fn fetch_all_maybe_extra_metas( - rpc: &solana_client::rpc_client::RpcClient, - addresses: &[solana_pubkey::Pubkey], -) -> Result>, std::io::Error> { - let accounts = rpc - .get_multiple_accounts(addresses) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; - let mut decoded_accounts: Vec> = Vec::new(); - for i in 0..addresses.len() { - let address = addresses[i]; - if let Some(account) = accounts[i].as_ref() { - let data = ExtraMetas::from_bytes(&account.data)?; - decoded_accounts.push(crate::shared::MaybeAccount::Exists( - crate::shared::DecodedAccount { - address, - account: account.clone(), - data, - }, - )); - } else { - decoded_accounts.push(crate::shared::MaybeAccount::NotFound(address)); - } - } - Ok(decoded_accounts) -} - -#[cfg(feature = "anchor")] -impl anchor_lang::AccountDeserialize for ExtraMetas { - fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { - Ok(Self::deserialize(buf)?) - } -} - -#[cfg(feature = "anchor")] -impl anchor_lang::AccountSerialize for ExtraMetas {} - -#[cfg(feature = "anchor")] -impl anchor_lang::Owner for ExtraMetas { - fn owner() -> Pubkey { - crate::BLOCK_LIST_ID - } -} - -#[cfg(feature = "anchor-idl-build")] -impl anchor_lang::IdlBuild for ExtraMetas {} - -#[cfg(feature = "anchor-idl-build")] -impl anchor_lang::Discriminator for ExtraMetas { - const DISCRIMINATOR: [u8; 8] = [0; 8]; -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/accounts/mod.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/accounts/mod.rs deleted file mode 100644 index e9b3a41c5..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/accounts/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! This code was AUTOGENERATED using the codama library. -//! Please DO NOT EDIT THIS FILE, instead use visitors -//! to add features, then rerun codama to update it. -//! -//! -//! - -pub(crate) mod r#config; -pub(crate) mod r#extra_metas; -pub(crate) mod r#wallet_block; - -pub use self::r#config::*; -pub use self::r#extra_metas::*; -pub use self::r#wallet_block::*; diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/accounts/wallet_block.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/accounts/wallet_block.rs deleted file mode 100644 index a5275d76a..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/accounts/wallet_block.rs +++ /dev/null @@ -1,163 +0,0 @@ -//! This code was AUTOGENERATED using the codama library. -//! Please DO NOT EDIT THIS FILE, instead use visitors -//! to add features, then rerun codama to update it. -//! -//! -//! - -use borsh::BorshDeserialize; -use borsh::BorshSerialize; -use solana_pubkey::Pubkey; - -/// The config PDA account - -#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct WalletBlock { - #[cfg_attr( - feature = "serde", - serde(with = "serde_with::As::") - )] - pub authority: Pubkey, -} - -impl WalletBlock { - pub const LEN: usize = 33; - - /// Prefix values used to generate a PDA for this account. - /// - /// Values are positional and appear in the following order: - /// - /// 0. `WalletBlock::PREFIX` - /// 1. wallet (`Pubkey`) - pub const PREFIX: &'static [u8] = "wallet_block".as_bytes(); - - pub fn create_pda( - wallet: Pubkey, - bump: u8, - ) -> Result { - solana_pubkey::Pubkey::create_program_address( - &["wallet_block".as_bytes(), wallet.as_ref(), &[bump]], - &crate::BLOCK_LIST_ID, - ) - } - - pub fn find_pda(wallet: &Pubkey) -> (solana_pubkey::Pubkey, u8) { - solana_pubkey::Pubkey::find_program_address( - &["wallet_block".as_bytes(), wallet.as_ref()], - &crate::BLOCK_LIST_ID, - ) - } - - #[inline(always)] - pub fn from_bytes(data: &[u8]) -> Result { - let mut data = data; - Self::deserialize(&mut data) - } -} - -impl<'a> TryFrom<&solana_account_info::AccountInfo<'a>> for WalletBlock { - type Error = std::io::Error; - - fn try_from( - account_info: &solana_account_info::AccountInfo<'a>, - ) -> Result { - let mut data: &[u8] = &(*account_info.data).borrow(); - Self::deserialize(&mut data) - } -} - -#[cfg(feature = "fetch")] -pub fn fetch_wallet_block( - rpc: &solana_client::rpc_client::RpcClient, - address: &solana_pubkey::Pubkey, -) -> Result, std::io::Error> { - let accounts = fetch_all_wallet_block(rpc, &[*address])?; - Ok(accounts[0].clone()) -} - -#[cfg(feature = "fetch")] -pub fn fetch_all_wallet_block( - rpc: &solana_client::rpc_client::RpcClient, - addresses: &[solana_pubkey::Pubkey], -) -> Result>, std::io::Error> { - let accounts = rpc - .get_multiple_accounts(addresses) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; - let mut decoded_accounts: Vec> = Vec::new(); - for i in 0..addresses.len() { - let address = addresses[i]; - let account = accounts[i].as_ref().ok_or(std::io::Error::new( - std::io::ErrorKind::Other, - format!("Account not found: {}", address), - ))?; - let data = WalletBlock::from_bytes(&account.data)?; - decoded_accounts.push(crate::shared::DecodedAccount { - address, - account: account.clone(), - data, - }); - } - Ok(decoded_accounts) -} - -#[cfg(feature = "fetch")] -pub fn fetch_maybe_wallet_block( - rpc: &solana_client::rpc_client::RpcClient, - address: &solana_pubkey::Pubkey, -) -> Result, std::io::Error> { - let accounts = fetch_all_maybe_wallet_block(rpc, &[*address])?; - Ok(accounts[0].clone()) -} - -#[cfg(feature = "fetch")] -pub fn fetch_all_maybe_wallet_block( - rpc: &solana_client::rpc_client::RpcClient, - addresses: &[solana_pubkey::Pubkey], -) -> Result>, std::io::Error> { - let accounts = rpc - .get_multiple_accounts(addresses) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?; - let mut decoded_accounts: Vec> = Vec::new(); - for i in 0..addresses.len() { - let address = addresses[i]; - if let Some(account) = accounts[i].as_ref() { - let data = WalletBlock::from_bytes(&account.data)?; - decoded_accounts.push(crate::shared::MaybeAccount::Exists( - crate::shared::DecodedAccount { - address, - account: account.clone(), - data, - }, - )); - } else { - decoded_accounts.push(crate::shared::MaybeAccount::NotFound(address)); - } - } - Ok(decoded_accounts) -} - -#[cfg(feature = "anchor")] -impl anchor_lang::AccountDeserialize for WalletBlock { - fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { - Ok(Self::deserialize(buf)?) - } -} - -#[cfg(feature = "anchor")] -impl anchor_lang::AccountSerialize for WalletBlock {} - -#[cfg(feature = "anchor")] -impl anchor_lang::Owner for WalletBlock { - fn owner() -> Pubkey { - crate::BLOCK_LIST_ID - } -} - -#[cfg(feature = "anchor-idl-build")] -impl anchor_lang::IdlBuild for WalletBlock {} - -#[cfg(feature = "anchor-idl-build")] -impl anchor_lang::Discriminator for WalletBlock { - const DISCRIMINATOR: [u8; 8] = [0; 8]; -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/errors/mod.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/errors/mod.rs deleted file mode 100644 index 6172ba60a..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/errors/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! This code was AUTOGENERATED using the codama library. -//! Please DO NOT EDIT THIS FILE, instead use visitors -//! to add features, then rerun codama to update it. -//! -//! -//! diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/block_wallet.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/block_wallet.rs deleted file mode 100644 index 282d7364d..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/block_wallet.rs +++ /dev/null @@ -1,447 +0,0 @@ -//! This code was AUTOGENERATED using the codama library. -//! Please DO NOT EDIT THIS FILE, instead use visitors -//! to add features, then rerun codama to update it. -//! -//! -//! - -use borsh::BorshDeserialize; -use borsh::BorshSerialize; - -/// Accounts. -#[derive(Debug)] -pub struct BlockWallet { - pub authority: solana_pubkey::Pubkey, - - pub config: solana_pubkey::Pubkey, - - pub wallet: solana_pubkey::Pubkey, - - pub wallet_block: solana_pubkey::Pubkey, - - pub system_program: solana_pubkey::Pubkey, -} - -impl BlockWallet { - pub fn instruction(&self) -> solana_instruction::Instruction { - self.instruction_with_remaining_accounts(&[]) - } - #[allow(clippy::arithmetic_side_effects)] - #[allow(clippy::vec_init_then_push)] - pub fn instruction_with_remaining_accounts( - &self, - remaining_accounts: &[solana_instruction::AccountMeta], - ) -> solana_instruction::Instruction { - let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); - accounts.push(solana_instruction::AccountMeta::new( - self.authority, - true, - )); - accounts.push(solana_instruction::AccountMeta::new( - self.config, - false, - )); - accounts.push(solana_instruction::AccountMeta::new_readonly( - self.wallet, - false, - )); - accounts.push(solana_instruction::AccountMeta::new( - self.wallet_block, - false, - )); - accounts.push(solana_instruction::AccountMeta::new_readonly( - self.system_program, - false, - )); - accounts.extend_from_slice(remaining_accounts); - let data = borsh::to_vec(&BlockWalletInstructionData::new()).unwrap(); - - solana_instruction::Instruction { - program_id: crate::BLOCK_LIST_ID, - accounts, - data, - } - } -} - -#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct BlockWalletInstructionData { - discriminator: u8, -} - -impl BlockWalletInstructionData { - pub fn new() -> Self { - Self { discriminator: 242 } - } -} - -impl Default for BlockWalletInstructionData { - fn default() -> Self { - Self::new() - } -} - -/// Instruction builder for `BlockWallet`. -/// -/// ### Accounts: -/// -/// 0. `[writable, signer]` authority -/// 1. `[writable]` config -/// 2. `[]` wallet -/// 3. `[writable]` wallet_block -/// 4. `[optional]` system_program (default to `11111111111111111111111111111111`) -#[derive(Clone, Debug, Default)] -pub struct BlockWalletBuilder { - authority: Option, - config: Option, - wallet: Option, - wallet_block: Option, - system_program: Option, - __remaining_accounts: Vec, -} - -impl BlockWalletBuilder { - pub fn new() -> Self { - Self::default() - } - #[inline(always)] - pub fn authority(&mut self, authority: solana_pubkey::Pubkey) -> &mut Self { - self.authority = Some(authority); - self - } - #[inline(always)] - pub fn config(&mut self, config: solana_pubkey::Pubkey) -> &mut Self { - self.config = Some(config); - self - } - #[inline(always)] - pub fn wallet(&mut self, wallet: solana_pubkey::Pubkey) -> &mut Self { - self.wallet = Some(wallet); - self - } - #[inline(always)] - pub fn wallet_block(&mut self, wallet_block: solana_pubkey::Pubkey) -> &mut Self { - self.wallet_block = Some(wallet_block); - self - } - /// `[optional account, default to '11111111111111111111111111111111']` - #[inline(always)] - pub fn system_program(&mut self, system_program: solana_pubkey::Pubkey) -> &mut Self { - self.system_program = Some(system_program); - self - } - /// Add an additional account to the instruction. - #[inline(always)] - pub fn add_remaining_account( - &mut self, - account: solana_instruction::AccountMeta, - ) -> &mut Self { - self.__remaining_accounts.push(account); - self - } - /// Add additional accounts to the instruction. - #[inline(always)] - pub fn add_remaining_accounts( - &mut self, - accounts: &[solana_instruction::AccountMeta], - ) -> &mut Self { - self.__remaining_accounts.extend_from_slice(accounts); - self - } - #[allow(clippy::clone_on_copy)] - pub fn instruction(&self) -> solana_instruction::Instruction { - let accounts = BlockWallet { - authority: self.authority.expect("authority is not set"), - config: self.config.expect("config is not set"), - wallet: self.wallet.expect("wallet is not set"), - wallet_block: self.wallet_block.expect("wallet_block is not set"), - system_program: self - .system_program - .unwrap_or(solana_pubkey::pubkey!("11111111111111111111111111111111")), - }; - - accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) - } -} - -/// `block_wallet` CPI accounts. -pub struct BlockWalletCpiAccounts<'a, 'b> { - pub authority: &'b solana_account_info::AccountInfo<'a>, - - pub config: &'b solana_account_info::AccountInfo<'a>, - - pub wallet: &'b solana_account_info::AccountInfo<'a>, - - pub wallet_block: &'b solana_account_info::AccountInfo<'a>, - - pub system_program: &'b solana_account_info::AccountInfo<'a>, -} - -/// `block_wallet` CPI instruction. -pub struct BlockWalletCpi<'a, 'b> { - /// The program to invoke. - pub __program: &'b solana_account_info::AccountInfo<'a>, - - pub authority: &'b solana_account_info::AccountInfo<'a>, - - pub config: &'b solana_account_info::AccountInfo<'a>, - - pub wallet: &'b solana_account_info::AccountInfo<'a>, - - pub wallet_block: &'b solana_account_info::AccountInfo<'a>, - - pub system_program: &'b solana_account_info::AccountInfo<'a>, -} - -impl<'a, 'b> BlockWalletCpi<'a, 'b> { - pub fn new( - program: &'b solana_account_info::AccountInfo<'a>, - accounts: BlockWalletCpiAccounts<'a, 'b>, - ) -> Self { - Self { - __program: program, - authority: accounts.authority, - config: accounts.config, - wallet: accounts.wallet, - wallet_block: accounts.wallet_block, - system_program: accounts.system_program, - } - } - #[inline(always)] - pub fn invoke(&self) -> solana_program_error::ProgramResult { - self.invoke_signed_with_remaining_accounts(&[], &[]) - } - #[inline(always)] - pub fn invoke_with_remaining_accounts( - &self, - remaining_accounts: &[( - &'b solana_account_info::AccountInfo<'a>, - bool, - bool, - )], - ) -> solana_program_error::ProgramResult { - self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) - } - #[inline(always)] - pub fn invoke_signed( - &self, - signers_seeds: &[&[&[u8]]], - ) -> solana_program_error::ProgramResult { - self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) - } - #[allow(clippy::arithmetic_side_effects)] - #[allow(clippy::clone_on_copy)] - #[allow(clippy::vec_init_then_push)] - pub fn invoke_signed_with_remaining_accounts( - &self, - signers_seeds: &[&[&[u8]]], - remaining_accounts: &[( - &'b solana_account_info::AccountInfo<'a>, - bool, - bool, - )], - ) -> solana_program_error::ProgramResult { - let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); - accounts.push(solana_instruction::AccountMeta::new( - *self.authority.key, - true, - )); - accounts.push(solana_instruction::AccountMeta::new( - *self.config.key, - false, - )); - accounts.push(solana_instruction::AccountMeta::new_readonly( - *self.wallet.key, - false, - )); - accounts.push(solana_instruction::AccountMeta::new( - *self.wallet_block.key, - false, - )); - accounts.push(solana_instruction::AccountMeta::new_readonly( - *self.system_program.key, - false, - )); - remaining_accounts.iter().for_each(|remaining_account| { - accounts.push(solana_instruction::AccountMeta { - pubkey: *remaining_account.0.key, - is_signer: remaining_account.1, - is_writable: remaining_account.2, - }) - }); - let data = borsh::to_vec(&BlockWalletInstructionData::new()).unwrap(); - - let instruction = solana_instruction::Instruction { - program_id: crate::BLOCK_LIST_ID, - accounts, - data, - }; - let mut account_infos = Vec::with_capacity(6 + remaining_accounts.len()); - account_infos.push(self.__program.clone()); - account_infos.push(self.authority.clone()); - account_infos.push(self.config.clone()); - account_infos.push(self.wallet.clone()); - account_infos.push(self.wallet_block.clone()); - account_infos.push(self.system_program.clone()); - remaining_accounts - .iter() - .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); - - if signers_seeds.is_empty() { - solana_cpi::invoke(&instruction, &account_infos) - } else { - solana_cpi::invoke_signed(&instruction, &account_infos, signers_seeds) - } - } -} - -/// Instruction builder for `BlockWallet` via CPI. -/// -/// ### Accounts: -/// -/// 0. `[writable, signer]` authority -/// 1. `[writable]` config -/// 2. `[]` wallet -/// 3. `[writable]` wallet_block -/// 4. `[]` system_program -#[derive(Clone, Debug)] -pub struct BlockWalletCpiBuilder<'a, 'b> { - instruction: Box>, -} - -impl<'a, 'b> BlockWalletCpiBuilder<'a, 'b> { - pub fn new(program: &'b solana_account_info::AccountInfo<'a>) -> Self { - let instruction = Box::new(BlockWalletCpiBuilderInstruction { - __program: program, - authority: None, - config: None, - wallet: None, - wallet_block: None, - system_program: None, - __remaining_accounts: Vec::new(), - }); - Self { instruction } - } - #[inline(always)] - pub fn authority( - &mut self, - authority: &'b solana_account_info::AccountInfo<'a>, - ) -> &mut Self { - self.instruction.authority = Some(authority); - self - } - #[inline(always)] - pub fn config( - &mut self, - config: &'b solana_account_info::AccountInfo<'a>, - ) -> &mut Self { - self.instruction.config = Some(config); - self - } - #[inline(always)] - pub fn wallet( - &mut self, - wallet: &'b solana_account_info::AccountInfo<'a>, - ) -> &mut Self { - self.instruction.wallet = Some(wallet); - self - } - #[inline(always)] - pub fn wallet_block( - &mut self, - wallet_block: &'b solana_account_info::AccountInfo<'a>, - ) -> &mut Self { - self.instruction.wallet_block = Some(wallet_block); - self - } - #[inline(always)] - pub fn system_program( - &mut self, - system_program: &'b solana_account_info::AccountInfo<'a>, - ) -> &mut Self { - self.instruction.system_program = Some(system_program); - self - } - /// Add an additional account to the instruction. - #[inline(always)] - pub fn add_remaining_account( - &mut self, - account: &'b solana_account_info::AccountInfo<'a>, - is_writable: bool, - is_signer: bool, - ) -> &mut Self { - self.instruction - .__remaining_accounts - .push((account, is_writable, is_signer)); - self - } - /// Add additional accounts to the instruction. - /// - /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, - /// and a `bool` indicating whether the account is a signer or not. - #[inline(always)] - pub fn add_remaining_accounts( - &mut self, - accounts: &[( - &'b solana_account_info::AccountInfo<'a>, - bool, - bool, - )], - ) -> &mut Self { - self.instruction - .__remaining_accounts - .extend_from_slice(accounts); - self - } - #[inline(always)] - pub fn invoke(&self) -> solana_program_error::ProgramResult { - self.invoke_signed(&[]) - } - #[allow(clippy::clone_on_copy)] - #[allow(clippy::vec_init_then_push)] - pub fn invoke_signed( - &self, - signers_seeds: &[&[&[u8]]], - ) -> solana_program_error::ProgramResult { - let instruction = BlockWalletCpi { - __program: self.instruction.__program, - - authority: self.instruction.authority.expect("authority is not set"), - - config: self.instruction.config.expect("config is not set"), - - wallet: self.instruction.wallet.expect("wallet is not set"), - - wallet_block: self - .instruction - .wallet_block - .expect("wallet_block is not set"), - - system_program: self - .instruction - .system_program - .expect("system_program is not set"), - }; - instruction.invoke_signed_with_remaining_accounts( - signers_seeds, - &self.instruction.__remaining_accounts, - ) - } -} - -#[derive(Clone, Debug)] -struct BlockWalletCpiBuilderInstruction<'a, 'b> { - __program: &'b solana_account_info::AccountInfo<'a>, - authority: Option<&'b solana_account_info::AccountInfo<'a>>, - config: Option<&'b solana_account_info::AccountInfo<'a>>, - wallet: Option<&'b solana_account_info::AccountInfo<'a>>, - wallet_block: Option<&'b solana_account_info::AccountInfo<'a>>, - system_program: Option<&'b solana_account_info::AccountInfo<'a>>, - /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. - __remaining_accounts: Vec<( - &'b solana_account_info::AccountInfo<'a>, - bool, - bool, - )>, -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/init.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/init.rs deleted file mode 100644 index f3141d07f..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/init.rs +++ /dev/null @@ -1,370 +0,0 @@ -//! This code was AUTOGENERATED using the codama library. -//! Please DO NOT EDIT THIS FILE, instead use visitors -//! to add features, then rerun codama to update it. -//! -//! -//! - -use borsh::BorshDeserialize; -use borsh::BorshSerialize; - -/// Accounts. -#[derive(Debug)] -pub struct Init { - pub authority: solana_pubkey::Pubkey, - - pub config: solana_pubkey::Pubkey, - - pub system_program: solana_pubkey::Pubkey, -} - -impl Init { - pub fn instruction(&self) -> solana_instruction::Instruction { - self.instruction_with_remaining_accounts(&[]) - } - #[allow(clippy::arithmetic_side_effects)] - #[allow(clippy::vec_init_then_push)] - pub fn instruction_with_remaining_accounts( - &self, - remaining_accounts: &[solana_instruction::AccountMeta], - ) -> solana_instruction::Instruction { - let mut accounts = Vec::with_capacity(3 + remaining_accounts.len()); - accounts.push(solana_instruction::AccountMeta::new( - self.authority, - true, - )); - accounts.push(solana_instruction::AccountMeta::new( - self.config, - false, - )); - accounts.push(solana_instruction::AccountMeta::new_readonly( - self.system_program, - false, - )); - accounts.extend_from_slice(remaining_accounts); - let data = borsh::to_vec(&InitInstructionData::new()).unwrap(); - - solana_instruction::Instruction { - program_id: crate::BLOCK_LIST_ID, - accounts, - data, - } - } -} - -#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct InitInstructionData { - discriminator: u8, -} - -impl InitInstructionData { - pub fn new() -> Self { - Self { discriminator: 241 } - } -} - -impl Default for InitInstructionData { - fn default() -> Self { - Self::new() - } -} - -/// Instruction builder for `Init`. -/// -/// ### Accounts: -/// -/// 0. `[writable, signer]` authority -/// 1. `[writable]` config -/// 2. `[optional]` system_program (default to `11111111111111111111111111111111`) -#[derive(Clone, Debug, Default)] -pub struct InitBuilder { - authority: Option, - config: Option, - system_program: Option, - __remaining_accounts: Vec, -} - -impl InitBuilder { - pub fn new() -> Self { - Self::default() - } - #[inline(always)] - pub fn authority(&mut self, authority: solana_pubkey::Pubkey) -> &mut Self { - self.authority = Some(authority); - self - } - #[inline(always)] - pub fn config(&mut self, config: solana_pubkey::Pubkey) -> &mut Self { - self.config = Some(config); - self - } - /// `[optional account, default to '11111111111111111111111111111111']` - #[inline(always)] - pub fn system_program(&mut self, system_program: solana_pubkey::Pubkey) -> &mut Self { - self.system_program = Some(system_program); - self - } - /// Add an additional account to the instruction. - #[inline(always)] - pub fn add_remaining_account( - &mut self, - account: solana_instruction::AccountMeta, - ) -> &mut Self { - self.__remaining_accounts.push(account); - self - } - /// Add additional accounts to the instruction. - #[inline(always)] - pub fn add_remaining_accounts( - &mut self, - accounts: &[solana_instruction::AccountMeta], - ) -> &mut Self { - self.__remaining_accounts.extend_from_slice(accounts); - self - } - #[allow(clippy::clone_on_copy)] - pub fn instruction(&self) -> solana_instruction::Instruction { - let accounts = Init { - authority: self.authority.expect("authority is not set"), - config: self.config.expect("config is not set"), - system_program: self - .system_program - .unwrap_or(solana_pubkey::pubkey!("11111111111111111111111111111111")), - }; - - accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) - } -} - -/// `init` CPI accounts. -pub struct InitCpiAccounts<'a, 'b> { - pub authority: &'b solana_account_info::AccountInfo<'a>, - - pub config: &'b solana_account_info::AccountInfo<'a>, - - pub system_program: &'b solana_account_info::AccountInfo<'a>, -} - -/// `init` CPI instruction. -pub struct InitCpi<'a, 'b> { - /// The program to invoke. - pub __program: &'b solana_account_info::AccountInfo<'a>, - - pub authority: &'b solana_account_info::AccountInfo<'a>, - - pub config: &'b solana_account_info::AccountInfo<'a>, - - pub system_program: &'b solana_account_info::AccountInfo<'a>, -} - -impl<'a, 'b> InitCpi<'a, 'b> { - pub fn new( - program: &'b solana_account_info::AccountInfo<'a>, - accounts: InitCpiAccounts<'a, 'b>, - ) -> Self { - Self { - __program: program, - authority: accounts.authority, - config: accounts.config, - system_program: accounts.system_program, - } - } - #[inline(always)] - pub fn invoke(&self) -> solana_program_error::ProgramResult { - self.invoke_signed_with_remaining_accounts(&[], &[]) - } - #[inline(always)] - pub fn invoke_with_remaining_accounts( - &self, - remaining_accounts: &[( - &'b solana_account_info::AccountInfo<'a>, - bool, - bool, - )], - ) -> solana_program_error::ProgramResult { - self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) - } - #[inline(always)] - pub fn invoke_signed( - &self, - signers_seeds: &[&[&[u8]]], - ) -> solana_program_error::ProgramResult { - self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) - } - #[allow(clippy::arithmetic_side_effects)] - #[allow(clippy::clone_on_copy)] - #[allow(clippy::vec_init_then_push)] - pub fn invoke_signed_with_remaining_accounts( - &self, - signers_seeds: &[&[&[u8]]], - remaining_accounts: &[( - &'b solana_account_info::AccountInfo<'a>, - bool, - bool, - )], - ) -> solana_program_error::ProgramResult { - let mut accounts = Vec::with_capacity(3 + remaining_accounts.len()); - accounts.push(solana_instruction::AccountMeta::new( - *self.authority.key, - true, - )); - accounts.push(solana_instruction::AccountMeta::new( - *self.config.key, - false, - )); - accounts.push(solana_instruction::AccountMeta::new_readonly( - *self.system_program.key, - false, - )); - remaining_accounts.iter().for_each(|remaining_account| { - accounts.push(solana_instruction::AccountMeta { - pubkey: *remaining_account.0.key, - is_signer: remaining_account.1, - is_writable: remaining_account.2, - }) - }); - let data = borsh::to_vec(&InitInstructionData::new()).unwrap(); - - let instruction = solana_instruction::Instruction { - program_id: crate::BLOCK_LIST_ID, - accounts, - data, - }; - let mut account_infos = Vec::with_capacity(4 + remaining_accounts.len()); - account_infos.push(self.__program.clone()); - account_infos.push(self.authority.clone()); - account_infos.push(self.config.clone()); - account_infos.push(self.system_program.clone()); - remaining_accounts - .iter() - .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); - - if signers_seeds.is_empty() { - solana_cpi::invoke(&instruction, &account_infos) - } else { - solana_cpi::invoke_signed(&instruction, &account_infos, signers_seeds) - } - } -} - -/// Instruction builder for `Init` via CPI. -/// -/// ### Accounts: -/// -/// 0. `[writable, signer]` authority -/// 1. `[writable]` config -/// 2. `[]` system_program -#[derive(Clone, Debug)] -pub struct InitCpiBuilder<'a, 'b> { - instruction: Box>, -} - -impl<'a, 'b> InitCpiBuilder<'a, 'b> { - pub fn new(program: &'b solana_account_info::AccountInfo<'a>) -> Self { - let instruction = Box::new(InitCpiBuilderInstruction { - __program: program, - authority: None, - config: None, - system_program: None, - __remaining_accounts: Vec::new(), - }); - Self { instruction } - } - #[inline(always)] - pub fn authority( - &mut self, - authority: &'b solana_account_info::AccountInfo<'a>, - ) -> &mut Self { - self.instruction.authority = Some(authority); - self - } - #[inline(always)] - pub fn config( - &mut self, - config: &'b solana_account_info::AccountInfo<'a>, - ) -> &mut Self { - self.instruction.config = Some(config); - self - } - #[inline(always)] - pub fn system_program( - &mut self, - system_program: &'b solana_account_info::AccountInfo<'a>, - ) -> &mut Self { - self.instruction.system_program = Some(system_program); - self - } - /// Add an additional account to the instruction. - #[inline(always)] - pub fn add_remaining_account( - &mut self, - account: &'b solana_account_info::AccountInfo<'a>, - is_writable: bool, - is_signer: bool, - ) -> &mut Self { - self.instruction - .__remaining_accounts - .push((account, is_writable, is_signer)); - self - } - /// Add additional accounts to the instruction. - /// - /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, - /// and a `bool` indicating whether the account is a signer or not. - #[inline(always)] - pub fn add_remaining_accounts( - &mut self, - accounts: &[( - &'b solana_account_info::AccountInfo<'a>, - bool, - bool, - )], - ) -> &mut Self { - self.instruction - .__remaining_accounts - .extend_from_slice(accounts); - self - } - #[inline(always)] - pub fn invoke(&self) -> solana_program_error::ProgramResult { - self.invoke_signed(&[]) - } - #[allow(clippy::clone_on_copy)] - #[allow(clippy::vec_init_then_push)] - pub fn invoke_signed( - &self, - signers_seeds: &[&[&[u8]]], - ) -> solana_program_error::ProgramResult { - let instruction = InitCpi { - __program: self.instruction.__program, - - authority: self.instruction.authority.expect("authority is not set"), - - config: self.instruction.config.expect("config is not set"), - - system_program: self - .instruction - .system_program - .expect("system_program is not set"), - }; - instruction.invoke_signed_with_remaining_accounts( - signers_seeds, - &self.instruction.__remaining_accounts, - ) - } -} - -#[derive(Clone, Debug)] -struct InitCpiBuilderInstruction<'a, 'b> { - __program: &'b solana_account_info::AccountInfo<'a>, - authority: Option<&'b solana_account_info::AccountInfo<'a>>, - config: Option<&'b solana_account_info::AccountInfo<'a>>, - system_program: Option<&'b solana_account_info::AccountInfo<'a>>, - /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. - __remaining_accounts: Vec<( - &'b solana_account_info::AccountInfo<'a>, - bool, - bool, - )>, -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/mod.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/mod.rs deleted file mode 100644 index 6f7ca3753..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! This code was AUTOGENERATED using the codama library. -//! Please DO NOT EDIT THIS FILE, instead use visitors -//! to add features, then rerun codama to update it. -//! -//! -//! - -pub(crate) mod r#block_wallet; -pub(crate) mod r#init; -pub(crate) mod r#setup_extra_metas; -pub(crate) mod r#unblock_wallet; - -pub use self::r#block_wallet::*; -pub use self::r#init::*; -pub use self::r#setup_extra_metas::*; -pub use self::r#unblock_wallet::*; diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/setup_extra_metas.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/setup_extra_metas.rs deleted file mode 100644 index e89ca0200..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/setup_extra_metas.rs +++ /dev/null @@ -1,483 +0,0 @@ -//! This code was AUTOGENERATED using the codama library. -//! Please DO NOT EDIT THIS FILE, instead use visitors -//! to add features, then rerun codama to update it. -//! -//! -//! - -use borsh::BorshDeserialize; -use borsh::BorshSerialize; - -/// Accounts. -#[derive(Debug)] -pub struct SetupExtraMetas { - pub authority: solana_pubkey::Pubkey, - - pub config: solana_pubkey::Pubkey, - - pub mint: solana_pubkey::Pubkey, - - pub extra_metas: solana_pubkey::Pubkey, - - pub system_program: solana_pubkey::Pubkey, -} - -impl SetupExtraMetas { - pub fn instruction( - &self, - args: SetupExtraMetasInstructionArgs, - ) -> solana_instruction::Instruction { - self.instruction_with_remaining_accounts(args, &[]) - } - #[allow(clippy::arithmetic_side_effects)] - #[allow(clippy::vec_init_then_push)] - pub fn instruction_with_remaining_accounts( - &self, - args: SetupExtraMetasInstructionArgs, - remaining_accounts: &[solana_instruction::AccountMeta], - ) -> solana_instruction::Instruction { - let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); - accounts.push(solana_instruction::AccountMeta::new( - self.authority, - true, - )); - accounts.push(solana_instruction::AccountMeta::new_readonly( - self.config, - false, - )); - accounts.push(solana_instruction::AccountMeta::new_readonly( - self.mint, false, - )); - accounts.push(solana_instruction::AccountMeta::new( - self.extra_metas, - false, - )); - accounts.push(solana_instruction::AccountMeta::new_readonly( - self.system_program, - false, - )); - accounts.extend_from_slice(remaining_accounts); - let mut data = borsh::to_vec(&SetupExtraMetasInstructionData::new()).unwrap(); - let mut args = borsh::to_vec(&args).unwrap(); - data.append(&mut args); - - solana_instruction::Instruction { - program_id: crate::BLOCK_LIST_ID, - accounts, - data, - } - } -} - -#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct SetupExtraMetasInstructionData { - discriminator: u8, -} - -impl SetupExtraMetasInstructionData { - pub fn new() -> Self { - Self { discriminator: 106 } - } -} - -impl Default for SetupExtraMetasInstructionData { - fn default() -> Self { - Self::new() - } -} - -#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct SetupExtraMetasInstructionArgs { - pub check_both_wallets: bool, -} - -/// Instruction builder for `SetupExtraMetas`. -/// -/// ### Accounts: -/// -/// 0. `[writable, signer]` authority -/// 1. `[]` config -/// 2. `[]` mint -/// 3. `[writable]` extra_metas -/// 4. `[optional]` system_program (default to `11111111111111111111111111111111`) -#[derive(Clone, Debug, Default)] -pub struct SetupExtraMetasBuilder { - authority: Option, - config: Option, - mint: Option, - extra_metas: Option, - system_program: Option, - check_both_wallets: Option, - __remaining_accounts: Vec, -} - -impl SetupExtraMetasBuilder { - pub fn new() -> Self { - Self::default() - } - #[inline(always)] - pub fn authority(&mut self, authority: solana_pubkey::Pubkey) -> &mut Self { - self.authority = Some(authority); - self - } - #[inline(always)] - pub fn config(&mut self, config: solana_pubkey::Pubkey) -> &mut Self { - self.config = Some(config); - self - } - #[inline(always)] - pub fn mint(&mut self, mint: solana_pubkey::Pubkey) -> &mut Self { - self.mint = Some(mint); - self - } - #[inline(always)] - pub fn extra_metas(&mut self, extra_metas: solana_pubkey::Pubkey) -> &mut Self { - self.extra_metas = Some(extra_metas); - self - } - /// `[optional account, default to '11111111111111111111111111111111']` - #[inline(always)] - pub fn system_program(&mut self, system_program: solana_pubkey::Pubkey) -> &mut Self { - self.system_program = Some(system_program); - self - } - /// `[optional argument, defaults to 'false']` - #[inline(always)] - pub fn check_both_wallets(&mut self, check_both_wallets: bool) -> &mut Self { - self.check_both_wallets = Some(check_both_wallets); - self - } - /// Add an additional account to the instruction. - #[inline(always)] - pub fn add_remaining_account( - &mut self, - account: solana_instruction::AccountMeta, - ) -> &mut Self { - self.__remaining_accounts.push(account); - self - } - /// Add additional accounts to the instruction. - #[inline(always)] - pub fn add_remaining_accounts( - &mut self, - accounts: &[solana_instruction::AccountMeta], - ) -> &mut Self { - self.__remaining_accounts.extend_from_slice(accounts); - self - } - #[allow(clippy::clone_on_copy)] - pub fn instruction(&self) -> solana_instruction::Instruction { - let accounts = SetupExtraMetas { - authority: self.authority.expect("authority is not set"), - config: self.config.expect("config is not set"), - mint: self.mint.expect("mint is not set"), - extra_metas: self.extra_metas.expect("extra_metas is not set"), - system_program: self - .system_program - .unwrap_or(solana_pubkey::pubkey!("11111111111111111111111111111111")), - }; - let args = SetupExtraMetasInstructionArgs { - check_both_wallets: self.check_both_wallets.clone().unwrap_or(false), - }; - - accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) - } -} - -/// `setup_extra_metas` CPI accounts. -pub struct SetupExtraMetasCpiAccounts<'a, 'b> { - pub authority: &'b solana_account_info::AccountInfo<'a>, - - pub config: &'b solana_account_info::AccountInfo<'a>, - - pub mint: &'b solana_account_info::AccountInfo<'a>, - - pub extra_metas: &'b solana_account_info::AccountInfo<'a>, - - pub system_program: &'b solana_account_info::AccountInfo<'a>, -} - -/// `setup_extra_metas` CPI instruction. -pub struct SetupExtraMetasCpi<'a, 'b> { - /// The program to invoke. - pub __program: &'b solana_account_info::AccountInfo<'a>, - - pub authority: &'b solana_account_info::AccountInfo<'a>, - - pub config: &'b solana_account_info::AccountInfo<'a>, - - pub mint: &'b solana_account_info::AccountInfo<'a>, - - pub extra_metas: &'b solana_account_info::AccountInfo<'a>, - - pub system_program: &'b solana_account_info::AccountInfo<'a>, - /// The arguments for the instruction. - pub __args: SetupExtraMetasInstructionArgs, -} - -impl<'a, 'b> SetupExtraMetasCpi<'a, 'b> { - pub fn new( - program: &'b solana_account_info::AccountInfo<'a>, - accounts: SetupExtraMetasCpiAccounts<'a, 'b>, - args: SetupExtraMetasInstructionArgs, - ) -> Self { - Self { - __program: program, - authority: accounts.authority, - config: accounts.config, - mint: accounts.mint, - extra_metas: accounts.extra_metas, - system_program: accounts.system_program, - __args: args, - } - } - #[inline(always)] - pub fn invoke(&self) -> solana_program_error::ProgramResult { - self.invoke_signed_with_remaining_accounts(&[], &[]) - } - #[inline(always)] - pub fn invoke_with_remaining_accounts( - &self, - remaining_accounts: &[( - &'b solana_account_info::AccountInfo<'a>, - bool, - bool, - )], - ) -> solana_program_error::ProgramResult { - self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) - } - #[inline(always)] - pub fn invoke_signed( - &self, - signers_seeds: &[&[&[u8]]], - ) -> solana_program_error::ProgramResult { - self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) - } - #[allow(clippy::arithmetic_side_effects)] - #[allow(clippy::clone_on_copy)] - #[allow(clippy::vec_init_then_push)] - pub fn invoke_signed_with_remaining_accounts( - &self, - signers_seeds: &[&[&[u8]]], - remaining_accounts: &[( - &'b solana_account_info::AccountInfo<'a>, - bool, - bool, - )], - ) -> solana_program_error::ProgramResult { - let mut accounts = Vec::with_capacity(5 + remaining_accounts.len()); - accounts.push(solana_instruction::AccountMeta::new( - *self.authority.key, - true, - )); - accounts.push(solana_instruction::AccountMeta::new_readonly( - *self.config.key, - false, - )); - accounts.push(solana_instruction::AccountMeta::new_readonly( - *self.mint.key, - false, - )); - accounts.push(solana_instruction::AccountMeta::new( - *self.extra_metas.key, - false, - )); - accounts.push(solana_instruction::AccountMeta::new_readonly( - *self.system_program.key, - false, - )); - remaining_accounts.iter().for_each(|remaining_account| { - accounts.push(solana_instruction::AccountMeta { - pubkey: *remaining_account.0.key, - is_signer: remaining_account.1, - is_writable: remaining_account.2, - }) - }); - let mut data = borsh::to_vec(&SetupExtraMetasInstructionData::new()).unwrap(); - let mut args = borsh::to_vec(&self.__args).unwrap(); - data.append(&mut args); - - let instruction = solana_instruction::Instruction { - program_id: crate::BLOCK_LIST_ID, - accounts, - data, - }; - let mut account_infos = Vec::with_capacity(6 + remaining_accounts.len()); - account_infos.push(self.__program.clone()); - account_infos.push(self.authority.clone()); - account_infos.push(self.config.clone()); - account_infos.push(self.mint.clone()); - account_infos.push(self.extra_metas.clone()); - account_infos.push(self.system_program.clone()); - remaining_accounts - .iter() - .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); - - if signers_seeds.is_empty() { - solana_cpi::invoke(&instruction, &account_infos) - } else { - solana_cpi::invoke_signed(&instruction, &account_infos, signers_seeds) - } - } -} - -/// Instruction builder for `SetupExtraMetas` via CPI. -/// -/// ### Accounts: -/// -/// 0. `[writable, signer]` authority -/// 1. `[]` config -/// 2. `[]` mint -/// 3. `[writable]` extra_metas -/// 4. `[]` system_program -#[derive(Clone, Debug)] -pub struct SetupExtraMetasCpiBuilder<'a, 'b> { - instruction: Box>, -} - -impl<'a, 'b> SetupExtraMetasCpiBuilder<'a, 'b> { - pub fn new(program: &'b solana_account_info::AccountInfo<'a>) -> Self { - let instruction = Box::new(SetupExtraMetasCpiBuilderInstruction { - __program: program, - authority: None, - config: None, - mint: None, - extra_metas: None, - system_program: None, - check_both_wallets: None, - __remaining_accounts: Vec::new(), - }); - Self { instruction } - } - #[inline(always)] - pub fn authority( - &mut self, - authority: &'b solana_account_info::AccountInfo<'a>, - ) -> &mut Self { - self.instruction.authority = Some(authority); - self - } - #[inline(always)] - pub fn config( - &mut self, - config: &'b solana_account_info::AccountInfo<'a>, - ) -> &mut Self { - self.instruction.config = Some(config); - self - } - #[inline(always)] - pub fn mint(&mut self, mint: &'b solana_account_info::AccountInfo<'a>) -> &mut Self { - self.instruction.mint = Some(mint); - self - } - #[inline(always)] - pub fn extra_metas( - &mut self, - extra_metas: &'b solana_account_info::AccountInfo<'a>, - ) -> &mut Self { - self.instruction.extra_metas = Some(extra_metas); - self - } - #[inline(always)] - pub fn system_program( - &mut self, - system_program: &'b solana_account_info::AccountInfo<'a>, - ) -> &mut Self { - self.instruction.system_program = Some(system_program); - self - } - /// `[optional argument, defaults to 'false']` - #[inline(always)] - pub fn check_both_wallets(&mut self, check_both_wallets: bool) -> &mut Self { - self.instruction.check_both_wallets = Some(check_both_wallets); - self - } - /// Add an additional account to the instruction. - #[inline(always)] - pub fn add_remaining_account( - &mut self, - account: &'b solana_account_info::AccountInfo<'a>, - is_writable: bool, - is_signer: bool, - ) -> &mut Self { - self.instruction - .__remaining_accounts - .push((account, is_writable, is_signer)); - self - } - /// Add additional accounts to the instruction. - /// - /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, - /// and a `bool` indicating whether the account is a signer or not. - #[inline(always)] - pub fn add_remaining_accounts( - &mut self, - accounts: &[( - &'b solana_account_info::AccountInfo<'a>, - bool, - bool, - )], - ) -> &mut Self { - self.instruction - .__remaining_accounts - .extend_from_slice(accounts); - self - } - #[inline(always)] - pub fn invoke(&self) -> solana_program_error::ProgramResult { - self.invoke_signed(&[]) - } - #[allow(clippy::clone_on_copy)] - #[allow(clippy::vec_init_then_push)] - pub fn invoke_signed( - &self, - signers_seeds: &[&[&[u8]]], - ) -> solana_program_error::ProgramResult { - let args = SetupExtraMetasInstructionArgs { - check_both_wallets: self.instruction.check_both_wallets.clone().unwrap_or(false), - }; - let instruction = SetupExtraMetasCpi { - __program: self.instruction.__program, - - authority: self.instruction.authority.expect("authority is not set"), - - config: self.instruction.config.expect("config is not set"), - - mint: self.instruction.mint.expect("mint is not set"), - - extra_metas: self - .instruction - .extra_metas - .expect("extra_metas is not set"), - - system_program: self - .instruction - .system_program - .expect("system_program is not set"), - __args: args, - }; - instruction.invoke_signed_with_remaining_accounts( - signers_seeds, - &self.instruction.__remaining_accounts, - ) - } -} - -#[derive(Clone, Debug)] -struct SetupExtraMetasCpiBuilderInstruction<'a, 'b> { - __program: &'b solana_account_info::AccountInfo<'a>, - authority: Option<&'b solana_account_info::AccountInfo<'a>>, - config: Option<&'b solana_account_info::AccountInfo<'a>>, - mint: Option<&'b solana_account_info::AccountInfo<'a>>, - extra_metas: Option<&'b solana_account_info::AccountInfo<'a>>, - system_program: Option<&'b solana_account_info::AccountInfo<'a>>, - check_both_wallets: Option, - /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. - __remaining_accounts: Vec<( - &'b solana_account_info::AccountInfo<'a>, - bool, - bool, - )>, -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/unblock_wallet.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/unblock_wallet.rs deleted file mode 100644 index 6aaf32036..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/instructions/unblock_wallet.rs +++ /dev/null @@ -1,410 +0,0 @@ -//! This code was AUTOGENERATED using the codama library. -//! Please DO NOT EDIT THIS FILE, instead use visitors -//! to add features, then rerun codama to update it. -//! -//! -//! - -use borsh::BorshDeserialize; -use borsh::BorshSerialize; - -/// Accounts. -#[derive(Debug)] -pub struct UnblockWallet { - pub authority: solana_pubkey::Pubkey, - - pub config: solana_pubkey::Pubkey, - - pub wallet_block: solana_pubkey::Pubkey, - - pub system_program: solana_pubkey::Pubkey, -} - -impl UnblockWallet { - pub fn instruction(&self) -> solana_instruction::Instruction { - self.instruction_with_remaining_accounts(&[]) - } - #[allow(clippy::arithmetic_side_effects)] - #[allow(clippy::vec_init_then_push)] - pub fn instruction_with_remaining_accounts( - &self, - remaining_accounts: &[solana_instruction::AccountMeta], - ) -> solana_instruction::Instruction { - let mut accounts = Vec::with_capacity(4 + remaining_accounts.len()); - accounts.push(solana_instruction::AccountMeta::new( - self.authority, - true, - )); - accounts.push(solana_instruction::AccountMeta::new( - self.config, - false, - )); - accounts.push(solana_instruction::AccountMeta::new( - self.wallet_block, - false, - )); - accounts.push(solana_instruction::AccountMeta::new_readonly( - self.system_program, - false, - )); - accounts.extend_from_slice(remaining_accounts); - let data = borsh::to_vec(&UnblockWalletInstructionData::new()).unwrap(); - - solana_instruction::Instruction { - program_id: crate::BLOCK_LIST_ID, - accounts, - data, - } - } -} - -#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct UnblockWalletInstructionData { - discriminator: u8, -} - -impl UnblockWalletInstructionData { - pub fn new() -> Self { - Self { discriminator: 243 } - } -} - -impl Default for UnblockWalletInstructionData { - fn default() -> Self { - Self::new() - } -} - -/// Instruction builder for `UnblockWallet`. -/// -/// ### Accounts: -/// -/// 0. `[writable, signer]` authority -/// 1. `[writable]` config -/// 2. `[writable]` wallet_block -/// 3. `[optional]` system_program (default to `11111111111111111111111111111111`) -#[derive(Clone, Debug, Default)] -pub struct UnblockWalletBuilder { - authority: Option, - config: Option, - wallet_block: Option, - system_program: Option, - __remaining_accounts: Vec, -} - -impl UnblockWalletBuilder { - pub fn new() -> Self { - Self::default() - } - #[inline(always)] - pub fn authority(&mut self, authority: solana_pubkey::Pubkey) -> &mut Self { - self.authority = Some(authority); - self - } - #[inline(always)] - pub fn config(&mut self, config: solana_pubkey::Pubkey) -> &mut Self { - self.config = Some(config); - self - } - #[inline(always)] - pub fn wallet_block(&mut self, wallet_block: solana_pubkey::Pubkey) -> &mut Self { - self.wallet_block = Some(wallet_block); - self - } - /// `[optional account, default to '11111111111111111111111111111111']` - #[inline(always)] - pub fn system_program(&mut self, system_program: solana_pubkey::Pubkey) -> &mut Self { - self.system_program = Some(system_program); - self - } - /// Add an additional account to the instruction. - #[inline(always)] - pub fn add_remaining_account( - &mut self, - account: solana_instruction::AccountMeta, - ) -> &mut Self { - self.__remaining_accounts.push(account); - self - } - /// Add additional accounts to the instruction. - #[inline(always)] - pub fn add_remaining_accounts( - &mut self, - accounts: &[solana_instruction::AccountMeta], - ) -> &mut Self { - self.__remaining_accounts.extend_from_slice(accounts); - self - } - #[allow(clippy::clone_on_copy)] - pub fn instruction(&self) -> solana_instruction::Instruction { - let accounts = UnblockWallet { - authority: self.authority.expect("authority is not set"), - config: self.config.expect("config is not set"), - wallet_block: self.wallet_block.expect("wallet_block is not set"), - system_program: self - .system_program - .unwrap_or(solana_pubkey::pubkey!("11111111111111111111111111111111")), - }; - - accounts.instruction_with_remaining_accounts(&self.__remaining_accounts) - } -} - -/// `unblock_wallet` CPI accounts. -pub struct UnblockWalletCpiAccounts<'a, 'b> { - pub authority: &'b solana_account_info::AccountInfo<'a>, - - pub config: &'b solana_account_info::AccountInfo<'a>, - - pub wallet_block: &'b solana_account_info::AccountInfo<'a>, - - pub system_program: &'b solana_account_info::AccountInfo<'a>, -} - -/// `unblock_wallet` CPI instruction. -pub struct UnblockWalletCpi<'a, 'b> { - /// The program to invoke. - pub __program: &'b solana_account_info::AccountInfo<'a>, - - pub authority: &'b solana_account_info::AccountInfo<'a>, - - pub config: &'b solana_account_info::AccountInfo<'a>, - - pub wallet_block: &'b solana_account_info::AccountInfo<'a>, - - pub system_program: &'b solana_account_info::AccountInfo<'a>, -} - -impl<'a, 'b> UnblockWalletCpi<'a, 'b> { - pub fn new( - program: &'b solana_account_info::AccountInfo<'a>, - accounts: UnblockWalletCpiAccounts<'a, 'b>, - ) -> Self { - Self { - __program: program, - authority: accounts.authority, - config: accounts.config, - wallet_block: accounts.wallet_block, - system_program: accounts.system_program, - } - } - #[inline(always)] - pub fn invoke(&self) -> solana_program_error::ProgramResult { - self.invoke_signed_with_remaining_accounts(&[], &[]) - } - #[inline(always)] - pub fn invoke_with_remaining_accounts( - &self, - remaining_accounts: &[( - &'b solana_account_info::AccountInfo<'a>, - bool, - bool, - )], - ) -> solana_program_error::ProgramResult { - self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) - } - #[inline(always)] - pub fn invoke_signed( - &self, - signers_seeds: &[&[&[u8]]], - ) -> solana_program_error::ProgramResult { - self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) - } - #[allow(clippy::arithmetic_side_effects)] - #[allow(clippy::clone_on_copy)] - #[allow(clippy::vec_init_then_push)] - pub fn invoke_signed_with_remaining_accounts( - &self, - signers_seeds: &[&[&[u8]]], - remaining_accounts: &[( - &'b solana_account_info::AccountInfo<'a>, - bool, - bool, - )], - ) -> solana_program_error::ProgramResult { - let mut accounts = Vec::with_capacity(4 + remaining_accounts.len()); - accounts.push(solana_instruction::AccountMeta::new( - *self.authority.key, - true, - )); - accounts.push(solana_instruction::AccountMeta::new( - *self.config.key, - false, - )); - accounts.push(solana_instruction::AccountMeta::new( - *self.wallet_block.key, - false, - )); - accounts.push(solana_instruction::AccountMeta::new_readonly( - *self.system_program.key, - false, - )); - remaining_accounts.iter().for_each(|remaining_account| { - accounts.push(solana_instruction::AccountMeta { - pubkey: *remaining_account.0.key, - is_signer: remaining_account.1, - is_writable: remaining_account.2, - }) - }); - let data = borsh::to_vec(&UnblockWalletInstructionData::new()).unwrap(); - - let instruction = solana_instruction::Instruction { - program_id: crate::BLOCK_LIST_ID, - accounts, - data, - }; - let mut account_infos = Vec::with_capacity(5 + remaining_accounts.len()); - account_infos.push(self.__program.clone()); - account_infos.push(self.authority.clone()); - account_infos.push(self.config.clone()); - account_infos.push(self.wallet_block.clone()); - account_infos.push(self.system_program.clone()); - remaining_accounts - .iter() - .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); - - if signers_seeds.is_empty() { - solana_cpi::invoke(&instruction, &account_infos) - } else { - solana_cpi::invoke_signed(&instruction, &account_infos, signers_seeds) - } - } -} - -/// Instruction builder for `UnblockWallet` via CPI. -/// -/// ### Accounts: -/// -/// 0. `[writable, signer]` authority -/// 1. `[writable]` config -/// 2. `[writable]` wallet_block -/// 3. `[]` system_program -#[derive(Clone, Debug)] -pub struct UnblockWalletCpiBuilder<'a, 'b> { - instruction: Box>, -} - -impl<'a, 'b> UnblockWalletCpiBuilder<'a, 'b> { - pub fn new(program: &'b solana_account_info::AccountInfo<'a>) -> Self { - let instruction = Box::new(UnblockWalletCpiBuilderInstruction { - __program: program, - authority: None, - config: None, - wallet_block: None, - system_program: None, - __remaining_accounts: Vec::new(), - }); - Self { instruction } - } - #[inline(always)] - pub fn authority( - &mut self, - authority: &'b solana_account_info::AccountInfo<'a>, - ) -> &mut Self { - self.instruction.authority = Some(authority); - self - } - #[inline(always)] - pub fn config( - &mut self, - config: &'b solana_account_info::AccountInfo<'a>, - ) -> &mut Self { - self.instruction.config = Some(config); - self - } - #[inline(always)] - pub fn wallet_block( - &mut self, - wallet_block: &'b solana_account_info::AccountInfo<'a>, - ) -> &mut Self { - self.instruction.wallet_block = Some(wallet_block); - self - } - #[inline(always)] - pub fn system_program( - &mut self, - system_program: &'b solana_account_info::AccountInfo<'a>, - ) -> &mut Self { - self.instruction.system_program = Some(system_program); - self - } - /// Add an additional account to the instruction. - #[inline(always)] - pub fn add_remaining_account( - &mut self, - account: &'b solana_account_info::AccountInfo<'a>, - is_writable: bool, - is_signer: bool, - ) -> &mut Self { - self.instruction - .__remaining_accounts - .push((account, is_writable, is_signer)); - self - } - /// Add additional accounts to the instruction. - /// - /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, - /// and a `bool` indicating whether the account is a signer or not. - #[inline(always)] - pub fn add_remaining_accounts( - &mut self, - accounts: &[( - &'b solana_account_info::AccountInfo<'a>, - bool, - bool, - )], - ) -> &mut Self { - self.instruction - .__remaining_accounts - .extend_from_slice(accounts); - self - } - #[inline(always)] - pub fn invoke(&self) -> solana_program_error::ProgramResult { - self.invoke_signed(&[]) - } - #[allow(clippy::clone_on_copy)] - #[allow(clippy::vec_init_then_push)] - pub fn invoke_signed( - &self, - signers_seeds: &[&[&[u8]]], - ) -> solana_program_error::ProgramResult { - let instruction = UnblockWalletCpi { - __program: self.instruction.__program, - - authority: self.instruction.authority.expect("authority is not set"), - - config: self.instruction.config.expect("config is not set"), - - wallet_block: self - .instruction - .wallet_block - .expect("wallet_block is not set"), - - system_program: self - .instruction - .system_program - .expect("system_program is not set"), - }; - instruction.invoke_signed_with_remaining_accounts( - signers_seeds, - &self.instruction.__remaining_accounts, - ) - } -} - -#[derive(Clone, Debug)] -struct UnblockWalletCpiBuilderInstruction<'a, 'b> { - __program: &'b solana_account_info::AccountInfo<'a>, - authority: Option<&'b solana_account_info::AccountInfo<'a>>, - config: Option<&'b solana_account_info::AccountInfo<'a>>, - wallet_block: Option<&'b solana_account_info::AccountInfo<'a>>, - system_program: Option<&'b solana_account_info::AccountInfo<'a>>, - /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. - __remaining_accounts: Vec<( - &'b solana_account_info::AccountInfo<'a>, - bool, - bool, - )>, -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/mod.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/mod.rs deleted file mode 100644 index 75684016c..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! This code was AUTOGENERATED using the codama library. -//! Please DO NOT EDIT THIS FILE, instead use visitors -//! to add features, then rerun codama to update it. -//! -//! -//! - -pub mod accounts; -pub mod errors; -pub mod instructions; -pub mod programs; -pub mod shared; - -pub(crate) use programs::*; diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/programs.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/programs.rs deleted file mode 100644 index 5a1848d80..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/programs.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! This code was AUTOGENERATED using the codama library. -//! Please DO NOT EDIT THIS FILE, instead use visitors -//! to add features, then rerun codama to update it. -//! -//! -//! - -use solana_pubkey::{pubkey, Pubkey}; - -/// `block_list` program ID. -pub const BLOCK_LIST_ID: Pubkey = pubkey!("BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf"); diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/shared.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/shared.rs deleted file mode 100644 index ed763f639..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/client/shared.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! This code was AUTOGENERATED using the codama library. -//! Please DO NOT EDIT THIS FILE, instead use visitors -//! to add features, then rerun codama to update it. -//! -//! -//! - -#[cfg(feature = "fetch")] -#[derive(Debug, Clone)] -pub struct DecodedAccount { - pub address: solana_pubkey::Pubkey, - pub account: solana_sdk::account::Account, - pub data: T, -} - -#[cfg(feature = "fetch")] -#[derive(Debug, Clone)] -pub enum MaybeAccount { - Exists(DecodedAccount), - NotFound(solana_pubkey::Pubkey), -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/lib.rs b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/lib.rs deleted file mode 100644 index 7c4d99ee6..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/rust/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod client; -pub use client::*; diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/accounts/config.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/accounts/config.ts deleted file mode 100644 index 03a52b664..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/accounts/config.ts +++ /dev/null @@ -1,145 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -import { - type Account, - type Address, - assertAccountExists, - assertAccountsExist, - type Codec, - combineCodec, - type Decoder, - decodeAccount, - type EncodedAccount, - type Encoder, - type FetchAccountConfig, - type FetchAccountsConfig, - fetchEncodedAccount, - fetchEncodedAccounts, - getAddressDecoder, - getAddressEncoder, - getStructDecoder, - getStructEncoder, - getU8Decoder, - getU8Encoder, - getU64Decoder, - getU64Encoder, - type MaybeAccount, - type MaybeEncodedAccount, -} from "@solana/kit"; -import { findConfigPda } from "../pdas"; - -export const CONFIG_DISCRIMINATOR = 0; - -export function getConfigDiscriminatorBytes() { - return getU8Encoder().encode(CONFIG_DISCRIMINATOR); -} - -export type Config = { - discriminator: number; - authority: Address; - blockedWalletsCount: bigint; -}; - -export type ConfigArgs = { - discriminator: number; - authority: Address; - blockedWalletsCount: number | bigint; -}; - -export function getConfigEncoder(): Encoder { - return getStructEncoder([ - ["discriminator", getU8Encoder()], - ["authority", getAddressEncoder()], - ["blockedWalletsCount", getU64Encoder()], - ]); -} - -export function getConfigDecoder(): Decoder { - return getStructDecoder([ - ["discriminator", getU8Decoder()], - ["authority", getAddressDecoder()], - ["blockedWalletsCount", getU64Decoder()], - ]); -} - -export function getConfigCodec(): Codec { - return combineCodec(getConfigEncoder(), getConfigDecoder()); -} - -export function decodeConfig( - encodedAccount: EncodedAccount, -): Account; -export function decodeConfig( - encodedAccount: MaybeEncodedAccount, -): MaybeAccount; -export function decodeConfig( - encodedAccount: EncodedAccount | MaybeEncodedAccount, -): Account | MaybeAccount { - return decodeAccount(encodedAccount as MaybeEncodedAccount, getConfigDecoder()); -} - -export async function fetchConfig( - rpc: Parameters[0], - address: Address, - config?: FetchAccountConfig, -): Promise> { - const maybeAccount = await fetchMaybeConfig(rpc, address, config); - assertAccountExists(maybeAccount); - return maybeAccount; -} - -export async function fetchMaybeConfig( - rpc: Parameters[0], - address: Address, - config?: FetchAccountConfig, -): Promise> { - const maybeAccount = await fetchEncodedAccount(rpc, address, config); - return decodeConfig(maybeAccount); -} - -export async function fetchAllConfig( - rpc: Parameters[0], - addresses: Array
, - config?: FetchAccountsConfig, -): Promise[]> { - const maybeAccounts = await fetchAllMaybeConfig(rpc, addresses, config); - assertAccountsExist(maybeAccounts); - return maybeAccounts; -} - -export async function fetchAllMaybeConfig( - rpc: Parameters[0], - addresses: Array
, - config?: FetchAccountsConfig, -): Promise[]> { - const maybeAccounts = await fetchEncodedAccounts(rpc, addresses, config); - return maybeAccounts.map((maybeAccount) => decodeConfig(maybeAccount)); -} - -export function getConfigSize(): number { - return 41; -} - -export async function fetchConfigFromSeeds( - rpc: Parameters[0], - config: FetchAccountConfig & { programAddress?: Address } = {}, -): Promise> { - const maybeAccount = await fetchMaybeConfigFromSeeds(rpc, config); - assertAccountExists(maybeAccount); - return maybeAccount; -} - -export async function fetchMaybeConfigFromSeeds( - rpc: Parameters[0], - config: FetchAccountConfig & { programAddress?: Address } = {}, -): Promise> { - const { programAddress, ...fetchConfig } = config; - const [address] = await findConfigPda({ programAddress }); - return await fetchMaybeConfig(rpc, address, fetchConfig); -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/accounts/extraMetas.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/accounts/extraMetas.ts deleted file mode 100644 index 93859232f..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/accounts/extraMetas.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -import { - type Account, - type Address, - assertAccountExists, - assertAccountsExist, - type Codec, - combineCodec, - type Decoder, - decodeAccount, - type EncodedAccount, - type Encoder, - type FetchAccountConfig, - type FetchAccountsConfig, - fetchEncodedAccount, - fetchEncodedAccounts, - getStructDecoder, - getStructEncoder, - type MaybeAccount, - type MaybeEncodedAccount, -} from "@solana/kit"; -import { type ExtraMetasSeeds, findExtraMetasPda } from "../pdas"; - -export type ExtraMetas = Record; - -export type ExtraMetasArgs = ExtraMetas; - -export function getExtraMetasEncoder(): Encoder { - return getStructEncoder([]); -} - -export function getExtraMetasDecoder(): Decoder { - return getStructDecoder([]); -} - -export function getExtraMetasCodec(): Codec { - return combineCodec(getExtraMetasEncoder(), getExtraMetasDecoder()); -} - -export function decodeExtraMetas( - encodedAccount: EncodedAccount, -): Account; -export function decodeExtraMetas( - encodedAccount: MaybeEncodedAccount, -): MaybeAccount; -export function decodeExtraMetas( - encodedAccount: EncodedAccount | MaybeEncodedAccount, -): Account | MaybeAccount { - return decodeAccount(encodedAccount as MaybeEncodedAccount, getExtraMetasDecoder()); -} - -export async function fetchExtraMetas( - rpc: Parameters[0], - address: Address, - config?: FetchAccountConfig, -): Promise> { - const maybeAccount = await fetchMaybeExtraMetas(rpc, address, config); - assertAccountExists(maybeAccount); - return maybeAccount; -} - -export async function fetchMaybeExtraMetas( - rpc: Parameters[0], - address: Address, - config?: FetchAccountConfig, -): Promise> { - const maybeAccount = await fetchEncodedAccount(rpc, address, config); - return decodeExtraMetas(maybeAccount); -} - -export async function fetchAllExtraMetas( - rpc: Parameters[0], - addresses: Array
, - config?: FetchAccountsConfig, -): Promise[]> { - const maybeAccounts = await fetchAllMaybeExtraMetas(rpc, addresses, config); - assertAccountsExist(maybeAccounts); - return maybeAccounts; -} - -export async function fetchAllMaybeExtraMetas( - rpc: Parameters[0], - addresses: Array
, - config?: FetchAccountsConfig, -): Promise[]> { - const maybeAccounts = await fetchEncodedAccounts(rpc, addresses, config); - return maybeAccounts.map((maybeAccount) => decodeExtraMetas(maybeAccount)); -} - -export async function fetchExtraMetasFromSeeds( - rpc: Parameters[0], - seeds: ExtraMetasSeeds, - config: FetchAccountConfig & { programAddress?: Address } = {}, -): Promise> { - const maybeAccount = await fetchMaybeExtraMetasFromSeeds(rpc, seeds, config); - assertAccountExists(maybeAccount); - return maybeAccount; -} - -export async function fetchMaybeExtraMetasFromSeeds( - rpc: Parameters[0], - seeds: ExtraMetasSeeds, - config: FetchAccountConfig & { programAddress?: Address } = {}, -): Promise> { - const { programAddress, ...fetchConfig } = config; - const [address] = await findExtraMetasPda(seeds, { programAddress }); - return await fetchMaybeExtraMetas(rpc, address, fetchConfig); -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/accounts/index.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/accounts/index.ts deleted file mode 100644 index 6135b7a67..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/accounts/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -export * from "./config"; -export * from "./extraMetas"; -export * from "./walletBlock"; diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/accounts/walletBlock.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/accounts/walletBlock.ts deleted file mode 100644 index 29a8309a9..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/accounts/walletBlock.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -import { - type Account, - type Address, - assertAccountExists, - assertAccountsExist, - type Codec, - combineCodec, - type Decoder, - decodeAccount, - type EncodedAccount, - type Encoder, - type FetchAccountConfig, - type FetchAccountsConfig, - fetchEncodedAccount, - fetchEncodedAccounts, - getAddressDecoder, - getAddressEncoder, - getStructDecoder, - getStructEncoder, - getU8Encoder, - type MaybeAccount, - type MaybeEncodedAccount, -} from "@solana/kit"; -import { findWalletBlockPda, type WalletBlockSeeds } from "../pdas"; - -export const WALLET_BLOCK_DISCRIMINATOR = 1; - -export function getWalletBlockDiscriminatorBytes() { - return getU8Encoder().encode(WALLET_BLOCK_DISCRIMINATOR); -} - -export type WalletBlock = { authority: Address }; - -export type WalletBlockArgs = WalletBlock; - -export function getWalletBlockEncoder(): Encoder { - return getStructEncoder([["authority", getAddressEncoder()]]); -} - -export function getWalletBlockDecoder(): Decoder { - return getStructDecoder([["authority", getAddressDecoder()]]); -} - -export function getWalletBlockCodec(): Codec { - return combineCodec(getWalletBlockEncoder(), getWalletBlockDecoder()); -} - -export function decodeWalletBlock( - encodedAccount: EncodedAccount, -): Account; -export function decodeWalletBlock( - encodedAccount: MaybeEncodedAccount, -): MaybeAccount; -export function decodeWalletBlock( - encodedAccount: EncodedAccount | MaybeEncodedAccount, -): Account | MaybeAccount { - return decodeAccount(encodedAccount as MaybeEncodedAccount, getWalletBlockDecoder()); -} - -export async function fetchWalletBlock( - rpc: Parameters[0], - address: Address, - config?: FetchAccountConfig, -): Promise> { - const maybeAccount = await fetchMaybeWalletBlock(rpc, address, config); - assertAccountExists(maybeAccount); - return maybeAccount; -} - -export async function fetchMaybeWalletBlock( - rpc: Parameters[0], - address: Address, - config?: FetchAccountConfig, -): Promise> { - const maybeAccount = await fetchEncodedAccount(rpc, address, config); - return decodeWalletBlock(maybeAccount); -} - -export async function fetchAllWalletBlock( - rpc: Parameters[0], - addresses: Array
, - config?: FetchAccountsConfig, -): Promise[]> { - const maybeAccounts = await fetchAllMaybeWalletBlock(rpc, addresses, config); - assertAccountsExist(maybeAccounts); - return maybeAccounts; -} - -export async function fetchAllMaybeWalletBlock( - rpc: Parameters[0], - addresses: Array
, - config?: FetchAccountsConfig, -): Promise[]> { - const maybeAccounts = await fetchEncodedAccounts(rpc, addresses, config); - return maybeAccounts.map((maybeAccount) => decodeWalletBlock(maybeAccount)); -} - -export function getWalletBlockSize(): number { - return 33; -} - -export async function fetchWalletBlockFromSeeds( - rpc: Parameters[0], - seeds: WalletBlockSeeds, - config: FetchAccountConfig & { programAddress?: Address } = {}, -): Promise> { - const maybeAccount = await fetchMaybeWalletBlockFromSeeds(rpc, seeds, config); - assertAccountExists(maybeAccount); - return maybeAccount; -} - -export async function fetchMaybeWalletBlockFromSeeds( - rpc: Parameters[0], - seeds: WalletBlockSeeds, - config: FetchAccountConfig & { programAddress?: Address } = {}, -): Promise> { - const { programAddress, ...fetchConfig } = config; - const [address] = await findWalletBlockPda(seeds, { programAddress }); - return await fetchMaybeWalletBlock(rpc, address, fetchConfig); -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/index.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/index.ts deleted file mode 100644 index cf9bea234..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -export * from "./accounts"; -export * from "./instructions"; -export * from "./pdas"; -export * from "./programs"; diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/blockWallet.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/blockWallet.ts deleted file mode 100644 index e49ba63b7..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/blockWallet.ts +++ /dev/null @@ -1,287 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -import { - type Address, - type Codec, - combineCodec, - type Decoder, - type Encoder, - getStructDecoder, - getStructEncoder, - getU8Decoder, - getU8Encoder, - type IAccountMeta, - type IAccountSignerMeta, - type IInstruction, - type IInstructionWithAccounts, - type IInstructionWithData, - type ReadonlyAccount, - type TransactionSigner, - transformEncoder, - type WritableAccount, - type WritableSignerAccount, -} from "@solana/kit"; -import { findConfigPda } from "../pdas"; -import { BLOCK_LIST_PROGRAM_ADDRESS } from "../programs"; -import { getAccountMetaFactory, type ResolvedAccount } from "../shared"; - -export const BLOCK_WALLET_DISCRIMINATOR = 242; - -export function getBlockWalletDiscriminatorBytes() { - return getU8Encoder().encode(BLOCK_WALLET_DISCRIMINATOR); -} - -export type BlockWalletInstruction< - TProgram extends string = typeof BLOCK_LIST_PROGRAM_ADDRESS, - TAccountAuthority extends string | IAccountMeta = string, - TAccountConfig extends string | IAccountMeta = string, - TAccountWallet extends string | IAccountMeta = string, - TAccountWalletBlock extends string | IAccountMeta = string, - TAccountSystemProgram extends string | IAccountMeta = "11111111111111111111111111111111", - TRemainingAccounts extends readonly IAccountMeta[] = [], -> = IInstruction & - IInstructionWithData & - IInstructionWithAccounts< - [ - TAccountAuthority extends string - ? WritableSignerAccount & IAccountSignerMeta - : TAccountAuthority, - TAccountConfig extends string ? WritableAccount : TAccountConfig, - TAccountWallet extends string ? ReadonlyAccount : TAccountWallet, - TAccountWalletBlock extends string ? WritableAccount : TAccountWalletBlock, - TAccountSystemProgram extends string ? ReadonlyAccount : TAccountSystemProgram, - ...TRemainingAccounts, - ] - >; - -export type BlockWalletInstructionData = { discriminator: number }; - -export type BlockWalletInstructionDataArgs = Record; - -export function getBlockWalletInstructionDataEncoder(): Encoder { - return transformEncoder(getStructEncoder([["discriminator", getU8Encoder()]]), (value) => ({ - ...value, - discriminator: 242, - })); -} - -export function getBlockWalletInstructionDataDecoder(): Decoder { - return getStructDecoder([["discriminator", getU8Decoder()]]); -} - -export function getBlockWalletInstructionDataCodec(): Codec< - BlockWalletInstructionDataArgs, - BlockWalletInstructionData -> { - return combineCodec(getBlockWalletInstructionDataEncoder(), getBlockWalletInstructionDataDecoder()); -} - -export type BlockWalletAsyncInput< - TAccountAuthority extends string = string, - TAccountConfig extends string = string, - TAccountWallet extends string = string, - TAccountWalletBlock extends string = string, - TAccountSystemProgram extends string = string, -> = { - authority: TransactionSigner; - config?: Address; - wallet: Address; - walletBlock: Address; - systemProgram?: Address; -}; - -export async function getBlockWalletInstructionAsync< - TAccountAuthority extends string, - TAccountConfig extends string, - TAccountWallet extends string, - TAccountWalletBlock extends string, - TAccountSystemProgram extends string, - TProgramAddress extends Address = typeof BLOCK_LIST_PROGRAM_ADDRESS, ->( - input: BlockWalletAsyncInput< - TAccountAuthority, - TAccountConfig, - TAccountWallet, - TAccountWalletBlock, - TAccountSystemProgram - >, - config?: { programAddress?: TProgramAddress }, -): Promise< - BlockWalletInstruction< - TProgramAddress, - TAccountAuthority, - TAccountConfig, - TAccountWallet, - TAccountWalletBlock, - TAccountSystemProgram - > -> { - // Program address. - const programAddress = config?.programAddress ?? BLOCK_LIST_PROGRAM_ADDRESS; - - // Original accounts. - const originalAccounts = { - authority: { value: input.authority ?? null, isWritable: true }, - config: { value: input.config ?? null, isWritable: true }, - wallet: { value: input.wallet ?? null, isWritable: false }, - walletBlock: { value: input.walletBlock ?? null, isWritable: true }, - systemProgram: { value: input.systemProgram ?? null, isWritable: false }, - }; - const accounts = originalAccounts as Record; - - // Resolve default values. - if (!accounts.config.value) { - accounts.config.value = await findConfigPda(); - } - if (!accounts.systemProgram.value) { - accounts.systemProgram.value = "11111111111111111111111111111111" as Address<"11111111111111111111111111111111">; - } - - const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); - const instruction = { - accounts: [ - getAccountMeta(accounts.authority), - getAccountMeta(accounts.config), - getAccountMeta(accounts.wallet), - getAccountMeta(accounts.walletBlock), - getAccountMeta(accounts.systemProgram), - ], - programAddress, - data: getBlockWalletInstructionDataEncoder().encode({}), - } as BlockWalletInstruction< - TProgramAddress, - TAccountAuthority, - TAccountConfig, - TAccountWallet, - TAccountWalletBlock, - TAccountSystemProgram - >; - - return instruction; -} - -export type BlockWalletInput< - TAccountAuthority extends string = string, - TAccountConfig extends string = string, - TAccountWallet extends string = string, - TAccountWalletBlock extends string = string, - TAccountSystemProgram extends string = string, -> = { - authority: TransactionSigner; - config: Address; - wallet: Address; - walletBlock: Address; - systemProgram?: Address; -}; - -export function getBlockWalletInstruction< - TAccountAuthority extends string, - TAccountConfig extends string, - TAccountWallet extends string, - TAccountWalletBlock extends string, - TAccountSystemProgram extends string, - TProgramAddress extends Address = typeof BLOCK_LIST_PROGRAM_ADDRESS, ->( - input: BlockWalletInput< - TAccountAuthority, - TAccountConfig, - TAccountWallet, - TAccountWalletBlock, - TAccountSystemProgram - >, - config?: { programAddress?: TProgramAddress }, -): BlockWalletInstruction< - TProgramAddress, - TAccountAuthority, - TAccountConfig, - TAccountWallet, - TAccountWalletBlock, - TAccountSystemProgram -> { - // Program address. - const programAddress = config?.programAddress ?? BLOCK_LIST_PROGRAM_ADDRESS; - - // Original accounts. - const originalAccounts = { - authority: { value: input.authority ?? null, isWritable: true }, - config: { value: input.config ?? null, isWritable: true }, - wallet: { value: input.wallet ?? null, isWritable: false }, - walletBlock: { value: input.walletBlock ?? null, isWritable: true }, - systemProgram: { value: input.systemProgram ?? null, isWritable: false }, - }; - const accounts = originalAccounts as Record; - - // Resolve default values. - if (!accounts.systemProgram.value) { - accounts.systemProgram.value = "11111111111111111111111111111111" as Address<"11111111111111111111111111111111">; - } - - const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); - const instruction = { - accounts: [ - getAccountMeta(accounts.authority), - getAccountMeta(accounts.config), - getAccountMeta(accounts.wallet), - getAccountMeta(accounts.walletBlock), - getAccountMeta(accounts.systemProgram), - ], - programAddress, - data: getBlockWalletInstructionDataEncoder().encode({}), - } as BlockWalletInstruction< - TProgramAddress, - TAccountAuthority, - TAccountConfig, - TAccountWallet, - TAccountWalletBlock, - TAccountSystemProgram - >; - - return instruction; -} - -export type ParsedBlockWalletInstruction< - TProgram extends string = typeof BLOCK_LIST_PROGRAM_ADDRESS, - TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], -> = { - programAddress: Address; - accounts: { - authority: TAccountMetas[0]; - config: TAccountMetas[1]; - wallet: TAccountMetas[2]; - walletBlock: TAccountMetas[3]; - systemProgram: TAccountMetas[4]; - }; - data: BlockWalletInstructionData; -}; - -export function parseBlockWalletInstruction( - instruction: IInstruction & IInstructionWithAccounts & IInstructionWithData, -): ParsedBlockWalletInstruction { - if (instruction.accounts.length < 5) { - // TODO: Coded error. - throw new Error("Not enough accounts"); - } - let accountIndex = 0; - const getNextAccount = () => { - const accountMeta = instruction.accounts[accountIndex]; - accountIndex += 1; - return accountMeta; - }; - return { - programAddress: instruction.programAddress, - accounts: { - authority: getNextAccount(), - config: getNextAccount(), - wallet: getNextAccount(), - walletBlock: getNextAccount(), - systemProgram: getNextAccount(), - }, - data: getBlockWalletInstructionDataDecoder().decode(instruction.data), - }; -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/index.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/index.ts deleted file mode 100644 index f4bba88c5..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -export * from "./blockWallet"; -export * from "./init"; -export * from "./setupExtraMetas"; -export * from "./unblockWallet"; diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/init.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/init.ts deleted file mode 100644 index 860840983..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/init.ts +++ /dev/null @@ -1,214 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -import { - type Address, - type Codec, - combineCodec, - type Decoder, - type Encoder, - getStructDecoder, - getStructEncoder, - getU8Decoder, - getU8Encoder, - type IAccountMeta, - type IAccountSignerMeta, - type IInstruction, - type IInstructionWithAccounts, - type IInstructionWithData, - type ReadonlyAccount, - type TransactionSigner, - transformEncoder, - type WritableAccount, - type WritableSignerAccount, -} from "@solana/kit"; -import { findConfigPda } from "../pdas"; -import { BLOCK_LIST_PROGRAM_ADDRESS } from "../programs"; -import { getAccountMetaFactory, type ResolvedAccount } from "../shared"; - -export const INIT_DISCRIMINATOR = 241; - -export function getInitDiscriminatorBytes() { - return getU8Encoder().encode(INIT_DISCRIMINATOR); -} - -export type InitInstruction< - TProgram extends string = typeof BLOCK_LIST_PROGRAM_ADDRESS, - TAccountAuthority extends string | IAccountMeta = string, - TAccountConfig extends string | IAccountMeta = string, - TAccountSystemProgram extends string | IAccountMeta = "11111111111111111111111111111111", - TRemainingAccounts extends readonly IAccountMeta[] = [], -> = IInstruction & - IInstructionWithData & - IInstructionWithAccounts< - [ - TAccountAuthority extends string - ? WritableSignerAccount & IAccountSignerMeta - : TAccountAuthority, - TAccountConfig extends string ? WritableAccount : TAccountConfig, - TAccountSystemProgram extends string ? ReadonlyAccount : TAccountSystemProgram, - ...TRemainingAccounts, - ] - >; - -export type InitInstructionData = { discriminator: number }; - -export type InitInstructionDataArgs = Record; - -export function getInitInstructionDataEncoder(): Encoder { - return transformEncoder(getStructEncoder([["discriminator", getU8Encoder()]]), (value) => ({ - ...value, - discriminator: 241, - })); -} - -export function getInitInstructionDataDecoder(): Decoder { - return getStructDecoder([["discriminator", getU8Decoder()]]); -} - -export function getInitInstructionDataCodec(): Codec { - return combineCodec(getInitInstructionDataEncoder(), getInitInstructionDataDecoder()); -} - -export type InitAsyncInput< - TAccountAuthority extends string = string, - TAccountConfig extends string = string, - TAccountSystemProgram extends string = string, -> = { - authority: TransactionSigner; - config?: Address; - systemProgram?: Address; -}; - -export async function getInitInstructionAsync< - TAccountAuthority extends string, - TAccountConfig extends string, - TAccountSystemProgram extends string, - TProgramAddress extends Address = typeof BLOCK_LIST_PROGRAM_ADDRESS, ->( - input: InitAsyncInput, - config?: { programAddress?: TProgramAddress }, -): Promise> { - // Program address. - const programAddress = config?.programAddress ?? BLOCK_LIST_PROGRAM_ADDRESS; - - // Original accounts. - const originalAccounts = { - authority: { value: input.authority ?? null, isWritable: true }, - config: { value: input.config ?? null, isWritable: true }, - systemProgram: { value: input.systemProgram ?? null, isWritable: false }, - }; - const accounts = originalAccounts as Record; - - // Resolve default values. - if (!accounts.config.value) { - accounts.config.value = await findConfigPda(); - } - if (!accounts.systemProgram.value) { - accounts.systemProgram.value = "11111111111111111111111111111111" as Address<"11111111111111111111111111111111">; - } - - const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); - const instruction = { - accounts: [ - getAccountMeta(accounts.authority), - getAccountMeta(accounts.config), - getAccountMeta(accounts.systemProgram), - ], - programAddress, - data: getInitInstructionDataEncoder().encode({}), - } as InitInstruction; - - return instruction; -} - -export type InitInput< - TAccountAuthority extends string = string, - TAccountConfig extends string = string, - TAccountSystemProgram extends string = string, -> = { - authority: TransactionSigner; - config: Address; - systemProgram?: Address; -}; - -export function getInitInstruction< - TAccountAuthority extends string, - TAccountConfig extends string, - TAccountSystemProgram extends string, - TProgramAddress extends Address = typeof BLOCK_LIST_PROGRAM_ADDRESS, ->( - input: InitInput, - config?: { programAddress?: TProgramAddress }, -): InitInstruction { - // Program address. - const programAddress = config?.programAddress ?? BLOCK_LIST_PROGRAM_ADDRESS; - - // Original accounts. - const originalAccounts = { - authority: { value: input.authority ?? null, isWritable: true }, - config: { value: input.config ?? null, isWritable: true }, - systemProgram: { value: input.systemProgram ?? null, isWritable: false }, - }; - const accounts = originalAccounts as Record; - - // Resolve default values. - if (!accounts.systemProgram.value) { - accounts.systemProgram.value = "11111111111111111111111111111111" as Address<"11111111111111111111111111111111">; - } - - const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); - const instruction = { - accounts: [ - getAccountMeta(accounts.authority), - getAccountMeta(accounts.config), - getAccountMeta(accounts.systemProgram), - ], - programAddress, - data: getInitInstructionDataEncoder().encode({}), - } as InitInstruction; - - return instruction; -} - -export type ParsedInitInstruction< - TProgram extends string = typeof BLOCK_LIST_PROGRAM_ADDRESS, - TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], -> = { - programAddress: Address; - accounts: { - authority: TAccountMetas[0]; - config: TAccountMetas[1]; - systemProgram: TAccountMetas[2]; - }; - data: InitInstructionData; -}; - -export function parseInitInstruction( - instruction: IInstruction & IInstructionWithAccounts & IInstructionWithData, -): ParsedInitInstruction { - if (instruction.accounts.length < 3) { - // TODO: Coded error. - throw new Error("Not enough accounts"); - } - let accountIndex = 0; - const getNextAccount = () => { - const accountMeta = instruction.accounts[accountIndex]; - accountIndex += 1; - return accountMeta; - }; - return { - programAddress: instruction.programAddress, - accounts: { - authority: getNextAccount(), - config: getNextAccount(), - systemProgram: getNextAccount(), - }, - data: getInitInstructionDataDecoder().decode(instruction.data), - }; -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/setupExtraMetas.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/setupExtraMetas.ts deleted file mode 100644 index c9004cd3b..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/setupExtraMetas.ts +++ /dev/null @@ -1,313 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -import { - type Address, - type Codec, - combineCodec, - type Decoder, - type Encoder, - getBooleanDecoder, - getBooleanEncoder, - getStructDecoder, - getStructEncoder, - getU8Decoder, - getU8Encoder, - type IAccountMeta, - type IAccountSignerMeta, - type IInstruction, - type IInstructionWithAccounts, - type IInstructionWithData, - type ReadonlyAccount, - type TransactionSigner, - transformEncoder, - type WritableAccount, - type WritableSignerAccount, -} from "@solana/kit"; -import { findConfigPda, findExtraMetasPda } from "../pdas"; -import { BLOCK_LIST_PROGRAM_ADDRESS } from "../programs"; -import { getAccountMetaFactory, type ResolvedAccount } from "../shared"; - -export const SETUP_EXTRA_METAS_DISCRIMINATOR = 106; - -export function getSetupExtraMetasDiscriminatorBytes() { - return getU8Encoder().encode(SETUP_EXTRA_METAS_DISCRIMINATOR); -} - -export type SetupExtraMetasInstruction< - TProgram extends string = typeof BLOCK_LIST_PROGRAM_ADDRESS, - TAccountAuthority extends string | IAccountMeta = string, - TAccountConfig extends string | IAccountMeta = string, - TAccountMint extends string | IAccountMeta = string, - TAccountExtraMetas extends string | IAccountMeta = string, - TAccountSystemProgram extends string | IAccountMeta = "11111111111111111111111111111111", - TRemainingAccounts extends readonly IAccountMeta[] = [], -> = IInstruction & - IInstructionWithData & - IInstructionWithAccounts< - [ - TAccountAuthority extends string - ? WritableSignerAccount & IAccountSignerMeta - : TAccountAuthority, - TAccountConfig extends string ? ReadonlyAccount : TAccountConfig, - TAccountMint extends string ? ReadonlyAccount : TAccountMint, - TAccountExtraMetas extends string ? WritableAccount : TAccountExtraMetas, - TAccountSystemProgram extends string ? ReadonlyAccount : TAccountSystemProgram, - ...TRemainingAccounts, - ] - >; - -export type SetupExtraMetasInstructionData = { - discriminator: number; - checkBothWallets: boolean; -}; - -export type SetupExtraMetasInstructionDataArgs = { checkBothWallets?: boolean }; - -export function getSetupExtraMetasInstructionDataEncoder(): Encoder { - return transformEncoder( - getStructEncoder([ - ["discriminator", getU8Encoder()], - ["checkBothWallets", getBooleanEncoder()], - ]), - (value) => ({ - ...value, - discriminator: 106, - checkBothWallets: value.checkBothWallets ?? false, - }), - ); -} - -export function getSetupExtraMetasInstructionDataDecoder(): Decoder { - return getStructDecoder([ - ["discriminator", getU8Decoder()], - ["checkBothWallets", getBooleanDecoder()], - ]); -} - -export function getSetupExtraMetasInstructionDataCodec(): Codec< - SetupExtraMetasInstructionDataArgs, - SetupExtraMetasInstructionData -> { - return combineCodec(getSetupExtraMetasInstructionDataEncoder(), getSetupExtraMetasInstructionDataDecoder()); -} - -export type SetupExtraMetasAsyncInput< - TAccountAuthority extends string = string, - TAccountConfig extends string = string, - TAccountMint extends string = string, - TAccountExtraMetas extends string = string, - TAccountSystemProgram extends string = string, -> = { - authority: TransactionSigner; - config?: Address; - mint: Address; - extraMetas?: Address; - systemProgram?: Address; - checkBothWallets?: SetupExtraMetasInstructionDataArgs["checkBothWallets"]; -}; - -export async function getSetupExtraMetasInstructionAsync< - TAccountAuthority extends string, - TAccountConfig extends string, - TAccountMint extends string, - TAccountExtraMetas extends string, - TAccountSystemProgram extends string, - TProgramAddress extends Address = typeof BLOCK_LIST_PROGRAM_ADDRESS, ->( - input: SetupExtraMetasAsyncInput< - TAccountAuthority, - TAccountConfig, - TAccountMint, - TAccountExtraMetas, - TAccountSystemProgram - >, - config?: { programAddress?: TProgramAddress }, -): Promise< - SetupExtraMetasInstruction< - TProgramAddress, - TAccountAuthority, - TAccountConfig, - TAccountMint, - TAccountExtraMetas, - TAccountSystemProgram - > -> { - // Program address. - const programAddress = config?.programAddress ?? BLOCK_LIST_PROGRAM_ADDRESS; - - // Original accounts. - const originalAccounts = { - authority: { value: input.authority ?? null, isWritable: true }, - config: { value: input.config ?? null, isWritable: false }, - mint: { value: input.mint ?? null, isWritable: false }, - extraMetas: { value: input.extraMetas ?? null, isWritable: true }, - systemProgram: { value: input.systemProgram ?? null, isWritable: false }, - }; - const accounts = originalAccounts as Record; - - // Original args. - const args = { ...input }; - - // Resolve default values. - if (!accounts.config.value) { - accounts.config.value = await findConfigPda(); - } - if (!accounts.extraMetas.value) { - accounts.extraMetas.value = await findExtraMetasPda(); - } - if (!accounts.systemProgram.value) { - accounts.systemProgram.value = "11111111111111111111111111111111" as Address<"11111111111111111111111111111111">; - } - - const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); - const instruction = { - accounts: [ - getAccountMeta(accounts.authority), - getAccountMeta(accounts.config), - getAccountMeta(accounts.mint), - getAccountMeta(accounts.extraMetas), - getAccountMeta(accounts.systemProgram), - ], - programAddress, - data: getSetupExtraMetasInstructionDataEncoder().encode(args as SetupExtraMetasInstructionDataArgs), - } as SetupExtraMetasInstruction< - TProgramAddress, - TAccountAuthority, - TAccountConfig, - TAccountMint, - TAccountExtraMetas, - TAccountSystemProgram - >; - - return instruction; -} - -export type SetupExtraMetasInput< - TAccountAuthority extends string = string, - TAccountConfig extends string = string, - TAccountMint extends string = string, - TAccountExtraMetas extends string = string, - TAccountSystemProgram extends string = string, -> = { - authority: TransactionSigner; - config: Address; - mint: Address; - extraMetas: Address; - systemProgram?: Address; - checkBothWallets?: SetupExtraMetasInstructionDataArgs["checkBothWallets"]; -}; - -export function getSetupExtraMetasInstruction< - TAccountAuthority extends string, - TAccountConfig extends string, - TAccountMint extends string, - TAccountExtraMetas extends string, - TAccountSystemProgram extends string, - TProgramAddress extends Address = typeof BLOCK_LIST_PROGRAM_ADDRESS, ->( - input: SetupExtraMetasInput< - TAccountAuthority, - TAccountConfig, - TAccountMint, - TAccountExtraMetas, - TAccountSystemProgram - >, - config?: { programAddress?: TProgramAddress }, -): SetupExtraMetasInstruction< - TProgramAddress, - TAccountAuthority, - TAccountConfig, - TAccountMint, - TAccountExtraMetas, - TAccountSystemProgram -> { - // Program address. - const programAddress = config?.programAddress ?? BLOCK_LIST_PROGRAM_ADDRESS; - - // Original accounts. - const originalAccounts = { - authority: { value: input.authority ?? null, isWritable: true }, - config: { value: input.config ?? null, isWritable: false }, - mint: { value: input.mint ?? null, isWritable: false }, - extraMetas: { value: input.extraMetas ?? null, isWritable: true }, - systemProgram: { value: input.systemProgram ?? null, isWritable: false }, - }; - const accounts = originalAccounts as Record; - - // Original args. - const args = { ...input }; - - // Resolve default values. - if (!accounts.systemProgram.value) { - accounts.systemProgram.value = "11111111111111111111111111111111" as Address<"11111111111111111111111111111111">; - } - - const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); - const instruction = { - accounts: [ - getAccountMeta(accounts.authority), - getAccountMeta(accounts.config), - getAccountMeta(accounts.mint), - getAccountMeta(accounts.extraMetas), - getAccountMeta(accounts.systemProgram), - ], - programAddress, - data: getSetupExtraMetasInstructionDataEncoder().encode(args as SetupExtraMetasInstructionDataArgs), - } as SetupExtraMetasInstruction< - TProgramAddress, - TAccountAuthority, - TAccountConfig, - TAccountMint, - TAccountExtraMetas, - TAccountSystemProgram - >; - - return instruction; -} - -export type ParsedSetupExtraMetasInstruction< - TProgram extends string = typeof BLOCK_LIST_PROGRAM_ADDRESS, - TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], -> = { - programAddress: Address; - accounts: { - authority: TAccountMetas[0]; - config: TAccountMetas[1]; - mint: TAccountMetas[2]; - extraMetas: TAccountMetas[3]; - systemProgram: TAccountMetas[4]; - }; - data: SetupExtraMetasInstructionData; -}; - -export function parseSetupExtraMetasInstruction( - instruction: IInstruction & IInstructionWithAccounts & IInstructionWithData, -): ParsedSetupExtraMetasInstruction { - if (instruction.accounts.length < 5) { - // TODO: Coded error. - throw new Error("Not enough accounts"); - } - let accountIndex = 0; - const getNextAccount = () => { - const accountMeta = instruction.accounts[accountIndex]; - accountIndex += 1; - return accountMeta; - }; - return { - programAddress: instruction.programAddress, - accounts: { - authority: getNextAccount(), - config: getNextAccount(), - mint: getNextAccount(), - extraMetas: getNextAccount(), - systemProgram: getNextAccount(), - }, - data: getSetupExtraMetasInstructionDataDecoder().decode(instruction.data), - }; -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/unblockWallet.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/unblockWallet.ts deleted file mode 100644 index 40b3f33f0..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/instructions/unblockWallet.ts +++ /dev/null @@ -1,257 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -import { - type Address, - type Codec, - combineCodec, - type Decoder, - type Encoder, - getStructDecoder, - getStructEncoder, - getU8Decoder, - getU8Encoder, - type IAccountMeta, - type IAccountSignerMeta, - type IInstruction, - type IInstructionWithAccounts, - type IInstructionWithData, - type ReadonlyAccount, - type TransactionSigner, - transformEncoder, - type WritableAccount, - type WritableSignerAccount, -} from "@solana/kit"; -import { findConfigPda } from "../pdas"; -import { BLOCK_LIST_PROGRAM_ADDRESS } from "../programs"; -import { getAccountMetaFactory, type ResolvedAccount } from "../shared"; - -export const UNBLOCK_WALLET_DISCRIMINATOR = 243; - -export function getUnblockWalletDiscriminatorBytes() { - return getU8Encoder().encode(UNBLOCK_WALLET_DISCRIMINATOR); -} - -export type UnblockWalletInstruction< - TProgram extends string = typeof BLOCK_LIST_PROGRAM_ADDRESS, - TAccountAuthority extends string | IAccountMeta = string, - TAccountConfig extends string | IAccountMeta = string, - TAccountWalletBlock extends string | IAccountMeta = string, - TAccountSystemProgram extends string | IAccountMeta = "11111111111111111111111111111111", - TRemainingAccounts extends readonly IAccountMeta[] = [], -> = IInstruction & - IInstructionWithData & - IInstructionWithAccounts< - [ - TAccountAuthority extends string - ? WritableSignerAccount & IAccountSignerMeta - : TAccountAuthority, - TAccountConfig extends string ? WritableAccount : TAccountConfig, - TAccountWalletBlock extends string ? WritableAccount : TAccountWalletBlock, - TAccountSystemProgram extends string ? ReadonlyAccount : TAccountSystemProgram, - ...TRemainingAccounts, - ] - >; - -export type UnblockWalletInstructionData = { discriminator: number }; - -export type UnblockWalletInstructionDataArgs = Record; - -export function getUnblockWalletInstructionDataEncoder(): Encoder { - return transformEncoder(getStructEncoder([["discriminator", getU8Encoder()]]), (value) => ({ - ...value, - discriminator: 243, - })); -} - -export function getUnblockWalletInstructionDataDecoder(): Decoder { - return getStructDecoder([["discriminator", getU8Decoder()]]); -} - -export function getUnblockWalletInstructionDataCodec(): Codec< - UnblockWalletInstructionDataArgs, - UnblockWalletInstructionData -> { - return combineCodec(getUnblockWalletInstructionDataEncoder(), getUnblockWalletInstructionDataDecoder()); -} - -export type UnblockWalletAsyncInput< - TAccountAuthority extends string = string, - TAccountConfig extends string = string, - TAccountWalletBlock extends string = string, - TAccountSystemProgram extends string = string, -> = { - authority: TransactionSigner; - config?: Address; - walletBlock: Address; - systemProgram?: Address; -}; - -export async function getUnblockWalletInstructionAsync< - TAccountAuthority extends string, - TAccountConfig extends string, - TAccountWalletBlock extends string, - TAccountSystemProgram extends string, - TProgramAddress extends Address = typeof BLOCK_LIST_PROGRAM_ADDRESS, ->( - input: UnblockWalletAsyncInput, - config?: { programAddress?: TProgramAddress }, -): Promise< - UnblockWalletInstruction< - TProgramAddress, - TAccountAuthority, - TAccountConfig, - TAccountWalletBlock, - TAccountSystemProgram - > -> { - // Program address. - const programAddress = config?.programAddress ?? BLOCK_LIST_PROGRAM_ADDRESS; - - // Original accounts. - const originalAccounts = { - authority: { value: input.authority ?? null, isWritable: true }, - config: { value: input.config ?? null, isWritable: true }, - walletBlock: { value: input.walletBlock ?? null, isWritable: true }, - systemProgram: { value: input.systemProgram ?? null, isWritable: false }, - }; - const accounts = originalAccounts as Record; - - // Resolve default values. - if (!accounts.config.value) { - accounts.config.value = await findConfigPda(); - } - if (!accounts.systemProgram.value) { - accounts.systemProgram.value = "11111111111111111111111111111111" as Address<"11111111111111111111111111111111">; - } - - const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); - const instruction = { - accounts: [ - getAccountMeta(accounts.authority), - getAccountMeta(accounts.config), - getAccountMeta(accounts.walletBlock), - getAccountMeta(accounts.systemProgram), - ], - programAddress, - data: getUnblockWalletInstructionDataEncoder().encode({}), - } as UnblockWalletInstruction< - TProgramAddress, - TAccountAuthority, - TAccountConfig, - TAccountWalletBlock, - TAccountSystemProgram - >; - - return instruction; -} - -export type UnblockWalletInput< - TAccountAuthority extends string = string, - TAccountConfig extends string = string, - TAccountWalletBlock extends string = string, - TAccountSystemProgram extends string = string, -> = { - authority: TransactionSigner; - config: Address; - walletBlock: Address; - systemProgram?: Address; -}; - -export function getUnblockWalletInstruction< - TAccountAuthority extends string, - TAccountConfig extends string, - TAccountWalletBlock extends string, - TAccountSystemProgram extends string, - TProgramAddress extends Address = typeof BLOCK_LIST_PROGRAM_ADDRESS, ->( - input: UnblockWalletInput, - config?: { programAddress?: TProgramAddress }, -): UnblockWalletInstruction< - TProgramAddress, - TAccountAuthority, - TAccountConfig, - TAccountWalletBlock, - TAccountSystemProgram -> { - // Program address. - const programAddress = config?.programAddress ?? BLOCK_LIST_PROGRAM_ADDRESS; - - // Original accounts. - const originalAccounts = { - authority: { value: input.authority ?? null, isWritable: true }, - config: { value: input.config ?? null, isWritable: true }, - walletBlock: { value: input.walletBlock ?? null, isWritable: true }, - systemProgram: { value: input.systemProgram ?? null, isWritable: false }, - }; - const accounts = originalAccounts as Record; - - // Resolve default values. - if (!accounts.systemProgram.value) { - accounts.systemProgram.value = "11111111111111111111111111111111" as Address<"11111111111111111111111111111111">; - } - - const getAccountMeta = getAccountMetaFactory(programAddress, "programId"); - const instruction = { - accounts: [ - getAccountMeta(accounts.authority), - getAccountMeta(accounts.config), - getAccountMeta(accounts.walletBlock), - getAccountMeta(accounts.systemProgram), - ], - programAddress, - data: getUnblockWalletInstructionDataEncoder().encode({}), - } as UnblockWalletInstruction< - TProgramAddress, - TAccountAuthority, - TAccountConfig, - TAccountWalletBlock, - TAccountSystemProgram - >; - - return instruction; -} - -export type ParsedUnblockWalletInstruction< - TProgram extends string = typeof BLOCK_LIST_PROGRAM_ADDRESS, - TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], -> = { - programAddress: Address; - accounts: { - authority: TAccountMetas[0]; - config: TAccountMetas[1]; - walletBlock: TAccountMetas[2]; - systemProgram: TAccountMetas[3]; - }; - data: UnblockWalletInstructionData; -}; - -export function parseUnblockWalletInstruction( - instruction: IInstruction & IInstructionWithAccounts & IInstructionWithData, -): ParsedUnblockWalletInstruction { - if (instruction.accounts.length < 4) { - // TODO: Coded error. - throw new Error("Not enough accounts"); - } - let accountIndex = 0; - const getNextAccount = () => { - const accountMeta = instruction.accounts[accountIndex]; - accountIndex += 1; - return accountMeta; - }; - return { - programAddress: instruction.programAddress, - accounts: { - authority: getNextAccount(), - config: getNextAccount(), - walletBlock: getNextAccount(), - systemProgram: getNextAccount(), - }, - data: getUnblockWalletInstructionDataDecoder().decode(instruction.data), - }; -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/pdas/config.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/pdas/config.ts deleted file mode 100644 index be83310f6..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/pdas/config.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -import { type Address, getProgramDerivedAddress, getUtf8Encoder, type ProgramDerivedAddress } from "@solana/kit"; - -export async function findConfigPda( - config: { programAddress?: Address | undefined } = {}, -): Promise { - const { - programAddress = "BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf" as Address<"BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf">, - } = config; - return await getProgramDerivedAddress({ - programAddress, - seeds: [getUtf8Encoder().encode("config")], - }); -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/pdas/extraMetas.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/pdas/extraMetas.ts deleted file mode 100644 index db4a84483..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/pdas/extraMetas.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -import { - type Address, - getAddressEncoder, - getProgramDerivedAddress, - getUtf8Encoder, - type ProgramDerivedAddress, -} from "@solana/kit"; - -export type ExtraMetasSeeds = { - mint: Address; -}; - -export async function findExtraMetasPda( - seeds: ExtraMetasSeeds, - config: { programAddress?: Address | undefined } = {}, -): Promise { - const { - programAddress = "BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf" as Address<"BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf">, - } = config; - return await getProgramDerivedAddress({ - programAddress, - seeds: [getUtf8Encoder().encode("extra-account-metas"), getAddressEncoder().encode(seeds.mint)], - }); -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/pdas/index.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/pdas/index.ts deleted file mode 100644 index 6135b7a67..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/pdas/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -export * from "./config"; -export * from "./extraMetas"; -export * from "./walletBlock"; diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/pdas/walletBlock.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/pdas/walletBlock.ts deleted file mode 100644 index 2705fbcef..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/pdas/walletBlock.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -import { - type Address, - getAddressEncoder, - getProgramDerivedAddress, - getUtf8Encoder, - type ProgramDerivedAddress, -} from "@solana/kit"; - -export type WalletBlockSeeds = { - wallet: Address; -}; - -export async function findWalletBlockPda( - seeds: WalletBlockSeeds, - config: { programAddress?: Address | undefined } = {}, -): Promise { - const { - programAddress = "BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf" as Address<"BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf">, - } = config; - return await getProgramDerivedAddress({ - programAddress, - seeds: [getUtf8Encoder().encode("wallet_block"), getAddressEncoder().encode(seeds.wallet)], - }); -} diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/programs/blockList.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/programs/blockList.ts deleted file mode 100644 index d22c9ccf9..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/programs/blockList.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -import { type Address, containsBytes, getU8Encoder, type ReadonlyUint8Array } from "@solana/kit"; -import type { - ParsedBlockWalletInstruction, - ParsedInitInstruction, - ParsedSetupExtraMetasInstruction, - ParsedUnblockWalletInstruction, -} from "../instructions"; - -export const BLOCK_LIST_PROGRAM_ADDRESS = - "BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf" as Address<"BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf">; - -export enum BlockListAccount { - Config, - WalletBlock, - ExtraMetas, -} - -export function identifyBlockListAccount(account: { data: ReadonlyUint8Array } | ReadonlyUint8Array): BlockListAccount { - const data = "data" in account ? account.data : account; - if (containsBytes(data, getU8Encoder().encode(0), 0)) { - return BlockListAccount.Config; - } - if (containsBytes(data, getU8Encoder().encode(1), 0)) { - return BlockListAccount.WalletBlock; - } - throw new Error("The provided account could not be identified as a blockList account."); -} - -export enum BlockListInstruction { - Init, - BlockWallet, - UnblockWallet, - SetupExtraMetas, -} - -export function identifyBlockListInstruction( - instruction: { data: ReadonlyUint8Array } | ReadonlyUint8Array, -): BlockListInstruction { - const data = "data" in instruction ? instruction.data : instruction; - if (containsBytes(data, getU8Encoder().encode(241), 0)) { - return BlockListInstruction.Init; - } - if (containsBytes(data, getU8Encoder().encode(242), 0)) { - return BlockListInstruction.BlockWallet; - } - if (containsBytes(data, getU8Encoder().encode(243), 0)) { - return BlockListInstruction.UnblockWallet; - } - if (containsBytes(data, getU8Encoder().encode(106), 0)) { - return BlockListInstruction.SetupExtraMetas; - } - throw new Error("The provided instruction could not be identified as a blockList instruction."); -} - -export type ParsedBlockListInstruction = - | ({ - instructionType: BlockListInstruction.Init; - } & ParsedInitInstruction) - | ({ - instructionType: BlockListInstruction.BlockWallet; - } & ParsedBlockWalletInstruction) - | ({ - instructionType: BlockListInstruction.UnblockWallet; - } & ParsedUnblockWalletInstruction) - | ({ - instructionType: BlockListInstruction.SetupExtraMetas; - } & ParsedSetupExtraMetasInstruction); diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/programs/index.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/programs/index.ts deleted file mode 100644 index 01ddfbdee..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/programs/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -export * from "./blockList"; diff --git a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/shared/index.ts b/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/shared/index.ts deleted file mode 100644 index 11ef6d72e..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/pinocchio/sdk/ts/src/shared/index.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * This code was AUTOGENERATED using the codama library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun codama to update it. - * - * @see https://github.com/codama-idl/codama - */ - -import { - AccountRole, - type Address, - type IAccountMeta, - type IAccountSignerMeta, - isProgramDerivedAddress, - isTransactionSigner as kitIsTransactionSigner, - type ProgramDerivedAddress, - type TransactionSigner, - upgradeRoleToSigner, -} from "@solana/kit"; - -/** - * Asserts that the given value is not null or undefined. - * @internal - */ -export function expectSome(value: T | null | undefined): T { - if (value == null) { - throw new Error("Expected a value but received null or undefined."); - } - return value; -} - -/** - * Asserts that the given value is a PublicKey. - * @internal - */ -export function expectAddress( - value: Address | ProgramDerivedAddress | TransactionSigner | null | undefined, -): Address { - if (!value) { - throw new Error("Expected a Address."); - } - if (typeof value === "object" && "address" in value) { - return value.address; - } - if (Array.isArray(value)) { - return value[0]; - } - return value as Address; -} - -/** - * Asserts that the given value is a PDA. - * @internal - */ -export function expectProgramDerivedAddress( - value: Address | ProgramDerivedAddress | TransactionSigner | null | undefined, -): ProgramDerivedAddress { - if (!value || !Array.isArray(value) || !isProgramDerivedAddress(value)) { - throw new Error("Expected a ProgramDerivedAddress."); - } - return value; -} - -/** - * Asserts that the given value is a TransactionSigner. - * @internal - */ -export function expectTransactionSigner( - value: Address | ProgramDerivedAddress | TransactionSigner | null | undefined, -): TransactionSigner { - if (!value || !isTransactionSigner(value)) { - throw new Error("Expected a TransactionSigner."); - } - return value; -} - -/** - * Defines an instruction account to resolve. - * @internal - */ -export type ResolvedAccount< - T extends string = string, - U extends Address | ProgramDerivedAddress | TransactionSigner | null = - | Address - | ProgramDerivedAddress - | TransactionSigner - | null, -> = { - isWritable: boolean; - value: U; -}; - -/** - * Defines an instruction that stores additional bytes onchain. - * @internal - */ -export type IInstructionWithByteDelta = { - byteDelta: number; -}; - -/** - * Get account metas and signers from resolved accounts. - * @internal - */ -export function getAccountMetaFactory(programAddress: Address, optionalAccountStrategy: "omitted" | "programId") { - return (account: ResolvedAccount): IAccountMeta | IAccountSignerMeta | undefined => { - if (!account.value) { - if (optionalAccountStrategy === "omitted") return; - return Object.freeze({ - address: programAddress, - role: AccountRole.READONLY, - }); - } - - const writableRole = account.isWritable ? AccountRole.WRITABLE : AccountRole.READONLY; - return Object.freeze({ - address: expectAddress(account.value), - role: isTransactionSigner(account.value) ? upgradeRoleToSigner(writableRole) : writableRole, - ...(isTransactionSigner(account.value) ? { signer: account.value } : {}), - }); - }; -} - -export function isTransactionSigner( - value: Address | ProgramDerivedAddress | TransactionSigner, -): value is TransactionSigner { - return !!value && typeof value === "object" && "address" in value && kitIsTransactionSigner(value); -} diff --git a/tokens/token-extensions/transfer-hook/block-list/readme.md b/tokens/token-extensions/transfer-hook/block-list/readme.md deleted file mode 100644 index af6281d47..000000000 --- a/tokens/token-extensions/transfer-hook/block-list/readme.md +++ /dev/null @@ -1,152 +0,0 @@ -# Block List - -A block-list [program](https://solana.com/docs/terminology#program) that implements the [Token Extensions](https://solana.com/docs/terminology#token-extensions-program) transfer-hook `execute` [instruction](https://solana.com/docs/terminology#instruction). - -A central authority maintains a block list - a collection of blocked wallets. Token issuers (transfer-hook extension authorities) can wire this program in as their hook and choose an operation mode: filter the source wallet only, or both source and destination. - -## Operation modes - -The mode depends on whether the block list is empty, plus the issuer's choice. Each mode corresponds to a different `extra-account-metas` [account](https://solana.com/docs/terminology#account) built for the [mint](https://solana.com/docs/terminology#token-mint) (see `setup_extra_metas` below). When the list goes from empty to non-empty, the issuer must call `setup_extra_metas` again. - -- **Empty extra metas** - default when the config counter is 0. -- **Check source** - default when the config counter is > 0. -- **Check both source and destination** - optional behavior when the counter is > 0. - -## Accounts - -### `Config` - -- Defines the block-list authority. -- Tracks the number of blocked wallets. - -### `WalletBlock` - -- Marks a single wallet as blocked. - -## Instruction handlers - -The program dispatches on a one-byte discriminator in `process_instruction` ([`program/src/lib.rs`](pinocchio/program/src/lib.rs)): - -### `Init` - -Initializes the global `Config` account with an authority. - -### `BlockWallet` - -Adds a wallet to the block list, creating a `WalletBlock` record. - -### `UnblockWallet` - -Removes a wallet from the block list, closing its `WalletBlock` record. - -### `SetupExtraMetas` - -Sets up the `extra-account-metas` account that the transfer-hook extension depends on. Takes an optional bool to switch operation modes when the counter is non-zero. - -Once wallets are added to the block list, the issuer must call this again to pick one of the blocking modes. - -### `TxHook` - -The hook invoked during token transfers. - -## Repository layout - -- **Program:** a Pinocchio-based block list under [`pinocchio/program/`](pinocchio/program/). -- **SDKs:** Codama-generated Rust and TypeScript SDKs under [`pinocchio/sdk/`](pinocchio/sdk/). -- **CLI:** a Rust CLI to interact with the program, under [`pinocchio/cli/`](pinocchio/cli/). - -## Building - -All commands below should be run from the [`pinocchio/`](pinocchio/) directory. - -Install dependencies: - -```bash -cd pinocchio -pnpm install -``` - -Build the program: - -```bash -cd program -cargo build-sbf -``` - -Deploy it: - -```bash -solana program deploy --program-id target/deploy/block_list.so -``` - -Generate the SDKs: - -```bash -pnpm run generate-sdks -``` - -Build the CLI: - -```bash -cd cli -cargo build -``` - -## Testing - -The tests are Rust + LiteSVM, in [`pinocchio/program/tests/`](pinocchio/program/tests/). Build the program first - the tests embed the `.so` at compile time, so rebuild after every program change or a stale binary silently tests old code: - -```bash -cargo build-sbf --manifest-path=./program/Cargo.toml -cargo test --manifest-path=./program/Cargo.toml -``` - -## Setup - -### Block list - -Initialize the list and set the authority: - -```bash -target/debug/block-list-cli init -``` - -Add a wallet: - -```bash -target/debug/block-list-cli block-wallet -``` - -Remove a wallet: - -```bash -target/debug/block-list-cli unblock-wallet -``` - -### Token mint - -The `spl-token` CLI references the Token Extensions program as `--program-2022`. Create a new mint with the hook wired up: - -```bash -spl-token create-token --program-2022 --transfer-hook BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf -``` - -Initialize the extra account metas: - -```bash -target/debug/block-list-cli setup-extra-metas -``` - -Switch to checking both source and destination wallets: - -```bash -target/debug/block-list-cli setup-extra-metas --check-both-wallets -``` - -## Devnet deployment - -The `declare_id!` in [`pinocchio/program/src/lib.rs`](pinocchio/program/src/lib.rs) is `BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf`. Whether this address is currently deployed on devnet is not tracked in this repo - verify with `solana program show BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf --url devnet`. - -## Disclaimer - -This code has not been audited or reviewed. Use at your own discretion. diff --git a/tokens/token-extensions/transfer-hook/whitelist/README.md b/tokens/token-extensions/transfer-hook/whitelist/README.md new file mode 100644 index 000000000..20b8a3c3e --- /dev/null +++ b/tokens/token-extensions/transfer-hook/whitelist/README.md @@ -0,0 +1,9 @@ +# Transfer Hook - Whitelist has moved + +This example is now [Transfer Hook - Allow List](../allow-list/), in all three variants: + +- [Anchor v1](../allow-list/anchor-v1/) +- [Anchor v2](../allow-list/anchor/) +- [Quasar](../allow-list/quasar/) + +The new version has no size limit: each allowed wallet is its own account, rather than one entry in a single account capped at eleven. Each mint has its own allow list, which only the mint's authority can create.