codama-renderers-dart 0.2.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.
@@ -0,0 +1,82 @@
1
+ import type { ErrorNode, ProgramNode } from "@codama/nodes";
2
+
3
+ import type { Fragment } from "../utils/fragment.js";
4
+ import {
5
+ emptyFragment,
6
+ fragment,
7
+ fragmentFromString,
8
+ mergeFragments,
9
+ use,
10
+ } from "../utils/fragment.js";
11
+ import type { RenderScope } from "../utils/options.js";
12
+ import { camelCase, pascalCase, screamingSnakeCase } from "../utils/nameTransformers.js";
13
+
14
+ /**
15
+ * Generate a full Dart file for a program's errors.
16
+ */
17
+ export function getErrorPageFragment(
18
+ programNode: ProgramNode,
19
+ scope: RenderScope,
20
+ ): Fragment {
21
+ const errors = programNode.errors ?? [];
22
+ if (errors.length === 0) return emptyFragment();
23
+
24
+ const programName = programNode.name as string;
25
+
26
+ // Error code constants
27
+ const errorConstants = errors
28
+ .map((err) => {
29
+ const constName = scope.nameApi.errorConstant(
30
+ programName,
31
+ err.name as string,
32
+ );
33
+ const hexCode = `0x${err.code.toString(16)}`;
34
+ const docs = err.docs?.length
35
+ ? err.docs.map((d) => `/// ${d}`).join("\n") + "\n"
36
+ : "";
37
+ const message = err.message ? `/// Message: "${err.message}"\n` : "";
38
+ return `${docs}${message}const int ${constName} = ${hexCode}; // ${err.code}`;
39
+ })
40
+ .join("\n\n");
41
+
42
+ // Error message map
43
+ const messageEntries = errors
44
+ .map((err) => {
45
+ const constName = scope.nameApi.errorConstant(
46
+ programName,
47
+ err.name as string,
48
+ );
49
+ const message = err.message ?? err.name as string;
50
+ return ` ${constName}: '${escapeString(message)}',`;
51
+ })
52
+ .join("\n");
53
+
54
+ const errorMessageFnName = scope.nameApi.errorMessageFunction(programName);
55
+ const isErrorFnName = `is${pascalCase(programName)}Error`;
56
+
57
+ return fragment`// Auto-generated. Do not edit.
58
+ // ignore_for_file: type=lint, constant_identifier_names
59
+
60
+ /// Error codes for the ${fragmentFromString(pascalCase(programName))} program.
61
+
62
+ ${fragmentFromString(errorConstants)}
63
+
64
+ /// Map of error codes to human-readable messages.
65
+ const Map<int, String> _${fragmentFromString(camelCase(programName))}ErrorMessages = {
66
+ ${fragmentFromString(messageEntries)}
67
+ };
68
+
69
+ /// Get the error message for a ${fragmentFromString(pascalCase(programName))} program error code.
70
+ String? ${fragmentFromString(errorMessageFnName)}(int code) {
71
+ return _${fragmentFromString(camelCase(programName))}ErrorMessages[code];
72
+ }
73
+
74
+ /// Check if an error code belongs to the ${fragmentFromString(pascalCase(programName))} program.
75
+ bool ${fragmentFromString(isErrorFnName)}(int code) {
76
+ return _${fragmentFromString(camelCase(programName))}ErrorMessages.containsKey(code);
77
+ }`;
78
+ }
79
+
80
+ function escapeString(str: string): string {
81
+ return str.replace(/'/g, "\\'").replace(/\n/g, "\\n");
82
+ }
@@ -0,0 +1,8 @@
1
+ export * from "./accountPage.js";
2
+ export * from "./discriminatorConstants.js";
3
+ export * from "./errorPage.js";
4
+ export * from "./indexPage.js";
5
+ export * from "./instructionPage.js";
6
+ export * from "./pdaPage.js";
7
+ export * from "./programPage.js";
8
+ export * from "./typePage.js";
@@ -0,0 +1,44 @@
1
+ import type { Fragment } from "../utils/fragment.js";
2
+ import { fragmentFromString } from "../utils/fragment.js";
3
+
4
+ /**
5
+ * Generate a barrel export Dart file that exports all files in a category.
6
+ */
7
+ export function getIndexPageFragment(
8
+ fileNames: string[],
9
+ relativePath: string = "",
10
+ ): Fragment {
11
+ if (fileNames.length === 0) {
12
+ return fragmentFromString(
13
+ "// Auto-generated. Do not edit.\n// ignore_for_file: type=lint\n",
14
+ );
15
+ }
16
+
17
+ const exports = fileNames
18
+ .sort()
19
+ .map((f) => {
20
+ const path = relativePath ? `${relativePath}/${f}` : f;
21
+ return `export '${path}';`;
22
+ })
23
+ .join("\n");
24
+
25
+ return fragmentFromString(
26
+ `// Auto-generated. Do not edit.\n// ignore_for_file: type=lint\n\n${exports}\n`,
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Generate the root barrel export file.
32
+ */
33
+ export function getRootIndexPageFragment(
34
+ categories: string[],
35
+ ): Fragment {
36
+ const exports = categories
37
+ .sort()
38
+ .map((cat) => `export '${cat}/${cat}.dart';`)
39
+ .join("\n");
40
+
41
+ return fragmentFromString(
42
+ `// Auto-generated. Do not edit.\n// ignore_for_file: type=lint\n\n${exports}\n`,
43
+ );
44
+ }
@@ -0,0 +1,306 @@
1
+ import type {
2
+ InstructionAccountNode,
3
+ InstructionArgumentNode,
4
+ InstructionNode,
5
+ } from "@codama/nodes";
6
+ import { visit } from "@codama/visitors-core";
7
+
8
+ import type { Fragment } from "../utils/fragment.js";
9
+ import {
10
+ emptyFragment,
11
+ fragment,
12
+ fragmentFromString,
13
+ mergeFragments,
14
+ use,
15
+ } from "../utils/fragment.js";
16
+ import type { RenderScope } from "../utils/options.js";
17
+ import { camelCase, pascalCase } from "../utils/nameTransformers.js";
18
+ import { getDiscriminatorConstantsFragment } from "./discriminatorConstants.js";
19
+
20
+ /**
21
+ * Generate a full Dart file for an instruction.
22
+ */
23
+ export function getInstructionPageFragment(
24
+ node: InstructionNode,
25
+ scope: RenderScope,
26
+ ): Fragment {
27
+ const name = node.name as string;
28
+ const typeName = scope.nameApi.dataType(name);
29
+ const instrFnName = scope.nameApi.instructionFunction(name);
30
+ const parseFnName = scope.nameApi.instructionParseFunction(name);
31
+
32
+ const accounts = node.accounts ?? [];
33
+ const args = (node.arguments ?? []).filter(
34
+ (arg) => !isDiscriminatorArg(arg, node),
35
+ );
36
+ const allArgs = node.arguments ?? [];
37
+
38
+ // Build the instruction data class
39
+ const dataClassName = `${typeName}InstructionData`;
40
+
41
+ // Visit each arg type once and collect manifests for reuse
42
+ const allArgManifests = allArgs.map((arg) => ({
43
+ arg,
44
+ manifest: visit(arg.type, scope.typeManifestVisitor),
45
+ }));
46
+
47
+ // Data fields (non-discriminator arguments)
48
+ const dataFieldDecls = allArgManifests
49
+ .map(({ arg, manifest }) => {
50
+ const fieldName = camelCase(arg.name as string);
51
+ return ` final ${manifest.type.content} ${fieldName};`;
52
+ })
53
+ .join("\n");
54
+
55
+ const dataCtorParams = allArgManifests
56
+ .map(({ arg }) => {
57
+ const fieldName = camelCase(arg.name as string);
58
+ if (isDiscriminatorArg(arg, node)) {
59
+ return ` this.${fieldName} = ${getDiscriminatorDefault(arg, node)},`;
60
+ }
61
+ return ` required this.${fieldName},`;
62
+ })
63
+ .join("\n");
64
+
65
+ // Encoder/decoder for instruction data
66
+ const encFields = allArgManifests
67
+ .map(({ arg, manifest }) => {
68
+ return ` ('${arg.name as string}', ${manifest.encoder.content}),`;
69
+ })
70
+ .join("\n");
71
+
72
+ const decFields = allArgManifests
73
+ .map(({ arg, manifest }) => {
74
+ return ` ('${arg.name as string}', ${manifest.decoder.content}),`;
75
+ })
76
+ .join("\n");
77
+
78
+ const toMapFields = allArgs
79
+ .map(
80
+ (arg) =>
81
+ ` '${arg.name as string}': value.${camelCase(arg.name as string)},`,
82
+ )
83
+ .join("\n");
84
+
85
+ const fromMapFields = allArgManifests
86
+ .map(({ arg, manifest }) => {
87
+ const typeStr = manifest.type.content;
88
+ const isNullable = typeStr.endsWith("?");
89
+ const accessor = isNullable ? `map['${arg.name as string}']` : `map['${arg.name as string}']!`;
90
+ return ` ${camelCase(arg.name as string)}: ${accessor} as ${typeStr},`;
91
+ })
92
+ .join("\n");
93
+
94
+ const dataEncoderName = `get${typeName}InstructionDataEncoder`;
95
+ const dataDecoderName = `get${typeName}InstructionDataDecoder`;
96
+ const dataCodecName = `get${typeName}InstructionDataCodec`;
97
+
98
+ // Build the instruction builder function
99
+ const accountParams = accounts
100
+ .map((acc) => {
101
+ const fieldName = camelCase(acc.name as string);
102
+ const isRequired = !(acc.isOptional ?? false);
103
+ return isRequired
104
+ ? ` required Address ${fieldName},`
105
+ : ` Address? ${fieldName},`;
106
+ })
107
+ .join("\n");
108
+
109
+ // Build argParams using manifests from the allArgManifests (filtered to non-discriminator args)
110
+ const argManifestMap = new Map(allArgManifests.map(({ arg, manifest }) => [arg, manifest]));
111
+ const argParams = args
112
+ .map((arg) => {
113
+ const manifest = argManifestMap.get(arg)!;
114
+ const fieldName = camelCase(arg.name as string);
115
+ const hasDefault = arg.defaultValue != null;
116
+ if (hasDefault) {
117
+ return ` ${manifest.type.content}? ${fieldName},`;
118
+ }
119
+ return ` required ${manifest.type.content} ${fieldName},`;
120
+ })
121
+ .join("\n");
122
+
123
+ // Account metas
124
+ const accountMetas = accounts
125
+ .map((acc) => {
126
+ const fieldName = camelCase(acc.name as string);
127
+ const role = getAccountRole(acc);
128
+ const isOptional = acc.isOptional ?? false;
129
+ if (isOptional) {
130
+ return ` if (${fieldName} != null) AccountMeta(address: ${fieldName}, role: ${role}),`;
131
+ }
132
+ return ` AccountMeta(address: ${fieldName}, role: ${role}),`;
133
+ })
134
+ .join("\n");
135
+
136
+ // Instruction data construction
137
+ const dataConstruction = allArgs
138
+ .map((arg) => {
139
+ const fieldName = camelCase(arg.name as string);
140
+ if (isDiscriminatorArg(arg, node)) {
141
+ return ""; // Use default
142
+ }
143
+ return ` ${fieldName}: ${fieldName}${arg.defaultValue != null ? ` ?? ${getDefaultValue(arg)}` : ""},`;
144
+ })
145
+ .filter(Boolean)
146
+ .join("\n");
147
+
148
+ // Discriminator
149
+ const discFragment = getDiscriminatorConstantsFragment(node, scope);
150
+
151
+ const parts: Fragment[] = [
152
+ fragment`// Auto-generated. Do not edit.
153
+ // ignore_for_file: type=lint
154
+
155
+ ${use("Uint8List", "dartTypedData")}
156
+ ${use("immutable", "meta")}
157
+ ${use("Encoder", "solanaCodecsCore")}
158
+ ${use("Decoder", "solanaCodecsCore")}
159
+ ${use("Codec", "solanaCodecsCore")}
160
+ ${use("combineCodec", "solanaCodecsCore")}
161
+ ${use("transformEncoder", "solanaCodecsCore")}
162
+ ${use("transformDecoder", "solanaCodecsCore")}
163
+ ${use("getStructEncoder", "solanaCodecsDataStructures")}
164
+ ${use("getStructDecoder", "solanaCodecsDataStructures")}
165
+ ${use("Address", "solanaAddresses")}
166
+ ${use("Instruction", "solanaInstructions")}
167
+ ${use("AccountMeta", "solanaInstructions")}
168
+ ${use("AccountRole", "solanaInstructions")}`,
169
+ ];
170
+
171
+ if (discFragment.content) parts.push(discFragment);
172
+
173
+ // Data class
174
+ parts.push(fragment`
175
+ @immutable
176
+ class ${fragmentFromString(dataClassName)} {
177
+ const ${fragmentFromString(dataClassName)}({
178
+ ${fragmentFromString(dataCtorParams)}
179
+ });
180
+
181
+ ${fragmentFromString(dataFieldDecls)}
182
+ }`);
183
+
184
+ // Data encoder/decoder/codec
185
+ parts.push(fragment`
186
+ Encoder<${fragmentFromString(dataClassName)}> ${fragmentFromString(dataEncoderName)}() {
187
+ final structEncoder = getStructEncoder(<(String, Encoder<Object?>)>[
188
+ ${fragmentFromString(encFields)}
189
+ ]);
190
+
191
+ return transformEncoder(
192
+ structEncoder,
193
+ (${fragmentFromString(dataClassName)} value) => <String, Object?>{
194
+ ${fragmentFromString(toMapFields)}
195
+ },
196
+ );
197
+ }
198
+
199
+ Decoder<${fragmentFromString(dataClassName)}> ${fragmentFromString(dataDecoderName)}() {
200
+ final structDecoder = getStructDecoder(<(String, Decoder<Object?>)>[
201
+ ${fragmentFromString(decFields)}
202
+ ]);
203
+
204
+ return transformDecoder(
205
+ structDecoder,
206
+ (Map<String, Object?> map, Uint8List bytes, int offset) => ${fragmentFromString(dataClassName)}(
207
+ ${fragmentFromString(fromMapFields)}
208
+ ),
209
+ );
210
+ }
211
+
212
+ Codec<${fragmentFromString(dataClassName)}, ${fragmentFromString(dataClassName)}> ${fragmentFromString(dataCodecName)}() {
213
+ return combineCodec(${fragmentFromString(dataEncoderName)}(), ${fragmentFromString(dataDecoderName)}());
214
+ }`);
215
+
216
+ // Instruction builder
217
+ parts.push(fragment`
218
+ /// Creates a [${fragmentFromString(typeName)}] instruction.
219
+ Instruction ${fragmentFromString(instrFnName)}({
220
+ required Address programAddress,
221
+ ${fragmentFromString(accountParams)}
222
+ ${fragmentFromString(argParams)}
223
+ }) {
224
+ final data = ${fragmentFromString(dataClassName)}(
225
+ ${fragmentFromString(dataConstruction)}
226
+ );
227
+
228
+ return Instruction(
229
+ programAddress: programAddress,
230
+ accounts: [
231
+ ${fragmentFromString(accountMetas)}
232
+ ],
233
+ data: ${fragmentFromString(dataEncoderName)}().encode(data),
234
+ );
235
+ }`);
236
+
237
+ // Parse function
238
+ parts.push(fragment`
239
+ /// Parses a [${fragmentFromString(typeName)}] instruction from raw instruction data.
240
+ ${fragmentFromString(dataClassName)} ${fragmentFromString(parseFnName)}(Instruction instruction) {
241
+ return ${fragmentFromString(dataDecoderName)}().decode(instruction.data!);
242
+ }`);
243
+
244
+ const result = mergeFragments(parts, (cs) => cs.join("\n"));
245
+
246
+ // Merge arg type manifest imports (encoder, decoder, type) into the result
247
+ for (const { manifest } of allArgManifests) {
248
+ result.imports.mergeWith(manifest.encoder.imports);
249
+ result.imports.mergeWith(manifest.decoder.imports);
250
+ result.imports.mergeWith(manifest.type.imports);
251
+ }
252
+
253
+ return result;
254
+ }
255
+
256
+ function getAccountRole(acc: InstructionAccountNode): string {
257
+ const isSigner = acc.isSigner === true || acc.isSigner === "either";
258
+ const isWritable = acc.isWritable ?? false;
259
+
260
+ if (isSigner && isWritable) return "AccountRole.writableSigner";
261
+ if (isSigner) return "AccountRole.readonlySigner";
262
+ if (isWritable) return "AccountRole.writable";
263
+ return "AccountRole.readonly";
264
+ }
265
+
266
+ function isDiscriminatorArg(
267
+ arg: InstructionArgumentNode,
268
+ node: InstructionNode,
269
+ ): boolean {
270
+ const discriminators = node.discriminators ?? [];
271
+ return discriminators.some(
272
+ (d) =>
273
+ d.kind === "fieldDiscriminatorNode" && d.name === arg.name,
274
+ );
275
+ }
276
+
277
+ function getDiscriminatorDefault(
278
+ arg: InstructionArgumentNode,
279
+ _node: InstructionNode,
280
+ ): string {
281
+ if (arg.defaultValue) {
282
+ if (arg.defaultValue.kind === "numberValueNode") {
283
+ return String(arg.defaultValue.number);
284
+ }
285
+ }
286
+ return "0";
287
+ }
288
+
289
+ function getDefaultValue(arg: InstructionArgumentNode): string {
290
+ if (!arg.defaultValue) return "null";
291
+ const dv = arg.defaultValue;
292
+ switch (dv.kind) {
293
+ case "numberValueNode":
294
+ return String(dv.number);
295
+ case "booleanValueNode":
296
+ return String(dv.boolean);
297
+ case "stringValueNode":
298
+ return `'${dv.string}'`;
299
+ case "publicKeyValueNode":
300
+ return `Address('${dv.publicKey}')`;
301
+ case "noneValueNode":
302
+ return "null";
303
+ default:
304
+ return "null";
305
+ }
306
+ }
@@ -0,0 +1,128 @@
1
+ import type { PdaNode } from "@codama/nodes";
2
+ import { visit } from "@codama/visitors-core";
3
+
4
+ import type { Fragment } from "../utils/fragment.js";
5
+ import {
6
+ fragment,
7
+ fragmentFromString,
8
+ mergeFragments,
9
+ use,
10
+ } from "../utils/fragment.js";
11
+ import type { RenderScope } from "../utils/options.js";
12
+ import { camelCase, pascalCase } from "../utils/nameTransformers.js";
13
+
14
+ /**
15
+ * Generate a full Dart file for a PDA.
16
+ */
17
+ export function getPdaPageFragment(
18
+ node: PdaNode,
19
+ scope: RenderScope,
20
+ ): Fragment {
21
+ const name = node.name as string;
22
+ const findFnName = scope.nameApi.pdaFindFunction(name);
23
+ const seedsClassName = `${pascalCase(name)}Seeds`;
24
+
25
+ const seeds = node.seeds ?? [];
26
+
27
+ // Separate variable seeds from constant seeds
28
+ const variableSeeds = seeds.filter(
29
+ (s) => s.kind === "variablePdaSeedNode",
30
+ );
31
+ const constantSeeds = seeds.filter(
32
+ (s) => s.kind === "constantPdaSeedNode",
33
+ );
34
+
35
+ // Build seeds class if there are variable seeds
36
+ const hasSeedsClass = variableSeeds.length > 0;
37
+
38
+ const seedFieldDecls = variableSeeds
39
+ .map((s) => {
40
+ if (s.kind !== "variablePdaSeedNode") return "";
41
+ const manifest = visit(s.type, scope.typeManifestVisitor);
42
+ return ` final ${manifest.type.content} ${camelCase(s.name as string)};`;
43
+ })
44
+ .join("\n");
45
+
46
+ const seedCtorParams = variableSeeds
47
+ .map((s) => {
48
+ if (s.kind !== "variablePdaSeedNode") return "";
49
+ return ` required this.${camelCase(s.name as string)},`;
50
+ })
51
+ .join("\n");
52
+
53
+ // Build the seed bytes list
54
+ const seedBytesList: string[] = [];
55
+ for (const seed of seeds) {
56
+ if (seed.kind === "constantPdaSeedNode") {
57
+ if (seed.value.kind === "bytesValueNode") {
58
+ seedBytesList.push(` ...${bytesValueToDart(seed.value.data)},`);
59
+ } else if (seed.value.kind === "stringValueNode") {
60
+ seedBytesList.push(` ...utf8.encode('${seed.value.string}'),`);
61
+ } else if (seed.value.kind === "publicKeyValueNode") {
62
+ seedBytesList.push(` ...getAddressEncoder().encode(Address('${seed.value.publicKey}')),`);
63
+ } else if (seed.value.kind === "numberValueNode") {
64
+ const manifest = visit(seed.type, scope.typeManifestVisitor);
65
+ seedBytesList.push(` ...${manifest.encoder.content}.encode(${seed.value.number}),`);
66
+ }
67
+ } else if (seed.kind === "variablePdaSeedNode") {
68
+ const manifest = visit(seed.type, scope.typeManifestVisitor);
69
+ seedBytesList.push(
70
+ ` ...${manifest.encoder.content}.encode(seeds.${camelCase(seed.name as string)}),`,
71
+ );
72
+ }
73
+ }
74
+
75
+ // Program address parameter
76
+ const programIdParam = node.programId
77
+ ? `Address programAddress = const Address('${node.programId}')`
78
+ : "required Address programAddress";
79
+
80
+ const parts: Fragment[] = [
81
+ fragment`// Auto-generated. Do not edit.
82
+ // ignore_for_file: type=lint
83
+
84
+ ${use("Uint8List", "dartTypedData")}
85
+ ${use("immutable", "meta")}
86
+ ${use("Address", "solanaAddresses")}
87
+ ${use("getAddressEncoder", "solanaAddresses")}`,
88
+ ];
89
+
90
+ if (hasSeedsClass) {
91
+ parts.push(fragment`
92
+ @immutable
93
+ class ${fragmentFromString(seedsClassName)} {
94
+ const ${fragmentFromString(seedsClassName)}({
95
+ ${fragmentFromString(seedCtorParams)}
96
+ });
97
+
98
+ ${fragmentFromString(seedFieldDecls)}
99
+ }`);
100
+ }
101
+
102
+ const seedsParam = hasSeedsClass ? ` required ${seedsClassName} seeds,` : "";
103
+
104
+ parts.push(fragment`
105
+ /// Finds the program derived address for [${fragmentFromString(pascalCase(name))}].
106
+ Future<(Address, int)> ${fragmentFromString(findFnName)}({
107
+ ${fragmentFromString(seedsParam)}
108
+ ${fragmentFromString(programIdParam)},
109
+ }) async {
110
+ final seedBytes = <int>[
111
+ ${fragmentFromString(seedBytesList.join("\n"))}
112
+ ];
113
+
114
+ // TODO: Call getProgramDerivedAddress with seedBytes and programAddress
115
+ throw UnimplementedError('PDA derivation not yet implemented');
116
+ }`);
117
+
118
+ return mergeFragments(parts, (cs) => cs.join("\n"));
119
+ }
120
+
121
+ function bytesValueToDart(hex: string): string {
122
+ const clean = hex.replace(/^0x/, "");
123
+ const pairs: string[] = [];
124
+ for (let i = 0; i < clean.length; i += 2) {
125
+ pairs.push(`0x${clean.slice(i, i + 2)}`);
126
+ }
127
+ return `[${pairs.join(", ")}]`;
128
+ }
@@ -0,0 +1,63 @@
1
+ import type { ProgramNode } from "@codama/nodes";
2
+
3
+ import type { Fragment } from "../utils/fragment.js";
4
+ import {
5
+ emptyFragment,
6
+ fragment,
7
+ fragmentFromString,
8
+ mergeFragments,
9
+ use,
10
+ } from "../utils/fragment.js";
11
+ import type { RenderScope } from "../utils/options.js";
12
+ import { camelCase, pascalCase } from "../utils/nameTransformers.js";
13
+
14
+ /**
15
+ * Generate a full Dart file for a program.
16
+ */
17
+ export function getProgramPageFragment(
18
+ node: ProgramNode,
19
+ scope: RenderScope,
20
+ ): Fragment {
21
+ const name = node.name as string;
22
+ const addressConstName = scope.nameApi.programAddressConstant(name);
23
+
24
+ const parts: Fragment[] = [
25
+ fragment`// Auto-generated. Do not edit.
26
+ // ignore_for_file: type=lint
27
+
28
+ ${use("Address", "solanaAddresses")}
29
+
30
+ /// The address of the ${fragmentFromString(pascalCase(name))} program.
31
+ const ${fragmentFromString(addressConstName)} = Address('${fragmentFromString(node.publicKey)}');`,
32
+ ];
33
+
34
+ // Account identifier enum
35
+ const accounts = node.accounts ?? [];
36
+ if (accounts.length > 0) {
37
+ const accountVariants = accounts
38
+ .map((acc) => ` ${camelCase(acc.name as string)},`)
39
+ .join("\n");
40
+
41
+ parts.push(fragment`
42
+ /// Known accounts for the ${fragmentFromString(pascalCase(name))} program.
43
+ enum ${fragmentFromString(pascalCase(name))}Account {
44
+ ${fragmentFromString(accountVariants)}
45
+ }`);
46
+ }
47
+
48
+ // Instruction identifier enum
49
+ const instructions = node.instructions ?? [];
50
+ if (instructions.length > 0) {
51
+ const instrVariants = instructions
52
+ .map((instr) => ` ${camelCase(instr.name as string)},`)
53
+ .join("\n");
54
+
55
+ parts.push(fragment`
56
+ /// Known instructions for the ${fragmentFromString(pascalCase(name))} program.
57
+ enum ${fragmentFromString(pascalCase(name))}Instruction {
58
+ ${fragmentFromString(instrVariants)}
59
+ }`);
60
+ }
61
+
62
+ return mergeFragments(parts, (cs) => cs.join("\n"));
63
+ }