Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

Commit

Permalink
refactor(experimental): rename Base58EncodedAddress to Address (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
steveluscher authored Nov 7, 2023
1 parent ffeddf6 commit 63683a4
Show file tree
Hide file tree
Showing 107 changed files with 709 additions and 740 deletions.
38 changes: 19 additions & 19 deletions packages/addresses/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ This package contains utilities for generating account addresses. It can be used

## Types

### `Base58EncodedAddress`
### `Address`

This type represents a string that validates as a Solana address. Functions that require well-formed addresses should specify their inputs in terms of this type.

Expand All @@ -38,7 +38,7 @@ This type represents an integer between 0-255 used as a seed when deriving a pro

### `address()`

This helper combines _asserting_ that a string is an address with _coercing_ it to the `Base58EncodedAddress` type. It's best used with untrusted input.
This helper combines _asserting_ that a string is an address with _coercing_ it to the `Address` type. It's best used with untrusted input.

```ts
import { address } from '@solana/addresses';
Expand All @@ -49,15 +49,15 @@ await transfer(address(fromAddress), address(toAddress), lamports(100000n));
When starting from a known-good address as a string, it's more efficient to typecast it rather than to use the `address()` helper, because the helper unconditionally performs validation on its input.

```ts
import { Base58EncodedAddress } from '@solana/addresses';
import { Address } from '@solana/addresses';

const MEMO_PROGRAM_ADDRESS =
'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr' as Base58EncodedAddress<'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'>;
'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr' as Address<'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'>;
```

### `assertIsAddress()`

Client applications primarily deal with addresses and public keys in the form of base58-encoded strings. Addresses returned from the RPC API conform to the type `Base58EncodedAddress`. You can use a value of that type wherever a base58-encoded address is expected.
Client applications primarily deal with addresses and public keys in the form of base58-encoded strings. Addresses returned from the RPC API conform to the type `Address`. You can use a value of that type wherever a base58-encoded address is expected.

From time to time you might acquire a string, that you expect to validate as an address, from an untrusted network API or user input. To assert that such an arbitrary string is a base58-encoded address, use the `assertIsAddress` function.

Expand All @@ -70,9 +70,9 @@ function handleSubmit() {
const address: string = accountAddressInput.value;
try {
// If this type assertion function doesn't throw, then
// Typescript will upcast `address` to `Base58EncodedAddress`.
// Typescript will upcast `address` to `Address`.
assertIsAddress(address);
// At this point, `address` is a `Base58EncodedAddress` that can be used with the RPC.
// At this point, `address` is an `Address` that can be used with the RPC.
const balanceInLamports = await rpc.getBalance(address).send();
} catch (e) {
// `address` turned out not to be a base58-encoded address
Expand All @@ -95,16 +95,16 @@ 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 Base58EncodedAddress,
baseAddress: 'B9Lf9z5BfNPT4d5KMeaBFx8x1G4CULZYR1jA2kmxRDka' as Address,
// Only this program will be able to write data to this account.
programAddress: '445erYq578p2aERrGW9mn9KiYe3fuG6uHdcJ2LPPShGw' as Base58EncodedAddress,
programAddress: '445erYq578p2aERrGW9mn9KiYe3fuG6uHdcJ2LPPShGw' as Address,
seed: 'data-account',
});
```

### `getAddressDecoder()`

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. Returns a tuple of the `Base58EncodedAddress` and the offset within the byte array at which the decoder stopped reading.
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. Returns a tuple of the `Address` and the offset within the byte array at which the decoder stopped reading.

```ts
import { getAddressDecoder } from '@solana/addresses';
Expand All @@ -124,7 +124,7 @@ Returns an encoder that you can use to encode a base58-encoded address to a byte
```ts
import { getAddressEncoder } from '@solana/addresses';

const address = 'B9Lf9z5BfNPT4d5KMeaBFx8x1G4CULZYR1jA2kmxRDka' as Base58EncodedAddress;
const address = 'B9Lf9z5BfNPT4d5KMeaBFx8x1G4CULZYR1jA2kmxRDka' as Address;
const addressEncoder = getAddressEncoder();
const addressBytes = addressEncoder.encode(address);
// Uint8Array(32) [
Expand All @@ -137,7 +137,7 @@ const addressBytes = addressEncoder.encode(address);

### `getAddressFromPublicKey()`

Given a public `CryptoKey`, this method will return its associated `Base58EncodedAddress`.
Given a public `CryptoKey`, this method will return its associated `Address`.

```ts
import { getAddressFromPublicKey } from '@solana/addresses';
Expand All @@ -147,35 +147,35 @@ const address = await getAddressFromPublicKey(publicKey);

### `getProgramDerivedAddress()`

Given a program's `Base58EncodedAddress` and up to 16 `Seeds`, this method will return the program derived address (PDA) associated with each.
Given a program's `Address` and up to 16 `Seeds`, this method will return the program derived address (PDA) associated with each.

```ts
import { getAddressEncoder, getProgramDerivedAddress } from '@solana/addresses';

const addressEncoder = getAddressEncoder();
const { bumpSeed, pda } = await getProgramDerivedAddress({
programAddress: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' as Base58EncodedAddress,
programAddress: 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' as Address,
seeds: [
// Owner
addressEncoder.encode('9fYLFVoVqwH37C3dyPi6cpeobfbQ2jtLpN5HgAYDDdkm' as Base58EncodedAddress),
addressEncoder.encode('9fYLFVoVqwH37C3dyPi6cpeobfbQ2jtLpN5HgAYDDdkm' as Address),
// Token program
addressEncoder.encode('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' as Base58EncodedAddress),
addressEncoder.encode('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' as Address),
// Mint
addressEncoder.encode('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' as Base58EncodedAddress),
addressEncoder.encode('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' as Address),
],
});
```

### `isAddress()`

This is a type guard that accepts a string as input. It will both return `true` if the string conforms to the `Base58EncodedAddress` type and will refine the type for use in your program.
This is a type guard that accepts a string as input. It will both return `true` if the string conforms to the `Address` type and will refine the type for use in your program.

```ts
import { isAddress } from '@solana/addresses';

if (isAddress(ownerAddress)) {
// At this point, `ownerAddress` has been refined to a
// `Base58EncodedAddress` that can be used with the RPC.
// `Address` that can be used with the RPC.
const { value: lamports } = await rpc.getBalance(ownerAddress).send();
setBalanceLamports(lamports);
} else {
Expand Down
12 changes: 6 additions & 6 deletions packages/addresses/src/__tests__/address-test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Encoder } from '@solana/codecs-core';
import { getBase58Decoder, getBase58Encoder } from '@solana/codecs-strings';

import { Base58EncodedAddress, getAddressCodec, getAddressComparator } from '../address';
import { Address, getAddressCodec, getAddressComparator } from '../address';

jest.mock('@solana/codecs-strings', () => ({
...jest.requireActual('@solana/codecs-strings'),
Expand All @@ -14,7 +14,7 @@ const originalBase58Module = jest.requireActual('@solana/codecs-strings');
const originalGetBase58Encoder = originalBase58Module.getBase58Encoder();
const originalGetBase58Decoder = originalBase58Module.getBase58Decoder();

describe('Base58EncodedAddress', () => {
describe('Address', () => {
describe('assertIsAddress()', () => {
let assertIsAddress: typeof import('../address').assertIsAddress;
// Reload `assertIsAddress` before each test to reset memoized state
Expand Down Expand Up @@ -118,7 +118,7 @@ describe('Base58EncodedAddress', () => {
it('serializes a base58 encoded address into a 32-byte buffer', () => {
expect(
address.encode(
'4wBqpZM9xaSheZzJSMawUHDgZ7miWfSsxmfVF5jJpYP' as Base58EncodedAddress<'4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw'>
'4wBqpZM9xaSheZzJSMawUHDgZ7miWfSsxmfVF5jJpYP' as Address<'4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw'>
)
).toEqual(
new Uint8Array([
Expand All @@ -138,7 +138,7 @@ describe('Base58EncodedAddress', () => {
])
)[0]
).toBe(
'4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw' as Base58EncodedAddress<'4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw'>
'4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw' as Address<'4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw'>
);
});
it('fatals when trying to deserialize a byte buffer shorter than 32-bytes', () => {
Expand All @@ -160,12 +160,12 @@ describe('Base58EncodedAddress', () => {

address = getAddressCodec!();
address.encode(
'4wBqpZM9xaSheZzJSMawUHDgZ7miWfSsxmfVF5jJpYP' as Base58EncodedAddress<'4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw'>
'4wBqpZM9xaSheZzJSMawUHDgZ7miWfSsxmfVF5jJpYP' as Address<'4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw'>
);

address = getAddressCodec!();
address.encode(
'4wBqpZM9xaSheZzJSMawUHDgZ7miWfSsxmfVF5jJpYP' as Base58EncodedAddress<'4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw'>
'4wBqpZM9xaSheZzJSMawUHDgZ7miWfSsxmfVF5jJpYP' as Address<'4wBqpZM9xaSheZzJSMawUKKwhdpChKbZ5eu5ky4Vigw'>
);

expect(jest.mocked(getBase58Encoder)).toHaveBeenCalledTimes(1);
Expand Down
8 changes: 4 additions & 4 deletions packages/addresses/src/__tests__/coercions-test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { address, Base58EncodedAddress } from '../address';
import { Address, address } from '../address';

describe('coercions', () => {
describe('address', () => {
it('can coerce to `Base58EncodedAddress`', () => {
it('can coerce to `Address`', () => {
// See scripts/fixtures/GQE2yjns7SKKuMc89tveBDpzYHwXfeuB2PGAbGaPWc6G.json
const raw =
'GQE2yjns7SKKuMc89tveBDpzYHwXfeuB2PGAbGaPWc6G' as Base58EncodedAddress<'GQE2yjns7SKKuMc89tveBDpzYHwXfeuB2PGAbGaPWc6G'>;
'GQE2yjns7SKKuMc89tveBDpzYHwXfeuB2PGAbGaPWc6G' as Address<'GQE2yjns7SKKuMc89tveBDpzYHwXfeuB2PGAbGaPWc6G'>;
const coerced = address('GQE2yjns7SKKuMc89tveBDpzYHwXfeuB2PGAbGaPWc6G');
expect(coerced).toBe(raw);
});
it('throws on invalid `Base58EncodedAddress`', () => {
it('throws on invalid `Address`', () => {
const thisThrows = () => address('3333333333333333');
expect(thisThrows).toThrow('`3333333333333333` is not a base-58 encoded address');
});
Expand Down
40 changes: 20 additions & 20 deletions packages/addresses/src/__tests__/program-derived-address-test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Base58EncodedAddress } from '../address';
import { Address } from '../address';
import { createAddressWithSeed, getProgramDerivedAddress } from '../program-derived-address';

describe('getProgramDerivedAddress()', () => {
it('fatals when supplied more than 16 seeds', async () => {
expect.assertions(1);
await expect(
getProgramDerivedAddress({
programAddress: 'FN2R9R724eb4WaxeDmDYrUtmJgoSzkBiQMEHELV3ocyg' as Base58EncodedAddress,
programAddress: 'FN2R9R724eb4WaxeDmDYrUtmJgoSzkBiQMEHELV3ocyg' as Address,
seeds: Array(17).fill(''),
})
).rejects.toThrow(/A maximum of 16 seeds/);
Expand All @@ -19,7 +19,7 @@ describe('getProgramDerivedAddress()', () => {
expect.assertions(1);
await expect(
getProgramDerivedAddress({
programAddress: '5eUi55m4FVaDqKubGH9r6ca1TxjmimmXEU9v1WUZJ47Z' as Base58EncodedAddress,
programAddress: '5eUi55m4FVaDqKubGH9r6ca1TxjmimmXEU9v1WUZJ47Z' as Address,
seeds: [oversizedSeed],
})
).rejects.toThrow(/exceeds the maximum length of 32 bytes/);
Expand All @@ -28,7 +28,7 @@ describe('getProgramDerivedAddress()', () => {
expect.assertions(1);
await expect(
getProgramDerivedAddress({
programAddress: 'CZ3TbkgUYpDAJVEWpujQhDSgzNTeqbokrJmYa1j4HAZc' as Base58EncodedAddress,
programAddress: 'CZ3TbkgUYpDAJVEWpujQhDSgzNTeqbokrJmYa1j4HAZc' as Address,
seeds: [],
})
).resolves.toStrictEqual(['9tVtkyCGAHSDDBPwz7895aC3p2gJRjpu2v26o35FTUco', 255]);
Expand All @@ -37,7 +37,7 @@ describe('getProgramDerivedAddress()', () => {
expect.assertions(1);
await expect(
getProgramDerivedAddress({
programAddress: 'EfTbwNBrSqSuCNBhWUHsBoBdSMWgRU1S47daqRNgW7aK' as Base58EncodedAddress,
programAddress: 'EfTbwNBrSqSuCNBhWUHsBoBdSMWgRU1S47daqRNgW7aK' as Address,
seeds: [],
})
).resolves.toStrictEqual(['CKWT8KZ5GMzKpVRiAULWKPg1LiHt9U3NdAtbuTErHCTq', 251]);
Expand All @@ -46,7 +46,7 @@ describe('getProgramDerivedAddress()', () => {
expect.assertions(1);
await expect(
getProgramDerivedAddress({
programAddress: 'FD3PDEvpQ9JXq8tv7FpJPyZrCjWkCnAaTju16gFPdpqP' as Base58EncodedAddress,
programAddress: 'FD3PDEvpQ9JXq8tv7FpJPyZrCjWkCnAaTju16gFPdpqP' as Address,
seeds: [new Uint8Array([1, 2, 3])],
})
).resolves.toStrictEqual(['9Tj3hpMWacDiZoBe94sjwJQ72zsUVvEQYsrqyy2CfHky', 255]);
Expand All @@ -55,7 +55,7 @@ describe('getProgramDerivedAddress()', () => {
expect.assertions(1);
await expect(
getProgramDerivedAddress({
programAddress: '9HT3iB4oX1aZPH5V8eNUGByKuwhfcKjBQ3x9rfEAuNeF' as Base58EncodedAddress,
programAddress: '9HT3iB4oX1aZPH5V8eNUGByKuwhfcKjBQ3x9rfEAuNeF' as Address,
seeds: [new Uint8Array([1, 2, 3])],
})
).resolves.toStrictEqual(['EeTcRajHcPh74C5D4GqZePac1wYB7Dj9ChTaNHaTH77V', 251]);
Expand All @@ -64,7 +64,7 @@ describe('getProgramDerivedAddress()', () => {
expect.assertions(1);
await expect(
getProgramDerivedAddress({
programAddress: 'EKaNRGA37uiGRyRPMap5EZg9cmbT5mt7KWrGwKwAQ3rK' as Base58EncodedAddress,
programAddress: 'EKaNRGA37uiGRyRPMap5EZg9cmbT5mt7KWrGwKwAQ3rK' as Address,
seeds: ['hello'],
})
).resolves.toStrictEqual(['6V76gtKMCmVVjrx4sxR9uB868HtZbL3piKEmadC7rSgf', 255]);
Expand All @@ -73,7 +73,7 @@ describe('getProgramDerivedAddress()', () => {
expect.assertions(1);
await expect(
getProgramDerivedAddress({
programAddress: '9PyoV2rqNtoboSvg2JD7GWhM5RQvHGwgdDvK7MCfpgX1' as Base58EncodedAddress,
programAddress: '9PyoV2rqNtoboSvg2JD7GWhM5RQvHGwgdDvK7MCfpgX1' as Address,
seeds: ['hello'],
})
).resolves.toStrictEqual(['E6npEurFu1UEbQFh1DsqBvny17XxUK2QPMgxD3Edn3aG', 251]);
Expand All @@ -82,7 +82,7 @@ describe('getProgramDerivedAddress()', () => {
expect.assertions(1);
await expect(
getProgramDerivedAddress({
programAddress: 'A5dcVPLJsE2vbf7hkqqyYkYDK9UjUfNxuwGtWF2m2vEz' as Base58EncodedAddress,
programAddress: 'A5dcVPLJsE2vbf7hkqqyYkYDK9UjUfNxuwGtWF2m2vEz' as Address,
seeds: ['\uD83D\uDE80'],
})
).resolves.toStrictEqual(['GYpAzW57Ex4Sw3rp4pq95QrjvtsDyqZsMhSZwqz3NMsE', 255]);
Expand All @@ -91,7 +91,7 @@ describe('getProgramDerivedAddress()', () => {
expect.assertions(1);
await expect(
getProgramDerivedAddress({
programAddress: 'H8gBP21L5ietkHgXcGbgQBCVVEdPUQyuP9Q5MPRLLSJu' as Base58EncodedAddress,
programAddress: 'H8gBP21L5ietkHgXcGbgQBCVVEdPUQyuP9Q5MPRLLSJu' as Address,
seeds: ['\uD83D\uDE80'],
})
).resolves.toStrictEqual(['46v3JvPtEPeQmH3euXydEbxYD6yfxeZjWSzkkYvvM5Pp', 251]);
Expand All @@ -100,11 +100,11 @@ describe('getProgramDerivedAddress()', () => {
expect.assertions(1);
const [pdaButterfly, pdaButterFly] = await Promise.all([
getProgramDerivedAddress({
programAddress: '9PyoV2rqNtoboSvg2JD7GWhM5RQvHGwgdDvK7MCfpgX1' as Base58EncodedAddress,
programAddress: '9PyoV2rqNtoboSvg2JD7GWhM5RQvHGwgdDvK7MCfpgX1' as Address,
seeds: ['butterfly'],
}),
getProgramDerivedAddress({
programAddress: '9PyoV2rqNtoboSvg2JD7GWhM5RQvHGwgdDvK7MCfpgX1' as Base58EncodedAddress,
programAddress: '9PyoV2rqNtoboSvg2JD7GWhM5RQvHGwgdDvK7MCfpgX1' as Address,
seeds: ['butter', 'fly'],
}),
]);
Expand All @@ -119,9 +119,9 @@ describe('getProgramDerivedAddress()', () => {
describe('createAddressWithSeed', () => {
it('returns an address that is the SHA-256 hash of the concatenated base address, seed, and program address', async () => {
expect.assertions(2);
const baseAddress = 'Bh1uUDP3ApWLeccVNHwyQKpnfGQbuE2UECbGA6M4jiZJ' as Base58EncodedAddress;
const programAddress = 'FGrddpvjBUAG6VdV4fR8Q2hEZTHS6w4SEveVBgfwbfdm' as Base58EncodedAddress;
const expectedAddress = 'HUKxCeXY6gZohFJFARbLE6L6C9wDEHz1SfK8ENM7QY7z' as Base58EncodedAddress;
const baseAddress = 'Bh1uUDP3ApWLeccVNHwyQKpnfGQbuE2UECbGA6M4jiZJ' as Address;
const programAddress = 'FGrddpvjBUAG6VdV4fR8Q2hEZTHS6w4SEveVBgfwbfdm' as Address;
const expectedAddress = 'HUKxCeXY6gZohFJFARbLE6L6C9wDEHz1SfK8ENM7QY7z' as Address;

await expect(createAddressWithSeed({ baseAddress, programAddress, seed: 'seed' })).resolves.toEqual(
expectedAddress
Expand All @@ -133,18 +133,18 @@ describe('createAddressWithSeed', () => {
});
it('fails when the seed is longer than 32 bytes', async () => {
expect.assertions(1);
const baseAddress = 'Bh1uUDP3ApWLeccVNHwyQKpnfGQbuE2UECbGA6M4jiZJ' as Base58EncodedAddress;
const programAddress = 'FGrddpvjBUAG6VdV4fR8Q2hEZTHS6w4SEveVBgfwbfdm' as Base58EncodedAddress;
const baseAddress = 'Bh1uUDP3ApWLeccVNHwyQKpnfGQbuE2UECbGA6M4jiZJ' as Address;
const programAddress = 'FGrddpvjBUAG6VdV4fR8Q2hEZTHS6w4SEveVBgfwbfdm' as Address;

await expect(createAddressWithSeed({ baseAddress, programAddress, seed: 'a'.repeat(33) })).rejects.toThrow(
'The seed exceeds the maximum length of 32 bytes'
);
});
it('fails with a malicious programAddress meant to produce an address that would collide with a PDA', async () => {
expect.assertions(1);
const baseAddress = 'Bh1uUDP3ApWLeccVNHwyQKpnfGQbuE2UECbGA6M4jiZJ' as Base58EncodedAddress;
const baseAddress = 'Bh1uUDP3ApWLeccVNHwyQKpnfGQbuE2UECbGA6M4jiZJ' as Address;
// The ending bytes of this address decode to the ASCII string 'ProgramDerivedAddress'
const programAddress = '4vJ9JU1bJJE96FbKdjWme2JfVK1knU936FHTDZV7AC2' as Base58EncodedAddress;
const programAddress = '4vJ9JU1bJJE96FbKdjWme2JfVK1knU936FHTDZV7AC2' as Address;

await expect(createAddressWithSeed({ baseAddress, programAddress, seed: 'seed' })).rejects.toThrow(
'programAddress cannot end with the PDA marker'
Expand Down
4 changes: 2 additions & 2 deletions packages/addresses/src/__typetests__/coercions-typetests.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { address, Base58EncodedAddress } from '../address';
import { Address, address } from '../address';

address('555555555555555555555555') satisfies Base58EncodedAddress<'555555555555555555555555'>;
address('555555555555555555555555') satisfies Address<'555555555555555555555555'>;
Loading

0 comments on commit 63683a4

Please sign in to comment.