Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Document @solana/signers with TypeDoc
Browse files Browse the repository at this point in the history
lorisleiva committed Jan 24, 2025

Verified

This commit was signed with the committer’s verified signature.
apmoore1 Andrew Moore
1 parent 821b80f commit 656e074
Showing 19 changed files with 1,259 additions and 85 deletions.
155 changes: 150 additions & 5 deletions packages/signers/src/account-signer-meta.ts
Original file line number Diff line number Diff line change
@@ -9,7 +9,31 @@ import { deduplicateSigners } from './deduplicate-signers';
import { ITransactionMessageWithFeePayerSigner } from './fee-payer-signer';
import { isTransactionSigner, TransactionSigner } from './transaction-signer';

/** An extension of the IAccountMeta type that keeps track of its transaction signer. */
/**
* An extension of the {@link IAccountMeta} type that allows us to store {@link TransactionSigner | TransactionSigners} inside it.
*
* Note that, because this type represents a signer, it must use one the following two roles:
* - {@link AccountRole.READONLY_SIGNER}
* - {@link AccountRole.WRITABLE_SIGNER}
*
* @typeParam TAddress - Supply a string literal to define an account having a particular address.
* @typeParam TSigner - Optionally provide a narrower type for the {@link TransactionSigner} to use within the account meta.
*
* @interface
*
* @example
* ```ts
* import { AccountRole } from '@solana/instructions';
* import { generateKeyPairSigner, IAccountSignerMeta } from '@solana/signers';
*
* const signer = await generateKeyPairSigner();
* const account: IAccountSignerMeta = {
* address: signer.address,
* role: AccountRole.READONLY_SIGNER,
* signer,
* };
* ```
*/
export interface IAccountSignerMeta<
TAddress extends string = string,
TSigner extends TransactionSigner<TAddress> = TransactionSigner<TAddress>,
@@ -18,18 +42,78 @@ export interface IAccountSignerMeta<
readonly signer: TSigner;
}

/**
* A union type that supports base account metas as well as {@link IAccountSignerMeta | signer account metas}.
*/
type IAccountMetaWithSigner<TSigner extends TransactionSigner = TransactionSigner> =
| IAccountLookupMeta
| IAccountMeta
| IAccountSignerMeta<string, TSigner>;

/** A variation of the instruction type that allows IAccountSignerMeta in its account metas. */
/**
* Composable type that allows {@link IAccountSignerMeta | IAccountSignerMetas} to be used inside the instruction's `accounts` array
*
* @typeParam TSigner - Optionally provide a narrower type for {@link TransactionSigner | TransactionSigners}.
* @typeParam TAccounts - Optionally provide a narrower type for the account metas.
*
* @interface
*
* @example
* ```ts
* import { AccountRole, IInstruction } from '@solana/instructions';
* import { generateKeyPairSigner, IInstructionWithSigners } from '@solana/signers';
*
* const [authority, buffer] = await Promise.all([
* generateKeyPairSigner(),
* generateKeyPairSigner(),
* ]);
* const instruction: IInstruction & IInstructionWithSigners = {
* programAddress: address('1234..5678'),
* accounts: [
* // The authority is a signer account.
* {
* address: authority.address,
* role: AccountRole.READONLY_SIGNER,
* signer: authority,
* },
* // The buffer is a writable account.
* { address: buffer.address, role: AccountRole.WRITABLE },
* ],
* };
* ```
*/
export type IInstructionWithSigners<
TSigner extends TransactionSigner = TransactionSigner,
TAccounts extends readonly IAccountMetaWithSigner<TSigner>[] = readonly IAccountMetaWithSigner<TSigner>[],
> = Pick<IInstruction<string, TAccounts>, 'accounts'>;

