Library defining a generic state interface to encode additional required accounts for an instruction, using Type-Length-Value structures.
If you want to encode the additional required accounts for your instruction into a TLV entry in an account, you can do the following:
```rust use { solanaprogram::{accountinfo::AccountInfo, instruction::{AccountMeta, Instruction}, pubkey::Pubkey}, spldiscriminator::{ArrayDiscriminator, SplDiscriminate}, spltlvaccountresolution::state::ExtraAccountMetas, };
struct MyInstruction; impl SplDiscriminate for MyInstruction { // For ease of use, give it the same discriminator as its instruction definition const SPL_DISCRIMINATOR: ArrayDiscriminator = ArrayDiscriminator::new([1; ArrayDiscriminator::LENGTH]); }
// Actually put it in the additional required account keys and signer / writable let extrametas = [ AccountMeta::new(Pubkey::newunique(), false), AccountMeta::new(Pubkey::newunique(), true), AccountMeta::newreadonly(Pubkey::newunique(), true), AccountMeta::newreadonly(Pubkey::new_unique(), false), ];
// Assume that this buffer is actually account data, already allocated to account_size
let accountsize = ExtraAccountMetas::sizeof(extrametas.len()).unwrap();
let mut buffer = vec![0; accountsize];
// Initialize the structure for your instruction
ExtraAccountMetas::initwithaccountmetas::
// Off-chain, you can add the additional accounts directly from the account data
let programid = Pubkey::newunique();
let mut instruction = Instruction::newwithbytes(programid, &[], vec![]);
ExtraAccountMetas::addto_instruction::
// On-chain, you can add the additional accounts and account infos let mut cpiinstruction = Instruction::newwithbytes(programid, &[], vec![]);
// Include all of the well-known required account infos here first let mut cpiaccountinfos = vec![];
// Provide all "remainingaccountinfos" that are not part of any other known interface
let remainingaccountinfos = &[];
ExtraAccountMetas::addtocpiinstruction::
For ease of use on-chain, ExtraAccountMetas::init_with_account_infos
is also
provided to initialize directly from a set of given accounts.
The Solana account model presents unique challeneges for program interfaces. Since it's impossible to load additional accounts on-chain, if a program requires additional accounts to properly implement an instruction, there's no clear way for clients to fetch these accounts.
There are two main ways to fetch additional accounts, dynamically through program simulation, or statically by fetching account data. This library implements additional account resolution statically. You can find more information about dynamic account resolution in the Appendix.
It's possible for programs to write the additional required account infos into account data, so that on-chain and off-chain clients simply need to read the data to figure out the additional required accounts.
Rather than exposing this data dynamically through program execution, this method uses static account data.
For example, let's imagine there's a Transferable
interface, along with a
transfer
instruction. Some programs that implement transfer
may need more
accounts than just the ones defined in the interface. How does a an on-chain or
off-chain client figure out the additional required accounts?
The "static" approach requires programs to write the extra required accounts to
an account defined at a given address. This could be directly in the mint
, or
some address derivable from the mint address.
Off-chain, a client must fetch this additional account and read its data to find out the additional required accounts, and then include them in the instruction.
On-chain, a program must have access to "remaining account infos" containing the special account and all other required accounts to properly create the CPI instruction and give the correct account infos.
This approach could also be called a "state interface".
This library uses spl-type-length-value
to read and write required instruction
accounts from account data.
Interface instructions must have an 8-byte discriminator, so that the exposed
ExtraAccountMetas
type can use the instruction discriminator as a ArrayDiscriminator
.
This can be confusing. Typically, a type implements SplDiscriminate
, so that
the type can be written into TLV data. In this case, ExtraAccountMetas
is
generic over SplDiscriminate
, meaning that a program can write many different instances of
ExtraAccountMetas
into one account, using different ArrayDiscriminator
s.
Also, it's reusing an instruction discriminator as a TLV discriminator. For example,
if the transfer
instruction has a discriminator of [1, 2, 3, 4, 5, 6, 7, 8]
,
then the account uses a TLV discriminator of [1, 2, 3, 4, 5, 6, 7, 8]
to denote
where the additional account metas are stored.
This isn't required, but makes it easier for clients to find the additional required accounts for an instruction.
To expose the additional accounts required, instruction interfaces can include supplemental instructions to return the required accounts.
For example, in the Transferable
interface example, along with a transfer
instruction, also requires implementations to expose a
get_additional_accounts_for_transfer
instruction.
In the program implementation, this instruction writes the additional accounts into return data, making it easy for on-chain and off-chain clients to consume.
See the relevant sRFC for more information about the dynamic approach.