Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document @solana/instructions with TypeDoc #166

Merged
merged 1 commit into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/instructions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ type StakeProgramInstruction = IInstruction<'StakeConfig111111111111111111111111

### `IInstructionWithAccounts<TAccounts>`

Use this type to specify an instruction that contains certain accounts.
Use this type to specify an instruction that loads certain accounts.

```ts
type InstructionWithTwoAccounts = IInstructionWithAccounts<
Expand All @@ -88,7 +88,7 @@ type InstructionWithTwoAccounts = IInstructionWithAccounts<

### `IInstructionWithData<TData>`

Use this type to specify an instruction whose data conforms to a certain type. This is most useful when you have a branded `Uint8Array` that represents a particular instruction.
Use this type to specify an instruction whose data conforms to a certain type. This is most useful when you have a branded `Uint8Array` that represents a particular instruction's data.

For example, here is how the `AdvanceNonce` instruction is typed.

Expand Down Expand Up @@ -134,7 +134,7 @@ Returns an `AccountRole` representing the non-signer variant of the supplied rol

### `downgradeRoleToReadonly(role: AccountRole)`

Returns an `AccountRole` representing the non-writable variant of the supplied role.
Returns an `AccountRole` representing the read-only variant of the supplied role.

### `upgradeRoleToSigner(role: AccountRole)`

Expand Down
57 changes: 57 additions & 0 deletions packages/instructions/src/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,90 @@ import { Address } from '@solana/addresses';

import { AccountRole } from './roles';

/**
* Represents an account's address and metadata about its mutability and whether it must be a signer
* of the transaction.
*
* Typically, you will use one of its subtypes.
*
* | | `role` | `isSigner` | `isWritable` |
* | --------------------------------- | ----------------------------- | ---------- | ------------ |
* | `ReadonlyAccount<TAddress>` | `AccountRole.READONLY` | No | No |
* | `WritableAccount<TAddress>` | `AccountRole.WRITABLE` | No | Yes |
* | `ReadonlySignerAccount<TAddress>` | `AccountRole.READONLY_SIGNER` | Yes | No |
* | `WritableSignerAccount<TAddress>` | `AccountRole.WRITABLE_SIGNER` | Yes | Yes |
*
* @example A type for the Rent sysvar account
* ```ts
* type RentSysvar = ReadonlyAccount<'SysvarRent111111111111111111111111111111111'>;
* ```
*/
export interface IAccountMeta<TAddress extends string = string> {
readonly address: Address<TAddress>;
readonly role: AccountRole;
}

/**
* @see {@link IAccountMeta}
*/
export type ReadonlyAccount<TAddress extends string = string> = IAccountMeta<TAddress> & {
readonly role: AccountRole.READONLY;
};
/**
* @see {@link IAccountMeta}
*/
export type WritableAccount<TAddress extends string = string> = IAccountMeta<TAddress> & { role: AccountRole.WRITABLE };
/**
* @see {@link IAccountMeta}
*/
export type ReadonlySignerAccount<TAddress extends string = string> = IAccountMeta<TAddress> & {
role: AccountRole.READONLY_SIGNER;
};
/**
* @see {@link IAccountMeta}
*/
export type WritableSignerAccount<TAddress extends string = string> = IAccountMeta<TAddress> & {
role: AccountRole.WRITABLE_SIGNER;
};

/**
* Represents a lookup of the account's address in an address lookup table. It specifies which
* lookup table account in which to perform the lookup, the index of the desired account address in
* that table, and metadata about its mutability. Notably, account addresses obtained via lookups
* may not act as signers.
*
* Typically, you will use one of its subtypes.
*
* | | `role` | `isSigner` | `isWritable` |
* | ------------------------------------------------------ | ---------------------- | ---------- | ------------ |
* | `ReadonlyLookupAccount<TAddress, TLookupTableAddress>` | `AccountRole.READONLY` | No | No |
* | `WritableLookupAccount<TAddress, TLookupTableAddress>` | `AccountRole.WRITABLE` | No | Yes |
*
* @example A type for the Rent sysvar account that you looked up in a lookup table
* ```ts
* type RentSysvar = ReadonlyLookupAccount<
* 'SysvarRent111111111111111111111111111111111',
* 'MyLookupTable111111111111111111111111111111'
* >;
* ```
*/
export interface IAccountLookupMeta<TAddress extends string = string, TLookupTableAddress extends string = string> {
readonly address: Address<TAddress>;
readonly addressIndex: number;
readonly lookupTableAddress: Address<TLookupTableAddress>;
readonly role: AccountRole.READONLY | AccountRole.WRITABLE;
}

/**
* @see {@link IAccountLookupMeta}
*/
export type ReadonlyAccountLookup<
TAddress extends string = string,
TLookupTableAddress extends string = string,
> = IAccountLookupMeta<TAddress, TLookupTableAddress> & { readonly role: AccountRole.READONLY };
/**
* @see {@link IAccountLookupMeta}
*/
export type WritableAccountLookup<
TAddress extends string = string,
TLookupTableAddress extends string = string,
Expand Down
4 changes: 4 additions & 0 deletions packages/instructions/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* This package contains types for creating transaction instructions. It can be used standalone, but it is also exported as part of Kit [`@solana/kit`](https://github.com/anza-xyz/kit/tree/main/packages/kit).
* @packageDocumentation
*/
export * from './accounts';
export * from './instruction';
export * from './roles';
43 changes: 43 additions & 0 deletions packages/instructions/src/instruction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ import {

import { IAccountLookupMeta, IAccountMeta } from './accounts';

/**
* An instruction destined for a given program.
*
* @example
* ```ts
* type StakeProgramInstruction = IInstruction<'StakeConfig11111111111111111111111111111111'>;
* ```
*/
export interface IInstruction<
TProgramAddress extends string = string,
TAccounts extends readonly (IAccountLookupMeta | IAccountMeta)[] = readonly (IAccountLookupMeta | IAccountMeta)[],
Expand All @@ -18,6 +26,19 @@ export interface IInstruction<
readonly programAddress: Address<TProgramAddress>;
}

/**
* An instruction that loads certain accounts.
*
* @example
* ```ts
* type InstructionWithTwoAccounts = IInstructionWithAccounts<
* [
* WritableAccount, // First account
* RentSysvar, // Second account
* ]
* >;
* ```
*/
export interface IInstructionWithAccounts<TAccounts extends readonly (IAccountLookupMeta | IAccountMeta)[]>
extends IInstruction {
readonly accounts: TAccounts;
Expand Down Expand Up @@ -61,6 +82,28 @@ export function assertIsInstructionWithAccounts<
}
}

/**
* An instruction whose data conforms to a certain type.
*
* This is most useful when you have a branded `Uint8Array` that represents a particular
* instruction's data.
*
* @example A type for the \`AdvanceNonce\` instruction of the System program
* ```ts
* type AdvanceNonceAccountInstruction<
* TNonceAccountAddress extends string = string,
* TNonceAuthorityAddress extends string = string,
* > = IInstruction<'11111111111111111111111111111111'> &
* IInstructionWithAccounts<
* [
* WritableAccount<TNonceAccountAddress>,
* ReadonlyAccount<'SysvarRecentB1ockHashes11111111111111111111'>,
* ReadonlySignerAccount<TNonceAuthorityAddress>,
* ]
* > &
* IInstructionWithData<AdvanceNonceAccountInstructionData>;
* ```
*/
export interface IInstructionWithData<TData extends ReadonlyUint8Array> extends IInstruction {
readonly data: TData;
}
Expand Down
45 changes: 43 additions & 2 deletions packages/instructions/src/roles.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
/**
* Quick primer on bitwise operations: https://stackoverflow.com/a/1436448/802047
* Describes the purpose for which an account participates in a transaction.
*
* Every account that participates in a transaction can be read from, but only ones that you mark as
* writable may be written to, and only ones that you indicate must sign the transaction will gain
* the privileges associated with signers at runtime.
*
* | | `isSigner` | `isWritable` |
* | ----------------------------- | ---------- | ------------ |
* | `AccountRole.READONLY` | &#x274c; | &#x274c; |
* | `AccountRole.WRITABLE` | &#x274c; | &#x2705; |
* | `AccountRole.READONLY_SIGNER` | &#x2705; | &#x274c; |
* | `AccountRole.WRITABLE_SIGNER` | &#x2705; | &#x2705; |
*/

export enum AccountRole {
// Bitflag guide: is signer ⌄⌄ is writable
WRITABLE_SIGNER = /* 3 */ 0b11, // prettier-ignore
Expand All @@ -10,31 +20,56 @@ export enum AccountRole {
READONLY = /* 0 */ 0b00, // prettier-ignore
}

// Quick primer on bitwise operations: https://stackoverflow.com/a/1436448/802047
const IS_SIGNER_BITMASK = 0b10;
const IS_WRITABLE_BITMASK = 0b01;

/**
* @returns An {@link AccountRole} representing the non-signer variant of the supplied role.
*/
export function downgradeRoleToNonSigner(role: AccountRole.READONLY_SIGNER): AccountRole.READONLY;
export function downgradeRoleToNonSigner(role: AccountRole.WRITABLE_SIGNER): AccountRole.WRITABLE;
export function downgradeRoleToNonSigner(role: AccountRole): AccountRole;
export function downgradeRoleToNonSigner(role: AccountRole): AccountRole {
return role & ~IS_SIGNER_BITMASK;
}

/**
* @returns An {@link AccountRole} representing the read-only variant of the supplied role.
*/
export function downgradeRoleToReadonly(role: AccountRole.WRITABLE): AccountRole.READONLY;
export function downgradeRoleToReadonly(role: AccountRole.WRITABLE_SIGNER): AccountRole.READONLY_SIGNER;
export function downgradeRoleToReadonly(role: AccountRole): AccountRole;
export function downgradeRoleToReadonly(role: AccountRole): AccountRole {
return role & ~IS_WRITABLE_BITMASK;
}

/**
* Returns `true` if the {@link AccountRole} given represents that of a signer. Also refines the
* TypeScript type of the supplied role.
*/
export function isSignerRole(role: AccountRole): role is AccountRole.READONLY_SIGNER | AccountRole.WRITABLE_SIGNER {
return role >= AccountRole.READONLY_SIGNER;
}

/**
* Returns `true` if the {@link AccountRole} given represents that of a writable account. Also
* refines the TypeScript type of the supplied role.
*/
export function isWritableRole(role: AccountRole): role is AccountRole.WRITABLE | AccountRole.WRITABLE_SIGNER {
return (role & IS_WRITABLE_BITMASK) !== 0;
}

/**
* Given two {@link AccountRole | AccountRoles}, will return the {@link AccountRole} that grants the
* highest privileges of both.
*
* @example
* ```ts
* // Returns `AccountRole.WRITABLE_SIGNER`
* mergeRoles(AccountRole.READONLY_SIGNER, AccountRole.WRITABLE);
* ```
*/
export function mergeRoles(roleA: AccountRole.WRITABLE, roleB: AccountRole.READONLY_SIGNER): AccountRole.WRITABLE_SIGNER; // prettier-ignore
export function mergeRoles(roleA: AccountRole.READONLY_SIGNER, roleB: AccountRole.WRITABLE): AccountRole.WRITABLE_SIGNER; // prettier-ignore
export function mergeRoles(roleA: AccountRole, roleB: AccountRole.WRITABLE_SIGNER): AccountRole.WRITABLE_SIGNER; // prettier-ignore
Expand All @@ -49,13 +84,19 @@ export function mergeRoles(roleA: AccountRole, roleB: AccountRole): AccountRole
return roleA | roleB;
}

/**
* @returns An {@link AccountRole} representing the signer variant of the supplied role.
*/
export function upgradeRoleToSigner(role: AccountRole.READONLY): AccountRole.READONLY_SIGNER;
export function upgradeRoleToSigner(role: AccountRole.WRITABLE): AccountRole.WRITABLE_SIGNER;
export function upgradeRoleToSigner(role: AccountRole): AccountRole;
export function upgradeRoleToSigner(role: AccountRole): AccountRole {
return role | IS_SIGNER_BITMASK;
}

/**
* @returns An {@link AccountRole} representing the writable variant of the supplied role.
*/
export function upgradeRoleToWritable(role: AccountRole.READONLY): AccountRole.WRITABLE;
export function upgradeRoleToWritable(role: AccountRole.READONLY_SIGNER): AccountRole.WRITABLE_SIGNER;
export function upgradeRoleToWritable(role: AccountRole): AccountRole;
Expand Down
Loading