@typesol/cli 0.19.0 → 0.20.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 CHANGED
@@ -5,6 +5,19 @@ until 1.0, a minor version may change the language, the IDL layout or error code
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 0.20.0 — 2026-10-28
9
+
10
+ The generated TypeScript clients catch up with Anchor's: remaining accounts, listing and watching accounts, sending
11
+ and simulating, views, `emitCpi` events and every Anchor error by name. Programs and their bytes are unchanged. Not
12
+ externally audited (see SECURITY.md).
13
+
14
+ ### Added
15
+ - Generated clients: remaining accounts on every instruction builder; `accounts.<Name>.fetchMultiple`, `fetchAll`
16
+ (getProgramAccounts by discriminator, with `{ offset, bytes }` filters) and `watch` (account subscription);
17
+ `sendInstructions` (send and confirm), `simulate`, and `views.<instruction>` (a return value by simulation, Anchor's
18
+ `.view()`); `parseCpiEvents` / `eventsOf` for `emitCpi` events, and `watchEvents` (log subscription). Every Anchor
19
+ framework error has its name in `errors`, so `isError` knows the codes `AnchorError` and the constraints raise.
20
+
8
21
  ## 0.19.0 — 2026-10-28
9
22
 
10
23
  Anchor's error codes where TypeSOL raised its own: `TokenOf` / `MintOf` constraints that name the parameters as
package/README.md CHANGED
@@ -306,7 +306,7 @@ Details are in [docs/testing.md](docs/testing.md).
306
306
 
307
307
  ## Status
308
308
 
