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/signers with TypeDoc #78

Merged
merged 1 commit into from
Jan 31, 2025
Merged
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
2 changes: 1 addition & 1 deletion packages/signers/README.md
Original file line number Diff line number Diff line change
@@ -326,7 +326,7 @@ const keypairFile = fs.readFileSync('~/.config/solana/id.json');
const keypairBytes = new Uint8Array(JSON.parse(keypairFile.toString()));

// Create a KeyPairSigner from the bytes.
const { privateKey, publicKey } = await createKeyPairSignerFromBytes(keypairBytes);
const signer = await createKeyPairSignerFromBytes(keypairBytes);
```

#### `createKeyPairSignerFromPrivateKeyBytes()`
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: 76 additions & 2 deletions 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),
@@ -27,7 +61,47 @@ 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