@typesol/cli 0.22.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 +82 -0
- package/README.md +13 -12
- package/SECURITY.md +1 -1
- package/dist/clientgen.js +190 -41
- package/dist/compiler/analyze.js +195 -25
- package/dist/compiler/emit/base.d.ts +28 -5
- package/dist/compiler/emit/base.js +81 -15
- package/dist/compiler/emit/calls.d.ts +18 -1
- package/dist/compiler/emit/calls.js +159 -7
- package/dist/compiler/emit/cpi.d.ts +23 -0
- package/dist/compiler/emit/cpi.js +274 -5
- package/dist/compiler/emit/expressions.d.ts +9 -1
- package/dist/compiler/emit/expressions.js +25 -12
- package/dist/compiler/emit/function.js +8 -4
- package/dist/compiler/emit/program.js +9 -2
- package/dist/compiler/emit/shared.d.ts +43 -3
- package/dist/compiler/emit/shared.js +43 -0
- package/dist/compiler/emit/statements.d.ts +39 -2
- package/dist/compiler/emit/statements.js +346 -87
- package/dist/compiler/emit/storage.d.ts +6 -2
- package/dist/compiler/emit/storage.js +75 -45
- package/dist/compiler/model.d.ts +26 -1
- 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 +21 -20
- 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 +127 -20
- package/package.json +1 -1
- package/runtime/typesol_rt.h +73 -18
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));`,
|