@solana/web3.js 2.0.0-experimental.fe07532 → 2.0.0-experimental.feaeef2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +1153 -43
  2. package/dist/index.browser.cjs +176 -785
  3. package/dist/index.browser.cjs.map +1 -1
  4. package/dist/index.browser.js +125 -764
  5. package/dist/index.browser.js.map +1 -1
  6. package/dist/index.development.js +5376 -2962
  7. package/dist/index.development.js.map +1 -1
  8. package/dist/index.native.js +125 -753
  9. package/dist/index.native.js.map +1 -1
  10. package/dist/index.node.cjs +176 -774
  11. package/dist/index.node.cjs.map +1 -1
  12. package/dist/index.node.js +125 -753
  13. package/dist/index.node.js.map +1 -1
  14. package/dist/index.production.min.js +286 -78
  15. package/dist/types/airdrop-internal.d.ts +16 -0
  16. package/dist/types/airdrop-internal.d.ts.map +1 -0
  17. package/dist/types/airdrop.d.ts +7 -18
  18. package/dist/types/airdrop.d.ts.map +1 -0
  19. package/dist/types/decode-transaction.d.ts +9 -0
  20. package/dist/types/decode-transaction.d.ts.map +1 -0
  21. package/dist/types/index.d.ts +11 -9
  22. package/dist/types/index.d.ts.map +1 -0
  23. package/dist/types/send-transaction-internal.d.ts +27 -0
  24. package/dist/types/send-transaction-internal.d.ts.map +1 -0
  25. package/dist/types/send-transaction.d.ts +17 -31
  26. package/dist/types/send-transaction.d.ts.map +1 -0
  27. package/package.json +24 -40
  28. package/dist/types/airdrop-confirmer.d.ts +0 -20
  29. package/dist/types/cached-abortable-iterable.d.ts +0 -11
  30. package/dist/types/rpc-default-config.d.ts +0 -3
  31. package/dist/types/rpc-integer-overflow-error.d.ts +0 -8
  32. package/dist/types/rpc-request-coalescer.d.ts +0 -5
  33. package/dist/types/rpc-request-deduplication.d.ts +0 -2
  34. package/dist/types/rpc-subscription-coalescer.d.ts +0 -10
  35. package/dist/types/rpc-transport.d.ts +0 -4
  36. package/dist/types/rpc-websocket-autopinger.d.ts +0 -8
  37. package/dist/types/rpc-websocket-connection-sharding.d.ts +0 -13
  38. package/dist/types/rpc-websocket-transport.d.ts +0 -13
  39. package/dist/types/rpc.d.ts +0 -7
  40. package/dist/types/transaction-confirmation-strategy-blockheight.d.ts +0 -10
  41. package/dist/types/transaction-confirmation-strategy-nonce.d.ts +0 -15
  42. package/dist/types/transaction-confirmation-strategy-racer.d.ts +0 -14
  43. package/dist/types/transaction-confirmation-strategy-recent-signature.d.ts +0 -13
  44. package/dist/types/transaction-confirmation-strategy-timeout.d.ts +0 -8
  45. package/dist/types/transaction-confirmation.d.ts +0 -37
