Skip to content

Commit

Permalink
point to dev hub
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkSackerberg committed Feb 20, 2024
1 parent 0f59ec2 commit 159fbaa
Show file tree
Hide file tree
Showing 16 changed files with 30 additions and 2,373 deletions.
30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ Umi is a modular framework for building and using JavaScript clients for Solana

## Documentation

- [Installation](https://github.com/metaplex-foundation/umi/blob/main/docs/installation.md)
- [Umi's interfaces](https://github.com/metaplex-foundation/umi/blob/main/docs/interfaces.md)
- [Interface implementations](https://github.com/metaplex-foundation/umi/blob/main/docs/implementations.md)
- [Public keys and signers](https://github.com/metaplex-foundation/umi/blob/main/docs/publickeys-signers.md)
- [Connecting with an RPC](https://github.com/metaplex-foundation/umi/blob/main/docs/rpc.md)
- [Sending transactions](https://github.com/metaplex-foundation/umi/blob/main/docs/transactions.md)
- [Fetching accounts](https://github.com/metaplex-foundation/umi/blob/main/docs/accounts.md)
- [Serializers](https://github.com/metaplex-foundation/umi/blob/main/docs/serializers.md)
- [Uploading and downloading assets](https://github.com/metaplex-foundation/umi/blob/main/docs/storage.md)
- [Sending Http requests](https://github.com/metaplex-foundation/umi/blob/main/docs/http.md)
- [Registering programs](https://github.com/metaplex-foundation/umi/blob/main/docs/programs.md)
- [Umi plugins](https://github.com/metaplex-foundation/umi/blob/main/docs/plugins.md)
- [Generating Umi clients via Kinobi](https://github.com/metaplex-foundation/umi/blob/main/docs/kinobi.md)
- [Umi helpers](https://github.com/metaplex-foundation/umi/blob/main/docs/helpers.md)
- [Web3.js adapters](https://github.com/metaplex-foundation/umi/blob/main/docs/web3js-adapters.md)
- [Installation](https://developers.metaplex.com/umi/getting-started)
- [Umi's interfaces](https://developers.metaplex.com/umi/interfaces)
- [Interface implementations](https://developers.metaplex.com/umi/implementations)
- [Public keys and signers](https://developers.metaplex.com/umi/public-keys-and-signers)
- [Connecting with an RPC](https://developers.metaplex.com/umi/rpc)
- [Sending transactions](https://developers.metaplex.com/umi/transactions)
- [Fetching accounts](https://developers.metaplex.com/umi/accounts)
- [Serializers](https://developers.metaplex.com/umi/serializers)
- [Uploading and downloading assets](https://developers.metaplex.com/umi/storage)
- [Sending Http requests](https://developers.metaplex.com/umi/http-requests)
- [Registering programs](https://developers.metaplex.com/umi/programs)
- [Umi plugins](https://developers.metaplex.com/umi/plugins)
- [Generating Umi clients via Kinobi](https://developers.metaplex.com/umi/kinobi)
- [Umi helpers](https://developers.metaplex.com/umi/helpers)
- [Web3.js adapters](https://developers.metaplex.com/umi/web3js-adapters)
131 changes: 1 addition & 130 deletions docs/accounts.md
Original file line number Diff line number Diff line change
@@ -1,130 +1 @@
# Fetching accounts

Let's see how we can fetch account data from the Solana blockchain using Umi. For that, we will need the [`RpcInterface`](https://umi-docs.vercel.app/interfaces/umi.RpcInterface.html) to fetch accounts with serialized data and [serializers](./serializers.md) to help deserialize them.

## Account definitions

Umi defines an account with serialized data as an `RpcAccount`. It contains information from the account header — i.e. the SOL on the account, the program owner, etc. — and the account's public key and serialized data.

```ts
type RpcAccount = AccountHeader & {
publicKey: PublicKey;
data: Uint8Array;
};
```

It also defines a `MaybeRpcAccount` type that represents an `RpcAccount` that may or may exist. When the account does not exist, it keeps track of its public key so that, in a list of accounts, we know which public key was not found.

```ts
type MaybeRpcAccount =
| ({ exists: true } & RpcAccount)
| { exists: false; publicKey: PublicKey };
```

When dealing with `MaybeRpcAccount`s, you may use the `assertAccountExists` helper method to assert that an account exists and fail otherwise.

```ts
assertAccountExists(myMaybeAccount);
// From now on, we know myMaybeAccount is an RpcAccount.
```

Last but not least, it provides a generic `Account` type that directly exposes the deserialized data — represented as a generic type `T` — with two extra attributes: `publicKey` and `header`. This allows us to directly access the deserialized data without nested `data` attributes.

```ts
type Account<T extends object> = T & {
publicKey: PublicKey;
header: AccountHeader;
};
```

## Fetching RPC accounts

Now that we know how accounts are represented in Umi, let's see how we can fetch them.

First of all, we can fetch a single account using the `getAccount` method of the `RpcInterface`. This will return a `MaybeRpcAccount` instance since the account may or may not exist. As mentioned above, you may use the `assertAccountExists` function to ensure it does.

```ts
const myAccount = await umi.rpc.getAccount(myPublicKey);
assertAccountExists(myAccount);
```

Note that if you are only interested to know if an account exists at the given address, you may use the `accountExists` method instead.

```ts
const accountExists = await umi.rpc.accountExists(myPublicKey);
```

If you need to fetch multiple accounts at once, you may use the `getAccounts` method instead. This will return a list of `MaybeRpcAccount`s, one for each public key you passed in.

```ts
const myAccounts = await umi.rpc.getAccounts(myPublicKeys);
```

Finally, the `getProgramAccounts` method can be used to fetch all accounts from a given program that match a given set of filters. This method returns a list of `RpcAccount` directly since it will only return accounts that exist. Refer to the following [Get Program Account documentation](https://solanacookbook.com/guides/get-program-accounts.html) to learn more about filters and data slicing.

```ts
// Fetch all accounts from a program.
const allProgramAccounts = await umi.rpc.getProgramAccounts(myProgramId);

// Fetch a slice of all accounts from a program.
const slicedProgramAccounts = await umi.rpc.getProgramAccounts(myProgramId, {
dataSlice: { offset: 32, length: 8 },
});

// Fetch some accounts from a program that matches a given set of filters.
const filteredProgramAccounts = await umi.rpc.getProgramAccounts(myProgramId, {
filters: [
{ dataSize: 42 },
{ memcmp: { offset: 0, bytes: new Uint8Array([1, 2, 3]) } },
],
});
```

Note that when fetching program accounts, you might be interested in [`GpaBuilder`s](./helpers.md#gpabuilders).

## Deserializing accounts

In order to turn a `RpcAccount` into a deserialized `Account<T>`, we simply need the `deserializeAccount` function and a `Serializer` that knows how to deserialize the account's data. You can read more about `Serializer`s in the [Serializers page](./serializers.md) but here's a quick example assuming the data is composed of two public keys and one `u64` number.

```ts
import { assertAccountExists, deserializeAccount } from '@metaplex-foundation/umi';
import { struct, publicKey, u64 } from '@metaplex-foundation/umi/serializers';

// Given an existing RPC account.
const myRpcAccount = await umi.rpc.getAccount(myPublicKey);
assertAccountExists(myRpcAccount);

// And an account data serializer.
const myDataSerializer = struct([
['source', publicKey()],
['destination', publicKey()],
['amount', u64()],
]);

// We can deserialize the account like so.
const myAccount = deserializeAccount(rawAccount, myDataSerializer);
// myAccount.source -> PublicKey
// myAccount.destination -> PublicKey
// myAccount.amount -> bigint
// myAccount.publicKey -> PublicKey
// myAccount.header -> AccountHeader
```

Note that, in practice, program libraries should provide account data serializers and helpers for you. Here's an example using a [Kinobi-generated library](./kinobi.md).

```ts
import { Metadata, deserializeMetadata, fetchMetadata, safeFetchMetadata } from '@metaplex-foundation/mpl-token-metadata';

// Deserializes a metadata account.
const metadata: Metadata = deserializeMetadata(umi, unparsedMetadataAccount);

// Fetch and deserialize a metadata account, fail if the account does not exist.
const metadata: Metadata = await fetchMetadata(umi, metadataPublicKey);

// Fetch and deserialize a metadata account, return null if the account does not exist.
const metadata: Metadata | null = await safeFetchMetadata(umi, metadataPublicKey);
```

<p align="center">
<strong>Next: <a href="./serializers.md">Serializers ≫</a></strong>
</p>
> :warning: **Not updated anymore**: Please see the page on the [Developer Hub](https://developers.metaplex.com/umi/accounts) instead!
195 changes: 1 addition & 194 deletions docs/helpers.md
Original file line number Diff line number Diff line change
@@ -1,194 +1 @@
# Umi helpers

On top of the core interfaces, Umi provides a set of helper functions that can be used to make working with Solana programs easier.

## Amounts

An `Amount` is a special type that allows us to define big decimal numbers. It does this by representing the number in its lowest possible unit (e.g. lamports) and then keeping track of the decimal number of that unit (e.g. 9). This allows for a more accurate representation of the number and avoids JavaScript rounding errors caused by IEEE 754 floating point numbers. It also uses a string identifier to ensure that we are dealing with amounts in the same unit when performing operations. Here's how the `Amount` generic type is defined:

```ts
type AmountIdentifier = 'SOL' | 'USD' | '%' | 'splToken' | string;
type AmountDecimals = number;
type Amount<
I extends AmountIdentifier = AmountIdentifier,
D extends AmountDecimals = AmountDecimals
> = {
/** The amount in its lower possible unit such that it does not contain decimals. */
basisPoints: bigint;
/** The identifier of the amount. */
identifier: I;
/** The number of decimals in the amount. */
decimals: D;
};
```

Umi also provides specific versions of this `Amount` type for specific cases like SOLs and USDs.

```ts
type SolAmount = Amount<'SOL', 9>;
type UsdAmount = Amount<'USD', 2>;
type PercentAmount<D extends AmountDecimals> = Amount<'%', D>;
```

To make it easier for developers to handle amounts, Umi provides a set of helper functions that can be used to create, format, and perform operations on amounts.

You may want to [check out the "Utils — Amounts" section of the API references](https://umi-docs.vercel.app/modules/umi.html) to learn more about all these helpers but here's a quick list of functions that can help create new amount types.

```ts
// Creates an amount from basis points.
createAmount(123, 'USD', 2); // -> Amount for "USD 1.23"

// Creates an amount from a decimal number.
createAmountFromDecimals(1.23, 'USD', 2); // -> Amount for "USD 1.23"

// Helper functions to create USD amounts.
usd(1.23) // -> Amount for "USD 1.23"

// Helper functions to handle SOL amounts.
sol(1.23) // -> Amount for "1.23 SOL"
lamports(1_230_000_000) // -> Amount for "1.23 SOL"

// Helper function to create percent amounts.
percentAmount(50.42); // -> Amount for "50.42%"
percentAmount(50.42, 2); // -> Amount for "50.42%"
percentAmount(50.42, 0); // -> Amount for "50%"

// Helper function to create token amounts.
tokenAmount(123); // -> Amount for "123 Tokens"
tokenAmount(123, 'splToken.BONK'); // -> Amount for "123 BONK"
tokenAmount(123.45, 'splToken.BONK', 2); // -> Amount for "123.45 BONK"
```

## Options

In Rust, we define optional values as an `Option<T>` enum which can either be `Some(T)` or `None`. This is usually represented as `T | null` in the JavaScript world. The issue with this approach is it doesn't work with nested options. For instance, an `Option<Option<T>>` in Rust would become a `T | null | null` in JavaScript which is equivalent to `T | null`. That means, there is no way for us to represent the `Some(None)` value in JavaScript or any other nested option.

To solve this issue, Umi provides [an `Option<T>` union type](https://umi-docs.vercel.app/types/umi.Option.html) that works very similarly to the Rust `Option<T>` type. It is defined as follows:

```ts
type Option<T> = Some<T> | None;
type Some<T> = { __option: 'Some'; value: T };
type None = { __option: 'None' };
```

To improve the developer experience, Umi offers a `some` and `none` function to create options. The type `T` of the option can either be inferred by TypeScript or explicitly provided.

```ts
// Create an option with a value.
some('Hello World');
some<number | string>(123);

// Create an empty option.
none();
none<number | string>();
```

Umi also provides a set of helper functions to verify and manipulate options.

```ts
// Check if an option is a `Some` or `None`.
isSome(some('Hello World')); // -> true
isSome(none()); // -> false
isNone(some('Hello World')); // -> false
isNone(none()); // -> true

// Unwrap the value of an option if it is a `Some` or return null.
// Supports custom fallback values for `None`.
unwrapOption(some('Hello World')) // -> 'Hello World'
unwrapOption(none()) // -> null
unwrapOption(some('Hello World'), () => 'Default'); // -> 'Hello World'
unwrapOption(none(), () => 'Default'); // -> 'Default'

// Same as `unwrapOption` but recursively (without mutating the original object/array).
// Also supports custom fallback values for `None`.
unwrapOptionRecursively({
a: 'hello',
b: none<string>(),
c: [{ c1: some(42) }, { c2: none<number>() }],
}) // -> { a: 'hello', b: null, c: [{ c1: 42 }, { c2: null }] }
```

## DateTimes

Umi provides a `DateTime` type that can be used to represent a date and time using a timestamp in seconds. It is simply defined as a `bigint` number and offers a set of helper functions to create and format date times.

```ts
// Create a new DateTime.
dateTime(1680097346);
dateTime(new Date(Date.now()));
dateTime("2021-12-31T23:59:59.000Z");

// Create a new DateTime for the current time.
now();

// Format a DateTime.
formatDateTime(now());
formatDateTime(now(), 'fr-FR', myFormatOptions);
```

## GpaBuilders

To help prepare `getProgramAccounts` RPC requests, Umi provides [an immutable `GpaBuilder` helper class](https://umi-docs.vercel.app/classes/umi.GpaBuilder.html). It can be used to add filters, slice data and fetch the raw accounts whilst mapping them to whatever we want. Here are some examples.

```ts
// Get all accounts for a program.
await gpaBuilder(umi, programId).get();

// Get the first 32 bytes of accounts that are 500 bytes long.
await gpaBuilder(umi, programId)
.slice(0, 32)
.whereSize(500)
.get();

// Get the public keys of accounts that have a given public key at offset 32.
await gpaBuilder(umi, programId)
.withoutData()
.where(32, myPublicKey)
.getPublicKey();

// Get the first 32 bytes of the account data as public keys.
await gpaBuilder(umi, programId)
.slice(0, 32)
.getDataAsPublicKey();

// Get the second byte of the account data and multiply it by 2.
await gpaBuilder(umi, programId)
.slice(1, 1)
.getAndMap((n) => n * 2);
```

`GpaBuilder`s can also be told how to deserialize a raw account into a deserialized account via the `deserializeUsing` method. Once a deserialization callback was provided, the `getDeserialized` method can be used to fetch the deserialized accounts.

```ts
const metadataGpaBuilder = gpaBuilder(umi, programId)
.deserializeUsing<Metadata>((account) => deserializeMetadata(umi, account));

const accounts: Metadata[] = await metadataGpaBuilder.getDeserialized();
```

Additionally, we can pass a set of fields with their offsets to a `GpaBuilder` to improve the developer experience around filtering and slicing data. To do so, we can use the `registerFields` method. For instance, say we know that starting from byte 16, the next 32 bytes represent a `name` via a fixed size string and the next 4 bytes after that represent an `age`. Here's how we could register those fields.

```ts
import { gpaBuilder } from '@metaplex-foundation/umi';
import { string, u32 } from '@metaplex-foundation/umi/serializers';

const myGpaBuilderWithFields = gpaBuilder(umi, programId)
.registerFields<{ name: string; age: number; }>({
name: [16, string({ size: 32 })],
age: [48, u32()],
})
```

Once the fields are registered, we can use the `whereField` and `sliceField` methods to filter and slice data using fields. Not only it will know which offset to use but how to serialize its value.

```ts
// Get the name of accounts that have an age of 42.
await myGpaBuilderWithFields
.whereField('age', 42)
.sliceField('name')
.get();
```

<p align="center">
<strong>Next: <a href="./web3js-adapters.md">Web3.js adapters ≫</a></strong>
</p>
> :warning: **Not updated anymore**: Please see the page on the [Developer Hub](https://developers.metaplex.com/umi/helpers) instead!
27 changes: 1 addition & 26 deletions docs/http.md
Original file line number Diff line number Diff line change
@@ -1,26 +1 @@
# Sending Http requests

Umi provides a simple `HttpInterface` that can be used to send HTTP requests. This allows any Umi plugin or third-party library to rely on whichever Http client the end-user chooses to use instead of ending up with multiple Http clients in the same project.

The `HttpInterface` only defines a single method `send` which accepts a generic `HttpRequest<T>` and returns a generic `HttpResponse<U>` such that `T` and `U` are the request and response body types respectively.

Umi defines various Http-related types such as `HttpHeaders` and `HttpMethod` that are used by the `HttpRequest` and `HttpResponse` types. In order to improve the developer experience around sending requests, Umi ships with a little request builder that can be used to create `HttpRequest` instances. You may want to check the [API references of the `HttpRequestBuilder` type](https://umi-docs.vercel.app/classes/umi.HttpRequestBuilder.html) to learn more about it but here are some examples:

```ts
// GET JSON request.
await umi.http.send(request().get('https://example.com/users/1').asJson());

// POST Form request.
const data = { name: 'John Doe', email: '[email protected]' };
await umi.http.send(request().post('https://example.com/users').asForm().withData(data));

// PUT request with bearer token.
await umi.http.send(request().put('https://example.com/users/1').withToken('my-token'));

// GET request with abort signal
await umi.http.send(request().get('https://example.com/users').withAbortSignal(mySignal));
```

<p align="center">
<strong>Next: <a href="./programs.md">Registering programs ≫</a></strong>
</p>
> :warning: **Not updated anymore**: Please see the page on the [Developer Hub](https://developers.metaplex.com/umi/http-requests) instead!
Loading

0 comments on commit 159fbaa

Please sign in to comment.