@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.
Files changed (40) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +13 -12
  3. package/SECURITY.md +1 -1
  4. package/dist/clientgen.js +190 -41
  5. package/dist/compiler/analyze.js +195 -25
  6. package/dist/compiler/emit/base.d.ts +28 -5
  7. package/dist/compiler/emit/base.js +81 -15
  8. package/dist/compiler/emit/calls.d.ts +18 -1
  9. package/dist/compiler/emit/calls.js +159 -7
  10. package/dist/compiler/emit/cpi.d.ts +23 -0
  11. package/dist/compiler/emit/cpi.js +274 -5
  12. package/dist/compiler/emit/expressions.d.ts +9 -1
  13. package/dist/compiler/emit/expressions.js +25 -12
  14. package/dist/compiler/emit/function.js +8 -4
  15. package/dist/compiler/emit/program.js +9 -2
  16. package/dist/compiler/emit/shared.d.ts +43 -3
  17. package/dist/compiler/emit/shared.js +43 -0
  18. package/dist/compiler/emit/statements.d.ts +39 -2
  19. package/dist/compiler/emit/statements.js +346 -87
  20. package/dist/compiler/emit/storage.d.ts +6 -2
  21. package/dist/compiler/emit/storage.js +75 -45
  22. package/dist/compiler/model.d.ts +26 -1
  23. package/dist/idlpublish.d.ts +6 -1
  24. package/dist/idlpublish.js +47 -5
  25. package/dist/portgen.js +1 -1
  26. package/dist/svmrpc.d.ts +7 -1
  27. package/dist/svmrpc.js +133 -19
  28. package/dist/testing.js +23 -3
  29. package/docs/cli.md +2 -2
  30. package/docs/client.md +13 -10
  31. package/docs/errors.md +9 -3
  32. package/docs/language.md +21 -20
  33. package/docs/mainnet.md +1 -1
  34. package/docs/migrate-from-anchor.md +8 -9
  35. package/docs/test-your-program.md +6 -2
  36. package/docs/testing.md +1 -1
  37. package/docs/troubleshooting.md +3 -2
  38. package/lib/typesol.d.ts +127 -20
  39. package/package.json +1 -1
  40. 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
- name(typeof d === "string" ? d : d?.name, where);
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
- const inst = (t, env) => {
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 "${t}" is not supported`;
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
- const input = (a) => `input${NAME.test(a.key) && !JS_RESERVED.has(a.key) ? `.${a.key}` : `[${JSON.stringify(a.key)}]`}`;
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${NAME.test(f.key) && !JS_RESERVED.has(f.key) ? `.${f.key}` : `[${JSON.stringify(f.key)}]`}`;
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\.([A-Za-z0-9_$]+)!\)/g, "key(seeds.$1)")
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
- ` ${acc(a)} = (await getProgramDerivedAddress({ programAddress: ${program}, seeds: [${seeds.join(", ")}] }))[0];`,
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 ${acc(a)} = options.signer;\n }`),
1370
- ...rels.map((r) => ` ${acc(r.a)} ??= await readRelation(rpc, key(${acc(r.source)}!), ${JSON.stringify(r.a.key)}, [${r.readers.join(", ")}]);`),
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 (rpc: Rpc<GetAccountInfoApi>, at: Address, name: string, readers: ((data: ReadonlyUint8Array) => Address | undefined)[]): Promise<Address> => {`,
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). Lines are matched by`,
1616
- ` * discriminator, whichever program wrote them; lines that do not decode are skipped. \`emitCpi\` events travel in inner`,
1617
- ` * instructions: see parseCpiEvents and eventsOf.`,
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
- ` if (!line.startsWith("Program data: ")) continue;`,
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
- `/** \`emitCpi\` events: the data of inner instructions (bytes, or base58 as RPC returns them) that carry one. */`,
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
- `/** Every event of a fetched transaction (getTransaction, json encoding): its logs, then its inner instructions. */`,
1652
- `export function eventsOf(tx: { meta: { logMessages?: readonly string[] | null; innerInstructions?: readonly { instructions: readonly { data: string }[] }[] | null } | null } | null): Event[] {`,
1653
- ` const meta = tx?.meta;`,
1654
- ` if (!meta) return [];`,
1655
- ` return [...parseEvents(meta.logMessages ?? []), ...parseCpiEvents((meta.innerInstructions ?? []).flatMap((g) => g.instructions.map((i) => i.data)))];`,
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\` aborts. With`,
1659
- ` * \`rpc\`, \`emitCpi\` events too: a transaction in which the program invoked itself is fetched (getTransaction) and its`,
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.meta?.innerInstructions ?? []).flatMap((g) => g.instructions.map((i) => i.data)))) yield { signature: n.value.signature, event };`,
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));`,