package/README.md CHANGED
@@ -12,88 +12,1198 @@
12
12
  [semantic-release-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
13
13
  [semantic-release-url]: https://github.com/semantic-release/semantic-release
14
14
 
15
- # Experimental Solana JavaScript SDK
15
+ # Solana JavaScript SDK Technology Preview
16
16
 
17
- Use this to interact with accounts and programs on the Solana network through the Solana [JSON-RPC API](https://docs.solana.com/apps/jsonrpc-api).
17
+ If you build JavaScript applications on Solana, it’s likely you’ve worked with `@solana/web3.js` or a library powered by it. With 400K+ weekly downloads on npm, it’s the most-used library in the ecosystem for building program clients, web applications, block explorers, and more.
18
+
19
+ Here’s an example of a common code snippet from `@solana/web3.js`:
20
+
21
+ ```tsx
22
+ const connection = new Connection('https://api.mainnet-beta.solana.com');
23
+ const instruction = SystemProgram.transfer({ fromPubkey, toPubkey, lamports });
24
+ const transaction = new Transaction().add(instruction);
25
+ await sendAndConfirmTransaction(connection, transaction, [payer]);
26
+ ```
27
+
28
+ In response to your feedback, we began a process of modernizing the library to prepare for the next generation of Solana applications. A Technology Preview of the new web3.js is now available for you to evaluate.
18
29
 
19
30
  **This library is experimental**. It is unsuitable for production use, because the API is unstable and may change without warning. If you want to build a production Solana application, use the [1.x branch](https://www.npmjs.com/package/@solana/web3.js).
20
31
 
21
32
  ## Installation
22
33
 
23
- ### For use in Node.js, React Native, or a web application
34
+ ### For use in Node.js or a web application
24
35
 
25
36
  ```shell
26
- npm install --save @solana/web3.js@experimental
37
+ npm install --save @solana/web3.js@tp
27
38
  ```
28
39
 
29
40
  ### For use in a browser, without a build system
30
41
 
31
42
  ```html
32
43
  <!-- Development (debug mode, unminified) -->
33
- <script src="https://unpkg.com/@solana/web3.js@experimental/dist/index.development.js"></script>
44
+ <script src="https://unpkg.com/@solana/web3.js@tp/dist/index.development.js"></script>
34
45
 
35
46
  <!-- Production (minified) -->
36
- <script src="https://unpkg.com/@solana/web3.js@experimental/dist/index.production.min.js"></script>
47
+ <script src="https://unpkg.com/@solana/web3.js@tp/dist/index.production.min.js"></script>
37
48
  ```
38
49
 
39
- ## Usage
50
+ What follows is an overview of *why* the library was re-engineered, what changes have been introduced, and how the JavaScript landscape might look across Solana in the near future.
40
51
 
41
- There are 3 main applications of this library.
52
+ # Community feedback in action
42
53
 
43
- 1. **RPC**. Solana apps interact with the network by calling methods on the Solana JSON-RPC.
44
- 2. **Transactions**. Solana apps interact with Solana program by building and sending transactions.
45
- 3. **Keys**. People use cryptographic keys to verify the provenance of messages and to attest to the ownership of their accounts.
54
+ We’re grateful to all of you for communicating the pain points you’ve experienced when developing Solana applications with web3.js. We’ve heard you loud and clear.
46
55
 
47
- ### RPC
56
+ ## Tree-shaking
48
57
 
49
- First, configure your connection to an RPC server. This might be a server that you host, one that you lease, or one of the limited-use [public RPC servers](https://docs.solana.com/cluster/rpc-endpoints).
58
+ The object-oriented design of the web3.js (1.x) API prevents optimizing compilers from being able to “tree-shake” unused code from your production builds. No matter how much of the web3.js API you use in your application, you have to package all of it.
50
59
 
51
- ```ts
52
- import { createDefaultRpcTransport } from '@solana/web3.js';
53
- const devnetTransport = createDefaultRpcTransport({ url: 'https://api.devnet.solana.com' });
60
+ Read more about tree-shaking here:
61
+
62
+ - [Mozilla Developer Docs: Tree Shaking](https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking)
63
+ - [WebPack Docs: Tree Shaking](https://webpack.js.org/guides/tree-shaking/)
64
+ - [Web.Dev Blog Article: Reduce JavaScript Payloads with Tree Shaking](https://web.dev/articles/reduce-javascript-payloads-with-tree-shaking)
65
+
66
+ One example of an API that can’t be tree-shaken is the `Connection` class. It has dozens of methods, but because it’s a *class* you have no choice but to include every method in your application’s final bundle, no matter how many you *actually* use.
67
+
68
+ Needlessly large JavaScript bundles can cause issues with deployments to cloud compute providers like Cloudflare or AWS Lambda. They also impact webapp startup performance because of longer download and JavaScript parse times.
69
+
70
+ ## Opinionated
71
+
72
+ Depending on your use case and your tolerance for certain application behaviours, you may be willing to configure your application to make a different set of tradeoffs than another developer. The default tradeoffs that we codify into the web3.js API on the other hand have to work for as large a population as possible, in the common case.
73
+
74
+ The inability to customize web3.js has been a source of frustration for some:
75
+
76
+ - The Mango team wanted to customize the transaction confirmation strategy, but all of that functionality is hidden away behind `confirmTransaction` – a static method of `Connection`. [Here’s the code for `confirmTransaction` on GitHub](https://github.com/solana-labs/solana-web3.js/blob/69a8ad25ef09f9e6d5bff1ffa8428d9be0bd32ac/packages/library-legacy/src/connection.ts#L3734).
77
+ - Solana developer ‘mPaella’ [wanted us to add a feature in the RPC](https://github.com/solana-labs/solana-web3.js/issues/1143#issuecomment-1435927152) that would failover to a set of backup URLs in case the primary one failed.
78
+ - Solana developer ‘epicfaace’ wanted first-class support for automatic time-windowed batching in the RPC transport. [Here’s their pull request](https://github.com/solana-labs/solana/pull/23628).
79
+ - Multiple folks have expressed the need for custom retry logic for failed requests or transactions. [Here’s a pull request from ‘dafyddd’](https://github.com/solana-labs/solana/pull/11811) and [another from ‘abrkn’](https://github.com/solana-labs/solana-web3.js/issues/1041) attempting to modify retry logic to suit their individual use cases.
80
+
81
+ ## Lagging Behind Modern JavaScript
82
+
83
+ The advance of modern JavaScript features presents an opportunity to developers of crypto applications, such as the ability to use native Ed25519 keys and to express large values as native `bigint`.
84
+
85
+ The Web Incubator Community Group has advocated for the addition of Ed25519 support to the [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API), and support has already landed in *most* modern JavaScript runtimes.
86
+
87
+ Support for `bigint` values has also become commonplace. The older `number` primitive in JavaScript has a maximum value of 2^53 - 1, whereas Rust’s `u64` can represent values up to 2^64.
88
+
89
+ ## Class-Based Architecture
90
+
91
+ The object oriented, class-based architecture of the legacy library causes unnecessary bundle bloat. Your application has no choice but to bundle *all* of the functionality and dependencies of a class no matter how many methods you actually use at runtime.
92
+
93
+ Class-based architecture also presents unique risks to developers who trigger the dual-package hazard. This describes a situation you can find yourself in if you build for both CommonJS and ES modules. The situation arises when two “copies” of the same class are present in the dependency tree, causing checks like `instanceof` to fail, which introduces aggravating and difficult to debug problems.
94
+
95
+ Read more about dual-package hazard:
96
+
97
+ - [NodeJS: Dual Package Hazard](https://nodejs.org/api/packages.html#dual-package-hazard)
98
+
99
+ # The New web3.js
100
+
101
+ Enter web3.js 2.0. The new API aims to deliver a re-imagined experience of building Solana applications, a high level of performance by default, and all with a minimum of code. From the ability to customize the behaviour of the library through composition, to the joy of being able to catch common errors during build time before they make it to production, we hope that you enjoy building with it as much as we’ve enjoyed creating it.
102
+
103
+ ## Features
104
+
105
+ The new (2.0) version of `@solana/web3.js` aims to address shortcomings in the legacy library first, then goes even further.
106
+
107
+ ### Tree-Shaking
108
+
109
+ The 2.0 library is tree-shakable, and that tree-shakeability is enforced in the CI. Anything you don’t use from web3.js 2.0 can now be discarded from your bundle by an optimizing compiler.
110
+
111
+ The new library itself is comprised of several smaller, modular packages under the `@solana` organization, including:
112
+
113
+ - `@solana/rpc-transport`: For building and managing RPC transports
114
+ - `@solana/rpc-core`: The type-spec of the Solana JSON RPC
115
+ - `@solana/transactions`: For building and transforming Solana transaction objects
116
+ - `@solana/codecs-*`: For building data (de)serializers
117
+
118
+ Developers can use the default configurations within the library itself (`@solana/web3.js:2.0`) or import any number of the modular packages for additional customization.
119
+
120
+ ### Minimally Opinionated
121
+
122
+ The individual modules that make up web3.js are assembled in a **default** configuration reminiscent of the legacy library as part of the npm package `@solana/web3.js`, but those who wish to assemble them in different configurations may do so.
123
+
124
+ Each package uses types and generics liberally, allowing you to inject new functionality, to make extensions to each API via composition and supertypes, and to encourage you to create higher-level opinionated abstractions of your own.
125
+
126
+ In fact, we expect you to do so, and to open source some of those for use by others with similar needs.
127
+
128
+ ### Modern JavaScript
129
+
130
+ The new API is built for compatibility with platform APIs to reduce our dependencies on userspace implementations that introduce supply chain risk and bundle bloat to your applications.
131
+
132
+ One such example is the integration of Ed25519 `CryptoKeys` – native platform primitives for managing cryptographic keys and signatures.
133
+
134
+ Read more about the Web Crypto API here:
135
+
136
+ - [Mozilla Developer Docs: Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API)
137
+ - [Node JS Documentation: Web Crypto API](https://nodejs.org/api/webcrypto.html)
138
+ - [Nieky Allen (Blog): The Web Crypto API in Action](https://medium.com/slalom-build/the-web-cryptography-api-in-action-89b2f68c602c)
139
+
140
+ web3.js 2.0 also further eliminates dependencies such as `BN.js` by implementing large integers with `bigint`.
141
+
142
+ ### Interface-Based Architecture
143
+
144
+ The new library employs interfaces and types for just about *everything*, expressing most objects as data. The dual-package hazard is no longer a threat to your development; any objects compatible with an interface are usable with functions that specify that interface.
145
+
146
+ This interface-based approach also allows for easy customization; for extending the library’s functionality or building on top of it.
147
+
148
+ ## Statistics
149
+
150
+ Consider these statistical comparisons between web3.js 2.0 and the legacy 1.x.
151
+
152
+ | | 1.x (Legacy) | 2.0 | +/- % |
153
+ | --- | --- | --- | --- |
154
+ | Total minified size of library | 90 KB | 33 KB | -63% |
155
+ | Total minified size of library (when runtime supports Ed25519) | 90 KB | 17 KB | -81% |
156
+ | Bundled size of a web application that only executes a transfer of lamports | 67 KB | 4.5 KB | -93% |
157
+ | Bundled size of a web application that only executes a transfer of lamports (when runtime supports Ed25519) | 67 KB | 4.5 KB | -93% |
158
+ | Bundled size of a worker that signs and sends a transaction | 5.4 MB | 1.7 MB | -68% |
159
+ | Performance of key generation, signing, and verifying signatures (Brave with Experimental API flag) | 700 ops/s | 7000 ops/s | +900% |
160
+ | First-load size for Solana Explorer | 311 KB | 228 KB | -26% |
161
+
162
+ The re-engineered library achieves these speedups and reductions in bundle size in large part through use of modern JavaScript APIs.
163
+
164
+ To validate our work, we replaced the legacy 1.x library with the new 2.0 library on the homepage of the Solana Explorer. Total first-load bundle size dropped by 26% without removing a single feature. [Here’s an X thread](https://twitter.com/callum_codes/status/1679124485218226176) by Callum McIntyre if you would like to dig deeper.
165
+
166
+ # A tour of the web3.js 2.0 API
167
+
168
+ Here’s an overview of how to use the new library to interact with the RPC, configure network transports, work with Ed25519 keys, and to serialize data.
169
+
170
+ ## RPC
171
+
172
+ web3.js 2.0 ships with an implementation of the [JSON RPC specification](https://www.jsonrpc.org/specification) and a type spec for the [Solana JSON RPC](https://docs.solana.com/api).
173
+
174
+ ### Initializing a Default RPC API
175
+
176
+ Here’s an example of creating the default API for interacting with the Solana JSON RPC:
177
+
178
+ ```tsx
179
+ import { createSolanaRpc, createDefaultRpcTransport } from '@solana/web3.js';
180
+
181
+ // Create an HTTP transport
182
+ const transport = createDefaultRpcTransport({ url: 'http://127.0.0.1:8899' });
183
+
184
+ // Create an RPC client
185
+ const rpc = createSolanaRpc({ transport });
186
+ // ^ RpcMethods<SolanaRpcMethods>
187
+
188
+ // Send a request
189
+ const slot = await rpc.getSlot().send();
190
+ ```
191
+
192
+ The function `createSolanaRpc(..)` accepts a transport to some endpoint that implements JSON RPC and provides all of the capabilities specified by the [Solana JSON RPC HTTP Methods](https://docs.solana.com/api/http).
193
+
194
+ ### Aborting Requests
195
+
196
+ RPC requests are now abortable with modern `AbortControllers`. The `send(..)` method on any `PendingRpcRequest<..>` allows an optional `abortSignal?: AbortSignal` argument.
197
+
198
+ Here’s an example of a custom `AbortController` used to abort a subscription:
199
+
200
+ ```tsx
201
+ import { createSolanaRpcSubscriptions, createDefaultRpcSubscriptionsTransport } from '@solana/web3.js';
202
+
203
+ const transport = createDefaultRpcSubscriptionsTransport({ url: 'ws://127.0.0.1:8900' });
204
+ const rpcSubscriptions = createSolanaRpcSubscriptions({ transport });
205
+
206
+ // Create a new AbortController
207
+ const abortController = new AbortController();
208
+
209
+ // Subscribe for slot notifications
210
+ const slotNotifications = await rpcSubscriptions
211
+ .slotNotifications()
212
+ .subscribe({ abortSignal: abortController.signal });
213
+
214
+ // Set a timer for 5 seconds, then abort the controller
215
+ setTimeout(() => { abortController.abort(); }, 5000);
216
+
217
+ // Log slot notifications
218
+ for await (const notif of slotNotifications) {
219
+ console.log('Slot notification', notif);
220
+ }
221
+
222
+ console.log('Done.');
54
223
  ```
55
224
 
56
- Second, construct an RPC instance that uses that transport.
225
+ Read more about `AbortController` at the following links:
226
+
227
+ - [Mozilla Developer Docs: `AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)
228
+ - [Mozilla Developer Docs: `AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
229
+ - [JavaScript.info: Fetch: Abort](https://javascript.info/fetch-abort)
230
+
231
+ ### Scoping the RPC API
232
+
233
+ The new library is comprised of many smaller modular libraries. The packages responsible for managing communication with an RPC are `@solana/rpc-transport` and `@solana/rpc-core`.
234
+
235
+ The `@solana/rpc-transport` library is responsible for creating transports to an RPC using some specified API – such as the Solana [JSON RPC HTTP API](https://docs.solana.com/api/http), while `@solana/rpc-core` provides the actual Solana JSON RPC API (a specification of each of its supported methods).
236
+
237
+ Here’s an example of using `@solana/rpc-transport` and `@solana/rpc-core` to create an RPC transport with the Solana API (note: this is the manual implementation of the code snippet above):
238
+
239
+ ```tsx
240
+ import { createSolanaRpcApi, SolanaRpcMethods } from '@solana/rpc-core';
241
+ import { createHttpTransport, createJsonRpc } from '@solana/rpc-transport';
242
+
243
+ const api = createSolanaRpcApi();
57
244
 
58
- ```ts
59
- const devnetRpc = createSolanaRpc({ transport: devnetTransport });
245
+ const transport = createHttpTransport({ url: 'http://127.0.0.1:8899' });
246
+
247
+ const rpc = createJsonRpc<SolanaRpcMethods>({ api, transport });
248
+ // ^ RpcMethods<SolanaRpcMethods>
60
249
  ```
61
250
 
62
- Now, you can use it to call methods on your RPC server. For instance, here is how you would fetch an account's balance.
251
+ If you want to, you can also reduce the scope of the API’s type-spec so you are left only with the types you need. Keep in mind types don’t affect bundle size, but you may choose to scope the type-spec for a variety of reasons, including reducing TypeScript noise.
252
+
253
+ ```tsx
254
+ import { createSolanaRpcApi, type GetAccountInfoApi } from '@solana/rpc-core';
255
+ import { createHttpTransport, createJsonRpc } from '@solana/rpc-transport';
256
+
257
+ const api = createSolanaRpcApi();
63
258
 
64
- ```ts
65
- const systemProgramAddress = '11111111111111111111111111111111' as Base58EncodedAddress;
66
- const balanceInLamports = await devnetRpc.getBalance(systemProgramAddress).send();
67
- console.log('Balance of System Program account in Lamports', balanceInLamports);
259
+ const transport = createHttpTransport({ url: 'http://127.0.0.1:8899' });
260
+
261
+ const getAccountInfoRpc = createJsonRpc<GetAccountInfoApi>({ api, transport });
262
+ // ^ RpcMethods<GetAccountInfoApi>
68
263
  ```
69
264
 
70
- ### Transactions
265
+ ### Creating a Custom RPC API
71
266
 
72
- Unimplemented.
267
+ The new library’s RPC specification supports an *infinite* number of JSON-RPC methods with **zero increase** in bundle size.
73
268
 
74
- ### Keys
269
+ This means the library can support future additions to the official [Solana JSON RPC](https://docs.solana.com/api), or [custom RPC methods](https://www.quicknode.com/docs/ethereum/qn_fetchNFTCollectionDetails_v2) defined by some development team – for example QuickNode or Helius.
75
270
 
76
- #### Addresses and public keys
271
+ Here’s an example of how a developer at QuickNode might build a custom RPC type-spec for their in-house RPC methods:
77
272
 
78
- Client applications primarily deal with addresses and public keys in the form of base58-encoded strings. Addresses and public keys returned from the RPC API conform to the type `Base58EncodedAddress`. You can use a value of that type wherever a base58-encoded address or key is expected.
273
+ ```tsx
274
+ // Define the method's response payload
275
+ type NftCollectionDetailsApiResponse = Readonly<{
276
+ address: string;
277
+ circulatingSupply: number;
278
+ description: string;
279
+ erc721: boolean;
280
+ erc1155: boolean;
281
+ genesisBlock: string;
282
+ genesisTransaction: string;
283
+ name: string;
284
+ totalSupply: number;
285
+ }>;
79
286
 
80
- 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.
287
+ // Set up an interface for the request method
288
+ interface NftCollectionDetailsApi {
289
+ // Define the method's name, parameters and response type
290
+ qn_fetchNFTCollectionDetails(args: {
291
+ contracts: string[],
292
+ }): NftCollectionDetailsApiResponse;
293
+ }
81
294
 
82
- ```ts
83
- import { assertIsAddress } from '@solana/web3.js';
295
+ // Export the type spec for downstream users
296
+ export type QuickNodeRpcMethods = NftCollectionDetailsApi;
297
+ ```
84
298
 
85
- // Imagine a function that fetches an account's balance when a user submits a form.
86
- async function handleSubmit() {
87
- // We know only that what the user typed conforms to the `string` type.
88
- const address: string = accountAddressInput.value;
299
+ Here’s how a developer might use it:
300
+
301
+ ```tsx
302
+ import { createHttpTransport, createJsonRpc, createJsonRpcApi } from '@solana/rpc-transport';
303
+
304
+ // Create the custom API
305
+ const api = createJsonRpcApi<QuickNodeRpcMethods>();
306
+
307
+ // Set up an HTTP transport
308
+ const transport = createHttpTransport({ url: 'http://127.0.0.1:8899' });
309
+
310
+ // Create the RPC client
311
+ const quickNodeRpc = createJsonRpc<QuickNodeRpcMethods>({ api, transport });
312
+ // ^ RpcMethods<QuickNodeRpcMethods>
313
+ ```
314
+
315
+ As long as a particular JSON RPC method adheres to the [official JSON RPC specification](https://www.jsonrpc.org/specification), it will be supported by web3.js 2.0.
316
+
317
+ ## Transports
318
+
319
+ Using the `@solana/rpc-transport` package, developers can create custom RPC transports. With this library, one can implement highly specialized functionality for leveraging multiple transports, attempting/handling retries, and more.
320
+
321
+ ### Round Robin
322
+
323
+ Here’s an example of how someone might implement a “round robin” approach to leveraging multiple RPC transports within their application:
324
+
325
+ ```tsx
326
+ import { createSolanaRpcApi } from '@solana/rpc-core';
327
+ import { createJsonRpc, type IRpcTransport } from '@solana/rpc-transport';
328
+ import { createDefaultRpcTransport } from '@solana/web3.js';
329
+
330
+ // Create a transport for each RPC server
331
+ const transports = [
332
+ createDefaultRpcTransport({ url: 'https://mainnet-beta.my-server-1.com' }),
333
+ createDefaultRpcTransport({ url: 'https://mainnet-beta.my-server-2.com' }),
334
+ createDefaultRpcTransport({ url: 'https://mainnet-beta.my-server-3.com' }),
335
+ ];
336
+
337
+ // Set up the round robin factory
338
+ let nextTransport = 0;
339
+ async function roundRobinTransport<TResponse>(...args: Parameters<IRpcTransport>): Promise<TResponse> {
340
+ const transport = transports[nextTransport];
341
+ nextTransport = (nextTransport + 1) % transports.length;
342
+ return await transport(...args);
343
+ }
344
+
345
+ // Create the RPC client
346
+ const rpc = createJsonRpc({
347
+ api: createSolanaRpcApi(),
348
+ transport: roundRobinTransport,
349
+ });
350
+ ```
351
+
352
+ ### Sharding
353
+
354
+ Another example of a possible customization for RPC transports is sharding. Here’s an example:
355
+
356
+ ```tsx
357
+ // TODO: Your turn; send us a pull request with an example.
358
+ ```
359
+
360
+ ### Retry Logic
361
+
362
+ The transport library can also be used to implement custom retry logic on any request:
363
+
364
+ ```tsx
365
+ import { createDefaultRpcTransport } from '@solana/web3.js';
366
+ import { createJsonRpc, IRpcTransport } from '@solana/rpc-transport';
367
+ import { createSolanaRpcApi } from '@solana/rpc-core';
368
+
369
+ // Set the maximum number of attempts to retry a request
370
+ const MAX_ATTEMPTS = 4;
371
+
372
+ // Create the default transport
373
+ const defaultTransport = createDefaultRpcTransport({ url: 'https://mainnet-beta.my-server-1.com' });
374
+
375
+ // Sleep function to wait for a given number of milliseconds
376
+ function sleep(ms: number): Promise<void> {
377
+ return new Promise((resolve) => setTimeout(resolve, ms));
378
+ }
379
+
380
+ // Calculate the delay for a given attempt
381
+ function calculateRetryDelay(attempt: number): number {
382
+ // Exponential backoff with a maximum of 1.5 seconds
383
+ return Math.min(100 * Math.pow(2, attempt), 1500);
384
+ }
385
+
386
+ // A retrying transport that will retry up to MAX_ATTEMPTS times before failing
387
+ async function retryingTransport<TResponse>(...args: Parameters<IRpcTransport>): Promise<TResponse> {
388
+ let requestError;
389
+ for (let attempts = 0; attempts < MAX_ATTEMPTS; attempts++) {
89
390
  try {
90
- // If this type assertion function doesn't throw, then
91
- // Typescript will upcast `address` to `Base58EncodedAddress`.
92
- assertIsAddress(address);
93
- // At this point, `address` is a `Base58EncodedAddress` that can be used with the RPC.
94
- const balanceInLamports = await rpc.getBalance(address).send();
95
- } catch (e) {
96
- // `address` turned out not to be a base58-encoded address
391
+ return await defaultTransport(...args);
392
+ } catch (err) {
393
+ requestError = err;
394
+ // Only sleep if we have more attempts remaining
395
+ if (attempts < MAX_ATTEMPTS - 1) {
396
+ const retryDelay = calculateRetryDelay(attempts);
397
+ await sleep(retryDelay);
398
+ }
399
+ }
400
+ }
401
+ throw requestError;
402
+ }
403
+
404
+ // Create the RPC client
405
+ const rpc = createJsonRpc({
406
+ api: createSolanaRpcApi(),
407
+ transport: retryingTransport,
408
+ });
409
+ ```
410
+
411
+ ### Failover
412
+
413
+ Support for handling failover can be implemented as a first-class citizen in your application using the new transport library. Here’s an example of some failover logic integrated into a transport:
414
+
415
+ ```tsx
416
+ // TODO: Your turn; send us a pull request with an example.
417
+ ```
418
+
419
+ ### Fanning Out
420
+
421
+ Perhaps your application needs to make a large number of requests, or needs to fan request for different methods out to different servers. Here’s an example of an implementation that does the latter:
422
+
423
+ ```tsx
424
+ import { createSolanaRpcApi } from '@solana/rpc-core';
425
+ import { createJsonRpc, IRpcTransport } from '@solana/rpc-transport';
426
+ import { createDefaultRpcTransport } from '@solana/web3.js';
427
+
428
+ // Create multiple transports
429
+ const transportA = createDefaultRpcTransport({ url: 'https://mainnet-beta.my-server-1.com' });
430
+ const transportB = createDefaultRpcTransport({ url: 'https://mainnet-beta.my-server-2.com' });
431
+ const transportC = createDefaultRpcTransport({ url: 'https://mainnet-beta.my-server-3.com' });
432
+ const transportD = createDefaultRpcTransport({ url: 'https://mainnet-beta.my-server-4.com' });
433
+
434
+ // Function to determine which shard to use based on the request method
435
+ function selectShard(method: string): IRpcTransport {
436
+ switch (method) {
437
+ case 'getAccountInfo':
438
+ case 'getBalance':
439
+ return transportA;
440
+ case 'getTransaction':
441
+ case 'getRecentBlockhash':
442
+ return transportB;
443
+ case 'sendTransaction':
444
+ return transportC;
445
+ default:
446
+ return transportD;
447
+ }
448
+ }
449
+
450
+ const rpc = createJsonRpc({
451
+ api: createSolanaRpcApi(),
452
+ transport: async (...args: Parameters<IRpcTransport>): Promise<any> => {
453
+ const payload = args[0].payload as { method: string };
454
+ const selectedTransport = selectShard(payload.method);
455
+ return await selectedTransport(...args);
456
+ },
457
+ });
458
+ ```
459
+
460
+ ## Subscriptions
461
+
462
+ Subscriptions in the legacy library do not allow custom retry logic, and do not give you the opportunity to recover from having potentially missed messages. The new version does away with silent retries, surfaces transport errors to your application, and gives you the opportunity to recover from gap events.
463
+
464
+ ### Async Iterator
465
+
466
+ The new subscriptions API vends subscription notifications as an `AsyncIterator`. The `AsyncIterator` conforms to the [async iterator protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols), which allows developers to consume messages using a `for await...of` loop.
467
+
468
+ Here’s an example of working with a subscription in the new library:
469
+
470
+ ```tsx
471
+ import { address, createSolanaRpcSubscriptions, createDefaultRpcSubscriptionsTransport } from '@solana/web3.js';
472
+
473
+ // Create the subscriptions transport
474
+ const transport = createDefaultRpcSubscriptionsTransport({ url: 'ws://127.0.0.1:8900' });
475
+
476
+ // Create the RPC client
477
+ const rpc = createSolanaRpcSubscriptions({ transport });
478
+
479
+ // Set up an abort controller
480
+ const abortController = new AbortController();
481
+
482
+ // Subscribe to account notifications
483
+ const accountNotifications = await rpc
484
+ .accountNotifications(address('AxZfZWeqztBCL37Mkjkd4b8Hf6J13WCcfozrBY6vZzv3'), { commitment: 'confirmed' })
485
+ .subscribe({ abortSignal: abortController.signal });
486
+
487
+ // Consume messages
488
+ try {
489
+ for await (const notification of accountNotifications) {
490
+ console.log('New balance', notification.value.lamports);
491
+ }
492
+ // Reaching this line means the subscription was aborted (ie. unsubscribed).
493
+ } catch (e) {
494
+ // The subscription went down.
495
+ // Retry it and then recover from potentially having missed
496
+ // a balance update, here (eg. by making a `getBalance()` call)
497
+ } finally {
498
+ // Whether the subscription failed or was aborted, you can run cleanup code here.
499
+ }
500
+ ```
501
+
502
+ The new subscriptions API also offers a separate rpc creator if you would like to use Solana’s [unstable subscription methods](https://docs.solana.com/api/websocket#blocksubscribe).
503
+
504
+ ```tsx
505
+ import { createSolanaRpcSubscriptions_UNSTABLE, createDefaultRpcSubscriptionsTransport } from '@solana/web3.js';
506
+
507
+ const transport = createDefaultRpcSubscriptionsTransport({ url: 'ws://127.0.0.1:8900' });
508
+
509
+ // For unstable methods, explicitly request them in the type spec
510
+ const unstableRpc = createSolanaRpcSubscriptions_UNSTABLE({ transport });
511
+ // ^ RpcSubscriptionMethods<SolanaRpcSubscriptions & SolanaRpcSubscriptionsUnstable>
512
+ ```
513
+
514
+ You can read more about `AsyncIterator` at the following links:
515
+
516
+ - [Mozilla Developer Docs: `AsyncIterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncIterator)
517
+ - [Luciano Mammino (Blog): JavaScript Async Iterators](https://www.nodejsdesignpatterns.com/blog/javascript-async-iterators/)
518
+
519
+ ### Cancelling Subscriptions
520
+
521
+ Similar to the `AbortSignal` logic in the HTTP methods provided by `@solana/rpc-core`, applications can terminate active subscriptions using an `AbortController`. In fact, this parameter is *required* for subscriptions to encourage you to cleanup subscriptions that your application no longer needs.
522
+
523
+ Consider this example of cancelling a subscription using an `AbortController`:
524
+
525
+ ```tsx
526
+ // Subscribe to account notifications
527
+ const accountNotifications = await rpc
528
+ .accountNotifications(address('AxZfZWeqztBCL37Mkjkd4b8Hf6J13WCcfozrBY6vZzv3'), { commitment: 'confirmed' })
529
+ .subscribe({ abortSignal });
530
+
531
+ // Consume messages
532
+ let previousOwner = null;
533
+ for await (const notification of accountNotifications) {
534
+ const { value: { owner } } = notification;
535
+ // Check the owner to see if it has changed
536
+ if (previousOwner && owner !== previousOwner) {
537
+ // If so, abort the subscription
538
+ abortController.abort();
539
+ } else {
540
+ console.log(notification);
541
+ }
542
+ previousOwner = owner;
543
+ }
544
+ ```
545
+
546
+ ### Message Gap Recovery
547
+
548
+ One of the most crucial aspects of any subscription API is managing potential missed messages. Missing messages, such as account state updates, could be catastrophic for an application. That’s why the new library provides native support for recovering missed messages using the `AsyncIterator`.
549
+
550
+ When a connection fails unexpectedly, any messages you miss while disconnected can result in your UI falling behind or becoming corrupt. Because subscription failure is now made explicit in the new API, you can implement ‘catch up’ logic after re-establishing the subscription.
551
+
552
+ Here’s an example of such logic:
553
+
554
+ ```tsx
555
+ try {
556
+ for await (const notif of accountNotifications) {
557
+ updateAccountBalance(notif.lamports);
97
558
  }
559
+ } catch(e) {
560
+ // The subscription failed.
561
+ // First, reestablish the subscription.
562
+ await setupAccountBalanceSubscription(address);
563
+ // Then make a one-shot request to 'catch up' on any missed balance changes.
564
+ const { value: lamports } = await rpc.getBalance(address).send();
565
+ updateAccountBalance(lamports);
566
+ }
567
+ ```
568
+
569
+ ## Keys
570
+
571
+ The new library takes a brand-new approach to Solana key pairs and addresses, which will feel quite different from the classes `PublicKey` and `Keypair` from version 1.x.
572
+
573
+ ### Web Crypto API
574
+
575
+ All key operations now use the native Ed25519 implementation in JavaScript’s Web Crypto API.
576
+
577
+ The API itself is designed to be a more reliably secure way to manage highly sensitive secret key information, but **********************************************************************************************************************************developers should still use extreme caution when dealing with secret key bytes in their applications**********************************************************************************************************************************.
578
+
579
+ One thing to note is that many operations from Web Crypto – such as importing, generating, signing, and verifying are now ************************asynchronous************************.
580
+
581
+ Here’s an example of generating a `CryptoKeyPair` using the Web Crypto API and signing a message:
582
+
583
+ ```tsx
584
+ import { generateKeyPair, signBytes, verifySignature } from '@solana/web3.js';
585
+
586
+ const keyPair: CryptoKeyPair = await generateKeyPair();
587
+
588
+ const message = new Uint8Array(8).fill(0);
589
+
590
+ const signedMessage = await signBytes(keyPair.privateKey, message);
591
+ // ^ Signature
592
+
593
+ const verified = await verifySignature(keyPair.publicKey, signedMessage, message);
594
+ ```
595
+
596
+ ### Web Crypto Polyfill
597
+
598
+ Wherever Ed25519 is not supported, we offer a polyfill for Web Crypto’s Ed25519 API.
599
+
600
+ This polyfill can be found at `@solana/webcrypto-ed25519-polyfill` and mimics the functionality of the Web Crypto API for Ed25519 key pairs using the same userspace implementation we used in web3.js 1.x. It does not polyfill other algorithms.
601
+
602
+ Determine if your target runtime supports Ed25519, and install the polyfill if it does not:
603
+
604
+ ```tsx
605
+ import '@solana/webcrypto-ed25519-polyfill';
606
+ import { generateKeyPair, signBytes, verifySignature } from '@solana/web3.js';
607
+
608
+ const keyPair: CryptoKeyPair = await generateKeyPair();
609
+
610
+ /* Remaining logic */
611
+ ```
612
+
613
+ You can see where Ed25519 is currently supported in [this GitHub issue](https://github.com/WICG/webcrypto-secure-curves/issues/20) on the Web Crypto repository. Consider sniffing the user-agent when deciding whether or not to deliver the polyfill to browsers.
614
+
615
+ Operations on `CryptoKey` objects using the Web Crypto API *or* the polyfill are mostly handled by the `@solana/keys` package.
616
+
617
+ ### String Addresses
618
+
619
+ All addresses are now JavaScript strings. They are represented by the opaque type `Address`, which describes exactly what a Solana address actually is.
620
+
621
+ Consequently, that means no more `PublicKey`.
622
+
623
+ Here’s what they look like in development:
624
+
625
+ ```tsx
626
+ import { Address, address, getAddressFromPublicKey, generateKeyPair } from '@solana/web3.js';
627
+
628
+ // Coerce a string to an `Address`
629
+ const myOtherAddress = address('AxZfZWeqztBCL37Mkjkd4b8Hf6J13WCcfozrBY6vZzv3');
630
+
631
+ // Typecast it instead
632
+ const myAddress =
633
+ 'AxZfZWeqztBCL37Mkjkd4b8Hf6J13WCcfozrBY6vZzv3' as Address<'AxZfZWeqztBCL37Mkjkd4b8Hf6J13WCcfozrBY6vZzv3'>;
634
+
635
+ // From CryptoKey
636
+ const keyPair = await generateKeyPair();
637
+ const myPublicKeyAsAddress = await getAddressFromPublicKey(keyPair.publicKey);
638
+ ```
639
+
640
+ Some tooling for working with base58-encoded addresses can be found in the `@solana/addresses` package.
641
+
642
+ ## Transactions
643
+
644
+ Just like many other familiar aspects of the 1.0 library, transactions have received a makeover as well.
645
+
646
+ For starters, all transactions are now version-aware, so there’s no longer a need to juggle two different types of transactions (`Transaction` vs. `VersionedTransaction`).
647
+
648
+ Address lookups are now completely described inside transaction instructions, so you don’t have to materialize `addressTableLookups` from the transaction object anymore.
649
+
650
+ Here’s a simple example of creating a transaction – notice how the type of the transaction is refined at each step of the process:
651
+
652
+ ```tsx
653
+ import {
654
+ address,
655
+ createTransaction,
656
+ setTransactionFeePayer,
657
+ setTransactionLifetimeUsingBlockhash,
658
+ Blockhash,
659
+ } from '@solana/web3.js';
660
+
661
+ const recentBlockhash = {
662
+ blockhash: '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY' as Blockhash,
663
+ lastValidBlockHeight: 196055492n,
664
+ };
665
+ const feePayer = address('AxZfZWeqztBCL37Mkjkd4b8Hf6J13WCcfozrBY6vZzv3');
666
+
667
+ // Create a new transaction (legacy)
668
+ const transactionLegacy = createTransaction({ version: 'legacy' });
669
+ // ^ LegacyTransaction
670
+
671
+ const transactionWithFeePayerLegacy = setTransactionFeePayer(feePayer, transactionLegacy);
672
+ // ^ LegacyTransaction & ITransactionWithFeePayer
673
+
674
+ const transactionWithFeePayerAndLifetimeLegacy = setTransactionLifetimeUsingBlockhash(
675
+ recentBlockhash,
676
+ transactionWithFeePayerLegacy
677
+ );
678
+ // ^ LegacyTransaction & ITransactionWithFeePayer & ITransactionWithBlockhash
679
+
680
+ // Create a new transaction (v0)
681
+ const transactionV0 = createTransaction({ version: 0 });
682
+ // ^ V0Transaction
683
+
684
+ // Set the fee payer
685
+ const transactionWithFeePayerV0 = setTransactionFeePayer(feePayer, transactionV0);
686
+ // ^ V0Transaction & ITransactionWithFeePayer
687
+
688
+ const transactionWithFeePayerAndLifetimeV0 = setTransactionLifetimeUsingBlockhash(
689
+ recentBlockhash,
690
+ transactionWithFeePayerV0
691
+ );
692
+ // ^ V0Transaction & ITransactionWithFeePayer & ITransactionWithBlockhash
693
+ ```
694
+
695
+ As you can see, each time a transaction is modified, the type reflects the current state. If you add a fee payer, you’ll get a type representing a transaction with a fee payer, and so on.
696
+
697
+ Additionally, transaction-modifying methods such as `setTransactionFeePayer(..)` and `setTransactionLifetimeUsingBlockhash(..)` will strip a transaction of its signatures, since those signatures would no longer match the modified transaction message.
698
+
699
+ ```tsx
700
+ import {
701
+ address,
702
+ createTransaction,
703
+ generateKeyPair,
704
+ setTransactionFeePayer,
705
+ setTransactionLifetimeUsingBlockhash,
706
+ signTransaction,
707
+ Blockhash,
708
+ } from '@solana/web3.js';
709
+
710
+ const recentBlockhash = {
711
+ blockhash: '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY' as Blockhash,
712
+ lastValidBlockHeight: 196055492n,
713
+ };
714
+ const feePayer = address('AxZfZWeqztBCL37Mkjkd4b8Hf6J13WCcfozrBY6vZzv3');
715
+ const signer = await generateKeyPair();
716
+
717
+ const transaction = createTransaction({ version: 'legacy' });
718
+ const transactionWithFeePayer = setTransactionFeePayer(feePayer, transaction);
719
+ const transactionWithFeePayerAndLifetime = setTransactionLifetimeUsingBlockhash(
720
+ recentBlockhash,
721
+ transactionWithFeePayer
722
+ );
723
+ const transactionSignedWithFeePayerAndLifetime = await signTransaction(
724
+ [signer],
725
+ transactionWithFeePayerAndLifetime
726
+ );
727
+ // ^ LegacyTransaction & ITransactionWithFeePayer & ITransactionWithBlockhash & ITransactionWithSignatures
728
+
729
+ // Setting the lifetime again will remove the signatures from the object
730
+ const transactionSignaturesStripped = setTransactionLifetimeUsingBlockhash(
731
+ recentBlockhash,
732
+ transactionSignedWithFeePayerAndLifetime,
733
+ );
734
+ // ^ LegacyTransaction & ITransactionWithFeePayer & ITransactionWithBlockhash
735
+ ```
736
+
737
+ The `signTransaction(..)` function will raise a type error if your unsigned transaction is not already equipped with a fee payer and a lifetime.
738
+
739
+ ```tsx
740
+ const feePayer = address('AxZfZWeqztBCL37Mkjkd4b8Hf6J13WCcfozrBY6vZzv3');
741
+ const signer = await generateKeyPair();
742
+
743
+ const transaction = createTransaction({ version: 'legacy' });
744
+ const transactionWithFeePayer = setTransactionFeePayer(feePayer, transaction);
745
+
746
+ // Attempting to sign the transaction without a lifetime will throw a type error
747
+ const transactionSignedWithFeePayer = await signTransaction([signer], transactionWithFeePayer);
748
+ // => "Property 'lifetimeConstraint' is missing in type"
749
+ ```
750
+
751
+ Transaction objects are also ********frozen by these functions******** to prevent transactions from being mutated in place by functions you pass them to.
752
+
753
+ Building transactions in this manner might feel different from what you’re used to. Also, we certainly wouldn’t want you to have to bind transformed transactions to a new variable at each step, so we have released a functional programming library dubbed `@solana/functional` that lets you build transactions in **********************************pipelines**********************************. Here’s how it can be used:
754
+
755
+ ```tsx
756
+ import { pipe } from '@solana/functional';
757
+ import {
758
+ address,
759
+ Blockhash,
760
+ createTransaction,
761
+ setTransactionFeePayer,
762
+ setTransactionLifetimeUsingBlockhash,
763
+ } from '@solana/web3.js';
764
+
765
+ // Use `pipe(..)` to create a pipeline of transaction transform operations
766
+ const transaction = pipe(
767
+ createTransaction({ version: 0 }),
768
+ tx => setTransactionFeePayer(feePayer, tx),
769
+ tx => setTransactionLifetimeUsingBlockhash(recentBlockhash, tx)
770
+ );
771
+ ```
772
+
773
+ Note that `pipe(..)` is completely decoupled from transactions, so it can be used to pipeline any compatible transforms.
774
+
775
+ ## Codecs
776
+
777
+ We have taken steps to make it easier to write data (de)serializers, especially as they pertain to Rust datatypes and byte buffers.
778
+
779
+ Solana’s codecs libraries are broken up into modular components so you only need to import the ones you need. They are:
780
+
781
+ - `@solana/codecs-core`: The core codecs library for working with codecs serializers and creating custom ones
782
+ - `@solana/codecs-numbers`: Used for serialization of numbers (little-endian and big-endian bytes, etc.)
783
+ - `@solana/codecs-strings`: Used for serialization of strings
784
+ - `@solana/codecs-data-structures`: Codecs and serializers for structs
785
+ - `@solana/options`: Designed to build codecs and serializers for types that mimic Rust’s enums, which can include embedded data within their variants such as values, tuples, and structs
786
+
787
+ These packages are included in the main `@solana/web3.js` library but you may also import them from `@solana/codecs` if you only need the codecs.
788
+
789
+ Here’s an example of encoding and decoding a custom struct with some strings and numbers:
790
+
791
+ ```tsx
792
+ import { getStructCodec } from "@solana/codecs-data-structures";
793
+ import { getU64Codec, getU8Codec } from "@solana/codecs-numbers";
794
+ import { getStringCodec } from "@solana/codecs-strings";
795
+
796
+ // Equivalent in Rust:
797
+ // struct {
798
+ // amount: u64,
799
+ // decimals: u8,
800
+ // name: String,
801
+ // }
802
+ const structCodec = getStructCodec([
803
+ ["amount", getU64Codec()],
804
+ ["decimals", getU8Codec()],
805
+ ["name", getStringCodec()],
806
+ ]);
807
+
808
+ const myToken = {
809
+ amount: 1000000000000000n, // `bigint` or `number` is supported
810
+ decimals: 2,
811
+ name: "My Token",
812
+ };
813
+
814
+ const myEncodedToken: Uint8Array = structCodec.encode(myToken);
815
+ const myDecodedToken = structCodec.decode(myEncodedToken);
816
+
817
+ myDecodedToken satisfies {
818
+ amount: bigint;
819
+ decimals: number;
820
+ name: string;
98
821
  }
99
822
  ```
823
+
824
+ You may only need to encode or decode data, but not both. Importing one or the other allows your optimizing compiler to tree-shake the other implementation away:
825
+
826
+ ```tsx
827
+ import { Codec, combineCodec, Decoder, Encoder } from "@solana/codecs-core";
828
+ import { getStructDecoder, getStructEncoder } from "@solana/codecs-data-structures";
829
+ import { getU8Decoder, getU8Encoder, getU64Decoder, getU64Encoder } from "@solana/codecs-numbers";
830
+ import { getStringDecoder, getStringEncoder } from "@solana/codecs-strings";
831
+
832
+ export type MyToken = {
833
+ amount: bigint,
834
+ decimals: number,
835
+ name: string,
836
+ }
837
+
838
+ export type MyTokenArgs = {
839
+ amount: number | bigint,
840
+ decimals: number,
841
+ name: string,
842
+ }
843
+
844
+ export const getMyTokenEncoder = (): Encoder<MyTokenArgs> => getStructEncoder([
845
+ ["amount", getU64Encoder()],
846
+ ["decimals", getU8Encoder()],
847
+ ["name", getStringEncoder()],
848
+ ]);
849
+
850
+ export const getMyTokenDecoder = (): Decoder<MyToken> => getStructDecoder([
851
+ ["amount", getU64Decoder()],
852
+ ["decimals", getU8Decoder()],
853
+ ["name", getStringDecoder()],
854
+ ]);
855
+
856
+ export const getMyTokenCodec = (): Codec<MyTokenArgs, MyToken> => combineCodec(
857
+ getMyTokenEncoder(),
858
+ getMyTokenDecoder()
859
+ );
860
+ ```
861
+
862
+ See more in the different packages’ [README files on GitHub](https://github.com/solana-labs/solana-web3.js/blob/master/packages/codecs-data-structures/README.md).
863
+
864
+ ## Type-Safety
865
+
866
+ The new library makes use of some advanced TypeScript features, including generic types, conditional types, `Parameters<..>`, `ReturnType<..>` and more.
867
+
868
+ We’ve described the RPC API in detail so that TypeScript can determine the *exact* type of the result you will receive from the server given a particular input. Change the type of the input, and you will see the return type reflect that change.
869
+
870
+ ### RPC Types
871
+
872
+ The RPC methods – both HTTP and subscriptions – are built with multiple overloads and conditional types. The expected HTTP response payload or subscription message format will be reflected in the return type of the function you’re working with when you provide the inputs in your code.
873
+
874
+ Here’s an example of this in action:
875
+
876
+ ```tsx
877
+ // Provide one set of parameters, get a certain type
878
+ // These parameters resolve to return type:
879
+ // {
880
+ // blockhash: Blockhash;
881
+ // blockHeight: bigint;
882
+ // blockTime: UnixTimestamp;
883
+ // parentSlot: bigint;
884
+ // previousBlockhash: Blockhash;
885
+ // }
886
+ const blockResponse = await rpc.getBlock(0n, {
887
+ rewards: false,
888
+ transactionDetails: 'none'
889
+ }).send();
890
+
891
+ // Switch `rewards` to `true`, get `rewards` in the return type
892
+ // {
893
+ // /* ... Previous response */
894
+ // rewards: Reward[];
895
+ // }
896
+ const blockWithRewardsResponse = await rpc.getBlock(0n, {
897
+ rewards: true,
898
+ transactionDetails: 'none'
899
+ }).send();
900
+
901
+ // Switch `transactionDetails` to `full`, get `transactions` in the return type
902
+ // {
903
+ // /* ... Previous response */
904
+ // transactions: TransactionResponse[];
905
+ // }
906
+ const blockWithRewardsAndTransactionsResponse = await rpc.getBlock(0n, {
907
+ rewards: true,
908
+ transactionDetails: 'full'
909
+ }).send();
910
+ ```
911
+
912
+ ### Catching Compile-Time Bugs with TypeScript
913
+
914
+ As previously mentioned, the type coverage in web3.js 2.0 allows developers to catch common bugs at compile time, rather than runtime.
915
+
916
+ In the example below, a transaction is created and then attempted to be compiled without setting the fee payer. This would result in a runtime error from the RPC, but instead you will see a type error from TypeScript as you type:
917
+
918
+ ```tsx
919
+ const encodedTx = pipe(
920
+ createTransaction({ version: 0 }),
921
+ tx => setTransactionLifetimeUsingBlockhash(recentBlockhash, tx),
922
+ tx => getBase64EncodedWireTransaction(tx), // Property 'feePayer' is missing in type
923
+ );
924
+ ```
925
+
926
+ Consider another example where a developer is attempting to send a transaction that has not been fully signed. Again, the TypeScript compiler will throw a type error:
927
+
928
+ ```tsx
929
+ const unsignedTransaction = pipe(
930
+ createTransaction({ version: 0 }),
931
+ tx => setTransactionFeePayer(feePayerAddress, tx),
932
+ tx => setTransactionLifetimeUsingBlockhash(recentBlockhash, tx),
933
+ );
934
+
935
+ const signature = sendAndConfirmTransaction({
936
+ confirmRecentTransaction: createDefaultRecentTransactionConfirmer({ rpc, rpcSubscriptions }),
937
+ rpc,
938
+ transaction: unsignedTransaction, // Transaction has not been signed: Type error
939
+ });
940
+
941
+ const transaction = await signTransaction([], unsignedTransaction);
942
+
943
+ // Asserts the transaction as a `IFullySignedTransaction`
944
+ // Throws an error if any signatures are missing!
945
+ assertTransactionIsFullySigned(transaction);
946
+ ```
947
+
948
+ Are you working with a nonce transaction and forgot to make `AdvanceNonce` the first instruction? That’s a type error:
949
+
950
+ ```tsx
951
+ const feePayer = await generateKeyPair();
952
+ const feePayerAddress = await getAddressFromPublicKey(feePayer.publicKey);
953
+
954
+ const notNonceTransaction = pipe(
955
+ createTransaction({ version: 0 }),
956
+ tx => setTransactionFeePayer(feePayerAddress, tx),
957
+ );
958
+
959
+ notNonceTransaction satisfies IDurableNonceTransaction;
960
+ // => Property 'lifetimeConstraint' is missing in type
961
+
962
+ const nonceConfig = {
963
+ nonce: 'nonce' as Nonce,
964
+ nonceAccountAddress: address('5tLU66bxQ35so2bReGcyf3GfMMAAauZdNA1N4uRnKQu4'),
965
+ nonceAuthorityAddress: address('GDhj8paPg8woUzp9n8fj7eAMocN5P7Ej3A7T9F5gotTX'),
966
+ };
967
+
968
+ const stillNotNonceTransaction = {
969
+ lifetimeConstraint: nonceConfig,
970
+ ...notNonceTransaction,
971
+ };
972
+
973
+ stillNotNonceTransaction satisfies IDurableNonceTransaction;
974
+ // => 'readonly IInstruction<string>[]' is not assignable to type 'readonly [AdvanceNonceAccountInstruction<string, string>, ...IInstruction<string>[]]'
975
+
976
+ const validNonceTransaction = pipe(
977
+ createTransaction({ version: 0 }),
978
+ tx => setTransactionFeePayer(feePayerAddress, tx),
979
+ tx => setTransactionLifetimeUsingDurableNonce(nonceConfig, tx), // Adds the instruction!
980
+ );
981
+
982
+ validNonceTransaction satisfies IDurableNonceTransaction; // OK
983
+ ```
984
+
985
+ The library’s type-checking can even catch you using lamports instead of SOL for a value:
986
+
987
+ ```tsx
988
+ const airdropAmount = 1n; // SOL
989
+ const signature = rpc.requestAirdrop(myAddress, airdropAmount).send();
990
+ ```
991
+
992
+ It will force you to cast the numerical value for your airdrop (or transfer, etc.) amount using `lamports()`, which should be a good reminder!
993
+
994
+ ```tsx
995
+ const airdropAmount = lamports(1000000000n);
996
+ const signature = rpc.requestAirdrop(myAddress, airdropAmount).send();With the new library, it’s possible to specify the nature of a transaction instruction completely, just using TypeScrip
997
+ ```
998
+
999
+ ## Compatibility Layer
1000
+
1001
+ You will have noticed by now that web3.js is a complete and total breaking change from the 1.x line. We want to provide you with a strategy for interacting with 1.x APIs while building your application using 2.0. You need a tool for commuting between 1.x and 2.0 data types.
1002
+
1003
+ The `@solana/compat` library allows for interoperability between functions and class objects from the legacy library - such as `VersionedTransaction`, `PublicKey`, and `Keypair` - and functions and types of the new library - such as `Address`, `Transaction`, and `CryptoKeyPair`.
1004
+
1005
+ Here’s how you can use `@solana/compat` to convert from a legacy `PublicKey` to an `Address`:
1006
+
1007
+ ```tsx
1008
+ import { fromLegacyPublicKey } from '@solana/compat';
1009
+
1010
+ const publicKey = new PublicKey('B3piXWBQLLRuk56XG5VihxR4oe2PSsDM8nTF6s1DeVF5');
1011
+ const address: Address = fromLegacyPublicKey(publicKey);
1012
+ ```
1013
+
1014
+ Here’s how to convert from a legacy `Keypair` to a `CryptoKeyPair`:
1015
+
1016
+ ```tsx
1017
+ import { fromLegacyKeypair } from '@solana/compat';
1018
+
1019
+ const keypairLegacy = Keypair.generate();
1020
+ const cryptoKeyPair: CryptoKeyPair = fromLegacyKeypair(keypair);
1021
+ ```
1022
+
1023
+ Here’s how to convert legacy transaction objects to the new library’s transaction types:
1024
+
1025
+ ```tsx
1026
+ // For a transaction using a blockhash lifetime
1027
+ const tx = fromVersionedTransactionWithBlockhash(legacyTransactionV0);
1028
+ // You can also optionally provide a `lastValidBlockheight` parameter to manage retries
1029
+ const tx = fromVersionedTransactionWithBlockhash(legacyTransactionV0, lastValidBlockheight);
1030
+
1031
+ // For a transaction using a durable nonce lifetime
1032
+ const tx = fromVersionedTransactionWithDurableNonce(transaction);
1033
+ // Again you can also optionally provide a `lastValidBlockheight`
1034
+ const tx = fromVersionedTransactionWithDurableNonce(transaction, lastValidBlockheight);
1035
+ ```
1036
+
1037
+ To see more conversions supported by `@solana/compat`, you can check out the package’s [README on GitHub](https://github.com/solana-labs/solana-web3.js/blob/master/packages/compat/README.md).
1038
+
1039
+ # Going Forward
1040
+
1041
+ This Technology Preview is just that, and development on the new web3.js is ongoing. We are working on tooling to accompany the new library to make building web applications on Solana easier, safer, and more scalable.
1042
+
1043
+ Although this new approach to JavaScript tooling is drastically different than the tooling you are used to, we are confident that the customizability, performance, bundle size, and safety characteristics of the new library will make it worth the migration. We’re here to help you every step of the way, via Github issues when you find problems with the library, and on the [Solana Stack Exchange](https://sola.na/sse) when you have questions on how something is supposed to work.
1044
+
1045
+ ## Program Clients
1046
+
1047
+ Writing JavaScript clients for on-chain programs has been done manually up until now. Without an IDL for some of the native programs, this process has been necessarily manual and has resulted in clients that lag behind the actual capabilities of the programs themselves.
1048
+
1049
+ We think that program clients should be *generated* rather than written. Developers should be able to write Rust programs, compile the program code, and generate all of the JavaScript client-side code to interact with the program.
1050
+
1051
+ Developers familiar with Shank and Solita may recognize this idea. We want to take it even further.
1052
+
1053
+ Here’s what the code could look like for a program client that includes Solana’s core programs like the System program or the Compute Budget program.
1054
+
1055
+ ```tsx
1056
+ import { createTransaction, pipe } from '@solana/web3.js';
1057
+ import { addMemo, setComputeUnitLimit, transferSol } from '@solana/spl-core';
1058
+
1059
+ const instructions = await Promise.all([
1060
+ setComputeUnitLimit({ units: 600_000 }),
1061
+ transferSol({ source, destination, amount: 1_000_000_000 }),
1062
+ addMemo({ memo: "I'm transferring some SOL!" })
1063
+ ]);
1064
+
1065
+ // Creates a V0 transaction with 3 instructions inside.
1066
+ const transaction = pipe(
1067
+ createTransaction({ version: 0 }),
1068
+ tx => appendTransactionInstructions(instructions, tx),
1069
+ );
1070
+ ```
1071
+
1072
+ ## GraphQL
1073
+
1074
+ Though not directly related to web3.js, we wanted to hijack your attention to show you something else that we’re working on, of particular interest to frontend developers. It’s a new API for interacting with the RPC: a GraphQL API.
1075
+
1076
+ The `@solana/rpc-graphql` package can be used to make GraphQL queries to Solana RPC endpoints, using the same transports described above (including any customizations).
1077
+
1078
+ Here’s an example of retrieving account data with GraphQL:
1079
+
1080
+ ```tsx
1081
+ const source = `
1082
+ query myQuery($address: String!) {
1083
+ account(address: $address) {
1084
+ lamports
1085
+ }
1086
+ }
1087
+ `;
1088
+
1089
+ const variableValues = {
1090
+ address: 'AyGCwnwxQMCqaU4ixReHt8h5W4dwmxU7eM3BEQBdWVca',
1091
+ };
1092
+
1093
+ const result = await rpcGraphQL.query(source, variableValues);
1094
+
1095
+ expect(result).toMatchObject({
1096
+ data: {
1097
+ account: {
1098
+ lamports: 10290815n,
1099
+ },
1100
+ },
1101
+ });
1102
+ ```
1103
+
1104
+ Using GraphQL allows developers to only specify which fields they *actually* need, and do away with the rest of the response.
1105
+
1106
+ However, GraphQL is also extremely powerful for **nesting queries**, which can be particularly useful if you want to, say, get the **sum** of every lamports balance of every **owner of the owner** of each token account, while discarding any mint accounts.
1107
+
1108
+ ```tsx
1109
+ const source = `
1110
+ query getLamportsOfOwnersOfOwnersOfTokenAccounts {
1111
+ programAccounts(programAddress: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") {
1112
+ ... on TokenAccount {
1113
+ data {
1114
+ parsed {
1115
+ info {
1116
+ owner {
1117
+ owner {
1118
+ lamports
1119
+ }
1120
+ }
1121
+ }
1122
+ }
1123
+ }
1124
+ }
1125
+ }
1126
+ }
1127
+ `;
1128
+
1129
+ const result = await rpcGraphQL.query(source);
1130
+
1131
+ const sumOfAllLamportsOfOwnersOfOwnersOfTokenAccounts = result
1132
+ .map(o => o.account.data.parsed.info.owner.owner.lamports)
1133
+ .reduce((acc, lamports) => acc + lamports, 0);
1134
+ ```
1135
+
1136
+ The new GraphQL package supports this same style of nested querying on transactions and blocks.
1137
+
1138
+ ```tsx
1139
+ const source = `
1140
+ query myQuery($signature: String!, $commitment: Commitment) {
1141
+ transaction(signature: $signature, commitment: $commitment) {
1142
+ ... on TransactionJsonParsed {
1143
+ transaction {
1144
+ message {
1145
+ ... on TransactionMessageParsed {
1146
+ instructions {
1147
+ ... on CreateAccountInstruction {
1148
+ parsed {
1149
+ info {
1150
+ lamports
1151
+ space
1152
+ }
1153
+ program
1154
+ }
1155
+ }
1156
+ }
1157
+ }
1158
+ }
1159
+ }
1160
+ }
1161
+ }
1162
+ }
1163
+ `;
1164
+
1165
+ const variableValues = {
1166
+ signature: '63zkpxATgAwXRGFQZPDESTw2m4uZQ99sX338ibgKtTcgG6v34E3MSS3zckCwJHrimS71cvei6h1Bn1K1De53BNWC',
1167
+ commitment: 'confirmed',
1168
+ };
1169
+
1170
+ const result = await rpcGraphQL.query(source, variableValues);
1171
+
1172
+ expect(result).toMatchObject({
1173
+ data: {
1174
+ transaction: {
1175
+ transaction: {
1176
+ message: {
1177
+ instructions: expect.arrayContaining([{
1178
+ parsed: {
1179
+ info: {
1180
+ lamports: expect.any(BigInt),
1181
+ space: expect.any(BigInt),
1182
+ },
1183
+ program: 'system',
1184
+ },
1185
+ }])
1186
+ },
1187
+ },
1188
+ },
1189
+ },
1190
+ });
1191
+ ```
1192
+
1193
+ See more in the package’s [README on GitHub](https://github.com/solana-labs/solana-web3.js/tree/master/packages/rpc-graphql).
1194
+
1195
+ ## Development
1196
+
1197
+ You can see all development of the new library and any associated tooling – such as program clients and GraphQL support – in the web3.js repository on GitHub.
1198
+
1199
+ https://github.com/solana-labs/solana-web3.js
1200
+
1201
+ Solana Labs develops these tools in public, with open source. We encourage any and all developers who would like to work on these tools to contribute to the codebase.
1202
+
1203
+ In fact, we welcome anyone who experiments with the new library to submit feedback via GitHub issues (or pull requests). A steady stream of feedback on the library from you will give us the confidence to propose a release candidate sooner.
1204
+
1205
+ You can find issues to tackle in the repository’s “issues” section, just make sure to follow the contributor guidelines!
1206
+
1207
+ ## Thank you
1208
+
1209
+ We’re grateful that you have read this far. If you are interested in migrating an existing application to the new web3.js to take advantage of some of the benefits we’ve demonstrated, we want to give you some direct support. Reach out to [@steveluscher](https://twitter.com/steveluscher/) on Twitter to start a conversation.