/** A variation of the transaction message type that allows IAccountSignerMeta in its account metas. */
/**
* A {@link BaseTransactionMessage} type extension that accept {@link TransactionSigner | TransactionSigners}.
*
* Namely, it allows:
* - a {@link TransactionSigner} to be used as the fee payer and
* - {@link IInstructionWithSigners} to be used in its instructions.
*
*
* @typeParam TAddress - Supply a string literal to define an account having a particular address.
* @typeParam TSigner - Optionally provide a narrower type for {@link TransactionSigner | TransactionSigners}.
* @typeParam TAccounts - Optionally provide a narrower type for the account metas.
*
* @example
* ```ts
* import { IInstruction } from '@solana/instructions';
* import { BaseTransactionMessage } from '@solana/transaction-messages';
* import { generateKeyPairSigner, IInstructionWithSigners, ITransactionMessageWithSigners } from '@solana/signers';
*
* const signer = await generateKeyPairSigner();
* const firstInstruction: IInstruction = { ... };
* const secondInstruction: IInstructionWithSigners = { ... };
* const transactionMessage: BaseTransactionMessage & ITransactionMessageWithSigners = {
* feePayer: signer,
* instructions: [firstInstruction, secondInstruction],
* }
* ```
*/
export type ITransactionMessageWithSigners<
TAddress extends string = string,
TSigner extends TransactionSigner<TAddress> = TransactionSigner<TAddress>,
@@ -40,7 +124,32 @@ export type ITransactionMessageWithSigners<
'instructions'
>;

/** Extract all signers from an instruction that may contain IAccountSignerMeta accounts. */
/**
* Extracts and deduplicates all {@link TransactionSigner | TransactionSigners} stored
* inside the account metas of an {@link IInstructionWithSigners | instruction}.
*
* Any extracted signers that share the same {@link Address} will be de-duplicated.
*
* @typeParam TSigner - Optionally provide a narrower type for {@link TransactionSigner | TransactionSigners}.
*
* @example
* ```ts
* import { IInstructionWithSigners, getSignersFromInstruction } from '@solana/signers';
*
* const signerA = { address: address('1111..1111'), signTransactions: async () => {} };
* const signerB = { address: address('2222..2222'), signTransactions: async () => {} };
* const instructionWithSigners: IInstructionWithSigners = {
* accounts: [
* { address: signerA.address, signer: signerA, ... },
* { address: signerB.address, signer: signerB, ... },
* { address: signerA.address, signer: signerA, ... },
* ],
* };
*
* const instructionSigners = getSignersFromInstruction(instructionWithSigners);
* // ^ [signerA, signerB]
* ```
*/
export function getSignersFromInstruction<TSigner extends TransactionSigner = TransactionSigner>(
instruction: IInstructionWithSigners<TSigner>,
): readonly TSigner[] {
@@ -49,7 +158,43 @@ export function getSignersFromInstruction<TSigner extends TransactionSigner = Tr
);
}

