From b75ee2be511bc9f15decc224d2376290b18a4a60 Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 24 Jan 2024 17:33:11 -0600 Subject: [PATCH 1/3] docs: transfer hook interface --- docs/sidebars.js | 11 + docs/src/token-2022/extensions.mdx | 5 +- .../configuring-extra-accounts.md | 178 ++++++++++++++ docs/src/transfer-hook-interface/examples.md | 223 ++++++++++++++++++ .../transfer-hook-interface/introduction.md | 28 +++ .../transfer-hook-interface/specification.md | 72 ++++++ 6 files changed, 515 insertions(+), 2 deletions(-) create mode 100644 docs/src/transfer-hook-interface/configuring-extra-accounts.md create mode 100644 docs/src/transfer-hook-interface/examples.md create mode 100644 docs/src/transfer-hook-interface/introduction.md create mode 100644 docs/src/transfer-hook-interface/specification.md diff --git a/docs/sidebars.js b/docs/sidebars.js index c8b4771e2c3..fac605c71b0 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -65,5 +65,16 @@ module.exports = { "account-compression/usage", ] }, + { + type: "category", + label: "Transfer Hook Interface", + collapsed: true, + items: [ + "transfer-hook-interface/introduction", + "transfer-hook-interface/specification", + "transfer-hook-interface/configuring-extra-accounts", + "transfer-hook-interface/examples", + ] + }, ], }; diff --git a/docs/src/token-2022/extensions.mdx b/docs/src/token-2022/extensions.mdx index 864e2a47665..f88534b0ca4 100644 --- a/docs/src/token-2022/extensions.mdx +++ b/docs/src/token-2022/extensions.mdx @@ -1467,8 +1467,9 @@ harder. #### Solution -To improve the situation, Token-2022 introduces the concept of the transfer-hook -interface and extension. A token creator must develop and deploy a program that +To improve the situation, Token-2022 introduces the concept of the +[transfer-hook interface](../transfer-hook-interface/introduction) +and extension. A token creator must develop and deploy a program that implements the interface and then configure their token mint to use their program. During transfer, Token-2022 calls into the program with the accounts specified diff --git a/docs/src/transfer-hook-interface/configuring-extra-accounts.md b/docs/src/transfer-hook-interface/configuring-extra-accounts.md new file mode 100644 index 00000000000..82f9afba4c2 --- /dev/null +++ b/docs/src/transfer-hook-interface/configuring-extra-accounts.md @@ -0,0 +1,178 @@ +--- +title: Configuring Extra Accounts +--- + +As mentioned previously, programs who implement the Transfer Hook interface can +provide additional custom functionality to token transfers. However, this +functionality may require additional accounts beyond those that exist in a +transfer instruction (source, mint, destination, etc.). + +Part of the Transfer Hook interface specification is the validation account - an +account which stores configurations for additional accounts required by the +transfer hook program. + +### The Validation Account + +The validation account is a PDA off of the transfer hook program derived from +the following seeds: + +``` +"extra-account-metas" + +``` + +As you can see, one validation account maps to one mint account. This means you +can customize the additional required accounts on a per-mint basis! + +The validation account stores configurations for extra accounts using +[Type-Length-Value](https://en.wikipedia.org/wiki/Type%E2%80%93length%E2%80%93value) +(TLV) encoding: +- **Type:** The instruction discriminator, in this case `Execute` +- **Length:** The total length of the subsequent data buffer, in this case a + `u32` +- **Data:** The data itself, in this case containing the extra account + configurations + +When a transfer hook program seeks to deserialize extra account configurations +from a validation account, it can find the 8-byte instruction discriminator for +`Execute`, then read the length, then use that length to deserialize the data. + +The data itself is a list of fixed-size configuration objects serialized into a +byte slab. Because the entries are fixed-length, we can use a custom "slice" +structure which divides the length by the fixed-length to determine the number +of entries. + +This custom slice structure is called a `PodSlice` and is part of the Solana +Program Library's +[Type-Length-Value](https://github.com/solana-labs/solana-program-library/tree/master/libraries/type-length-value) +library, which is used extensively to manage serialization/deserialization of +these lists of extra accounts. + +### Dynamic Account Resolution + +When clients build transactions to either your transfer hook program directly +or to programs that will CPI to your transfer hook program, they must ensure the +instruction includes all required accounts, especially the extra required accounts +you've specified in the validation account. + +These additional accounts must be _resolved_, and another library used to pull off +the resolution of additional accounts for transfer hooks is +[TLV Account +Resolution](https://github.com/solana-labs/solana-program-library/tree/master/libraries/tlv-account-resolution). + +Using the TLV Account Resolution library, transfer hook programs can empower +**dynamic account resolution** of additional required accounts. This means that +no particular client or program needs to know the specific accounts your +transfer hook requires. Instead, they can be automatically resolved from the +validation account's data. + +In fact, the Transfer Hook interface offers helpers that perform this account +resolution in the +[onchain](https://github.com/solana-labs/solana-program-library/blob/master/token/transfer-hook/interface/src/onchain.rs) +and +[offchain](https://github.com/solana-labs/solana-program-library/blob/master/token/transfer-hook/interface/src/offchain.rs) +modules of the Transfer Hook interface crate. + +The account resolution is powered by the way configurations for additional +accounts are stored, and how they can be used to derive actual Solana addresses +and roles (signer, writeable, etc.) for accounts. + +### The `ExtraAccountMeta` Struct + +A member of the TLV Account Resolution library, the +[`ExtrAccountMeta`](https://github.com/solana-labs/solana-program-library/blob/65a92e6e0a4346920582d9b3893cacafd85bb017/libraries/tlv-account-resolution/src/account.rs#L75) +struct allows account configurations to be serialized into a fixed-length data +format of length 35 bytes. + +```rust +pub struct ExtraAccountMeta { + /// Discriminator to tell whether this represents a standard + /// `AccountMeta` or a PDA + pub discriminator: u8, + /// This `address_config` field can either be the pubkey of the account + /// or the seeds used to derive the pubkey from provided inputs + pub address_config: [u8; 32], + /// Whether the account should sign + pub is_signer: PodBool, + /// Whether the account should be writable + pub is_writable: PodBool, +} +``` + +As the documentation on the struct conveys, an `ExtraAccountMeta` can store +configurations for three types of accounts: + +|Discriminator|Account Type| +|:------------|:-----------| +|`0` | An account with a static address | +| `1` | A PDA off of the transfer hook program itself | +| `(1 << 7) + i ` | A PDA off of another program, where `i` is that program's index in the accounts list | + +`1 << 7` is the top bit of the `u8`, or `128`. If the program you are deriving +this PDA from is at index `9` of the accounts list for `Execute`, then the +discriminator for this account configuration is `128 + 9 = 137`. More on +determining this index later. + +#### Accounts With Static Addresses + +Static-address additional accounts are straightforward to serialize with +`ExtrAccountMeta`. The discriminator is simply `0` and the `address_config` is +the 32-byte public key. + +#### PDAs Off the Transfer Hook Program + +You might be wondering: "how can I store all of my PDA seeds in only 32 bytes?". +Well, you don't. Instead, you tell the account resolution functionality _where_ +to find the seeds you need. + +To do this, program can use the +[`Seed`](https://github.com/solana-labs/solana-program-library/blob/65a92e6e0a4346920582d9b3893cacafd85bb017/libraries/tlv-account-resolution/src/seeds.rs#L38) +enum to describe their seeds and where to find them. With the exception of +literals, these seed configurations comprise only a small handful of bytes. + +The following types of seeds are supported by the `Seed` enum and can be used to +create an `address_config` array of bytes. +- **Literal**: The literal seed itself encoded to bytes +- **Instruction Data:** A slice of the instruction data, denoted by the `index` + (offset) and `length` of bytes to slice +- **AccountKey:** The address of some account in the list as bytes, denoted by + the `index` at which this account can be found in the accounts list +- **Account Data:** A slice of an account's data, denoted by the `account_index` + at which this account can be found in the accounts list, as well as the + `data_index` (offset) and `length` of bytes to slice + +Here's an example of packing a list of `Seed` entries into a 32-byte +`address_config`: + +```rust +let seed1 = Seed::Literal { bytes: vec![1; 8] }; +let seed2 = Seed::InstructionData { + index: 0, + length: 4, +}; +let seed3 = Seed::AccountKey { index: 0 }; +let address_config: [u8; 32] = Seed::pack_into_address_config( + &[seed1, seed2, seed3] +)?; +``` + +#### PDAs Off Another Program + +Storing configurations for seeds for an address that is a PDA off of another +program is the same as above. However, the program whose address this account is +a PDA off of must be present in the account list. Its index in the accounts list +is required to build the proper discriminator, and thus resolve the proper PDA. + +```rust +let program_index = 7; +let seeds = &[seed1, seed2, seed3]; +let is_signer = false; +let is_writable = true; + +let extra_meta = ExtraAccountMeta::new_external_pda_with_seeds( + program_index, + seeds, + is_signer, + is_writable, +)?; +``` + diff --git a/docs/src/transfer-hook-interface/examples.md b/docs/src/transfer-hook-interface/examples.md new file mode 100644 index 00000000000..dc491b3e9fe --- /dev/null +++ b/docs/src/transfer-hook-interface/examples.md @@ -0,0 +1,223 @@ +--- +title: Examples +--- + +More examples can be found in the +[reference implementation](https://github.com/solana-labs/solana-program-library/blob/master/token/transfer-hook/example/) +and the tests within +the Transfer Hook interface and TLV Account Resolution library. + +### Initializing Extra Account Metas On-Chain + +The +[`ExtraAccountMetaList`](https://github.com/solana-labs/solana-program-library/blob/65a92e6e0a4346920582d9b3893cacafd85bb017/libraries/tlv-account-resolution/src/state.rs#L167) +struct is designed to make working with extra account +configurations as seamless as possible. + +Using `ExtraAccountMetaList::init(..)`, you can initialize a buffer with the +serialized `ExtraAccountMeta` configurations by simply providing a mutable +reference to the buffer and a slice of `ExtraAccountMeta`. The generic `T` is +the instruction whose discriminator the extra account configurations should be +assigned to. + +> Note: All instructions from the SPL Transfer Hook interface implement the +> trait +> [`SplDiscriminate`](https://github.com/solana-labs/solana-program-library/blob/65a92e6e0a4346920582d9b3893cacafd85bb017/libraries/discriminator/src/discriminator.rs#L9), +> which provides a constant 8-byte discriminator that +> can be used to create a TLV data entry. + +```rust +pub fn process_initialize_extra_account_meta_list( + program_id: &Pubkey, + accounts: &[AccountInfo], + extra_account_metas: &[ExtraAccountMeta], +) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); + + let validation_info = next_account_info(account_info_iter)?; + let mint_info = next_account_info(account_info_iter)?; + let authority_info = next_account_info(account_info_iter)?; + let _system_program_info = next_account_info(account_info_iter)?; + + // Check validation account + let (expected_validation_address, bump_seed) = + get_extra_account_metas_address_and_bump_seed(mint_info.key, program_id); + if expected_validation_address != *validation_info.key { + return Err(ProgramError::InvalidSeeds); + } + + // Create the account + let bump_seed = [bump_seed]; + let signer_seeds = collect_extra_account_metas_signer_seeds(mint_info.key, &bump_seed); + let length = extra_account_metas.len(); + let account_size = ExtraAccountMetaList::size_of(length)?; + invoke_signed( + &system_instruction::allocate(validation_info.key, account_size as u64), + &[validation_info.clone()], + &[&signer_seeds], + )?; + invoke_signed( + &system_instruction::assign(validation_info.key, program_id), + &[validation_info.clone()], + &[&signer_seeds], + )?; + + // Write the data + let mut data = validation_info.try_borrow_mut_data()?; + ExtraAccountMetaList::init::(&mut data, extra_account_metas)?; + + Ok(()) +} +``` + +After calling `ExtraAccountMetaList::init::(..)` on the +mutable account data, the account now stores all of the serialized extra account +configurations for an `Execute` instruction! + +### Resolving Extra Account Metas Off-Chain + +When building a transaction with an instruction, either for your transfer hook +program directly or for a program that will CPI to your transfer hook program, +you must include all required accounts - including the extra accounts. + +Below is an example of the logic contained in the Transfer Hook interface's +[offchain +helper](https://github.com/solana-labs/solana-program-library/blob/65a92e6e0a4346920582d9b3893cacafd85bb017/token/transfer-hook/interface/src/offchain.rs#L50). + +```rust +// You'll need to provide an "account data function", which is a function that +// can, given a `Pubkey`, return account data within an `AccountDataResult`. +// This is most likely based off of an RPC call like `getAccountInfo`. + +// Load the validation state data +let validate_state_pubkey = get_extra_account_metas_address(mint_pubkey, program_id); +let validate_state_data = fetch_account_data_fn(validate_state_pubkey) + .await? + .ok_or(ProgramError::InvalidAccountData)?; + + +// First create an `ExecuteInstruction` +let mut execute_instruction = execute( + program_id, + source_pubkey, + mint_pubkey, + destination_pubkey, + authority_pubkey, + &validate_state_pubkey, + amount, +); + +// Resolve all additional required accounts for `ExecuteInstruction` +ExtraAccountMetaList::add_to_instruction::( + &mut execute_instruction, + fetch_account_data_fn, + &validate_state_data, +) +.await?; + +// Add only the extra accounts resolved from the validation state +instruction + .accounts + .extend_from_slice(&execute_instruction.accounts[5..]); + +// Add the program id and validation state account +instruction + .accounts + .push(AccountMeta::new_readonly(*program_id, false)); +instruction + .accounts + .push(AccountMeta::new_readonly(validate_state_pubkey, false)); +``` + +As you can see from the example, an important concept to remember is which +instruction these extra accounts are for. Even though you might be building an +instruction for some other program, which may not need them, if that program is +going to CPI to your transfer hook program, it needs to have the proper +accounts. + +Additionally, in order to perform a succesful dynamic account resolution, the +proper instruction needs to be provided to align with the instruction that was +configured in the validation account - in this case the Transfer Hook +interface's `ExecuteInstruction`. This is why we first create an +`ExecuteInstruction`, then resolve the extra accounts for that instruction, and +finally add those accounts to our current instruction. + +### Resolving Extra Account Metas On-Chain for CPI + +During the execution of a program that seeks to CPI to your transfer hook +program, even though the additional required accounts were provided by the +offchain account resolution, the executing program has to know how to build a +CPI instruction with the proper accounts as well! + +Below is an example of the logic contained in the Transfer Hook interface's +[onchain +helper](https://github.com/solana-labs/solana-program-library/blob/65a92e6e0a4346920582d9b3893cacafd85bb017/token/transfer-hook/interface/src/onchain.rs#L67). + +```rust +// Find the validation account from the list of `AccountInfo`s and load its +// data +let validate_state_pubkey = get_extra_account_metas_address(mint_info.key, program_id); +let validate_state_info = account_infos + .iter() + .find(|&x| *x.key == validate_state_pubkey) + .ok_or(TransferHookError::IncorrectAccount)?; + +// Find the transfer hook program ID +let program_info = account_infos + .iter() + .find(|&x| x.key == program_id) + .ok_or(TransferHookError::IncorrectAccount)?; + +// First create an `ExecuteInstruction` +let mut execute_instruction = instruction::execute( + program_id, + source_info.key, + mint_info.key, + destination_info.key, + authority_info.key, + &validate_state_pubkey, + amount, +); +let mut execute_account_infos = vec![ + source_info, + mint_info, + destination_info, + authority_info, + validate_state_info.clone(), +]; + +// Resolve all additional required accounts for `ExecuteInstruction` +ExtraAccountMetaList::add_to_cpi_instruction::( + &mut execute_instruction, + &mut execute_account_infos, + &validate_state_info.try_borrow_data()?, + account_infos, +)?; + +// Add only the extra accounts resolved from the validation state +cpi_instruction + .accounts + .extend_from_slice(&execute_instruction.accounts[5..]); +cpi_account_infos.extend_from_slice(&execute_account_infos[5..]); + +// Add the program id and validation state account +cpi_instruction + .accounts + .push(AccountMeta::new_readonly(*program_id, false)); +cpi_instruction + .accounts + .push(AccountMeta::new_readonly(validate_state_pubkey, false)); +cpi_account_infos.push(program_info.clone()); +cpi_account_infos.push(validate_state_info.clone()); +``` + +Although this example may appear more verbose than its offchain counterpart, +it's actually doing the exact same steps, just with an instruction _and_ a list +of account infos, since CPI requires both. + +The key difference between `ExtraAccountMetaList::add_to_instruction(..)` and +`ExtraAccountMetaList::add_to_cpi_instruction(..)` is that the latter method +will find the corresponding `AccountInfo` in the list and add it to +`cpi_account_infos` at the same time as it adds the resolved `AccountMeta` to +the instruction, ensuring all resolved account keys are present in the +`AccountInfo` list. diff --git a/docs/src/transfer-hook-interface/introduction.md b/docs/src/transfer-hook-interface/introduction.md new file mode 100644 index 00000000000..3ec3a28624d --- /dev/null +++ b/docs/src/transfer-hook-interface/introduction.md @@ -0,0 +1,28 @@ +--- +title: Introduction +--- + +The Transfer Hook Interface is one of several interfaces introduced within the +Solana Program Library that can be implemented by any Solana program. + +Token-2022 implements this interface, as described in the [Transfer Hook +Extension Guide](../../token-2022/extensions#transfer-hook). Additionally, a +[reference +implementation](https://github.com/solana-labs/solana-program-library/tree/master/token/transfer-hook/example) +can be found in the SPL GitHub repository, detailing +how one might implement this interface in their own program. + +Transfer Hook is designed to allow programs to "hook" additional functionality +into token transfers. The program performing the transfer can then CPI into the +transfer hook program using the interface-defined instructions to perform the +custom functionality. + +In the case of Token-2022, the presence of a transfer hook program is assigned +to a mint using a mint extension, and this extension tells Token-2022 which +program to CPI to whenever a transfer is conducted. However, this particular +implementation is not required, and the interface can be implemented in a +variety of ways! + +With this interface, programs can compose highly customizable transfer +functionality that can be compatible with many other programs - particularly +tokens who implement the SPL Token interface. diff --git a/docs/src/transfer-hook-interface/specification.md b/docs/src/transfer-hook-interface/specification.md new file mode 100644 index 00000000000..95535b08b45 --- /dev/null +++ b/docs/src/transfer-hook-interface/specification.md @@ -0,0 +1,72 @@ +--- +title: Specification +--- + +The Transfer Hook interface specification includes two optional instructions and +one required one. + +Each instruction of the Transfer Hook interface uses a specific 8-byte +discriminator at the start of its instruction data. + +### Instruction: `Execute` + +The `Execute` instruction is required by any program who wishes to implement the +interface, and this is the instruction in which custom transfer functionality +will live. + +- **Discriminator:** First 8 bytes of the hash of the string literal + `"spl-transfer-hook-interface:execute"` +- **Data:** + - `amount: u64` - The transfer amount +- **Accounts:** + - 1 `[]`: Source + - 2 `[]`: Mint + - 3 `[]`: Destination + - 4 `[]`: Authority + - 5 `[]`: Validation account + - `n` number of additional accounts, written into the validation account + +The **validation account** is a key piece of the Transfer Hook interface, and is +covered in more detail in the [next section](./configuring-extra-accounts). In +short, it's an account whose data stores configurations that can be deserialized +to determine which additional accounts are required by the transfer hook +program. + +The next two instructions of the interface deal with these configurations. + +### (Optional) Instruction: `InitializeExtraAccountMetaList` + +This instruction does exactly what the name implies: it intializes the +validation account to store a list of extra required +[`AccountMeta`](https://docs.rs/solana-program/latest/solana_program/instruction/struct.AccountMeta.html) +configurations for the `Execute` instruction. + +- **Discriminator:** First 8 bytes of the hash of the string literal + `"spl-transfer-hook-interface:initialize-extra-account-metas"` +- **Data:** + - `extra_account_metas: Vec` - A list of extra account + configurations to be written into the validation account +- **Accounts:** + - 1 `[writable]`: Validation account + - 2 `[]`: Mint + - 3 `[signer]`: Mint authority + - 4 `[]`: System program + +### (Optional) Instruction: `UpdateExtraAccountMetaList` + +In the case that an on-chain program needs a method for updating its list of +required accounts for `Execute`, the `UpdateExtraAccountMetaList` instruction +allows for exactly that. By implementing this instruction, developers can make +updates to their list of required extra accounts stored in the validation +account. + +- **Discriminator:** First 8 bytes of the hash of the string literal + `"spl-transfer-hook-interface:update-extra-account-metas"` +- **Data:** + - `extra_account_metas: Vec` - A list of extra account + configurations to be written into the validation account +- **Accounts:** + - 1 `[writable]`: Validation account + - 2 `[]`: Mint + - 3 `[signer]`: Mint authority + From 6a74289602353886d0727ba04aec0814b5360284 Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 25 Jan 2024 13:05:48 -0600 Subject: [PATCH 2/3] address review feedback --- .../configuring-extra-accounts.md | 27 +++++++++++-------- docs/src/transfer-hook-interface/examples.md | 16 +++++------ .../transfer-hook-interface/introduction.md | 27 +++++++++---------- .../transfer-hook-interface/specification.md | 15 +++++------ 4 files changed, 44 insertions(+), 41 deletions(-) diff --git a/docs/src/transfer-hook-interface/configuring-extra-accounts.md b/docs/src/transfer-hook-interface/configuring-extra-accounts.md index 82f9afba4c2..7de29932ccb 100644 --- a/docs/src/transfer-hook-interface/configuring-extra-accounts.md +++ b/docs/src/transfer-hook-interface/configuring-extra-accounts.md @@ -43,21 +43,26 @@ of entries. This custom slice structure is called a `PodSlice` and is part of the Solana Program Library's +[Pod](https://github.com/solana-labs/solana-program-library/tree/master/libraries/pod) +library. The Pod library provides a handful of fixed-length types that +implement the `bytemuck` +[`Pod`](https://docs.rs/bytemuck/latest/bytemuck/trait.Pod.html) trait, as well +as the `PodSlice`. + +Another SPL library +useful for Type-Length-Value encoded data is [Type-Length-Value](https://github.com/solana-labs/solana-program-library/tree/master/libraries/type-length-value) -library, which is used extensively to manage serialization/deserialization of -these lists of extra accounts. +which is used extensively to manage TLV-encoded data structures. ### Dynamic Account Resolution -When clients build transactions to either your transfer hook program directly -or to programs that will CPI to your transfer hook program, they must ensure the -instruction includes all required accounts, especially the extra required accounts -you've specified in the validation account. +When clients build a transfer instruction to the token program, they must +ensure the instruction includes all required accounts, especially the extra +required accounts you've specified in the validation account. These additional accounts must be _resolved_, and another library used to pull off the resolution of additional accounts for transfer hooks is -[TLV Account -Resolution](https://github.com/solana-labs/solana-program-library/tree/master/libraries/tlv-account-resolution). +[TLV Account Resolution](https://github.com/solana-labs/solana-program-library/tree/master/libraries/tlv-account-resolution). Using the TLV Account Resolution library, transfer hook programs can empower **dynamic account resolution** of additional required accounts. This means that @@ -79,7 +84,7 @@ and roles (signer, writeable, etc.) for accounts. ### The `ExtraAccountMeta` Struct A member of the TLV Account Resolution library, the -[`ExtrAccountMeta`](https://github.com/solana-labs/solana-program-library/blob/65a92e6e0a4346920582d9b3893cacafd85bb017/libraries/tlv-account-resolution/src/account.rs#L75) +[`ExtraAccountMeta`](https://github.com/solana-labs/solana-program-library/blob/65a92e6e0a4346920582d9b3893cacafd85bb017/libraries/tlv-account-resolution/src/account.rs#L75) struct allows account configurations to be serialized into a fixed-length data format of length 35 bytes. @@ -115,7 +120,7 @@ determining this index later. #### Accounts With Static Addresses Static-address additional accounts are straightforward to serialize with -`ExtrAccountMeta`. The discriminator is simply `0` and the `address_config` is +`ExtraAccountMeta`. The discriminator is simply `0` and the `address_config` is the 32-byte public key. #### PDAs Off the Transfer Hook Program @@ -124,7 +129,7 @@ You might be wondering: "how can I store all of my PDA seeds in only 32 bytes?". Well, you don't. Instead, you tell the account resolution functionality _where_ to find the seeds you need. -To do this, program can use the +To do this, the transfer hook program can use the [`Seed`](https://github.com/solana-labs/solana-program-library/blob/65a92e6e0a4346920582d9b3893cacafd85bb017/libraries/tlv-account-resolution/src/seeds.rs#L38) enum to describe their seeds and where to find them. With the exception of literals, these seed configurations comprise only a small handful of bytes. diff --git a/docs/src/transfer-hook-interface/examples.md b/docs/src/transfer-hook-interface/examples.md index dc491b3e9fe..9ac5672d41e 100644 --- a/docs/src/transfer-hook-interface/examples.md +++ b/docs/src/transfer-hook-interface/examples.md @@ -3,9 +3,9 @@ title: Examples --- More examples can be found in the -[reference implementation](https://github.com/solana-labs/solana-program-library/blob/master/token/transfer-hook/example/) -and the tests within -the Transfer Hook interface and TLV Account Resolution library. +[Transfer Hook example tests](https://github.com/solana-labs/solana-program-library/blob/master/token/transfer-hook/example/tests/functional.rs), +as well as the +[TLV Account Resolution tests](https://github.com/solana-labs/solana-program-library/blob/master/libraries/tlv-account-resolution/src/state.rs). ### Initializing Extra Account Metas On-Chain @@ -18,7 +18,9 @@ Using `ExtraAccountMetaList::init(..)`, you can initialize a buffer with the serialized `ExtraAccountMeta` configurations by simply providing a mutable reference to the buffer and a slice of `ExtraAccountMeta`. The generic `T` is the instruction whose discriminator the extra account configurations should be -assigned to. +assigned to. In our case, this will be +[`spl_transfer_hook_interface::instruction::ExecuteInstruction`](https://github.com/solana-labs/solana-program-library/blob/eb32c5e72c6d917e732bded9863db7657b23e428/token/transfer-hook/interface/src/instruction.rs#L68) +from the Transfer Hook interface. > Note: All instructions from the SPL Transfer Hook interface implement the > trait @@ -81,8 +83,7 @@ program directly or for a program that will CPI to your transfer hook program, you must include all required accounts - including the extra accounts. Below is an example of the logic contained in the Transfer Hook interface's -[offchain -helper](https://github.com/solana-labs/solana-program-library/blob/65a92e6e0a4346920582d9b3893cacafd85bb017/token/transfer-hook/interface/src/offchain.rs#L50). +[offchain helper](https://github.com/solana-labs/solana-program-library/blob/65a92e6e0a4346920582d9b3893cacafd85bb017/token/transfer-hook/interface/src/offchain.rs#L50). ```rust // You'll need to provide an "account data function", which is a function that @@ -150,8 +151,7 @@ offchain account resolution, the executing program has to know how to build a CPI instruction with the proper accounts as well! Below is an example of the logic contained in the Transfer Hook interface's -[onchain -helper](https://github.com/solana-labs/solana-program-library/blob/65a92e6e0a4346920582d9b3893cacafd85bb017/token/transfer-hook/interface/src/onchain.rs#L67). +[onchain helper](https://github.com/solana-labs/solana-program-library/blob/65a92e6e0a4346920582d9b3893cacafd85bb017/token/transfer-hook/interface/src/onchain.rs#L67). ```rust // Find the validation account from the list of `AccountInfo`s and load its diff --git a/docs/src/transfer-hook-interface/introduction.md b/docs/src/transfer-hook-interface/introduction.md index 3ec3a28624d..a39f36adf87 100644 --- a/docs/src/transfer-hook-interface/introduction.md +++ b/docs/src/transfer-hook-interface/introduction.md @@ -1,27 +1,26 @@ --- -title: Introduction +title: Transfer Hook Interface --- The Transfer Hook Interface is one of several interfaces introduced within the Solana Program Library that can be implemented by any Solana program. -Token-2022 implements this interface, as described in the [Transfer Hook -Extension Guide](../../token-2022/extensions#transfer-hook). Additionally, a -[reference -implementation](https://github.com/solana-labs/solana-program-library/tree/master/token/transfer-hook/example) +During transfers, Token-2022 calls a mint's configured transfer hook program +using this interface, as described in the +[Transfer Hook Extension Guide](../../token-2022/extensions#transfer-hook). +Additionally, a +[reference implementation](https://github.com/solana-labs/solana-program-library/tree/master/token/transfer-hook/example) can be found in the SPL GitHub repository, detailing how one might implement this interface in their own program. -Transfer Hook is designed to allow programs to "hook" additional functionality -into token transfers. The program performing the transfer can then CPI into the -transfer hook program using the interface-defined instructions to perform the -custom functionality. +The Transfer Hook Interface is designed to allow token creators to "hook" +additional functionality into token transfers. The token program CPIs into the +transfer hook program using the interface-defined instruction. The transfer +hook program can then perform any custom functionality. -In the case of Token-2022, the presence of a transfer hook program is assigned -to a mint using a mint extension, and this extension tells Token-2022 which -program to CPI to whenever a transfer is conducted. However, this particular -implementation is not required, and the interface can be implemented in a -variety of ways! +In the case of Token-2022, a token creator configures a transfer hook program +using a mint extension, and this extension tells Token-2022 which program to +invoke whenever a transfer is conducted. With this interface, programs can compose highly customizable transfer functionality that can be compatible with many other programs - particularly diff --git a/docs/src/transfer-hook-interface/specification.md b/docs/src/transfer-hook-interface/specification.md index 95535b08b45..723920e01b5 100644 --- a/docs/src/transfer-hook-interface/specification.md +++ b/docs/src/transfer-hook-interface/specification.md @@ -19,10 +19,10 @@ will live. - **Data:** - `amount: u64` - The transfer amount - **Accounts:** - - 1 `[]`: Source + - 1 `[]`: Source token account - 2 `[]`: Mint - - 3 `[]`: Destination - - 4 `[]`: Authority + - 3 `[]`: Destination token account + - 4 `[]`: Source token account authority - 5 `[]`: Validation account - `n` number of additional accounts, written into the validation account @@ -54,11 +54,10 @@ configurations for the `Execute` instruction. ### (Optional) Instruction: `UpdateExtraAccountMetaList` -In the case that an on-chain program needs a method for updating its list of -required accounts for `Execute`, the `UpdateExtraAccountMetaList` instruction -allows for exactly that. By implementing this instruction, developers can make -updates to their list of required extra accounts stored in the validation -account. +The `UpdateExtraAccountMetaList` instruction allows an on-chain program to +update its list of required accounts for `Execute`. By implementing this +instruction, developers can make updates to their list of required extra +accounts stored in the validation account. - **Discriminator:** First 8 bytes of the hash of the string literal `"spl-transfer-hook-interface:update-extra-account-metas"` From 24842cc807b6b289fbff0f89325c7cdde3ce41ae Mon Sep 17 00:00:00 2001 From: Joe Date: Thu, 25 Jan 2024 13:11:22 -0600 Subject: [PATCH 3/3] follow sidebar pattern for transfer hook pages --- docs/sidebars.js | 6 +++++- docs/src/token-2022/extensions.mdx | 2 +- .../introduction.md => transfer-hook-interface.md} | 0 3 files changed, 6 insertions(+), 2 deletions(-) rename docs/src/{transfer-hook-interface/introduction.md => transfer-hook-interface.md} (100%) diff --git a/docs/sidebars.js b/docs/sidebars.js index fac605c71b0..6f89b2c158b 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -70,7 +70,11 @@ module.exports = { label: "Transfer Hook Interface", collapsed: true, items: [ - "transfer-hook-interface/introduction", + { + type: 'doc', + label: 'Introduction', + id: 'transfer-hook-interface', + }, "transfer-hook-interface/specification", "transfer-hook-interface/configuring-extra-accounts", "transfer-hook-interface/examples", diff --git a/docs/src/token-2022/extensions.mdx b/docs/src/token-2022/extensions.mdx index f88534b0ca4..a19d4e3e3af 100644 --- a/docs/src/token-2022/extensions.mdx +++ b/docs/src/token-2022/extensions.mdx @@ -1468,7 +1468,7 @@ harder. #### Solution To improve the situation, Token-2022 introduces the concept of the -[transfer-hook interface](../transfer-hook-interface/introduction) +[transfer-hook interface](../transfer-hook-interface) and extension. A token creator must develop and deploy a program that implements the interface and then configure their token mint to use their program. diff --git a/docs/src/transfer-hook-interface/introduction.md b/docs/src/transfer-hook-interface.md similarity index 100% rename from docs/src/transfer-hook-interface/introduction.md rename to docs/src/transfer-hook-interface.md