@typesol/cli 0.23.0 → 0.24.1
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 +77 -2
- package/README.md +18 -15
- package/SECURITY.md +1 -1
- package/dist/clientgen.js +222 -61
- package/dist/compiler/analyze.js +132 -20
- package/dist/compiler/emit/base.d.ts +27 -4
- package/dist/compiler/emit/base.js +68 -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 +33 -1
- package/dist/compiler/emit/cpi.js +353 -110
- 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/architecture.md +26 -2
- 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/runtime.md +59 -0
- 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 +106 -28
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;
|
|
@@ -1033,7 +1102,10 @@ export function generateClient(idl, opts = {}) {
|
|
|
1033
1102
|
// A seed or program path names an account: in the same group first, then at the top level.
|
|
1034
1103
|
const byPath = (p, group) => flat.find((f) => `${f.group}${f.name}` === `${group}${p}`) ?? flat.find((f) => f.group === "" && f.name === p);
|
|
1035
1104
|
const argType = new Map(ix.args.map((a) => [a.name, a.type]));
|
|
1036
|
-
const
|
|
1105
|
+
const seedsProp = (k) => `seeds${NAME.test(k) && !JS_RESERVED.has(k) ? `.${k}` : `[${JSON.stringify(k)}]`}`;
|
|
1106
|
+
const BUILDER = { arg: (n) => `args.${n}`, account: (k) => `$${k}` };
|
|
1107
|
+
const HELPER = { arg: (n) => seedsProp(n), account: (k) => seedsProp(k) };
|
|
1108
|
+
const seedExpr = (s, group, refs = BUILDER) => {
|
|
1037
1109
|
if (s.kind === "const")
|
|
1038
1110
|
return bytes(s.value);
|
|
1039
1111
|
if (s.kind === "arg") {
|
|
@@ -1042,7 +1114,7 @@ export function generateClient(idl, opts = {}) {
|
|
|
1042
1114
|
let t = argType.get(head);
|
|
1043
1115
|
if (t === undefined)
|
|
1044
1116
|
return { why: `the seed argument ${head} does not exist` };
|
|
1045
|
-
let v =
|
|
1117
|
+
let v = refs.arg(camel(head));
|
|
1046
1118
|
for (const part of rest) {
|
|
1047
1119
|
const d = defs.get(definedName(t) ?? "");
|
|
1048
1120
|
const f = d?.type.kind === "struct" ? (d.type.fields ?? []).find((x) => isField(x) && x.name === part) : undefined;
|
|
@@ -1065,25 +1137,30 @@ export function generateClient(idl, opts = {}) {
|
|
|
1065
1137
|
if (!target)
|
|
1066
1138
|
return { why: `the seed account ${s.path} is not an account of the instruction` };
|
|
1067
1139
|
use("getAddressEncoder");
|
|
1068
|
-
return `getAddressEncoder().encode(key(
|
|
1140
|
+
return `getAddressEncoder().encode(key(${refs.account(target.key)}))`;
|
|
1069
1141
|
};
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
if (!a.pda || a.address)
|
|
1073
|
-
continue;
|
|
1074
|
-
const seeds = a.pda.seeds.map((s) => seedExpr(s, a.group));
|
|
1075
|
-
let program;
|
|
1142
|
+
/** A PDA's program as the code being written names it (undefined: this program). */
|
|
1143
|
+
const pdaProgram = (a, refs) => {
|
|
1076
1144
|
const p = a.pda.program;
|
|
1077
1145
|
if (p?.kind === "const")
|
|
1078
|
-
|
|
1079
|
-
|
|
1146
|
+
return (use("getAddressDecoder"), `programFromBytes(${bytes(p.value)})`);
|
|
1147
|
+
if (p?.kind === "account") {
|
|
1080
1148
|
const target = p.path.includes(".") ? undefined : byPath(p.path, a.group);
|
|
1081
|
-
|
|
1149
|
+
return target ? `key(${refs.account(target.key)})` : { why: `the PDA program ${p.path} is not an account of the instruction` };
|
|
1082
1150
|
}
|
|
1083
|
-
|
|
1151
|
+
if (p?.kind === "arg") {
|
|
1084
1152
|
const t = p.path.includes(".") ? undefined : argType.get(p.path);
|
|
1085
|
-
|
|
1153
|
+
return t === "pubkey" || t === "publicKey" ? refs.arg(camel(p.path)) : { why: "the PDA program comes from an argument that is not a Pubkey" };
|
|
1086
1154
|
}
|
|
1155
|
+
return undefined;
|
|
1156
|
+
};
|
|
1157
|
+
const resolvable = new Map();
|
|
1158
|
+
for (const a of flat) {
|
|
1159
|
+
if (!a.pda || a.address)
|
|
1160
|
+
continue;
|
|
1161
|
+
const seeds = a.pda.seeds.map((s) => seedExpr(s, a.group));
|
|
1162
|
+
const program = pdaProgram(a, BUILDER);
|
|
1163
|
+
const p = a.pda.program;
|
|
1087
1164
|
const why = [...seeds, ...(program === undefined ? [] : [program])].find((x) => typeof x === "object");
|
|
1088
1165
|
if (why) {
|
|
1089
1166
|
notes.push(`instruction ${ix.name}: ${a.name} is not filled in (${why.why}); pass it`);
|
|
@@ -1121,8 +1198,9 @@ export function generateClient(idl, opts = {}) {
|
|
|
1121
1198
|
? `export interface ${P}Args {\n${argsField.map((a) => `${doc(a.docs, " ")} ${prop(a.key)}: ${tsType(a.type, "in")};`).join("\n")}\n}\n`
|
|
1122
1199
|
: "";
|
|
1123
1200
|
// 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
|
-
|
|
1201
|
+
// account out (sent as the program id); `undefined` fills it in. Accounts are read as own properties, so one named
|
|
1202
|
+
// `constructor` or `toString` is not taken from Object.prototype.
|
|
1203
|
+
const input = (a) => `ownProp(input, ${JSON.stringify(a.key)})`;
|
|
1126
1204
|
const fill = (a, value) => (a.optional ? `${input(a)} === null ? null : (${input(a)} ?? ${value})` : `${input(a)} ?? ${value}`);
|
|
1127
1205
|
const lines = [];
|
|
1128
1206
|
for (const a of flat) {
|
|
@@ -1158,11 +1236,13 @@ export function generateClient(idl, opts = {}) {
|
|
|
1158
1236
|
return [k, `${k}: Address | TransactionSigner`];
|
|
1159
1237
|
})).values(),
|
|
1160
1238
|
];
|
|
1161
|
-
|
|
1239
|
+
// The same derivation, its inputs named as the helper's `seeds` parameter.
|
|
1240
|
+
const helperSeeds = a.pda.seeds.map((s) => seedExpr(s, a.group, HELPER));
|
|
1241
|
+
const helperProgram = pdaProgram(a, HELPER) ?? "PROGRAM_ADDRESS";
|
|
1162
1242
|
use("type ProgramDerivedAddress");
|
|
1163
1243
|
pdaFns.set(pn, {
|
|
1164
1244
|
json,
|
|
1165
|
-
source: ` /** The ${a.name} PDA (as ${ix.name} derives it). */\n ${prop(pn)}(seeds: { ${params.join("; ")} }): Promise<ProgramDerivedAddress> {\n return getProgramDerivedAddress({ programAddress: ${
|
|
1245
|
+
source: ` /** The ${a.name} PDA (as ${ix.name} derives it). */\n ${prop(pn)}(seeds: { ${params.join("; ")} }): Promise<ProgramDerivedAddress> {\n return getProgramDerivedAddress({ programAddress: ${helperProgram}, seeds: [${helperSeeds.join(", ")}] });\n },`,
|
|
1166
1246
|
});
|
|
1167
1247
|
}
|
|
1168
1248
|
const metas = flat.map((a) => {
|
|
@@ -1190,7 +1270,8 @@ export function generateClient(idl, opts = {}) {
|
|
|
1190
1270
|
ixKeys.push(`${prop(fn)}: build${P}`);
|
|
1191
1271
|
// `resolve.<instruction>`: what needs the chain or the caller's wallet, filled in on a copy of the accounts in this
|
|
1192
1272
|
// order: signers (a default signer), has_one accounts, then PDAs seeded from other accounts' fields.
|
|
1193
|
-
const acc = (f) => `acc
|
|
1273
|
+
const acc = (f) => `ownProp(acc, ${JSON.stringify(f.key)})`;
|
|
1274
|
+
const setAcc = (f) => `acc${NAME.test(f.key) && !JS_RESERVED.has(f.key) ? `.${f.key}` : `[${JSON.stringify(f.key)}]`}`;
|
|
1194
1275
|
// A field's value as seed bytes: scalars by their codec, strings as UTF-8, byte fields as they are.
|
|
1195
1276
|
const seedOfField = (t, v) => {
|
|
1196
1277
|
if (typeof t === "string") {
|
|
@@ -1255,6 +1336,7 @@ export function generateClient(idl, opts = {}) {
|
|
|
1255
1336
|
}
|
|
1256
1337
|
const bases = new Map();
|
|
1257
1338
|
const seeds = [];
|
|
1339
|
+
const helperSeeds = []; // the same seeds, named as a `pdas` helper's `seeds` (for one seeded from its own fields)
|
|
1258
1340
|
const own = [];
|
|
1259
1341
|
// Inputs of a `pdas` helper for a PDA seeded from its own fields: its own field values, accounts and arguments.
|
|
1260
1342
|
const params = new Map();
|
|
@@ -1263,13 +1345,14 @@ export function generateClient(idl, opts = {}) {
|
|
|
1263
1345
|
let usesArgs = p?.kind === "arg";
|
|
1264
1346
|
for (const sd of a.pda.seeds) {
|
|
1265
1347
|
if (sd.kind === "const")
|
|
1266
|
-
seeds.push(bytes(sd.value));
|
|
1348
|
+
(seeds.push(bytes(sd.value)), helperSeeds.push(bytes(sd.value)));
|
|
1267
1349
|
else if (sd.kind === "arg") {
|
|
1268
1350
|
const e = seedExpr(sd, a.group);
|
|
1269
1351
|
if (typeof e !== "string")
|
|
1270
1352
|
ok = false;
|
|
1271
1353
|
else {
|
|
1272
1354
|
seeds.push(e);
|
|
1355
|
+
helperSeeds.push(seedExpr(sd, a.group, HELPER));
|
|
1273
1356
|
usesArgs = true;
|
|
1274
1357
|
const head = sd.path.split(".")[0];
|
|
1275
1358
|
params.set(camel(head), `${camel(head)}: ${tsType(argType.get(head), "in")}`);
|
|
@@ -1282,6 +1365,7 @@ export function generateClient(idl, opts = {}) {
|
|
|
1282
1365
|
else {
|
|
1283
1366
|
use("getAddressEncoder");
|
|
1284
1367
|
seeds.push(`getAddressEncoder().encode(key(${acc(t)}!))`);
|
|
1368
|
+
helperSeeds.push(`getAddressEncoder().encode(key(${seedsProp(t.key)}))`);
|
|
1285
1369
|
params.set(t.key, `${t.key}: Address | TransactionSigner`);
|
|
1286
1370
|
if (auto(t))
|
|
1287
1371
|
autoDep = true;
|
|
@@ -1299,6 +1383,7 @@ export function generateClient(idl, opts = {}) {
|
|
|
1299
1383
|
own.push({ field: parts[1], type: field.type });
|
|
1300
1384
|
params.set(camel(parts[1]), `${camel(parts[1])}: ${tsType(field.type, "in")}`);
|
|
1301
1385
|
seeds.push(enc);
|
|
1386
|
+
helperSeeds.push(enc);
|
|
1302
1387
|
}
|
|
1303
1388
|
else if (!consts || auto(base))
|
|
1304
1389
|
ok = false;
|
|
@@ -1328,14 +1413,11 @@ export function generateClient(idl, opts = {}) {
|
|
|
1328
1413
|
continue;
|
|
1329
1414
|
if (p?.kind === "arg")
|
|
1330
1415
|
params.set(camel(p.path), `${camel(p.path)}: Address`);
|
|
1331
|
-
const
|
|
1332
|
-
.replace(/\bargs\.([A-Za-z0-9_$]+)/g, "seeds.$1")
|
|
1333
|
-
.replace(/key\(acc\.([A-Za-z0-9_$]+)!\)/g, "key(seeds.$1)")
|
|
1334
|
-
.replace(/key\(acc\["([^"]+)"\]!\)/g, 'key(seeds["$1"])');
|
|
1416
|
+
const helperProgram = p?.kind === "arg" ? seedsProp(camel(p.path)) : program;
|
|
1335
1417
|
use("type ProgramDerivedAddress", "getProgramDerivedAddress");
|
|
1336
1418
|
pdaFns.set(pn, {
|
|
1337
1419
|
json,
|
|
1338
|
-
source: ` /** The ${a.name} PDA (as ${ix.name} derives it), from the values of its own fields it is seeded with (${own.map((o) => camel(o.field)).join(", ")}). */\n ${prop(pn)}(seeds: { ${[...params.values()].join("; ")} }): Promise<ProgramDerivedAddress> {\n return getProgramDerivedAddress({ programAddress: ${
|
|
1420
|
+
source: ` /** The ${a.name} PDA (as ${ix.name} derives it), from the values of its own fields it is seeded with (${own.map((o) => camel(o.field)).join(", ")}). */\n ${prop(pn)}(seeds: { ${[...params.values()].join("; ")} }): Promise<ProgramDerivedAddress> {\n return getProgramDerivedAddress({ programAddress: ${helperProgram}, seeds: [${helperSeeds.join(", ")}] });\n },`,
|
|
1339
1421
|
});
|
|
1340
1422
|
continue;
|
|
1341
1423
|
}
|
|
@@ -1348,17 +1430,38 @@ export function generateClient(idl, opts = {}) {
|
|
|
1348
1430
|
lines: [
|
|
1349
1431
|
` if (${acc(a)} === undefined) {`,
|
|
1350
1432
|
...[...bases.values()].map(({ base, A }) => ` const ${"$$" + base.key} = await ${A}.fetch(rpc, key(${acc(base)}!));`),
|
|
1351
|
-
` ${
|
|
1433
|
+
` ${setAcc(a)} = (await getProgramDerivedAddress({ programAddress: ${program}, seeds: [${seeds.join(", ")}] }))[0];`,
|
|
1352
1434
|
` }`,
|
|
1353
1435
|
],
|
|
1354
1436
|
});
|
|
1355
1437
|
}
|
|
1438
|
+
// A has_one source the builder derives (a `config` PDA) is derived here too, after the accounts its seeds name,
|
|
1439
|
+
// so its fields can be read.
|
|
1440
|
+
const needed = new Set();
|
|
1441
|
+
const need = (k) => {
|
|
1442
|
+
if (!resolvable.has(k) || needed.has(k))
|
|
1443
|
+
return;
|
|
1444
|
+
needed.add(k);
|
|
1445
|
+
for (const d of resolvable.get(k).deps)
|
|
1446
|
+
need(d);
|
|
1447
|
+
};
|
|
1448
|
+
for (const r of rels)
|
|
1449
|
+
need(r.source.key);
|
|
1450
|
+
const derived = order.filter((a) => needed.has(a.key));
|
|
1451
|
+
const ref = (f) => (f.address ? `(${acc(f)} ?? address(${JSON.stringify(f.address)}))` : acc(f));
|
|
1452
|
+
const sub = (e) => e.replace(/key\(\$([A-Za-z0-9_]+)\)/g, (_, k) => `key(${ref(flat.find((f) => f.key === k))}, ${JSON.stringify(k)})`);
|
|
1453
|
+
const deriveLines = derived.map((a) => {
|
|
1454
|
+
const r = resolvable.get(a.key);
|
|
1455
|
+
return ` if (${acc(a)} === undefined) ${setAcc(a)} = (await getProgramDerivedAddress({ programAddress: ${sub(r.program ?? "PROGRAM_ADDRESS")}, seeds: [${r.seeds.map(sub).join(", ")}] }))[0];`;
|
|
1456
|
+
});
|
|
1356
1457
|
if (rels.length || fieldPdas.length || signers.length) {
|
|
1357
1458
|
const filled = [...signers, ...rels.map((r) => r.a), ...fieldPdas.map((f) => f.a)];
|
|
1358
1459
|
const keysOf = [...new Set(filled.map((a) => JSON.stringify(a.key)))].join(" | ");
|
|
1359
|
-
const withArgs = fieldPdas.some((f) => f.usesArgs)
|
|
1460
|
+
const withArgs = fieldPdas.some((f) => f.usesArgs) ||
|
|
1461
|
+
derived.some((a) => /\bargs\./.test([...resolvable.get(a.key).seeds, resolvable.get(a.key).program ?? ""].join(" ")));
|
|
1360
1462
|
const what = [
|
|
1361
1463
|
...(signers.length ? [`${signers.map((a) => a.key).join(", ")} (options.signer when left out)`] : []),
|
|
1464
|
+
...derived.map((a) => `${a.key} (derived, to read it)`),
|
|
1362
1465
|
...rels.map((r) => `${r.a.key} (from ${r.source.key}.${camel(r.a.name)})`),
|
|
1363
1466
|
...fieldPdas.map((f) => `${f.a.key} (a PDA seeded from account fields)`),
|
|
1364
1467
|
].join(", ");
|
|
@@ -1366,8 +1469,9 @@ export function generateClient(idl, opts = {}) {
|
|
|
1366
1469
|
` 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
1470
|
` const acc: Partial<${P}Accounts> = { ...input };\n` +
|
|
1368
1471
|
[
|
|
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
|
-
...
|
|
1472
|
+
...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 }`),
|
|
1473
|
+
...deriveLines,
|
|
1474
|
+
...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
1475
|
...fieldPdas.flatMap((f) => f.lines),
|
|
1372
1476
|
].join("\n") +
|
|
1373
1477
|
`\n void rpc;\n return acc as ${P}Accounts;\n },`);
|
|
@@ -1420,8 +1524,8 @@ export function generateClient(idl, opts = {}) {
|
|
|
1420
1524
|
{
|
|
1421
1525
|
name: "key",
|
|
1422
1526
|
lines: [
|
|
1423
|
-
`const key = (v: TransactionSigner | Address | undefined | null): Address => {`,
|
|
1424
|
-
` if (v === undefined || v === null) throw new Error("a seed account is missing");`,
|
|
1527
|
+
`const key = (v: TransactionSigner | Address | undefined | null, name?: string): Address => {`,
|
|
1528
|
+
` if (v === undefined || v === null) throw new Error(name === undefined ? "a seed account is missing" : "a seed account is missing: pass " + name);`,
|
|
1425
1529
|
` return isSigner(v) ? v.address : v;`,
|
|
1426
1530
|
`};`,
|
|
1427
1531
|
],
|
|
@@ -1442,11 +1546,26 @@ export function generateClient(idl, opts = {}) {
|
|
|
1442
1546
|
],
|
|
1443
1547
|
},
|
|
1444
1548
|
{ name: "programFromBytes", lines: [`const programFromBytes = (b: Uint8Array): Address => getAddressDecoder().decode(b);`] },
|
|
1549
|
+
{
|
|
1550
|
+
name: "ownProp",
|
|
1551
|
+
lines: [
|
|
1552
|
+
`/** An account given in \`o\` (an own property: never one inherited from Object.prototype, such as \`constructor\`). */`,
|
|
1553
|
+
`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);`,
|
|
1554
|
+
],
|
|
1555
|
+
},
|
|
1445
1556
|
{
|
|
1446
1557
|
name: "readRelation",
|
|
1447
1558
|
lines: [
|
|
1448
|
-
`/** A Pubkey field of an account on chain, read by the first reader that knows its type. */`,
|
|
1449
|
-
`const readRelation = async (
|
|
1559
|
+
`/** A Pubkey field (\`name\`) of an account on chain (\`from\`), read by the first reader that knows its type. */`,
|
|
1560
|
+
`const readRelation = async (`,
|
|
1561
|
+
` rpc: Rpc<GetAccountInfoApi>,`,
|
|
1562
|
+
` from: TransactionSigner | Address | undefined,`,
|
|
1563
|
+
` fromName: string,`,
|
|
1564
|
+
` name: string,`,
|
|
1565
|
+
` readers: ((data: ReadonlyUint8Array) => Address | undefined)[],`,
|
|
1566
|
+
`): Promise<Address> => {`,
|
|
1567
|
+
` if (from === undefined) throw new Error("cannot fill in " + name + ": pass " + fromName + ", the account it is read from");`,
|
|
1568
|
+
` const at = key(from);`,
|
|
1450
1569
|
` const found = await fetchEncodedAccount(rpc, at);`,
|
|
1451
1570
|
` if (!found.exists) throw new Error("cannot fill in " + name + ": account " + at + " does not exist");`,
|
|
1452
1571
|
` for (const read of readers) {`,
|
|
@@ -1581,6 +1700,11 @@ export function generateClient(idl, opts = {}) {
|
|
|
1581
1700
|
` for (let cur: unknown = e, depth = 0; cur !== undefined && cur !== null && depth < 8; depth++) {`,
|
|
1582
1701
|
` if (typeof cur === "number") return cur;`,
|
|
1583
1702
|
` if (isSolanaError(cur, SOLANA_ERROR__INSTRUCTION_ERROR__CUSTOM)) return Number(cur.context.code);`,
|
|
1703
|
+
` // The JSON an RPC node returns (simulations, signature statuses): { InstructionError: [0, { Custom: 6000 }] }.`,
|
|
1704
|
+
` if (typeof cur === "object" && "InstructionError" in cur && Array.isArray(cur.InstructionError)) {`,
|
|
1705
|
+
` const inner: unknown = cur.InstructionError[1];`,
|
|
1706
|
+
` if (typeof inner === "object" && inner !== null && "Custom" in inner) return Number(inner.Custom);`,
|
|
1707
|
+
` }`,
|
|
1584
1708
|
` if (typeof cur === "object" && "err" in cur && typeof cur.err === "function") cur = String(cur.err());`,
|
|
1585
1709
|
` if (typeof cur === "string" || cur instanceof Error) {`,
|
|
1586
1710
|
` 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 +1736,25 @@ export function generateClient(idl, opts = {}) {
|
|
|
1612
1736
|
: ` void data;\n return null;`,
|
|
1613
1737
|
`}`,
|
|
1614
1738
|
`/**`,
|
|
1615
|
-
` * Events in a transaction's log messages (\`Program data:\` lines, as \`emit\` writes them).
|
|
1616
|
-
` *
|
|
1617
|
-
` *
|
|
1739
|
+
` * Events in a transaction's log messages (\`Program data:\` lines, as \`emit\` writes them). Only this program's lines`,
|
|
1740
|
+
` * count: the \`Program <id> invoke\` and \`success\` / \`failed\` lines are followed, and a data line is read only while`,
|
|
1741
|
+
` * this program is the innermost one running (as Anchor's EventParser does). Lines that do not decode are skipped.`,
|
|
1742
|
+
` * \`emitCpi\` events travel in inner instructions: see eventsOf.`,
|
|
1618
1743
|
` */`,
|
|
1619
1744
|
`export function parseEvents(logs: readonly string[]): Event[] {`,
|
|
1620
1745
|
` const found: Event[] = [];`,
|
|
1746
|
+
` const running: string[] = [];`,
|
|
1621
1747
|
` for (const line of logs) {`,
|
|
1622
|
-
`
|
|
1748
|
+
` const invoke = /^Program (\\S+) invoke \\[\\d+\\]$/.exec(line);`,
|
|
1749
|
+
` if (invoke) {`,
|
|
1750
|
+
` running.push(invoke[1]!);`,
|
|
1751
|
+
` continue;`,
|
|
1752
|
+
` }`,
|
|
1753
|
+
` if (/^Program \\S+ (success$|failed)/.test(line)) {`,
|
|
1754
|
+
` running.pop();`,
|
|
1755
|
+
` continue;`,
|
|
1756
|
+
` }`,
|
|
1757
|
+
` if (running.at(-1) !== PROGRAM_ADDRESS || !line.startsWith("Program data: ")) continue;`,
|
|
1623
1758
|
` let data: ReadonlyUint8Array;`,
|
|
1624
1759
|
` try {`,
|
|
1625
1760
|
` data = getBase64Encoder().encode(line.slice("Program data: ".length));`,
|
|
@@ -1631,7 +1766,10 @@ export function generateClient(idl, opts = {}) {
|
|
|
1631
1766
|
` }`,
|
|
1632
1767
|
` return found;`,
|
|
1633
1768
|
`}`,
|
|
1634
|
-
|
|
1769
|
+
`/**`,
|
|
1770
|
+
` * \`emitCpi\` events in instruction data (bytes, or base58 as RPC returns them). This does not check which program the`,
|
|
1771
|
+
` * instructions called, so give it only this program's inner instructions; eventsOf picks them from a transaction.`,
|
|
1772
|
+
` */`,
|
|
1635
1773
|
`export function parseCpiEvents(instructionData: Iterable<ReadonlyUint8Array | string>): Event[] {`,
|
|
1636
1774
|
` const tag = new Uint8Array([228, 69, 165, 46, 81, 203, 154, 29]);`,
|
|
1637
1775
|
` const found: Event[] = [];`,
|
|
@@ -1648,16 +1786,38 @@ export function generateClient(idl, opts = {}) {
|
|
|
1648
1786
|
` }`,
|
|
1649
1787
|
` return found;`,
|
|
1650
1788
|
`}`,
|
|
1651
|
-
`/**
|
|
1652
|
-
`export
|
|
1653
|
-
`
|
|
1654
|
-
`
|
|
1655
|
-
`
|
|
1789
|
+
`/** A transaction as getTransaction returns it (json encoding), as far as events need it. */`,
|
|
1790
|
+
`export interface FetchedTransaction {`,
|
|
1791
|
+
` transaction?: { message: { accountKeys: readonly (string | { pubkey: string })[] } } | null;`,
|
|
1792
|
+
` meta: {`,
|
|
1793
|
+
` logMessages?: readonly string[] | null;`,
|
|
1794
|
+
` innerInstructions?: readonly { instructions: readonly { programIdIndex?: number; data: string }[] }[] | null;`,
|
|
1795
|
+
` loadedAddresses?: { writable: readonly string[]; readonly: readonly string[] } | null;`,
|
|
1796
|
+
` } | null;`,
|
|
1797
|
+
`}`,
|
|
1798
|
+
`/** The data of the inner instructions of \`tx\` that call this program (found by their program's account index). */`,
|
|
1799
|
+
`function cpiData(tx: FetchedTransaction): string[] {`,
|
|
1800
|
+
` const keys = [`,
|
|
1801
|
+
` ...(tx.transaction?.message.accountKeys ?? []).map((k) => (typeof k === "string" ? k : k.pubkey)),`,
|
|
1802
|
+
` ...(tx.meta?.loadedAddresses?.writable ?? []),`,
|
|
1803
|
+
` ...(tx.meta?.loadedAddresses?.readonly ?? []),`,
|
|
1804
|
+
` ];`,
|
|
1805
|
+
` return (tx.meta?.innerInstructions ?? []).flatMap((g) =>`,
|
|
1806
|
+
` g.instructions.filter((i) => i.programIdIndex !== undefined && keys[i.programIdIndex] === PROGRAM_ADDRESS).map((i) => i.data),`,
|
|
1807
|
+
` );`,
|
|
1808
|
+
`}`,
|
|
1809
|
+
`/**`,
|
|
1810
|
+
` * Every event of a fetched transaction (getTransaction, json encoding): its log events, then the \`emitCpi\` events of`,
|
|
1811
|
+
` * its inner instructions that call this program (read only when \`tx.transaction\` gives the account keys).`,
|
|
1812
|
+
` */`,
|
|
1813
|
+
`export function eventsOf(tx: FetchedTransaction | null): Event[] {`,
|
|
1814
|
+
` if (!tx?.meta) return [];`,
|
|
1815
|
+
` return [...parseEvents(tx.meta.logMessages ?? []), ...parseCpiEvents(cpiData(tx))];`,
|
|
1656
1816
|
`}`,
|
|
1657
1817
|
`/**`,
|
|
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.`,
|
|
1818
|
+
` * Events of every transaction that mentions the program and succeeded, as their logs arrive, until \`abortSignal\``,
|
|
1819
|
+
` * aborts (a failed transaction's events did not happen). With \`rpc\`, \`emitCpi\` events too: a transaction in which`,
|
|
1820
|
+
` * the program invoked itself is fetched (getTransaction) and its inner instructions read, after its log events.`,
|
|
1661
1821
|
` */`,
|
|
1662
1822
|
`export async function* watchEvents(`,
|
|
1663
1823
|
` rpcSubscriptions: RpcSubscriptions<LogsNotificationsApi>,`,
|
|
@@ -1666,6 +1826,7 @@ export function generateClient(idl, opts = {}) {
|
|
|
1666
1826
|
` const logs = await rpcSubscriptions.logsNotifications({ mentions: [PROGRAM_ADDRESS] }, options.commitment ? { commitment: options.commitment } : {}).subscribe({ abortSignal: options.abortSignal });`,
|
|
1667
1827
|
` const selfInvoke = new RegExp("^Program " + PROGRAM_ADDRESS + " invoke \\\\[([2-9]|\\\\d{2,})\\\\]$");`,
|
|
1668
1828
|
` for await (const n of logs) {`,
|
|
1829
|
+
` if (n.value.err !== null) continue;`,
|
|
1669
1830
|
` for (const event of parseEvents(n.value.logs)) yield { signature: n.value.signature, event };`,
|
|
1670
1831
|
` if (!options.rpc || !n.value.logs.some((l) => selfInvoke.test(l))) continue;`,
|
|
1671
1832
|
` // The transaction can trail its logs by a moment: a few tries.`,
|
|
@@ -1674,7 +1835,7 @@ export function generateClient(idl, opts = {}) {
|
|
|
1674
1835
|
` .getTransaction(n.value.signature, { encoding: "json", maxSupportedTransactionVersion: 0, commitment: options.commitment === "finalized" ? "finalized" : "confirmed" })`,
|
|
1675
1836
|
` .send();`,
|
|
1676
1837
|
` if (tx) {`,
|
|
1677
|
-
` for (const event of parseCpiEvents((tx
|
|
1838
|
+
` for (const event of parseCpiEvents(cpiData(tx))) yield { signature: n.value.signature, event };`,
|
|
1678
1839
|
` break;`,
|
|
1679
1840
|
` }`,
|
|
1680
1841
|
` await new Promise((resolve) => setTimeout(resolve, 400));`,
|