Skip to content

Commit

Permalink
Document @solana/signers with TypeDoc (#78)
Browse files Browse the repository at this point in the history
This PR moves content from the README into the code.

Addresses #50

# Test Plan

```shell
cd packages/signers
pnpm typedoc --watch --plugin typedoc-plugin-missing-exports
python3 -m http.server -d docs
```

Preview here: https://boisterous-alfajores-c0ea79.netlify.app/
  • Loading branch information
lorisleiva authored Jan 31, 2025
1 parent dedf8b6 commit 5a6a463
Show file tree
Hide file tree
Showing 20 changed files with 1,234 additions and 62 deletions.
2 changes: 1 addition & 1 deletion packages/signers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()`
Expand Down
155 changes: 150 additions & 5 deletions packages/signers/src/account-signer-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand All @@ -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>,
Expand All @@ -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[] {
Expand All @@ -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>,
Expand Down
78 changes: 76 additions & 2 deletions packages/signers/src/add-signers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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),
Expand Down
8 changes: 7 additions & 1 deletion packages/signers/src/deduplicate-signers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] {
Expand Down
38 changes: 38 additions & 0 deletions packages/signers/src/fee-payer-signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 &
Expand Down
Loading

0 comments on commit 5a6a463

Please sign in to comment.