From 64421f9a2203667781da6d27e7f63570f97e7303 Mon Sep 17 00:00:00 2001 From: Steven Luscher Date: Thu, 16 Jan 2025 00:19:25 +0000 Subject: [PATCH] Document `@solana/addresses` with TypeDoc Preview at https://peaceful-sorbet-e44996.netlify.app/ --- packages/addresses/src/address.ts | 115 ++++++++++++++++++ packages/addresses/src/index.ts | 7 ++ .../addresses/src/program-derived-address.ts | 62 +++++++++- packages/addresses/src/public-key.ts | 10 ++ packages/addresses/typedoc.json | 3 +- 5 files changed, 190 insertions(+), 7 deletions(-) diff --git a/packages/addresses/src/address.ts b/packages/addresses/src/address.ts index 7de3b9ffe..eff6caa09 100644 --- a/packages/addresses/src/address.ts +++ b/packages/addresses/src/address.ts @@ -16,6 +16,13 @@ import { SolanaError, } from '@solana/errors'; +/** + * Represents a string that validates as a Solana address. Functions that require well-formed + * addresses should specify their inputs in terms of this type. + * + * Whenever you need to validate an arbitrary string as a base58-encoded address, use the + * {@link address}, {@link assertIsAddress}, or {@link isAddress} functions in this package. + */ export type Address = TAddress & { readonly __brand: unique symbol; }; @@ -33,6 +40,24 @@ function getMemoizedBase58Decoder(): Decoder { return memoizedBase58Decoder; } +/** + * A type guard that returns `true` if the input string conforms to the {@link Address} type, and + * refines its type for use in your program. + * + * @example + * ```ts + * import { isAddress } from '@solana/addresses'; + * + * if (isAddress(ownerAddress)) { + * // At this point, `ownerAddress` has been refined to a + * // `Address` that can be used with the RPC. + * const { value: lamports } = await rpc.getBalance(ownerAddress).send(); + * setBalanceLamports(lamports); + * } else { + * setError(`${ownerAddress} is not an address`); + * } + * ``` + */ export function isAddress(putativeAddress: string): putativeAddress is Address { // Fast-path; see if the input string is of an acceptable length. if ( @@ -52,6 +77,31 @@ export function isAddress(putativeAddress: string): putativeAddress is Address { // Fast-path; see if the input string is of an acceptable length. if ( @@ -75,21 +125,86 @@ export function assertIsAddress(putativeAddress: string): asserts putativeAddres } } +/** + * Combines _asserting_ that a string is an address with _coercing_ it to the {@link Address} type. + * It's most useful with untrusted input. + * + * @example + * ```ts + * import { address } from '@solana/addresses'; + * + * await transfer(address(fromAddress), address(toAddress), lamports(100000n)); + * ``` + * + * > [!TIP] + * > When starting from a known-good address as a string, it's more efficient to typecast it rather + * than to use the {@link address} helper, because the helper unconditionally performs validation on + * its input. + * > + * > ```ts + * > import { Address } from '@solana/addresses'; + * > + * > const MEMO_PROGRAM_ADDRESS = + * > 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr' as Address<'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'>; + * > ``` + */ export function address(putativeAddress: TAddress): Address { assertIsAddress(putativeAddress); return putativeAddress as Address; } +/** + * Returns an encoder that you can use to encode a base58-encoded address to a byte array. + * + * @example + * ```ts + * import { getAddressEncoder } from '@solana/addresses'; + * + * const address = 'B9Lf9z5BfNPT4d5KMeaBFx8x1G4CULZYR1jA2kmxRDka' as Address; + * const addressEncoder = getAddressEncoder(); + * const addressBytes = addressEncoder.encode(address); + * // Uint8Array(32) [ + * // 150, 183, 190, 48, 171, 8, 39, 156, + * // 122, 213, 172, 108, 193, 95, 26, 158, + * // 149, 243, 115, 254, 20, 200, 36, 30, + * // 248, 179, 178, 232, 220, 89, 53, 127 + * // ] + * ``` + */ export function getAddressEncoder(): FixedSizeEncoder { return transformEncoder(fixEncoderSize(getMemoizedBase58Encoder(), 32), putativeAddress => address(putativeAddress), ); } +/** + * Returns a decoder that you can use to convert an array of 32 bytes representing an address to the + * base58-encoded representation of that address. + * + * @example + * ```ts + * import { getAddressDecoder } from '@solana/addresses'; + * + * const addressBytes = new Uint8Array([ + * 150, 183, 190, 48, 171, 8, 39, 156, + * 122, 213, 172, 108, 193, 95, 26, 158, + * 149, 243, 115, 254, 20, 200, 36, 30, + * 248, 179, 178, 232, 220, 89, 53, 127 + * ]); + * const addressDecoder = getAddressDecoder(); + * const address = addressDecoder.decode(address); // B9Lf9z5BfNPT4d5KMeaBFx8x1G4CULZYR1jA2kmxRDka + * ``` + */ export function getAddressDecoder(): FixedSizeDecoder { return fixDecoderSize(getMemoizedBase58Decoder(), 32) as FixedSizeDecoder; } +/** + * Returns a codec that you can use to encode from or decode to a base-58 encoded address. + * + * @see {@link getAddressDecoder} + * @see {@link getAddressEncoder} + */ export function getAddressCodec(): FixedSizeCodec { return combineCodec(getAddressEncoder(), getAddressDecoder()); } diff --git a/packages/addresses/src/index.ts b/packages/addresses/src/index.ts index 3773ecc87..f1d25f58e 100644 --- a/packages/addresses/src/index.ts +++ b/packages/addresses/src/index.ts @@ -1,3 +1,10 @@ +/** + * This package contains utilities for generating account addresses. It can be used standalone, but + * it is also exported as part of the Solana JavaScript SDK + * [`@solana/web3.js@next`](https://github.com/anza-xyz/solana-web3.js/tree/main/packages/library). + * + * @packageDocumentation + */ export * from './address'; export * from './program-derived-address'; export * from './public-key'; diff --git a/packages/addresses/src/program-derived-address.ts b/packages/addresses/src/program-derived-address.ts index 5443db8ae..13decf927 100644 --- a/packages/addresses/src/program-derived-address.ts +++ b/packages/addresses/src/program-derived-address.ts @@ -16,23 +16,31 @@ import { Address, assertIsAddress, getAddressCodec, isAddress } from './address' import { compressedPointBytesAreOnCurve } from './curve'; /** - * An address derived from a program address and a set of seeds. - * It includes the bump seed used to derive the address and - * ensure the address is not on the Ed25519 curve. + * A tuple representing a program derived address (derived from the address of some program and a + * set of seeds) and the associated bump seed used to ensure that the address, as derived, does not + * fall on the Ed25519 curve. + * + * Whenever you need to validate an arbitrary tuple as one that represents a program derived + * address, use the {@link assertIsProgramDerivedAddress} or {@link isProgramDerivedAddress} + * functions in this package. */ export type ProgramDerivedAddress = Readonly< [Address, ProgramDerivedAddressBump] >; /** - * A number between 0 and 255, inclusive. + * Represents an integer in the range [0,255] used in the derivation of a program derived address to + * ensure that it does not fall on the Ed25519 curve. */ export type ProgramDerivedAddressBump = number & { readonly __brand: unique symbol; }; /** - * Returns true if the input value is a program derived address. + * A type guard that returns `true` if the input tuple conforms to the {@link ProgramDerivedAddress} + * type, and refines its type for use in your program. + * + * @see The {@link isAddress} function for an example of how to use a type guard. */ export function isProgramDerivedAddress( value: unknown, @@ -49,7 +57,10 @@ export function isProgramDerivedAddress( } /** - * Fails if the input value is not a program derived address. + * In the event that you receive an address/bump-seed tuple from some untrusted source, use this + * function to assert that it conforms to the {@link ProgramDerivedAddress} interface. + * + * @see The {@link assertIsAddress} function for an example of how to use an assertion function. */ export function assertIsProgramDerivedAddress( value: unknown, @@ -121,6 +132,28 @@ async function createProgramDerivedAddress({ programAddress, seeds }: ProgramDer return base58EncodedAddressCodec.decode(addressBytes); } +/** + * Given a program's {@link Address} and up to 16 {@link Seed | Seeds}, this method will return the + * program derived address (PDA) associated with each. + * + * @example + * ```ts + * import { getAddressEncoder, getProgramDerivedAddress } from '@solana/addresses'; + * + * const addressEncoder = getAddressEncoder(); + * const { bumpSeed, pda } = await getProgramDerivedAddress({ + * programAddress: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' as Address, + * seeds: [ + * // Owner + * addressEncoder.encode('9fYLFVoVqwH37C3dyPi6cpeobfbQ2jtLpN5HgAYDDdkm' as Address), + * // Token program + * addressEncoder.encode('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' as Address), + * // Mint + * addressEncoder.encode('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' as Address), + * ], + * }); + * ``` + */ export async function getProgramDerivedAddress({ programAddress, seeds, @@ -144,6 +177,23 @@ export async function getProgramDerivedAddress({ throw new SolanaError(SOLANA_ERROR__ADDRESSES__FAILED_TO_FIND_VIABLE_PDA_BUMP_SEED); } +/** + * Returns a base58-encoded address derived from some base address, some program address, and a seed + * string or byte array. + * + * @example + * ```ts + * import { createAddressWithSeed } from '@solana/addresses'; + * + * const derivedAddress = await createAddressWithSeed({ + * // The private key associated with this address will be able to sign for `derivedAddress`. + * baseAddress: 'B9Lf9z5BfNPT4d5KMeaBFx8x1G4CULZYR1jA2kmxRDka' as Address, + * // Only this program will be able to write data to this account. + * programAddress: '445erYq578p2aERrGW9mn9KiYe3fuG6uHdcJ2LPPShGw' as Address, + * seed: 'data-account', + * }); + * ``` + */ export async function createAddressWithSeed({ baseAddress, programAddress, seed }: SeedInput): Promise
{ const { encode, decode } = getAddressCodec(); diff --git a/packages/addresses/src/public-key.ts b/packages/addresses/src/public-key.ts index 717293101..48e7678f2 100644 --- a/packages/addresses/src/public-key.ts +++ b/packages/addresses/src/public-key.ts @@ -3,6 +3,16 @@ import { SOLANA_ERROR__ADDRESSES__INVALID_ED25519_PUBLIC_KEY, SolanaError } from import { Address, getAddressDecoder } from './address'; +/** + * Given a public {@link CryptoKey}, this method will return its associated {@link Address}. + * + * @example + * ```ts + * import { getAddressFromPublicKey } from '@solana/addresses'; + * + * const address = await getAddressFromPublicKey(publicKey); + * ``` + */ export async function getAddressFromPublicKey(publicKey: CryptoKey): Promise
{ assertKeyExporterIsAvailable(); if (publicKey.type !== 'public' || publicKey.algorithm.name !== 'Ed25519') { diff --git a/packages/addresses/typedoc.json b/packages/addresses/typedoc.json index d99f37ef3..3a90a65cc 100644 --- a/packages/addresses/typedoc.json +++ b/packages/addresses/typedoc.json @@ -1,5 +1,6 @@ { "$schema": "https://typedoc.org/schema.json", "extends": ["../typedoc.base.json"], - "entryPoints": ["src/index.ts"] + "entryPoints": ["src/index.ts"], + "readme": "none" }