309
- **TypeSOL is pre-1.0 (0.19).** Releases follow the organization's
309
+ **TypeSOL is pre-1.0 (0.20).** Releases follow the organization's
310
310
  [versioning policy](https://github.com/typesol/.github/blob/main/VERSIONING.md); [CHANGELOG.md](CHANGELOG.md) lists every
311
311
  change.
312
312
  - The compiler, runtime and tooling have been through internal security reviews and are covered by the tests above.
@@ -90,7 +90,10 @@ export interface ClientGenResult {
90
90
  /** An IDL the generator refuses: malformed, unsafe to embed, or older than the 0.30 IDL format. */
91
91
  export declare class ClientIdlError extends Error {
92
92
  }
93
- /** TypeSOL runtime and Anchor framework errors a TypeSOL program can return, by code (runtime/typesol_rt.h). */
93
+ /**
94
+ * Errors a TypeSOL program can return, by code: every Anchor framework error (`AnchorError` raises any of them, and
95
+ * the constraints raise their Anchor codes), then TypeSOL's own runtime errors (runtime/typesol_rt.h).
96
+ */
94
97
  export declare const TYPESOL_ERRORS: Record<number, string>;
95
98
  export declare function generateClient(idl: ClientIdl, opts?: {
96
99
  address?: string;
package/dist/clientgen.js CHANGED
@@ -18,12 +18,17 @@
18
18
  * sanitized comments.
19
19
  */
20
20
  import { mkdirSync, writeFileSync } from "node:fs";
21
+ import { ANCHOR_ERRORS } from "./compiler/anchor-errors.js";
21
22
  import path from "node:path";
22
23
  /** An IDL the generator refuses: malformed, unsafe to embed, or older than the 0.30 IDL format. */
23
24
  export class ClientIdlError extends Error {
24
25
  }
25
- /** TypeSOL runtime and Anchor framework errors a TypeSOL program can return, by code (runtime/typesol_rt.h). */
26
+ /**
27
+ * Errors a TypeSOL program can return, by code: every Anchor framework error (`AnchorError` raises any of them, and
28
+ * the constraints raise their Anchor codes), then TypeSOL's own runtime errors (runtime/typesol_rt.h).
29
+ */
26
30
  export const TYPESOL_ERRORS = {
31
+ ...Object.fromEntries(Object.entries(ANCHOR_ERRORS).map(([name, code]) => [code, name])),
27
32
  100: "InstructionMissing",
28
33
  101: "InstructionFallbackNotFound",
29
34
  102: "InstructionDidNotDeserialize",
@@ -164,7 +169,24 @@ const TAKEN = [
164
169
  "globalThis",
165
170
  ];
166
171
  const KIT_TYPES = [
172
+ "AccountMeta",
173
+ "AccountNotificationsApi",
167
174
  "Address",
175
+ "Base58EncodedBytes",
176
+ "Commitment",
177
+ "FetchAccountsConfig",
178
+ "GetEpochInfoApi",
179
+ "GetLatestBlockhashApi",
180
+ "GetMultipleAccountsApi",
181
+ "GetProgramAccountsApi",
182
+ "GetSignatureStatusesApi",
183
+ "LogsNotificationsApi",
184
+ "RpcSubscriptions",
185
+ "SendTransactionApi",
186
+ "Signature",
187
+ "SignatureNotificationsApi",
188
+ "SimulateTransactionApi",
189
+ "SlotNotificationsApi",
168
190
  "Codec",
169
191
  "FetchAccountConfig",
170
192
  "GetAccountInfoApi",
@@ -176,6 +198,21 @@ const KIT_TYPES = [
176
198
  ];
177
199
  const KIT_VALUES = [
178
200
  "AccountRole",
201
+ "appendTransactionMessageInstructions",
202
+ "assertIsTransactionWithBlockhashLifetime",
203
+ "compileTransaction",
204
+ "createTransactionMessage",
205
+ "fetchEncodedAccounts",
206
+ "getBase58Decoder",
207
+ "getBase58Encoder",
208
+ "getBase64EncodedWireTransaction",
209
+ "getSignatureFromTransaction",
210
+ "pipe",
211
+ "sendAndConfirmTransactionFactory",
212
+ "setTransactionMessageFeePayer",
213
+ "setTransactionMessageFeePayerSigner",
214
+ "setTransactionMessageLifetimeUsingBlockhash",
215
+ "signTransactionMessageWithSigners",
179
216
  "SOLANA_ERROR__INSTRUCTION_ERROR__CUSTOM",
180
217
  "addCodecSizePrefix",
181
218
  "address",
@@ -214,7 +251,22 @@ const KIT_VALUES = [
214
251
  "transformCodec",
215
252
  ];
216
253
  // Keys of the program object that instructions must not take.
217
- const PROGRAM_KEYS = new Set(["address", "accounts", "pdas", "returns", "errors", "errorCode", "isError", "parseEvents"]);
254
+ const PROGRAM_KEYS = new Set([
255
+ "address",
256
+ "accounts",
257
+ "pdas",
258
+ "returns",
259
+ "views",
260
+ "errors",
261
+ "errorCode",
262
+ "isError",
263
+ "parseEvents",
264
+ "parseCpiEvents",
265
+ "eventsOf",
266
+ "watchEvents",
267
+ "sendInstructions",
268
+ "simulate",
269
+ ]);
218
270
  // snake_case → camelCase by splitting (linear time, whatever the input): "taker_token_a" → "takerTokenA".
219
271
  const camel = (s) => s
220
272
  .split("_")
@@ -699,6 +751,24 @@ export function generateClient(idl, opts = {}) {
699
751
  ` const found = await fetchEncodedAccount(rpc, at, config);\n` +
700
752
  ` return found.exists ? ${A}.decode(found.data) : null;\n` +
701
753
  ` },\n` +
754
+ ` /** Several accounts in one request, in order; null where one does not exist. */\n` +
755
+ ` async fetchMultiple(rpc: Rpc<GetMultipleAccountsApi>, at: readonly Address[], config?: FetchAccountsConfig): Promise<(${T} | null)[]> {\n` +
756
+ ` const found = await fetchEncodedAccounts(rpc, [...at], config);\n` +
757
+ ` return found.map((a) => (a.exists ? ${A}.decode(a.data) : null));\n` +
758
+ ` },\n` +
759
+ ` /** Every ${commentText(a.name)} account of the program (getProgramAccounts by discriminator), optionally where \`bytes\` are at \`offset\` of the data. */\n` +
760
+ ` async fetchAll(rpc: Rpc<GetProgramAccountsApi>, filters: readonly { offset: number; bytes: ReadonlyUint8Array }[] = []): Promise<{ address: Address; data: ${T} }[]> {\n` +
761
+ ` const found = await rpc.getProgramAccounts(PROGRAM_ADDRESS, { encoding: "base64", filters: memcmp([{ offset: 0, bytes: ${A}_DISCRIMINATOR }, ...filters]) }).send();\n` +
762
+ ` return found.map((a) => ({ address: a.pubkey, data: ${A}.decode(getBase64Encoder().encode(a.account.data[0])) }));\n` +
763
+ ` },\n` +
764
+ ` /** Each change of the account, decoded (null once it is closed), until \`abortSignal\` aborts. */\n` +
765
+ ` async *watch(rpcSubscriptions: RpcSubscriptions<AccountNotificationsApi>, at: Address, options: { abortSignal: AbortSignal; commitment?: Commitment }): AsyncGenerator<${T} | null> {\n` +
766
+ ` const updates = await rpcSubscriptions.accountNotifications(at, { encoding: "base64", ...(options.commitment ? { commitment: options.commitment } : {}) }).subscribe({ abortSignal: options.abortSignal });\n` +
767
+ ` for await (const u of updates) {\n` +
768
+ ` const data = getBase64Encoder().encode(u.value.data[0]);\n` +
769
+ ` yield data.length === 0 ? null : ${A}.decode(data);\n` +
770
+ ` }\n` +
771
+ ` },\n` +
702
772
  `};\n`);
703
773
  accountKeys.push(`${prop(a.name)}: ${A}`);
704
774
  }
@@ -724,6 +794,7 @@ export function generateClient(idl, opts = {}) {
724
794
  const ixKeys = [];
725
795
  const pdaFns = new Map();
726
796
  const returnKeys = [];
797
+ const viewKeys = [];
727
798
  const usedKeys = new Set();
728
799
  for (const ix of idl.instructions) {
729
800
  const argIssue = ix.args.map((a) => issue(a.type)).find(Boolean);
@@ -825,7 +896,7 @@ export function generateClient(idl, opts = {}) {
825
896
  resolvable.delete(a.key);
826
897
  }
827
898
  const auto = (a) => Boolean(a.address) || resolvable.has(a.key);
828
- use("type Address", "type TransactionSigner", "type Instruction", "AccountRole");
899
+ use("type Address", "type TransactionSigner", "type Instruction", "type AccountMeta", "AccountRole");
829
900
  const accType = (a) => `${a.signer ? "TransactionSigner | Address" : "Address"}${a.optional ? " | null" : ""}`;
830
901
  const accountsIface = `export interface ${P}Accounts {\n` +
831
902
  flat.map((a) => `${doc(a.docs, " ")} ${prop(a.key)}${auto(a) || a.optional ? "?" : ""}: ${accType(a)};`).join("\n") +
@@ -896,16 +967,25 @@ export function generateClient(idl, opts = {}) {
896
967
  : bytes(ix.discriminator);
897
968
  ixOut.push(accountsIface +
898
969
  argsIface +
899
- `${doc(ix.docs)}async function build${P}(input: ${P}Accounts${argsField.length ? `, args: ${P}Args` : ""}): Promise<Instruction> {\n` +
970
+ `${doc(ix.docs)}async function build${P}(input: ${P}Accounts${argsField.length ? `, args: ${P}Args` : ""}, remaining: readonly AccountMeta[] = []): Promise<Instruction> {\n` +
900
971
  lines.join("\n") +
901
- `\n return {\n programAddress: PROGRAM_ADDRESS,\n accounts: [\n${metas.join("\n")}\n ],\n data: ${data},\n } as Instruction;\n}\n`);
972
+ `\n return {\n programAddress: PROGRAM_ADDRESS,\n accounts: [\n${metas.join("\n")}\n ...remaining,\n ],\n data: ${data},\n } as Instruction;\n}\n`);
902
973
  ixKeys.push(`${prop(fn)}: build${P}`);
903
974
  if (ix.returns !== undefined) {
904
975
  const why = issue(ix.returns);
905
976
  if (why)
906
977
  notes.push(`instruction ${ix.name}: the return value is not decoded (${why})`);
907
- else
978
+ else {
908
979
  returnKeys.push(` ${prop(fn)}: (data: ReadonlyUint8Array): ${tsType(ix.returns, "out")} => ${codec(ix.returns)}.decode(data),`);
980
+ // Read-only call: simulate the instruction and decode what it returns (Anchor's `.view()`).
981
+ viewKeys.push(` /** ${ix.name}'s return value, by simulation (nothing is sent). */\n` +
982
+ ` async ${prop(fn)}(rpc: Rpc<SimulateTransactionApi & GetLatestBlockhashApi>, feePayer: Address | TransactionSigner, input: ${P}Accounts${argsField.length ? `, args: ${P}Args` : ""}): Promise<${tsType(ix.returns, "out")}> {\n` +
983
+ ` const res = await simulate(rpc, feePayer, [await build${P}(input${argsField.length ? ", args" : ""})]);\n` +
984
+ ` if (res.err !== null) throw new Error(${JSON.stringify(`${ix.name} failed in simulation: `)} + JSON.stringify(res.err, (_, v) => (typeof v === "bigint" ? v.toString() : v)) + "\\n" + res.logs.join("\\n"));\n` +
985
+ ` if (res.returnData === null) throw new Error(${JSON.stringify(`${ix.name} returned nothing`)});\n` +
986
+ ` return ${codec(ix.returns)}.decode(res.returnData);\n` +
987
+ ` },`);
988
+ }
909
989
  }
910
990
  }
911
991
  // ---- Assemble ---------------------------------------------------------------------------------------------
@@ -961,6 +1041,13 @@ export function generateClient(idl, opts = {}) {
961
1041
  ],
962
1042
  },
963
1043
  { name: "programFromBytes", lines: [`const programFromBytes = (b: Uint8Array): Address => getAddressDecoder().decode(b);`] },
1044
+ {
1045
+ name: "memcmp",
1046
+ lines: [
1047
+ `const memcmp = (filters: readonly { offset: number; bytes: ReadonlyUint8Array }[]) =>`,
1048
+ ` filters.map((f) => ({ memcmp: { offset: BigInt(f.offset), bytes: getBase58Decoder().decode(f.bytes) as Base58EncodedBytes, encoding: "base58" as const } }));`,
1049
+ ],
1050
+ },
964
1051
  ];
965
1052
  if (needsFixedString)
966
1053
  helperDefs.push({
@@ -1078,30 +1165,113 @@ export function generateClient(idl, opts = {}) {
1078
1165
  "",
1079
1166
  `// ---- Events ----`,
1080
1167
  `export type Event = ${events.length ? events.map((e) => `{ name: ${JSON.stringify(e.name)}; data: ${tsName(e.name)} }`).join(" | ") : "never"};`,
1168
+ `/** One event's bytes (discriminator, then the fields), or null when they are not one of this program's events. */`,
1169
+ `function decodeEvent(data: ReadonlyUint8Array): Event | null {`,
1170
+ events.length
1171
+ ? [
1172
+ ` try {`,
1173
+ ...events.map((e) => ` if (hasPrefix(data, ${bytes(e.discriminator)})) return { name: ${JSON.stringify(e.name)}, data: get${tsName(e.name)}Codec().decode(data, ${e.discriminator.length}) };`),
1174
+ ` } catch {`,
1175
+ ` // not one of this program's events`,
1176
+ ` }`,
1177
+ ` return null;`,
1178
+ ].join("\n")
1179
+ : ` void data;\n return null;`,
1180
+ `}`,
1081
1181
  `/**`,
1082
1182
  ` * Events in a transaction's log messages (\`Program data:\` lines, as \`emit\` writes them). Lines are matched by`,
1083
1183
  ` * discriminator, whichever program wrote them; lines that do not decode are skipped. \`emitCpi\` events travel in inner`,
1084
- ` * instructions, not logs.`,
1184
+ ` * instructions: see parseCpiEvents and eventsOf.`,
1085
1185
  ` */`,
1086
1186
  `export function parseEvents(logs: readonly string[]): Event[] {`,
1087
- events.length
1088
- ? [
1089
- ` const found: Event[] = [];`,
1090
- ` for (const line of logs) {`,
1091
- ` if (!line.startsWith("Program data: ")) continue;`,
1092
- ` try {`,
1093
- ` const data = getBase64Encoder().encode(line.slice("Program data: ".length));`,
1094
- ...events.map((e) => ` if (hasPrefix(data, ${bytes(e.discriminator)})) found.push({ name: ${JSON.stringify(e.name)}, data: get${tsName(e.name)}Codec().decode(data, ${e.discriminator.length}) });`),
1095
- ` } catch {`,
1096
- ` // not one of this program's events`,
1097
- ` }`,
1098
- ` }`,
1099
- ` return found;`,
1100
- ].join("\n")
1101
- : ` void logs;\n return [];`,
1187
+ ` const found: Event[] = [];`,
1188
+ ` for (const line of logs) {`,
1189
+ ` if (!line.startsWith("Program data: ")) continue;`,
1190
+ ` let data: ReadonlyUint8Array;`,
1191
+ ` try {`,
1192
+ ` data = getBase64Encoder().encode(line.slice("Program data: ".length));`,
1193
+ ` } catch {`,
1194
+ ` continue;`,
1195
+ ` }`,
1196
+ ` const e = decodeEvent(data);`,
1197
+ ` if (e) found.push(e);`,
1198
+ ` }`,
1199
+ ` return found;`,
1200
+ `}`,
1201
+ `/** \`emitCpi\` events: the data of inner instructions (bytes, or base58 as RPC returns them) that carry one. */`,
1202
+ `export function parseCpiEvents(instructionData: Iterable<ReadonlyUint8Array | string>): Event[] {`,
1203
+ ` const tag = new Uint8Array([228, 69, 165, 46, 81, 203, 154, 29]);`,
1204
+ ` const found: Event[] = [];`,
1205
+ ` for (const item of instructionData) {`,
1206
+ ` let data: ReadonlyUint8Array;`,
1207
+ ` try {`,
1208
+ ` data = typeof item === "string" ? getBase58Encoder().encode(item) : item;`,
1209
+ ` } catch {`,
1210
+ ` continue;`,
1211
+ ` }`,
1212
+ ` if (!hasPrefix(data, tag)) continue;`,
1213
+ ` const e = decodeEvent(data.slice(tag.length));`,
1214
+ ` if (e) found.push(e);`,
1215
+ ` }`,
1216
+ ` return found;`,
1217
+ `}`,
1218
+ `/** Every event of a fetched transaction (getTransaction, json encoding): its logs, then its inner instructions. */`,
1219
+ `export function eventsOf(tx: { meta: { logMessages?: readonly string[] | null; innerInstructions?: readonly { instructions: readonly { data: string }[] }[] | null } | null } | null): Event[] {`,
1220
+ ` const meta = tx?.meta;`,
1221
+ ` if (!meta) return [];`,
1222
+ ` return [...parseEvents(meta.logMessages ?? []), ...parseCpiEvents((meta.innerInstructions ?? []).flatMap((g) => g.instructions.map((i) => i.data)))];`,
1223
+ `}`,
1224
+ `/** Events of every transaction that mentions the program, as their logs arrive, until \`abortSignal\` aborts. */`,
1225
+ `export async function* watchEvents(rpcSubscriptions: RpcSubscriptions<LogsNotificationsApi>, options: { abortSignal: AbortSignal; commitment?: Commitment }): AsyncGenerator<{ signature: Signature; event: Event }> {`,
1226
+ ` const logs = await rpcSubscriptions.logsNotifications({ mentions: [PROGRAM_ADDRESS] }, options.commitment ? { commitment: options.commitment } : {}).subscribe({ abortSignal: options.abortSignal });`,
1227
+ ` for await (const n of logs) for (const event of parseEvents(n.value.logs)) yield { signature: n.value.signature, event };`,
1102
1228
  `}`,
1103
1229
  "",
1104
- `/** The ${displayName} program: instructions, accounts, PDAs, errors, events and return values. */`,
1230
+ `// ---- Transactions ----`,
1231
+ `/** Send instructions in one transaction paid by \`feePayer\` (signers in the accounts sign too) and wait for confirmation. */`,
1232
+ `export async function sendInstructions(`,
1233
+ ` connection: { rpc: Rpc<GetEpochInfoApi & GetLatestBlockhashApi & GetSignatureStatusesApi & SendTransactionApi>; rpcSubscriptions: RpcSubscriptions<SignatureNotificationsApi & SlotNotificationsApi> },`,
1234
+ ` feePayer: TransactionSigner,`,
1235
+ ` instructions: readonly Instruction[],`,
1236
+ ` options: { commitment?: Commitment; abortSignal?: AbortSignal } = {},`,
1237
+ `): Promise<Signature> {`,
1238
+ ` const { value: latestBlockhash } = await connection.rpc.getLatestBlockhash().send();`,
1239
+ ` const message = pipe(`,
1240
+ ` createTransactionMessage({ version: 0 }),`,
1241
+ ` (m) => setTransactionMessageFeePayerSigner(feePayer, m),`,
1242
+ ` (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),`,
1243
+ ` (m) => appendTransactionMessageInstructions(instructions, m),`,
1244
+ ` );`,
1245
+ ` const signed = await signTransactionMessageWithSigners(message);`,
1246
+ ` assertIsTransactionWithBlockhashLifetime(signed);`,
1247
+ ` await sendAndConfirmTransactionFactory(connection)(signed, { commitment: options.commitment ?? "confirmed", ...(options.abortSignal ? { abortSignal: options.abortSignal } : {}) });`,
1248
+ ` return getSignatureFromTransaction(signed);`,
1249
+ `}`,
1250
+ `/** Simulate instructions (nothing is sent, signatures are not checked): the error if any, logs, units and the program's return data. */`,
1251
+ `export async function simulate(`,
1252
+ ` rpc: Rpc<SimulateTransactionApi & GetLatestBlockhashApi>,`,
1253
+ ` feePayer: Address | TransactionSigner,`,
1254
+ ` instructions: readonly Instruction[],`,
1255
+ `): Promise<{ err: unknown; logs: readonly string[]; unitsConsumed: bigint | null; returnData: ReadonlyUint8Array | null }> {`,
1256
+ ` const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();`,
1257
+ ` const message = pipe(`,
1258
+ ` createTransactionMessage({ version: 0 }),`,
1259
+ ` (m) => setTransactionMessageFeePayer(key(feePayer), m),`,
1260
+ ` (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),`,
1261
+ ` (m) => appendTransactionMessageInstructions(instructions, m),`,
1262
+ ` );`,
1263
+ ` const wire = getBase64EncodedWireTransaction(compileTransaction(message));`,
1264
+ ` const { value } = await rpc.simulateTransaction(wire, { encoding: "base64", sigVerify: false, replaceRecentBlockhash: true }).send();`,
1265
+ ` const ret = value.returnData;`,
1266
+ ` return {`,
1267
+ ` err: value.err,`,
1268
+ ` logs: value.logs ?? [],`,
1269
+ ` unitsConsumed: value.unitsConsumed ?? null,`,
1270
+ ` returnData: ret && ret.programId === PROGRAM_ADDRESS ? getBase64Encoder().encode(ret.data[0]) : null,`,
1271
+ ` };`,
1272
+ `}`,
1273
+ "",
1274
+ `/** The ${displayName} program: instructions, accounts, PDAs, errors, events, return values and transactions. */`,
1105
1275
  `export const ${programName} = {`,
1106
1276
  ` address: PROGRAM_ADDRESS,`,
1107
1277
  ...ixKeys.map((k) => ` ${k},`),
@@ -1112,10 +1282,18 @@ export function generateClient(idl, opts = {}) {
1112
1282
  ` returns: {`,
1113
1283
  ...returnKeys,
1114
1284
  ` },`,
1285
+ ` views: {`,
1286
+ ...viewKeys,
1287
+ ` },`,
1115
1288
  ` errors: ERRORS,`,
1116
1289
  ` errorCode,`,
1117
1290
  ` isError,`,
1118
1291
  ` parseEvents,`,
1292
+ ` parseCpiEvents,`,
1293
+ ` eventsOf,`,
1294
+ ` watchEvents,`,
1295
+ ` sendInstructions,`,
1296
+ ` simulate,`,
1119
1297
  `};`,
1120
1298
  "",
1121
1299
  ];
package/docs/client.md CHANGED
@@ -25,23 +25,45 @@ ledgerbook.errorCode(e); // 6002
25
25
  // Events written by `emit`, from a transaction's log messages
26
26
  for (const ev of ledgerbook.parseEvents(logs)) if (ev.name === "Filed") ev.data.amount;
27
27
 
28
- // Return data of an instruction that returns a value
28
+ // Return data of an instruction that returns a value, or the value itself by simulation (Anchor's `.view()`)
29
29
  ledgerbook.returns.total(returnDataBytes); // bigint
30
+ await ledgerbook.views.total(rpc, signer, { book }, { status: Status.Approved }); // bigint, nothing sent
31
+
32
+ // Send and confirm (one transaction, signers in the accounts sign too), or simulate
33
+ const signature = await ledgerbook.sendInstructions({ rpc, rpcSubscriptions }, signer, [create, file]);
34
+ const { err, logs, unitsConsumed, returnData } = await ledgerbook.simulate(rpc, signer, [file]);
35
+
36
+ // Every Book of the program, or those with given bytes at an offset (after the 8-byte discriminator)
37
+ const books = await ledgerbook.accounts.Book.fetchAll(rpc); // [{ address, data }]
38
+ const several = await ledgerbook.accounts.Book.fetchMultiple(rpc, [a, b]); // (Book | null)[]
39
+
40
+ // Live updates, until the AbortSignal aborts
41
+ for await (const book of ledgerbook.accounts.Book.watch(rpcSubscriptions, address, { abortSignal })) render(book);
42
+ for await (const { signature, event } of ledgerbook.watchEvents(rpcSubscriptions, { abortSignal })) show(event);
43
+
44
+ // Remaining accounts go after the instruction's own (Anchor's `.remainingAccounts([...])`)
45
+ await ledgerbook.approveMany({ approver: signer, book }, { indices: [0n, 1n] }, [{ address: extra, role: AccountRole.WRITABLE }]);
46
+
47
+ // Events of a fetched transaction (getTransaction, json): log events and emitCpi events from inner instructions
48
+ ledgerbook.eventsOf(await rpc.getTransaction(signature, { maxSupportedTransactionVersion: 0 }).send());
30
49
  ```
31
50
 
32
- Send instructions the usual kit way (`createTransactionMessage` … `signTransactionMessageWithSigners`). Signer
33
- accounts take a `TransactionSigner`, which kit uses to sign; an `Address` works too when you sign separately.
51
+ Signer accounts take a `TransactionSigner`, which kit uses to sign; an `Address` works too when you sign separately.
52
+ Kit's own transaction functions work with the instructions as well; `sendInstructions` is that pipeline in one call.
34
53
 
35
54
  ## What the client contains
36
55
 
37
56
  | | |
38
57
  | --- | --- |
39
58
  | `<program>.<instruction>(accounts, args)` | Builds the `Instruction`. Accounts with a fixed address and PDAs whose seeds are constants, arguments or other accounts' keys are optional and filled in; pass one to override. An optional account passed as `null` is left out (sent as the program id, the convention TypeSOL and Anchor share); left undefined, it is filled in like any other. |
40
- | `<program>.accounts.<Name>` | `decode(bytes)`, `encode(value)`, `fetch(rpc, address)`, `fetchMaybe(rpc, address)`, `discriminator`. `decode` checks the discriminator. |
59
+ | `<program>.<instruction>(accounts, args, remaining?)` | The same, with remaining accounts appended in order (kit `AccountMeta`s). |
60
+ | `<program>.accounts.<Name>` | `decode(bytes)`, `encode(value)`, `fetch(rpc, address)`, `fetchMaybe(rpc, address)`, `fetchMultiple(rpc, addresses)`, `fetchAll(rpc, filters?)` (getProgramAccounts by discriminator, plus `{ offset, bytes }` filters), `watch(rpcSubscriptions, address, { abortSignal })`, `discriminator`. `decode` checks the discriminator. |
41
61
  | `<program>.pdas.<account>(seeds)` | The PDA (address and bump) for each account an instruction derives. |
42
62
  | `<program>.returns.<instruction>(bytes)` | Decodes return data. |
43
- | `<program>.errors`, `errorCode(e)`, `isError(e, name)` | The program's errors (6000+), and for TypeSOL programs also TypeSOL's runtime errors and the Anchor framework errors it uses (2001, 2006, 3007, …). |
44
- | `<program>.parseEvents(logs)` | Events from `Program data:` log lines. |
63
+ | `<program>.views.<instruction>(rpc, feePayer, accounts, args)` | Simulates an instruction that returns a value and decodes it (throws with the logs if it fails). |
64
+ | `<program>.sendInstructions(connection, feePayer, instructions)`, `simulate(rpc, feePayer, instructions)` | One transaction sent and confirmed (kit's sendAndConfirm), or simulated without signatures (error, logs, units, this program's return data). |
65
+ | `<program>.errors`, `errorCode(e)`, `isError(e, name)` | The program's errors (6000+), and for TypeSOL programs also every Anchor framework error (`AnchorError` and the constraints raise them) and TypeSOL's runtime errors. |
66
+ | `<program>.parseEvents(logs)`, `parseCpiEvents(data)`, `eventsOf(tx)`, `watchEvents(rpcSubscriptions, { abortSignal })` | Events from `Program data:` log lines, from `emitCpi` inner instructions (bytes or base58), from a fetched transaction (both), or live from log subscriptions. |
45
67
  | Types and codecs | Every defined type as a TypeScript type (`Book`, the decoded value) and an input type (`BookArgs`, which also accepts `number` for 64- and 128-bit integers), with a kit codec (`getBookCodec()`). |
46
68
 
47
69
  Types follow the program's TypeScript: `u64`/`i64`/`u128`/`i128` are `bigint` (inputs accept `number` too), smaller
@@ -65,5 +87,6 @@ Anchor IDLs are refused with a hint to convert them (`anchor idl convert`).
65
87
  for each.
66
88
  - **Left out of a client generated from an Anchor IDL**, with a note: generic types and anything using them, and
67
89
  zero-copy (`bytemuck`) accounts.
68
- - **Not done yet**: events sent with `emitCpi` (they travel in inner instructions, not logs), and a LiteSVM loader for
69
- tests (use `svm.addProgramFromFile(ledgerbook.address, soPath)`).
90
+ - **Not done yet**: a LiteSVM loader for tests (use `svm.addProgramFromFile(ledgerbook.address, soPath)`, or
91
+ `typesol/testing`). `watchEvents` reads log events; `emitCpi` events are in inner instructions, which log
92
+ subscriptions do not carry (use `eventsOf` on the fetched transaction).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typesol/cli",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "Compile a TypeScript-syntax language directly to Solana SBF programs.",
5
5
  "type": "module",
6
6
  "license": "MIT",