@typesol/cli 0.23.0 → 0.24.0
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.
- package/CHANGELOG.md +61 -0
- package/README.md +13 -12
- package/SECURITY.md +1 -1
- package/dist/clientgen.js +190 -41
- package/dist/compiler/analyze.js +111 -19
- package/dist/compiler/emit/base.d.ts +17 -4
- package/dist/compiler/emit/base.js +60 -15
- package/dist/compiler/emit/calls.d.ts +7 -0
- package/dist/compiler/emit/calls.js +99 -9
- package/dist/compiler/emit/cpi.d.ts +23 -0
- package/dist/compiler/emit/cpi.js +232 -1
- package/dist/compiler/emit/expressions.d.ts +9 -1
- package/dist/compiler/emit/expressions.js +16 -12
- package/dist/compiler/emit/function.js +3 -1
- package/dist/compiler/emit/shared.d.ts +11 -1
- package/dist/compiler/emit/statements.d.ts +4 -2
- package/dist/compiler/emit/statements.js +128 -27
- package/dist/compiler/emit/storage.d.ts +6 -2
- package/dist/compiler/emit/storage.js +36 -32
- package/dist/compiler/model.d.ts +8 -0
- package/dist/idlpublish.d.ts +6 -1
- package/dist/idlpublish.js +47 -5
- package/dist/portgen.js +1 -1
- package/dist/svmrpc.d.ts +7 -1
- package/dist/svmrpc.js +133 -19
- package/dist/testing.js +23 -3
- package/docs/cli.md +2 -2
- package/docs/client.md +13 -10
- package/docs/errors.md +9 -3
- package/docs/language.md +18 -17
- package/docs/mainnet.md +1 -1
- package/docs/migrate-from-anchor.md +8 -9
- package/docs/test-your-program.md +6 -2
- package/docs/testing.md +1 -1
- package/docs/troubleshooting.md +3 -2
- package/lib/typesol.d.ts +120 -21
- package/package.json +1 -1
- package/runtime/typesol_rt.h +54 -18
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,67 @@ until 1.0, a minor version may change the language, the IDL layout or error code
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 0.24.0 — 2026-10-28
|
|
9
|
+
|
|
10
|
+
Functions as values, closures for any helper, stored `map` to structs and text, Metaplex Core external plugins, and
|
|
11
|
+
the fixes and identical-result compute-unit savings from a full code review: expanded helpers see only their own
|
|
12
|
+
scope, generated clients read only their program's events, and IDLs from the chain are read with limits. Not externally
|
|
13
|
+
audited (see SECURITY.md).
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- Functions as values: `const h = g`, and functions that return functions (`function adder(n: u64): (x: u64) => u64
|
|
17
|
+
{ return (x) => x + n; }`), which make a closure where they are called (`const add5 = adder(5n)`, `adder(5n)(x)`).
|
|
18
|
+
- Closures given to helpers with type parameters or accounts: the helper is expanded where it is called, its type
|
|
19
|
+
parameters bound from the call and its accounts the caller's.
|
|
20
|
+
- A stored `map` to structs or text: `const ps: FixedVec<Pair, 8> = xs.map((o) => ({ qty: o.qty, doubled: o.qty * 2n }))`,
|
|
21
|
+
`const names: FixedVec<FixedString<8>, 4> = xs.map(…)`.
|
|
22
|
+
- A struct argument holding a FixedVec, when the struct is only an argument: read as Anchor sends it (the FixedVec a
|
|
23
|
+
Borsh Vec), as a `@layout borsh` struct is.
|
|
24
|
+
- Metaplex Core external plugin adapters: `addExternalPlugin`, `removeExternalPlugin`, `writeExternalPluginData` and
|
|
25
|
+
their Collection forms, for AppData, Oracle and LifecycleHook (and the Linked forms), checked against the Core program
|
|
26
|
+
(Core itself answers lifecycle hooks with NotAvailable for now).
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- Fewer compute units, same results: the PDA bump search builds its seed list once, a growing account reads the rent
|
|
30
|
+
sysvar once, integers become text two digits at a time, a lone `${n}` template is not copied again, and
|
|
31
|
+
`list = fixedVec([])` clears the list in place. For example escrow `make` 3638 → 3605, `take_offer` 7117 → 7058,
|
|
32
|
+
`transfer_tokens` 8467 → 8367 CU.
|
|
33
|
+
- `eventsOf` takes the transaction with its account keys (`transaction.message.accountKeys`, as getTransaction returns
|
|
34
|
+
it); without them it reads log events only. `errorCode` / `isError` also read the RPC's JSON error
|
|
35
|
+
(`{ InstructionError: [0, { Custom: 6000 }] }`).
|
|
36
|
+
- `svmConnection`: `accountNotifications` yields after each later transaction that names the account (no snapshot of
|
|
37
|
+
it as it is), `logsNotifications` honors `{ mentions }` and includes failed transactions (with `err`), simulation
|
|
38
|
+
errors are in the RPC's JSON shape, and `getTransaction` returns the account keys and each inner instruction's
|
|
39
|
+
program index.
|
|
40
|
+
- Compiler messages about accounts with a growable Vec say "has a growable Vec" instead of "ends in a Vec", and the
|
|
41
|
+
field-type list no longer says `Vec<T> (last)`, since a Vec can be any field (0.23.0).
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
- A helper expanded where it is called (given a closure), and a function a helper made, read a module constant as
|
|
45
|
+
the instruction argument of the same name; they now see only their parameters and the module.
|
|
46
|
+
- A struct parameter of a closure or expanded helper could be used after a push, pop or clear moved the Vec it points
|
|
47
|
+
into (a stale pointer); that is refused like any other reference across the move.
|
|
48
|
+
- A closure parameter named like an account read the account; it is refused.
|
|
49
|
+
- A helper from another file, expanded here or typed from an arrow passed to it, resolved its types with this file's
|
|
50
|
+
imports (an aliased or unimported type failed to compile, or an annotation was dropped); its own file's imports are
|
|
51
|
+
used, and `applyT<u64>((v) => …)` takes the type given at the call.
|
|
52
|
+
- A struct with a FixedVec held in a data enum variant was read as Borsh when also an argument; that conflict is refused.
|
|
53
|
+
- Generated clients read only this program's events: `parseEvents` follows the invoke stack and reads a `Program data:`
|
|
54
|
+
line only while the program is the innermost one running, and `eventsOf` reads only `emitCpi` inner instructions that
|
|
55
|
+
call the program. Another program can no longer forge them.
|
|
56
|
+
- `watchEvents` skips failed transactions.
|
|
57
|
+
- `typesol client` refuses malformed generic arguments, array lengths that are not whole numbers and `repr` alignments
|
|
58
|
+
that are not powers of two, and caps generic instantiation (256 types, 16 levels): a ClientIdlError instead of a crash
|
|
59
|
+
or a hang. Sizing deeply nested structs no longer takes exponential time.
|
|
60
|
+
- Accounts named like `Object.prototype` properties (`constructor`, `toString`, …) are read as given, not inherited.
|
|
61
|
+
- `resolve.<ix>` derives a has_one source that is a PDA (a `config` PDA) before reading it, and names the account to
|
|
62
|
+
pass when a source is missing.
|
|
63
|
+
- Notes and errors escape control characters from the IDL.
|
|
64
|
+
- Reading an IDL from the chain caps inflation at 16 MiB, checks Anchor's IdlAccount discriminator, and refuses a
|
|
65
|
+
Program Metadata IDL that links to a URL or another account instead of following it.
|
|
66
|
+
- `typesol/testing`'s `returned()` decodes string, bytes, f32 and f64 return values, and throws for types it cannot
|
|
67
|
+
decode instead of misreading them.
|
|
68
|
+
|
|
8
69
|
## 0.23.0 — 2026-10-28
|
|
9
70
|
|
|
10
71
|
Language limits lifted: `map` and `filter`, any number of growable Vecs per account, closures and arrow functions as
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<a href="https://github.com/typesol/typesol/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/typesol/typesol/actions/workflows/ci.yml/badge.svg?branch=main"></a>
|
|
9
9
|
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-3178C6"></a>
|
|
10
|
-
<a href="
|
|
10
|
+
<a href="https://www.npmjs.com/package/@typesol/cli"><img alt="npm version" src="https://img.shields.io/npm/v/@typesol/cli"></a>
|
|
11
11
|
<img alt="Target: Solana SBF" src="https://img.shields.io/badge/target-Solana%20SBF-14F195">
|
|
12
12
|
</p>
|
|
13
13
|
|
|
@@ -31,9 +31,9 @@ clients read TypeSOL programs unchanged.
|
|
|
31
31
|
- Every arithmetic operation is checked.
|
|
32
32
|
- Account owner, discriminator, signer, writability and aliasing checks are generated from your parameter types.
|
|
33
33
|
- Constraints follow Anchor's rules and use Anchor's error codes.
|
|
34
|
-
- **Small and fast.**
|
|
35
|
-
|
|
36
|
-
instruction.
|
|
34
|
+
- **Small and fast.** In the LiteSVM benchmarks below, TypeSOL uses 5–32% of Anchor's compute units, with binaries
|
|
35
|
+
about 10× smaller, and fewer compute units than native Rust and hand-tuned Pinocchio on every instruction. On
|
|
36
|
+
mainnet the results differ by instruction; see [the mainnet table](#on-mainnet).
|
|
37
37
|
- **Reproducible.** The same sources and toolchain produce the same bytes. `typesol verify` reads a deployed program
|
|
38
38
|
from the cluster and checks it, and the IDL published on chain, against its source.
|
|
39
39
|
|
|
@@ -175,14 +175,14 @@ counter.ts ─▶ tsc typecheck ─▶ subset check ─▶ C + typesol_rt.h ─
|
|
|
175
175
|
|
|
176
176
|
1. **Typecheck.** The TypeScript compiler checks your code against [`lib/typesol.d.ts`](lib/typesol.d.ts), which
|
|
177
177
|
declares every TypeSOL type and built-in.
|
|
178
|
-
2. **Subset check.** TypeSOL rejects anything outside the language (floats,
|
|
178
|
+
2. **Subset check.** TypeSOL rejects anything outside the language (floats, classes, `any`, foreign imports)
|
|
179
179
|
with a `file:line:col` diagnostic. Nothing is half-supported.
|
|
180
180
|
3. **Lower.** Instruction bodies become C. Account validation, checked arithmetic, Borsh encoding and CPI plumbing
|
|
181
181
|
come from [`runtime/typesol_rt.h`](runtime/typesol_rt.h).
|
|
182
182
|
4. **Build.** The Agave platform-tools `clang` and `ld.lld` produce the `.so` (SBPF v0 by default; v1 and v2 with
|
|
183
183
|
`--sbpf`).
|
|
184
184
|
|
|
185
|
-
Runtime failures point back to your source: `Error 6000 Unauthorized (counter.ts:
|
|
185
|
+
Runtime failures point back to your source: `Error 6000 Unauthorized (counter.ts:25)`, or
|
|
186
186
|
`TypeSOL: arithmetic overflow (vault.ts:42)`.
|
|
187
187
|
|
|
188
188
|
## What you can write
|
|
@@ -225,7 +225,8 @@ import { build } from "typesol/compiler";
|
|
|
225
225
|
const { soPath, idl } = build("programs/counter.ts", "target/counter", programAddress);
|
|
226
226
|
```
|
|
227
227
|
|
|
228
|
-
Subpath exports: `typesol/compiler`, `typesol/workspace`, `typesol/cpi`, `typesol/
|
|
228
|
+
Subpath exports: `typesol/compiler`, `typesol/workspace`, `typesol/cpi`, `typesol/client`, `typesol/idl`,
|
|
229
|
+
`typesol/profile`, `typesol/testing`, `typesol/ts-plugin`.
|
|
229
230
|
|
|
230
231
|
## Numbers
|
|
231
232
|
|
|
@@ -236,16 +237,16 @@ shows mean compute units per call (generated by `scripts/readme-bench.ts` from t
|
|
|
236
237
|
<!-- bench:table -->
|
|
237
238
|
| Instruction | TypeSOL | Pinocchio 0.10 | Native Rust | Anchor 1.0.2 |
|
|
238
239
|
| --- | ---: | ---: | ---: | ---: |
|
|
239
|
-
| counter.initialize | **1,
|
|
240
|
+
| counter.initialize | **1,866** | 3,186 | 4,601 | 6,573 |
|
|
240
241
|
| counter.increment | **110** | 113 | 857 | 2,323 |
|
|
241
|
-
| vault.open | **1,
|
|
242
|
+
| vault.open | **1,885** | 3,185 | 4,613 | 6,627 |
|
|
242
243
|
| vault.deposit | **1,314** | 1,377 | 2,832 | 4,355 |
|
|
243
244
|
| vault.withdraw | **315** | 370 | 1,176 | 3,628 |
|
|
244
245
|
| vault.shut | **161** | 167 | 950 | 2,024 |
|
|
245
|
-
| escrow.make | **3,
|
|
246
|
+
| escrow.make | **3,605** | 6,179 | 9,240 | 13,304 |
|
|
246
247
|
| escrow.take | **4,035** | 4,051 | 8,500 | 14,365 |
|
|
247
248
|
| escrow.refund | **2,716** | 2,769 | 5,695 | 8,449 |
|
|
248
|
-
| binary, counter / vault / escrow | 13.
|
|
249
|
+
| binary, counter / vault / escrow | 13.1 / 16.5 / 23.9 KB | 19.6 / 22.9 / 29.4 KB | 27.9 / 35.0 / 46.5 KB | 145.2 / 158.9 / 190.7 KB |
|
|
249
250
|
<!-- /bench:table -->
|
|
250
251
|
|
|
251
252
|
`initialize`, `open` and `make` include the PDA bump search. TypeSOL hashes PDA candidates with the sha256 and
|
|
@@ -306,7 +307,7 @@ Details are in [docs/testing.md](docs/testing.md).
|
|
|
306
307
|
|
|
307
308
|
## Status
|
|
308
309
|
|
|
309
|
-
**TypeSOL is pre-1.0 (0.
|
|
310
|
+
**TypeSOL is pre-1.0 (0.24).** Releases follow the organization's
|
|
310
311
|
[versioning policy](https://github.com/typesol/.github/blob/main/VERSIONING.md); [CHANGELOG.md](CHANGELOG.md) lists every
|
|
311
312
|
change.
|
|
312
313
|
- The compiler, runtime and tooling have been through internal security reviews and are covered by the tests above.
|
package/SECURITY.md
CHANGED
|
@@ -4,7 +4,7 @@ TypeSOL generates programs that hold funds. Treat it accordingly.
|
|
|
4
4
|
|
|
5
5
|
## Status
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- The compiler, runtime and tooling have been through internal security reviews. Every release is covered by execution
|
|
8
8
|
tests, differential tests against Anchor, and randomized tests. **TypeSOL has not been audited by an independent
|
|
9
9
|
firm.** Do not put significant value behind programs built with it until an external audit of the compiler and
|
|
10
10
|
runtime is published, and audit your own programs as you would any Solana program.
|
package/dist/clientgen.js
CHANGED
|
@@ -93,6 +93,11 @@ const TAKEN = [
|
|
|
93
93
|
"required",
|
|
94
94
|
"meta",
|
|
95
95
|
"programFromBytes",
|
|
96
|
+
"ownProp",
|
|
97
|
+
"readRelation",
|
|
98
|
+
"memcmp",
|
|
99
|
+
"cpiData",
|
|
100
|
+
"FetchedTransaction",
|
|
96
101
|
"fixedString",
|
|
97
102
|
"fixedOption",
|
|
98
103
|
"unitEnum",
|
|
@@ -279,6 +284,8 @@ const pascal = (s) => {
|
|
|
279
284
|
return c.charAt(0).toUpperCase() + c.slice(1);
|
|
280
285
|
};
|
|
281
286
|
const prop = (s) => (NAME.test(s) && !JS_RESERVED.has(s) ? s : JSON.stringify(s));
|
|
287
|
+
/** Text safe to print on a terminal (notes, errors): C0 and C1 control characters and bidi overrides escaped. */
|
|
288
|
+
const printable = (s) => s.replace(/[\u0000-\u001f\u007f-\u009f\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
282
289
|
/** Text that can sit inside a block comment: printable ASCII, no comment terminator. */
|
|
283
290
|
const commentText = (s) => s.replace(/[^\x20-\x7e]/g, " ").replace(/\*\//g, "* /");
|
|
284
291
|
const doc = (lines, indent = "") => Array.isArray(lines) && lines.length && lines.every((l) => typeof l === "string")
|
|
@@ -291,7 +298,7 @@ const sortCodeUnits = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
|
291
298
|
/** Throws ClientIdlError unless every value that reaches the generated source is well-formed. */
|
|
292
299
|
function validate(idl, programAddress) {
|
|
293
300
|
const fail = (what) => {
|
|
294
|
-
throw new ClientIdlError(`invalid IDL: ${what}`);
|
|
301
|
+
throw new ClientIdlError(printable(`invalid IDL: ${what}`));
|
|
295
302
|
};
|
|
296
303
|
const name = (n, where) => (typeof n === "string" && NAME.test(n) ? n : fail(`${where}: name ${JSON.stringify(n)} is not an identifier`));
|
|
297
304
|
const byteList = (b, where) => Array.isArray(b) && b.every((x) => Number.isInteger(x) && x >= 0 && x <= 255) ? b : fail(`${where}: expected a list of bytes (0-255)`);
|
|
@@ -304,6 +311,12 @@ function validate(idl, programAddress) {
|
|
|
304
311
|
if (o.array !== undefined) {
|
|
305
312
|
if (!Array.isArray(o.array) || o.array.length !== 2)
|
|
306
313
|
fail(`${where}: bad array type`);
|
|
314
|
+
const len = o.array[1];
|
|
315
|
+
// A length: a whole number, or a const generic parameter (`{ generic: "N" }`).
|
|
316
|
+
if (typeof len === "object" && len !== null)
|
|
317
|
+
name(len.generic, `${where} array length`);
|
|
318
|
+
else if (!(Number.isSafeInteger(len) && len >= 0))
|
|
319
|
+
fail(`${where}: array length ${JSON.stringify(len)} is not a whole number`);
|
|
307
320
|
type(o.array[0], where);
|
|
308
321
|
return;
|
|
309
322
|
}
|
|
@@ -312,7 +325,24 @@ function validate(idl, programAddress) {
|
|
|
312
325
|
return type(o[k], where);
|
|
313
326
|
if (o.defined !== undefined) {
|
|
314
327
|
const d = o.defined;
|
|
315
|
-
|
|
328
|
+
if (typeof d === "string")
|
|
329
|
+
return void name(d, where);
|
|
330
|
+
if (typeof d !== "object" || d === null)
|
|
331
|
+
return fail(`${where}: bad defined type ${JSON.stringify(d)}`);
|
|
332
|
+
name(d.name, where);
|
|
333
|
+
// Generic arguments: `{ kind: "type", type }` or `{ kind: "const", value: "4" }`.
|
|
334
|
+
const args = d.generics;
|
|
335
|
+
if (args === undefined)
|
|
336
|
+
return;
|
|
337
|
+
if (!Array.isArray(args))
|
|
338
|
+
return fail(`${where}: generic arguments must be a list`);
|
|
339
|
+
for (const a of args) {
|
|
340
|
+
const g = (typeof a === "object" && a !== null ? a : {});
|
|
341
|
+
if (g.kind === "type")
|
|
342
|
+
type(g.type, `${where} generic argument`);
|
|
343
|
+
else if (g.kind !== "const" || !(typeof g.value === "string" && /^\d{1,15}$/.test(g.value)))
|
|
344
|
+
fail(`${where}: bad generic argument ${JSON.stringify(a)}`);
|
|
345
|
+
}
|
|
316
346
|
return;
|
|
317
347
|
}
|
|
318
348
|
if (o.generic !== undefined)
|
|
@@ -380,6 +410,19 @@ function validate(idl, programAddress) {
|
|
|
380
410
|
name(t.name, "type");
|
|
381
411
|
if (typeof t.type !== "object" || t.type === null)
|
|
382
412
|
fail(`type ${t.name} has no definition`);
|
|
413
|
+
if (t.generics !== undefined) {
|
|
414
|
+
if (!Array.isArray(t.generics))
|
|
415
|
+
fail(`type ${t.name}: generics must be a list`);
|
|
416
|
+
for (const g of t.generics)
|
|
417
|
+
name(g?.name, `type ${t.name} generic parameter`);
|
|
418
|
+
}
|
|
419
|
+
if (t.repr !== undefined) {
|
|
420
|
+
const align = t.repr?.align;
|
|
421
|
+
if (typeof t.repr !== "object" || t.repr === null)
|
|
422
|
+
fail(`type ${t.name}: bad repr`);
|
|
423
|
+
if (align !== undefined && !(Number.isSafeInteger(align) && align > 0 && Number.isInteger(Math.log2(align))))
|
|
424
|
+
fail(`type ${t.name}: repr align ${JSON.stringify(align)} is not a power of two`);
|
|
425
|
+
}
|
|
383
426
|
fields(t.type.fields, `type ${t.name}`);
|
|
384
427
|
for (const v of t.type.variants ?? []) {
|
|
385
428
|
name(v.name, `type ${t.name} variant`);
|
|
@@ -447,7 +490,10 @@ function monomorphize(idl) {
|
|
|
447
490
|
return pascal((definedName(t) ?? "T").replace(/[^A-Za-z0-9_]/g, ""));
|
|
448
491
|
};
|
|
449
492
|
const hasGeneric = (t) => JSON.stringify(t ?? null).includes('"generic"');
|
|
450
|
-
|
|
493
|
+
// Instantiations can nest without end (`A<T> { next: Option<A<Vec<T>>> }`): cap their number and nesting.
|
|
494
|
+
const MAX_INSTANCES = 256;
|
|
495
|
+
const MAX_DEPTH = 16;
|
|
496
|
+
const inst = (t, env, depth = 0) => {
|
|
451
497
|
if (typeof t === "string")
|
|
452
498
|
return t;
|
|
453
499
|
if (t.generic !== undefined) {
|
|
@@ -455,16 +501,16 @@ function monomorphize(idl) {
|
|
|
455
501
|
return v === undefined || typeof v === "number" ? t : v;
|
|
456
502
|
}
|
|
457
503
|
if (t.vec !== undefined)
|
|
458
|
-
return { vec: inst(t.vec, env) };
|
|
504
|
+
return { vec: inst(t.vec, env, depth) };
|
|
459
505
|
if (t.option !== undefined)
|
|
460
|
-
return { option: inst(t.option, env) };
|
|
506
|
+
return { option: inst(t.option, env, depth) };
|
|
461
507
|
if (t.coption !== undefined)
|
|
462
|
-
return { coption: inst(t.coption, env) };
|
|
508
|
+
return { coption: inst(t.coption, env, depth) };
|
|
463
509
|
if (t.array) {
|
|
464
510
|
const len = t.array[1];
|
|
465
511
|
const g = typeof len === "object" && len !== null ? len.generic : undefined;
|
|
466
512
|
const n = g !== undefined ? env.get(g) : len;
|
|
467
|
-
return { array: [inst(t.array[0], env), typeof n === "number" ? n : len] };
|
|
513
|
+
return { array: [inst(t.array[0], env, depth), typeof n === "number" ? n : len] };
|
|
468
514
|
}
|
|
469
515
|
const name = definedName(t);
|
|
470
516
|
const args = typeof t.defined === "object" ? (t.defined.generics ?? []) : [];
|
|
@@ -476,7 +522,7 @@ function monomorphize(idl) {
|
|
|
476
522
|
: undefined
|
|
477
523
|
: a?.type === undefined
|
|
478
524
|
? undefined
|
|
479
|
-
: inst(a.type, env));
|
|
525
|
+
: inst(a.type, env, depth));
|
|
480
526
|
const def = defs.get(name);
|
|
481
527
|
const params = (def?.generics ?? []);
|
|
482
528
|
if (!def ||
|
|
@@ -487,6 +533,8 @@ function monomorphize(idl) {
|
|
|
487
533
|
const key = JSON.stringify([name, values]);
|
|
488
534
|
let made1 = made.get(key);
|
|
489
535
|
if (!made1) {
|
|
536
|
+
if (extra.length >= MAX_INSTANCES || depth >= MAX_DEPTH)
|
|
537
|
+
throw new ClientIdlError(`generic type ${name} is instantiated ${depth >= MAX_DEPTH ? `more than ${MAX_DEPTH} levels deep` : `more than ${MAX_INSTANCES} times`} (a generic type that uses itself with growing arguments?)`);
|
|
490
538
|
let base = `${name}${values.map((v) => label(v)).join("")}`;
|
|
491
539
|
if (!NAME.test(base))
|
|
492
540
|
base = `${name}Instance`;
|
|
@@ -497,7 +545,7 @@ function monomorphize(idl) {
|
|
|
497
545
|
made.set(key, made1);
|
|
498
546
|
const env2 = new Map(params.map((p, i) => [p.name, values[i]]));
|
|
499
547
|
const body = def.type;
|
|
500
|
-
const field = (f) => (isField(f) ? { ...f, type: inst(f.type, env2) } : inst(f, env2));
|
|
548
|
+
const field = (f) => (isField(f) ? { ...f, type: inst(f.type, env2, depth + 1) } : inst(f, env2, depth + 1));
|
|
501
549
|
extra.push({
|
|
502
550
|
...def,
|
|
503
551
|
name: made1,
|
|
@@ -506,7 +554,7 @@ function monomorphize(idl) {
|
|
|
506
554
|
...body,
|
|
507
555
|
...(body.fields ? { fields: body.fields.map(field) } : {}),
|
|
508
556
|
...(body.variants ? { variants: body.variants.map((v) => ({ ...v, ...(v.fields ? { fields: v.fields.map(field) } : {}) })) } : {}),
|
|
509
|
-
...(body.alias !== undefined ? { alias: inst(body.alias, env2) } : {}),
|
|
557
|
+
...(body.alias !== undefined ? { alias: inst(body.alias, env2, depth + 1) } : {}),
|
|
510
558
|
},
|
|
511
559
|
});
|
|
512
560
|
}
|
|
@@ -533,6 +581,18 @@ function monomorphize(idl) {
|
|
|
533
581
|
return { ...idl, instructions, types: [...types, ...extra] };
|
|
534
582
|
}
|
|
535
583
|
export function generateClient(idl, opts = {}) {
|
|
584
|
+
try {
|
|
585
|
+
const res = generate(idl, opts);
|
|
586
|
+
return { ...res, notes: res.notes.map(printable) };
|
|
587
|
+
}
|
|
588
|
+
catch (e) {
|
|
589
|
+
if (e instanceof ClientIdlError)
|
|
590
|
+
throw e;
|
|
591
|
+
// A malformed IDL that validation let through is still the IDL's fault: the same error, the original as cause.
|
|
592
|
+
throw new ClientIdlError(printable(`cannot generate a client from this IDL: ${e instanceof Error ? e.message : String(e)}`), { cause: e });
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
function generate(idl, opts) {
|
|
536
596
|
const programAddress = opts.address ?? idl.address;
|
|
537
597
|
validate(idl, programAddress);
|
|
538
598
|
idl = monomorphize(idl);
|
|
@@ -633,7 +693,7 @@ export function generateClient(idl, opts = {}) {
|
|
|
633
693
|
if (typeof t === "string") {
|
|
634
694
|
if (t in INTS || t in FLOATS || ["bool", "pubkey", "publicKey", "string", "bytes"].includes(t))
|
|
635
695
|
return undefined;
|
|
636
|
-
return `type
|
|
696
|
+
return `type ${JSON.stringify(t)} is not supported`;
|
|
637
697
|
}
|
|
638
698
|
if (t.generic !== undefined)
|
|
639
699
|
return "generic parameters are not supported";
|
|
@@ -668,7 +728,7 @@ export function generateClient(idl, opts = {}) {
|
|
|
668
728
|
why = l;
|
|
669
729
|
}
|
|
670
730
|
else if (d.serialization && d.serialization !== "borsh")
|
|
671
|
-
why = `type ${n} uses ${d.serialization} serialization, which is not supported`;
|
|
731
|
+
why = `type ${n} uses ${JSON.stringify(d.serialization)} serialization, which is not supported`;
|
|
672
732
|
else if (special(n)?.kind === "foption")
|
|
673
733
|
why = issue(special(n).inner);
|
|
674
734
|
else if (d.type.kind === "struct")
|
|
@@ -678,7 +738,7 @@ export function generateClient(idl, opts = {}) {
|
|
|
678
738
|
else if (d.type.kind === "type" && d.type.alias !== undefined)
|
|
679
739
|
why = issue(d.type.alias);
|
|
680
740
|
else
|
|
681
|
-
why = `type ${n} (${d.type.kind}) is not supported`;
|
|
741
|
+
why = `type ${n} (${JSON.stringify(d.type.kind)}) is not supported`;
|
|
682
742
|
typeIssues.set(n, why);
|
|
683
743
|
return why;
|
|
684
744
|
}
|
|
@@ -788,7 +848,8 @@ export function generateClient(idl, opts = {}) {
|
|
|
788
848
|
return (use("fixCodecSize"), `fixCodecSize(get${tsName(s.base)}Codec(), ${s.size})`);
|
|
789
849
|
return `get${tsName(n)}Codec()`;
|
|
790
850
|
}
|
|
791
|
-
/** Byte size of a fixed-size IDL type (undefined when it varies). */
|
|
851
|
+
/** Byte size of a fixed-size IDL type (undefined when it varies). Memoized by type name, so shared types are sized once. */
|
|
852
|
+
const sizes = new Map();
|
|
792
853
|
function idlSize(t) {
|
|
793
854
|
if (typeof t === "string") {
|
|
794
855
|
if (t in INTS)
|
|
@@ -808,6 +869,14 @@ export function generateClient(idl, opts = {}) {
|
|
|
808
869
|
const n = definedName(t);
|
|
809
870
|
if (n === undefined)
|
|
810
871
|
return undefined;
|
|
872
|
+
if (sizes.has(n))
|
|
873
|
+
return sizes.get(n);
|
|
874
|
+
sizes.set(n, undefined); // a type that holds itself has no fixed size
|
|
875
|
+
const size = definedSize(n);
|
|
876
|
+
sizes.set(n, size);
|
|
877
|
+
return size;
|
|
878
|
+
}
|
|
879
|
+
function definedSize(n) {
|
|
811
880
|
const sp = special(n);
|
|
812
881
|
if (sp?.kind === "fstring")
|
|
813
882
|
return 4 + sp.cap;
|
|
@@ -1121,8 +1190,9 @@ export function generateClient(idl, opts = {}) {
|
|
|
1121
1190
|
? `export interface ${P}Args {\n${argsField.map((a) => `${doc(a.docs, " ")} ${prop(a.key)}: ${tsType(a.type, "in")};`).join("\n")}\n}\n`
|
|
1122
1191
|
: "";
|
|
1123
1192
|
// Locals: given accounts and fixed addresses first, then PDAs in dependency order. `null` leaves an optional
|
|
1124
|
-
// account out (sent as the program id); `undefined` fills it in.
|
|
1125
|
-
|
|
1193
|
+
// account out (sent as the program id); `undefined` fills it in. Accounts are read as own properties, so one named
|
|
1194
|
+
// `constructor` or `toString` is not taken from Object.prototype.
|
|
1195
|
+
const input = (a) => `ownProp(input, ${JSON.stringify(a.key)})`;
|
|
1126
1196
|
const fill = (a, value) => (a.optional ? `${input(a)} === null ? null : (${input(a)} ?? ${value})` : `${input(a)} ?? ${value}`);
|
|
1127
1197
|
const lines = [];
|
|
1128
1198
|
for (const a of flat) {
|
|
@@ -1190,7 +1260,8 @@ export function generateClient(idl, opts = {}) {
|
|
|
1190
1260
|
ixKeys.push(`${prop(fn)}: build${P}`);
|
|
1191
1261
|
// `resolve.<instruction>`: what needs the chain or the caller's wallet, filled in on a copy of the accounts in this
|
|
1192
1262
|
// order: signers (a default signer), has_one accounts, then PDAs seeded from other accounts' fields.
|
|
1193
|
-
const acc = (f) => `acc
|
|
1263
|
+
const acc = (f) => `ownProp(acc, ${JSON.stringify(f.key)})`;
|
|
1264
|
+
const setAcc = (f) => `acc${NAME.test(f.key) && !JS_RESERVED.has(f.key) ? `.${f.key}` : `[${JSON.stringify(f.key)}]`}`;
|
|
1194
1265
|
// A field's value as seed bytes: scalars by their codec, strings as UTF-8, byte fields as they are.
|
|
1195
1266
|
const seedOfField = (t, v) => {
|
|
1196
1267
|
if (typeof t === "string") {
|
|
@@ -1330,8 +1401,7 @@ export function generateClient(idl, opts = {}) {
|
|
|
1330
1401
|
params.set(camel(p.path), `${camel(p.path)}: Address`);
|
|
1331
1402
|
const rewrite = (e) => e
|
|
1332
1403
|
.replace(/\bargs\.([A-Za-z0-9_$]+)/g, "seeds.$1")
|
|
1333
|
-
.replace(/key\(acc
|
|
1334
|
-
.replace(/key\(acc\["([^"]+)"\]!\)/g, 'key(seeds["$1"])');
|
|
1404
|
+
.replace(/key\(ownProp\(acc, "([^"]+)"\)!\)/g, (_, k) => `key(seeds${NAME.test(k) && !JS_RESERVED.has(k) ? `.${k}` : `[${JSON.stringify(k)}]`})`);
|
|
1335
1405
|
use("type ProgramDerivedAddress", "getProgramDerivedAddress");
|
|
1336
1406
|
pdaFns.set(pn, {
|
|
1337
1407
|
json,
|
|
@@ -1348,17 +1418,38 @@ export function generateClient(idl, opts = {}) {
|
|
|
1348
1418
|
lines: [
|
|
1349
1419
|
` if (${acc(a)} === undefined) {`,
|
|
1350
1420
|
...[...bases.values()].map(({ base, A }) => ` const ${"$$" + base.key} = await ${A}.fetch(rpc, key(${acc(base)}!));`),
|
|
1351
|
-
` ${
|
|
1421
|
+
` ${setAcc(a)} = (await getProgramDerivedAddress({ programAddress: ${program}, seeds: [${seeds.join(", ")}] }))[0];`,
|
|
1352
1422
|
` }`,
|
|
1353
1423
|
],
|
|
1354
1424
|
});
|
|
1355
1425
|
}
|
|
1426
|
+
// A has_one source the builder derives (a `config` PDA) is derived here too, after the accounts its seeds name,
|
|
1427
|
+
// so its fields can be read.
|
|
1428
|
+
const needed = new Set();
|
|
1429
|
+
const need = (k) => {
|
|
1430
|
+
if (!resolvable.has(k) || needed.has(k))
|
|
1431
|
+
return;
|
|
1432
|
+
needed.add(k);
|
|
1433
|
+
for (const d of resolvable.get(k).deps)
|
|
1434
|
+
need(d);
|
|
1435
|
+
};
|
|
1436
|
+
for (const r of rels)
|
|
1437
|
+
need(r.source.key);
|
|
1438
|
+
const derived = order.filter((a) => needed.has(a.key));
|
|
1439
|
+
const ref = (f) => (f.address ? `(${acc(f)} ?? address(${JSON.stringify(f.address)}))` : acc(f));
|
|
1440
|
+
const sub = (e) => e.replace(/key\(\$([A-Za-z0-9_]+)\)/g, (_, k) => `key(${ref(flat.find((f) => f.key === k))}, ${JSON.stringify(k)})`);
|
|
1441
|
+
const deriveLines = derived.map((a) => {
|
|
1442
|
+
const r = resolvable.get(a.key);
|
|
1443
|
+
return ` if (${acc(a)} === undefined) ${setAcc(a)} = (await getProgramDerivedAddress({ programAddress: ${sub(r.program ?? "PROGRAM_ADDRESS")}, seeds: [${r.seeds.map(sub).join(", ")}] }))[0];`;
|
|
1444
|
+
});
|
|
1356
1445
|
if (rels.length || fieldPdas.length || signers.length) {
|
|
1357
1446
|
const filled = [...signers, ...rels.map((r) => r.a), ...fieldPdas.map((f) => f.a)];
|
|
1358
1447
|
const keysOf = [...new Set(filled.map((a) => JSON.stringify(a.key)))].join(" | ");
|
|
1359
|
-
const withArgs = fieldPdas.some((f) => f.usesArgs)
|
|
1448
|
+
const withArgs = fieldPdas.some((f) => f.usesArgs) ||
|
|
1449
|
+
derived.some((a) => /\bargs\./.test([...resolvable.get(a.key).seeds, resolvable.get(a.key).program ?? ""].join(" ")));
|
|
1360
1450
|
const what = [
|
|
1361
1451
|
...(signers.length ? [`${signers.map((a) => a.key).join(", ")} (options.signer when left out)`] : []),
|
|
1452
|
+
...derived.map((a) => `${a.key} (derived, to read it)`),
|
|
1362
1453
|
...rels.map((r) => `${r.a.key} (from ${r.source.key}.${camel(r.a.name)})`),
|
|
1363
1454
|
...fieldPdas.map((f) => `${f.a.key} (a PDA seeded from account fields)`),
|
|
1364
1455
|
].join(", ");
|
|
@@ -1366,8 +1457,9 @@ export function generateClient(idl, opts = {}) {
|
|
|
1366
1457
|
` async ${prop(fn)}(rpc: Rpc<GetAccountInfoApi>, input: Omit<${P}Accounts, ${keysOf}> & Partial<Pick<${P}Accounts, ${keysOf}>>${withArgs ? `, args: ${P}Args` : ""}, options: { signer?: TransactionSigner } = {}): Promise<${P}Accounts> {\n` +
|
|
1367
1458
|
` const acc: Partial<${P}Accounts> = { ...input };\n` +
|
|
1368
1459
|
[
|
|
1369
|
-
...signers.map((a) => ` if (${acc(a)} === undefined) {\n if (!options.signer) throw new Error(${JSON.stringify(`${ix.name}: pass ${a.key}, or a default signer as options.signer`)});\n ${
|
|
1370
|
-
...
|
|
1460
|
+
...signers.map((a) => ` if (${acc(a)} === undefined) {\n if (!options.signer) throw new Error(${JSON.stringify(`${ix.name}: pass ${a.key}, or a default signer as options.signer`)});\n ${setAcc(a)} = options.signer;\n }`),
|
|
1461
|
+
...deriveLines,
|
|
1462
|
+
...rels.map((r) => ` if (${acc(r.a)} === undefined) ${setAcc(r.a)} = await readRelation(rpc, ${ref(r.source)}, ${JSON.stringify(r.source.key)}, ${JSON.stringify(r.a.key)}, [${r.readers.join(", ")}]);`),
|
|
1371
1463
|
...fieldPdas.flatMap((f) => f.lines),
|
|
1372
1464
|
].join("\n") +
|
|
1373
1465
|
`\n void rpc;\n return acc as ${P}Accounts;\n },`);
|
|
@@ -1420,8 +1512,8 @@ export function generateClient(idl, opts = {}) {
|
|
|
1420
1512
|
{
|
|
1421
1513
|
name: "key",
|
|
1422
1514
|
lines: [
|
|
1423
|
-
`const key = (v: TransactionSigner | Address | undefined | null): Address => {`,
|
|
1424
|
-
` if (v === undefined || v === null) throw new Error("a seed account is missing");`,
|
|
1515
|
+
`const key = (v: TransactionSigner | Address | undefined | null, name?: string): Address => {`,
|
|
1516
|
+
` if (v === undefined || v === null) throw new Error(name === undefined ? "a seed account is missing" : "a seed account is missing: pass " + name);`,
|
|
1425
1517
|
` return isSigner(v) ? v.address : v;`,
|
|
1426
1518
|
`};`,
|
|
1427
1519
|
],
|
|
@@ -1442,11 +1534,26 @@ export function generateClient(idl, opts = {}) {
|
|
|
1442
1534
|
],
|
|
1443
1535
|
},
|
|
1444
1536
|
{ name: "programFromBytes", lines: [`const programFromBytes = (b: Uint8Array): Address => getAddressDecoder().decode(b);`] },
|
|
1537
|
+
{
|
|
1538
|
+
name: "ownProp",
|
|
1539
|
+
lines: [
|
|
1540
|
+
`/** An account given in \`o\` (an own property: never one inherited from Object.prototype, such as \`constructor\`). */`,
|
|
1541
|
+
`const ownProp = <T extends object, K extends keyof T>(o: T, k: K): T[K] | undefined => (Object.prototype.hasOwnProperty.call(o, k) ? o[k] : undefined);`,
|
|
1542
|
+
],
|
|
1543
|
+
},
|
|
1445
1544
|
{
|
|
1446
1545
|
name: "readRelation",
|
|
1447
1546
|
lines: [
|
|
1448
|
-
`/** A Pubkey field of an account on chain, read by the first reader that knows its type. */`,
|
|
1449
|
-
`const readRelation = async (
|
|
1547
|
+
`/** A Pubkey field (\`name\`) of an account on chain (\`from\`), read by the first reader that knows its type. */`,
|
|
1548
|
+
`const readRelation = async (`,
|
|
1549
|
+
` rpc: Rpc<GetAccountInfoApi>,`,
|
|
1550
|
+
` from: TransactionSigner | Address | undefined,`,
|
|
1551
|
+
` fromName: string,`,
|
|
1552
|
+
` name: string,`,
|
|
1553
|
+
` readers: ((data: ReadonlyUint8Array) => Address | undefined)[],`,
|
|
1554
|
+
`): Promise<Address> => {`,
|
|
1555
|
+
` if (from === undefined) throw new Error("cannot fill in " + name + ": pass " + fromName + ", the account it is read from");`,
|
|
1556
|
+
` const at = key(from);`,
|
|
1450
1557
|
` const found = await fetchEncodedAccount(rpc, at);`,
|
|
1451
1558
|
` if (!found.exists) throw new Error("cannot fill in " + name + ": account " + at + " does not exist");`,
|
|
1452
1559
|
` for (const read of readers) {`,
|
|
@@ -1581,6 +1688,11 @@ export function generateClient(idl, opts = {}) {
|
|
|
1581
1688
|
` for (let cur: unknown = e, depth = 0; cur !== undefined && cur !== null && depth < 8; depth++) {`,
|
|
1582
1689
|
` if (typeof cur === "number") return cur;`,
|
|
1583
1690
|
` if (isSolanaError(cur, SOLANA_ERROR__INSTRUCTION_ERROR__CUSTOM)) return Number(cur.context.code);`,
|
|
1691
|
+
` // The JSON an RPC node returns (simulations, signature statuses): { InstructionError: [0, { Custom: 6000 }] }.`,
|
|
1692
|
+
` if (typeof cur === "object" && "InstructionError" in cur && Array.isArray(cur.InstructionError)) {`,
|
|
1693
|
+
` const inner: unknown = cur.InstructionError[1];`,
|
|
1694
|
+
` if (typeof inner === "object" && inner !== null && "Custom" in inner) return Number(inner.Custom);`,
|
|
1695
|
+
` }`,
|
|
1584
1696
|
` if (typeof cur === "object" && "err" in cur && typeof cur.err === "function") cur = String(cur.err());`,
|
|
1585
1697
|
` if (typeof cur === "string" || cur instanceof Error) {`,
|
|
1586
1698
|
` const m = /custom program error: (0x[0-9a-f]+)|Custom(?:\\(|\\s*\\{\\s*code:\\s*)(\\d+)/i.exec(String(cur instanceof Error ? cur.message : cur));`,
|
|
@@ -1612,14 +1724,25 @@ export function generateClient(idl, opts = {}) {
|
|
|
1612
1724
|
: ` void data;\n return null;`,
|
|
1613
1725
|
`}`,
|
|
1614
1726
|
`/**`,
|
|
1615
|
-
` * Events in a transaction's log messages (\`Program data:\` lines, as \`emit\` writes them).
|
|
1616
|
-
` *
|
|
1617
|
-
` *
|
|
1727
|
+
` * Events in a transaction's log messages (\`Program data:\` lines, as \`emit\` writes them). Only this program's lines`,
|
|
1728
|
+
` * count: the \`Program <id> invoke\` and \`success\` / \`failed\` lines are followed, and a data line is read only while`,
|
|
1729
|
+
` * this program is the innermost one running (as Anchor's EventParser does). Lines that do not decode are skipped.`,
|
|
1730
|
+
` * \`emitCpi\` events travel in inner instructions: see eventsOf.`,
|
|
1618
1731
|
` */`,
|
|
1619
1732
|
`export function parseEvents(logs: readonly string[]): Event[] {`,
|
|
1620
1733
|
` const found: Event[] = [];`,
|
|
1734
|
+
` const running: string[] = [];`,
|
|
1621
1735
|
` for (const line of logs) {`,
|
|
1622
|
-
`
|
|
1736
|
+
` const invoke = /^Program (\\S+) invoke \\[\\d+\\]$/.exec(line);`,
|
|
1737
|
+
` if (invoke) {`,
|
|
1738
|
+
` running.push(invoke[1]!);`,
|
|
1739
|
+
` continue;`,
|
|
1740
|
+
` }`,
|
|
1741
|
+
` if (/^Program \\S+ (success$|failed)/.test(line)) {`,
|
|
1742
|
+
` running.pop();`,
|
|
1743
|
+
` continue;`,
|
|
1744
|
+
` }`,
|
|
1745
|
+
` if (running.at(-1) !== PROGRAM_ADDRESS || !line.startsWith("Program data: ")) continue;`,
|
|
1623
1746
|
` let data: ReadonlyUint8Array;`,
|
|
1624
1747
|
` try {`,
|
|
1625
1748
|
` data = getBase64Encoder().encode(line.slice("Program data: ".length));`,
|
|
@@ -1631,7 +1754,10 @@ export function generateClient(idl, opts = {}) {
|
|
|
1631
1754
|
` }`,
|
|
1632
1755
|
` return found;`,
|
|
1633
1756
|
`}`,
|
|
1634
|
-
|
|
1757
|
+
`/**`,
|
|
1758
|
+
` * \`emitCpi\` events in instruction data (bytes, or base58 as RPC returns them). This does not check which program the`,
|
|
1759
|
+
` * instructions called, so give it only this program's inner instructions; eventsOf picks them from a transaction.`,
|
|
1760
|
+
` */`,
|
|
1635
1761
|
`export function parseCpiEvents(instructionData: Iterable<ReadonlyUint8Array | string>): Event[] {`,
|
|
1636
1762
|
` const tag = new Uint8Array([228, 69, 165, 46, 81, 203, 154, 29]);`,
|
|
1637
1763
|
` const found: Event[] = [];`,
|
|
@@ -1648,16 +1774,38 @@ export function generateClient(idl, opts = {}) {
|
|
|
1648
1774
|
` }`,
|
|
1649
1775
|
` return found;`,
|
|
1650
1776
|
`}`,
|
|
1651
|
-
`/**
|
|
1652
|
-
`export
|
|
1653
|
-
`
|
|
1654
|
-
`
|
|
1655
|
-
`
|
|
1777
|
+
`/** A transaction as getTransaction returns it (json encoding), as far as events need it. */`,
|
|
1778
|
+
`export interface FetchedTransaction {`,
|
|
1779
|
+
` transaction?: { message: { accountKeys: readonly (string | { pubkey: string })[] } } | null;`,
|
|
1780
|
+
` meta: {`,
|
|
1781
|
+
` logMessages?: readonly string[] | null;`,
|
|
1782
|
+
` innerInstructions?: readonly { instructions: readonly { programIdIndex?: number; data: string }[] }[] | null;`,
|
|
1783
|
+
` loadedAddresses?: { writable: readonly string[]; readonly: readonly string[] } | null;`,
|
|
1784
|
+
` } | null;`,
|
|
1785
|
+
`}`,
|
|
1786
|
+
`/** The data of the inner instructions of \`tx\` that call this program (found by their program's account index). */`,
|
|
1787
|
+
`function cpiData(tx: FetchedTransaction): string[] {`,
|
|
1788
|
+
` const keys = [`,
|
|
1789
|
+
` ...(tx.transaction?.message.accountKeys ?? []).map((k) => (typeof k === "string" ? k : k.pubkey)),`,
|
|
1790
|
+
` ...(tx.meta?.loadedAddresses?.writable ?? []),`,
|
|
1791
|
+
` ...(tx.meta?.loadedAddresses?.readonly ?? []),`,
|
|
1792
|
+
` ];`,
|
|
1793
|
+
` return (tx.meta?.innerInstructions ?? []).flatMap((g) =>`,
|
|
1794
|
+
` g.instructions.filter((i) => i.programIdIndex !== undefined && keys[i.programIdIndex] === PROGRAM_ADDRESS).map((i) => i.data),`,
|
|
1795
|
+
` );`,
|
|
1796
|
+
`}`,
|
|
1797
|
+
`/**`,
|
|
1798
|
+
` * Every event of a fetched transaction (getTransaction, json encoding): its log events, then the \`emitCpi\` events of`,
|
|
1799
|
+
` * its inner instructions that call this program (read only when \`tx.transaction\` gives the account keys).`,
|
|
1800
|
+
` */`,
|
|
1801
|
+
`export function eventsOf(tx: FetchedTransaction | null): Event[] {`,
|
|
1802
|
+
` if (!tx?.meta) return [];`,
|
|
1803
|
+
` return [...parseEvents(tx.meta.logMessages ?? []), ...parseCpiEvents(cpiData(tx))];`,
|
|
1656
1804
|
`}`,
|
|
1657
1805
|
`/**`,
|
|
1658
|
-
` * Events of every transaction that mentions the program, as their logs arrive, until \`abortSignal
|
|
1659
|
-
` * \`rpc\`, \`emitCpi\` events too: a transaction in which
|
|
1660
|
-
` * inner instructions read, after its log events.`,
|
|
1806
|
+
` * Events of every transaction that mentions the program and succeeded, as their logs arrive, until \`abortSignal\``,
|
|
1807
|
+
` * aborts (a failed transaction's events did not happen). With \`rpc\`, \`emitCpi\` events too: a transaction in which`,
|
|
1808
|
+
` * the program invoked itself is fetched (getTransaction) and its inner instructions read, after its log events.`,
|
|
1661
1809
|
` */`,
|
|
1662
1810
|
`export async function* watchEvents(`,
|
|
1663
1811
|
` rpcSubscriptions: RpcSubscriptions<LogsNotificationsApi>,`,
|
|
@@ -1666,6 +1814,7 @@ export function generateClient(idl, opts = {}) {
|
|
|
1666
1814
|
` const logs = await rpcSubscriptions.logsNotifications({ mentions: [PROGRAM_ADDRESS] }, options.commitment ? { commitment: options.commitment } : {}).subscribe({ abortSignal: options.abortSignal });`,
|
|
1667
1815
|
` const selfInvoke = new RegExp("^Program " + PROGRAM_ADDRESS + " invoke \\\\[([2-9]|\\\\d{2,})\\\\]$");`,
|
|
1668
1816
|
` for await (const n of logs) {`,
|
|
1817
|
+
` if (n.value.err !== null) continue;`,
|
|
1669
1818
|
` for (const event of parseEvents(n.value.logs)) yield { signature: n.value.signature, event };`,
|
|
1670
1819
|
` if (!options.rpc || !n.value.logs.some((l) => selfInvoke.test(l))) continue;`,
|
|
1671
1820
|
` // The transaction can trail its logs by a moment: a few tries.`,
|
|
@@ -1674,7 +1823,7 @@ export function generateClient(idl, opts = {}) {
|
|
|
1674
1823
|
` .getTransaction(n.value.signature, { encoding: "json", maxSupportedTransactionVersion: 0, commitment: options.commitment === "finalized" ? "finalized" : "confirmed" })`,
|
|
1675
1824
|
` .send();`,
|
|
1676
1825
|
` if (tx) {`,
|
|
1677
|
-
` for (const event of parseCpiEvents((tx
|
|
1826
|
+
` for (const event of parseCpiEvents(cpiData(tx))) yield { signature: n.value.signature, event };`,
|
|
1678
1827
|
` break;`,
|
|
1679
1828
|
` }`,
|
|
1680
1829
|
` await new Promise((resolve) => setTimeout(resolve, 400));`,
|