/** Extract all signers from a transaction message that may contain IAccountSignerMeta accounts. */
/**
* Extracts and deduplicates all {@link TransactionSigner | TransactionSigners} stored
* inside a given {@link ITransactionMessageWithSigners | transaction message}.
*
* This includes any {@link TransactionSigner | TransactionSigners} stored
* as the fee payer or in the instructions of the transaction message.
*
* Any extracted signers that share the same {@link Address} will be de-duplicated.
*
* @typeParam TAddress - Supply a string literal to define an account having a particular address.
* @typeParam TSigner - Optionally provide a narrower type for {@link TransactionSigner | TransactionSigners}.
* @typeParam TTransactionMessage - The inferred type of the transaction message provided.
*
* @example
* ```ts
* import { IInstruction } from '@solana/instructions';
* import { IInstructionWithSigners, ITransactionMessageWithSigners, getSignersFromTransactionMessage } from '@solana/signers';
*
* const signerA = { address: address('1111..1111'), signTransactions: async () => {} };
* const signerB = { address: address('2222..2222'), signTransactions: async () => {} };
* const firstInstruction: IInstruction & IInstructionWithSigners = {
* programAddress: address('1234..5678'),
* accounts: [{ address: signerA.address, signer: signerA, ... }],
* };
* const secondInstruction: IInstruction & IInstructionWithSigners = {
* programAddress: address('1234..5678'),
* accounts: [{ address: signerB.address, signer: signerB, ... }],
* };
* const transactionMessage: ITransactionMessageWithSigners = {
* feePayer: signerA,
* instructions: [firstInstruction, secondInstruction],
* }
*
* const transactionSigners = getSignersFromTransactionMessage(transactionMessage);
* // ^ [signerA, signerB]
* ```
*/
export function getSignersFromTransactionMessage<
TAddress extends string = string,
TSigner extends TransactionSigner<TAddress> = TransactionSigner<TAddress>,
78 changes: 77 additions & 1 deletion packages/signers/src/add-signers.ts
Original file line number Diff line number Diff line change
@@ -5,7 +5,41 @@ import { IAccountSignerMeta, IInstructionWithSigners, ITransactionMessageWithSig
import { deduplicateSigners } from './deduplicate-signers';
import { TransactionSigner } from './transaction-signer';

/** Attaches the provided signers to the account metas of an instruction when applicable. */
/**
* Attaches the provided {@link TransactionSigner | TransactionSigners} to the
* account metas of an instruction when applicable.
*
* For an account meta to match a provided signer it:
* - Must have a signer role ({@link AccountRole.READONLY_SIGNER} or {@link AccountRole.WRITABLE_SIGNER}).
* - Must have the same address as the provided signer.
* - Must not have an attached signer already.
*
* @typeParam TInstruction - The inferred type of the instruction provided.
*
* @example
* ```ts
* import { AccountRole, IInstruction } from '@solana/instructions';
* import { addSignersToInstruction, TransactionSigner } from '@solana/signers';
*
* const instruction: IInstruction = {
* accounts: [
* { address: '1111' as Address, role: AccountRole.READONLY_SIGNER },
* { address: '2222' as Address, role: AccountRole.WRITABLE_SIGNER },
* ],
* // ...
* };
*
* const signerA: TransactionSigner<'1111'>;
* const signerB: TransactionSigner<'2222'>;
* const instructionWithSigners = addSignersToInstruction(
* [signerA, signerB],
* instruction
* );
*
* // instructionWithSigners.accounts[0].signer === signerA
* // instructionWithSigners.accounts[1].signer === signerB
* ```
*/
export function addSignersToInstruction<TInstruction extends IInstruction>(
signers: TransactionSigner[],
instruction: TInstruction | (IInstructionWithSigners & TInstruction),
@@ -28,6 +62,48 @@ export function addSignersToInstruction<TInstruction extends IInstruction>(
}

/** Attaches the provided signers to the account metas of a transaction message when applicable. */

/**
* Attaches the provided {@link TransactionSigner | TransactionSigners} to the
* account metas of all instructions inside a transaction message, when applicable.
*
* For an account meta to match a provided signer it:
* - Must have a signer role ({@link AccountRole.READONLY_SIGNER} or {@link AccountRole.WRITABLE_SIGNER}).
* - Must have the same address as the provided signer.
* - Must not have an attached signer already.
*
* @typeParam TTransactionMessage - The inferred type of the transaction message provided.
*
* @example
* ```ts
* import { AccountRole, IInstruction } from '@solana/instructions';
* import { BaseTransactionMessage } from '@solana/transaction-messages';
* import { addSignersToTransactionMessage, TransactionSigner } from '@solana/signers';
*
* const instructionA: IInstruction = {
* accounts: [{ address: '1111' as Address, role: AccountRole.READONLY_SIGNER }],
* // ...
* };
* const instructionB: IInstruction = {
* accounts: [{ address: '2222' as Address, role: AccountRole.WRITABLE_SIGNER }],
* // ...
* };
* const transactionMessage: BaseTransactionMessage = {
* instructions: [instructionA, instructionB],
* // ...
* }
*
* const signerA: TransactionSigner<'1111'>;
* const signerB: TransactionSigner<'2222'>;
* const transactionMessageWithSigners = addSignersToTransactionMessage(
* [signerA, signerB],
* transactionMessage
* );
*
* // transactionMessageWithSigners.instructions[0].accounts[0].signer === signerA
* // transactionMessageWithSigners.instructions[1].accounts[0].signer === signerB
* ```
*/
export function addSignersToTransactionMessage<TTransactionMessage extends BaseTransactionMessage>(
signers: TransactionSigner[],
transactionMessage: TTransactionMessage | (ITransactionMessageWithSigners & TTransactionMessage),
8 changes: 7 additions & 1 deletion packages/signers/src/deduplicate-signers.ts
Original file line number Diff line number Diff line change
@@ -4,7 +4,13 @@ import { SOLANA_ERROR__SIGNER__ADDRESS_CANNOT_HAVE_MULTIPLE_SIGNERS, SolanaError
import { MessageSigner } from './message-signer';
import { TransactionSigner } from './transaction-signer';

/** Removes all duplicated signers from a provided array by comparing their addresses. */
/**
* Removes all duplicated {@link MessageSigner | MessageSigners} and
* {@link TransactionSigner | TransactionSigners} from a provided array
* by comparing their {@link Address | addresses}.
*
* @internal
*/
export function deduplicateSigners<TSigner extends MessageSigner | TransactionSigner>(
signers: readonly TSigner[],
): readonly TSigner[] {
38 changes: 38 additions & 0 deletions packages/signers/src/fee-payer-signer.ts
Original file line number Diff line number Diff line change
@@ -2,13 +2,51 @@ import { BaseTransactionMessage, ITransactionMessageWithFeePayer } from '@solana

import { TransactionSigner } from './transaction-signer';

/**
* Alternative to {@link ITransactionMessageWithFeePayer} that uses a {@link TransactionSigner} for the fee payer.
*
* @typeParam TAddress - Supply a string literal to define a fee payer having a particular address.
* @typeParam TSigner - Optionally provide a narrower type for the {@link TransactionSigner}.
*
* @example
* ```ts
* import { BaseTransactionMessage } from '@solana/transaction-messages';
* import { generateKeyPairSigner, ITransactionMessageWithFeePayerSigner } from '@solana/signers';
*
* const transactionMessage: BaseTransactionMessage & ITransactionMessageWithFeePayerSigner = {
* feePayer: await generateKeyPairSigner(),
* instructions: [],
* version: 0,
* };
* ```
*/
export interface ITransactionMessageWithFeePayerSigner<
TAddress extends string = string,
TSigner extends TransactionSigner<TAddress> = TransactionSigner<TAddress>,
> {
readonly feePayer: TSigner;
}

/**
* Sets the fee payer of a {@link BaseTransactionMessage | transaction message}
* using a {@link TransactionSigner}.
*
* @typeParam TFeePayerAddress - Supply a string literal to define a fee payer having a particular address.
* @typeParam TTransactionMessage - The inferred type of the transaction message provided.
*
* @example
* ```ts
* import { pipe } from '@solana/functional';
* import { generateKeyPairSigner, setTransactionMessageFeePayerSigner } from '@solana/signers';
* import { createTransactionMessage } from '@solana/transaction-messages';
*
* const feePayer = await generateKeyPairSigner();
* const transactionMessage = pipe(
* createTransactionMessage({ version: 0 }),
* message => setTransactionMessageFeePayerSigner(signer, message),
* );
* ```
*/
export function setTransactionMessageFeePayerSigner<
TFeePayerAddress extends string,
TTransactionMessage extends BaseTransactionMessage &
Loading

0 comments on commit 656e074

Please sign in to comment.