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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ifiok Jr.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "codama-renderers-dart",
3
+ "version": "0.2.0",
4
+ "description": "Codama renderer for generating Dart code targeting the solana_kit SDK",
5
+ "exports": {
6
+ "node": {
7
+ "import": "./dist/index.node.mjs",
8
+ "require": "./dist/index.node.cjs"
9
+ },
10
+ "browser": {
11
+ "import": "./dist/index.browser.mjs",
12
+ "require": "./dist/index.browser.cjs"
13
+ },
14
+ "react-native": "./dist/index.react-native.mjs",
15
+ "types": "./dist/types/index.d.ts"
16
+ },
17
+ "main": "./dist/index.node.cjs",
18
+ "module": "./dist/index.node.mjs",
19
+ "types": "./dist/types/index.d.ts",
20
+ "type": "module",
21
+ "files": [
22
+ "dist",
23
+ "src"
24
+ ],
25
+ "keywords": [
26
+ "codama",
27
+ "solana",
28
+ "dart",
29
+ "renderer",
30
+ "code-generator"
31
+ ],
32
+ "license": "MIT",
33
+ "dependencies": {
34
+ "@codama/errors": "^1.4.4",
35
+ "@codama/nodes": "^1.4.4",
36
+ "@codama/renderers-core": "^1.3.3",
37
+ "@codama/visitors-core": "^1.4.4"
38
+ },
39
+ "devDependencies": {
40
+ "@codama/nodes-from-anchor": "^1.3.9",
41
+ "@codama/renderers-js": "^2.0.2",
42
+ "@types/node": "^25.3.0",
43
+ "rimraf": "^6.0.1",
44
+ "tsup": "^8.4.0",
45
+ "typescript": "^5.7.3",
46
+ "vitest": "^3.2.1"
47
+ },
48
+ "engines": {
49
+ "node": ">=18"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "scripts": {
55
+ "build": "rimraf dist && tsup && tsc -p tsconfig.declarations.json",
56
+ "lint": "eslint --ext .ts src",
57
+ "test": "vitest run",
58
+ "test:watch": "vitest",
59
+ "typecheck": "tsc --noEmit"
60
+ }
61
+ }
package/readme.md ADDED
@@ -0,0 +1,297 @@
1
+ # codama-renderers-dart
2
+
3
+ A [Codama](https://github.com/codama-idl/codama) renderer that generates Dart code targeting the [solana_kit](https://github.com/openbudgetfun/solana_kit) SDK.
4
+
5
+ Given a Codama IDL (Interface Description Language) describing a Solana program, this renderer produces a complete Dart package with typed account classes, instruction builders, codec functions, error definitions, PDA helpers, and barrel exports.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add codama-renderers-dart
11
+ # or
12
+ npm install codama-renderers-dart
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ### Programmatic API
18
+
19
+ ```typescript
20
+ import { renderVisitor } from "codama-renderers-dart";
21
+ import { visit } from "@codama/visitors-core";
22
+ import { rootNode, programNode /* ... */ } from "@codama/nodes";
23
+
24
+ // Build or load a Codama IDL root node
25
+ const root = rootNode(programNode({ /* ... */ }));
26
+
27
+ // Generate Dart files into the output directory
28
+ visit(root, renderVisitor("lib/src/generated", {
29
+ formatCode: true, // Run `dart format` on output (default: false)
30
+ deleteFolderBeforeRendering: true, // Clean output dir first (default: true)
31
+ }));
32
+ ```
33
+
34
+ ### Codama CLI
35
+
36
+ Create a `codama.json` configuration file:
37
+
38
+ ```json
39
+ {
40
+ "idl": "idl.json",
41
+ "scripts": {
42
+ "dart": {
43
+ "from": "codama-renderers-dart",
44
+ "args": ["lib/src/generated"]
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ Then run:
51
+
52
+ ```bash
53
+ codama run dart
54
+ ```
55
+
56
+ ## Generated Output Structure
57
+
58
+ For a program called `myProgram`, the renderer generates:
59
+
60
+ ```
61
+ lib/src/generated/
62
+ my_program.dart # Root barrel export
63
+ accounts/
64
+ accounts.dart # Category barrel
65
+ my_account.dart # Account type + codecs + decode helper
66
+ instructions/
67
+ instructions.dart # Category barrel
68
+ my_instruction.dart # Instruction data + builder + parser
69
+ types/
70
+ types.dart # Category barrel
71
+ my_struct.dart # Struct type + codecs
72
+ my_enum.dart # Enum/sealed class + codecs
73
+ errors/
74
+ errors.dart # Category barrel
75
+ my_program.dart # Error constants + message helpers
76
+ programs/
77
+ programs.dart # Category barrel
78
+ my_program.dart # Program address + identifier enums
79
+ pdas/
80
+ pdas.dart # Category barrel
81
+ my_pda.dart # PDA seeds class + finder function
82
+ ```
83
+
84
+ ## Generated Code Patterns
85
+
86
+ ### Accounts
87
+
88
+ Each account generates an `@immutable` Dart class with typed fields, const constructor, proper `==`/`hashCode`/`toString` implementations, and codec functions:
89
+
90
+ ```dart
91
+ @immutable
92
+ class MyAccount {
93
+ const MyAccount({
94
+ required this.authority,
95
+ required this.count,
96
+ });
97
+
98
+ final Address authority;
99
+ final BigInt count;
100
+
101
+ // ... equality, hashCode, toString
102
+ }
103
+
104
+ Encoder<MyAccount> getMyAccountEncoder() { ... }
105
+ Decoder<MyAccount> getMyAccountDecoder() { ... }
106
+ Codec<MyAccount, MyAccount> getMyAccountCodec() { ... }
107
+ Account<MyAccount> decodeMyAccount(EncodedAccount encodedAccount) { ... }
108
+ ```
109
+
110
+ ### Instructions
111
+
112
+ Each instruction generates a data class, codec functions, a builder function, and a parse function:
113
+
114
+ ```dart
115
+ @immutable
116
+ class TransferInstructionData {
117
+ const TransferInstructionData({
118
+ this.discriminator = 3,
119
+ required this.amount,
120
+ });
121
+
122
+ final int discriminator;
123
+ final BigInt amount;
124
+ }
125
+
126
+ Instruction getTransferInstruction({
127
+ required Address programAddress,
128
+ required Address source,
129
+ required Address destination,
130
+ required BigInt amount,
131
+ }) { ... }
132
+
133
+ TransferInstructionData parseTransferInstruction(Instruction instruction) { ... }
134
+ ```
135
+
136
+ ### Scalar Enums
137
+
138
+ Scalar enums (all-empty variants) generate a Dart `enum` with index-based encoder/decoder:
139
+
140
+ ```dart
141
+ enum AccountStatus {
142
+ active,
143
+ frozen,
144
+ closed,
145
+ }
146
+
147
+ Encoder<AccountStatus> getAccountStatusEncoder() { ... }
148
+ Decoder<AccountStatus> getAccountStatusDecoder() { ... }
149
+ ```
150
+
151
+ ### Data Enums (Discriminated Unions)
152
+
153
+ Data enums generate Dart 3 `sealed class` hierarchies:
154
+
155
+ ```dart
156
+ sealed class TokenInstruction {
157
+ const TokenInstruction();
158
+ }
159
+
160
+ final class Transfer extends TokenInstruction {
161
+ const Transfer({required this.amount});
162
+ final BigInt amount;
163
+ }
164
+
165
+ final class Approve extends TokenInstruction {
166
+ const Approve({required this.amount});
167
+ final BigInt amount;
168
+ }
169
+ ```
170
+
171
+ ### Errors
172
+
173
+ Program errors generate constants, a message map, and helper functions:
174
+
175
+ ```dart
176
+ const int myProgramErrorInvalidAuthority = 0x1770; // 6000
177
+
178
+ String? getMyProgramErrorMessage(int code) { ... }
179
+ bool isMyProgramError(int code) { ... }
180
+ ```
181
+
182
+ ### PDAs
183
+
184
+ PDAs generate a seeds class and finder function:
185
+
186
+ ```dart
187
+ @immutable
188
+ class MyPdaSeeds {
189
+ const MyPdaSeeds({required this.authority});
190
+ final Address authority;
191
+ }
192
+
193
+ Future<(Address, int)> findMyPdaPda({
194
+ required MyPdaSeeds seeds,
195
+ required Address programAddress,
196
+ }) async { ... }
197
+ ```
198
+
199
+ ## Type Mapping
200
+
201
+ | Codama Type | Dart Type | Codec |
202
+ | ----------------------------------- | --------------- | -------------------------------- |
203
+ | `numberTypeNode(u8/u16/u32)` | `int` | `getU8Encoder()` etc. |
204
+ | `numberTypeNode(u64/u128/i64/i128)` | `BigInt` | `getU64Encoder()` etc. |
205
+ | `numberTypeNode(f32/f64)` | `double` | `getF32Encoder()` etc. |
206
+ | `booleanTypeNode` | `bool` | `getBooleanEncoder()` |
207
+ | `stringTypeNode` | `String` | `getUtf8Encoder()` etc. |
208
+ | `publicKeyTypeNode` | `Address` | `getAddressEncoder()` |
209
+ | `bytesTypeNode` | `Uint8List` | `getBytesEncoder()` |
210
+ | `arrayTypeNode` | `List<T>` | `getArrayEncoder()` |
211
+ | `mapTypeNode` | `Map<K, V>` | `getMapEncoder()` |
212
+ | `setTypeNode` | `Set<T>` | `getSetEncoder()` |
213
+ | `tupleTypeNode` | `(T1, T2, ...)` | `getTupleEncoder()` |
214
+ | `optionTypeNode` | `T?` | `getNullableEncoder()` |
215
+ | `structTypeNode` | Named class | `getStructEncoder()` |
216
+ | `enumTypeNode` (scalar) | `enum` | `transformEncoder()` |
217
+ | `enumTypeNode` (data) | `sealed class` | `getDiscriminatedUnionEncoder()` |
218
+
219
+ ## Options
220
+
221
+ ### `RenderOptions`
222
+
223
+ | Option | Type | Default | Description |
224
+ | ----------------------------- | ------------------------ | ------- | --------------------------------------------------- |
225
+ | `deleteFolderBeforeRendering` | `boolean` | `true` | Delete output directory before generating |
226
+ | `formatCode` | `boolean` | `false` | Run `dart format` on generated files |
227
+ | `nameApi` | `Partial<DartNameApi>` | — | Override naming conventions |
228
+ | `dependencyMap` | `Record<string, string>` | — | Override logical module to Dart package URI mapping |
229
+
230
+ ### `DartNameApi`
231
+
232
+ All naming conventions are customizable:
233
+
234
+ ```typescript
235
+ import { renderVisitor, createDartNameApi } from "codama-renderers-dart";
236
+
237
+ const nameApi = {
238
+ ...createDartNameApi(),
239
+ dataType: (name) => `My${pascalCase(name)}`,
240
+ };
241
+
242
+ visit(root, renderVisitor("output", { nameApi }));
243
+ ```
244
+
245
+ ## Target Packages
246
+
247
+ Generated Dart code depends on these solana_kit packages:
248
+
249
+ - `solana_kit_addresses` — `Address` type and encoder/decoder
250
+ - `solana_kit_codecs_core` — `Encoder`, `Decoder`, `Codec` base types
251
+ - `solana_kit_codecs_data_structures` — Struct, array, boolean, nullable codecs
252
+ - `solana_kit_codecs_numbers` — Number codecs (u8–u128, i8–i128, f32, f64)
253
+ - `solana_kit_codecs_strings` — String codecs (utf8, base58, base64, base16)
254
+ - `solana_kit_accounts` — `Account`, `EncodedAccount`, `decodeAccount`
255
+ - `solana_kit_instructions` — `Instruction`, `AccountMeta`, `AccountRole`
256
+ - `solana_kit_errors` — `SolanaError` error types
257
+ - `meta` — `@immutable` annotation
258
+
259
+ ## Architecture
260
+
261
+ This renderer follows the same architecture as the official Codama renderers:
262
+
263
+ 1. **Visitor pattern** — Uses `@codama/visitors-core` to traverse the IDL node tree
264
+ 2. **Fragment system** — Composable tagged template literals that track imports
265
+ 3. **RenderMap** — Maps file paths to code fragments
266
+ 4. **Type manifest** — Maps Codama type nodes to Dart types and codec expressions
267
+
268
+ Key source files:
269
+
270
+ - `src/visitors/renderVisitor.ts` — Top-level entry point (file I/O)
271
+ - `src/visitors/getRenderMapVisitor.ts` — Maps IDL nodes to output files
272
+ - `src/visitors/getTypeManifestVisitor.ts` — Maps type nodes to Dart types/codecs
273
+ - `src/fragments/*.ts` — Code generation for each entity type
274
+ - `src/utils/` — Import maps, naming, fragment helpers
275
+
276
+ ## Development
277
+
278
+ ```bash
279
+ # Install dependencies
280
+ pnpm install
281
+
282
+ # Type check
283
+ npx tsc --noEmit
284
+
285
+ # Run tests
286
+ pnpm test
287
+
288
+ # Watch mode
289
+ pnpm test:watch
290
+
291
+ # Build
292
+ pnpm build
293
+ ```
294
+
295
+ ## License
296
+
297
+ MIT
@@ -0,0 +1,201 @@
1
+ import type { AccountNode, StructFieldTypeNode } from "@codama/nodes";
2
+ import { resolveNestedTypeNode } from "@codama/nodes";
3
+ import { visit } from "@codama/visitors-core";
4
+
5
+ import type { Fragment } from "../utils/fragment.js";
6
+ import {
7
+ emptyFragment,
8
+ fragment,
9
+ fragmentFromString,
10
+ mergeFragments,
11
+ use,
12
+ } from "../utils/fragment.js";
13
+ import type { RenderScope } from "../utils/options.js";
14
+ import { camelCase } from "../utils/nameTransformers.js";
15
+ import { getDiscriminatorConstantsFragment } from "./discriminatorConstants.js";
16
+
17
+ /**
18
+ * Generate a full Dart file for an account.
19
+ */
20
+ export function getAccountPageFragment(
21
+ node: AccountNode,
22
+ scope: RenderScope,
23
+ ): Fragment {
24
+ const name = node.name as string;
25
+ const typeName = scope.nameApi.dataType(name);
26
+ const dataNode = resolveNestedTypeNode(node.data);
27
+
28
+ // Fields from the struct data
29
+ const fields = dataNode.fields;
30
+
31
+ // Visit each field type once and collect manifests for reuse
32
+ const fieldManifests = fields.map((f: StructFieldTypeNode) => ({
33
+ field: f,
34
+ manifest: visit(f.type, scope.typeManifestVisitor),
35
+ }));
36
+
37
+ const fieldDecls = fieldManifests
38
+ .map(({ field: f, manifest }) => {
39
+ return ` final ${manifest.type.content} ${camelCase(f.name as string)};`;
40
+ })
41
+ .join("\n");
42
+
43
+ const ctorParams = fields
44
+ .map((f: StructFieldTypeNode) => ` required this.${camelCase(f.name as string)},`)
45
+ .join("\n");
46
+
47
+ const eqChecks =
48
+ fields.length === 0
49
+ ? "true"
50
+ : fields
51
+ .map(
52
+ (f: StructFieldTypeNode) =>
53
+ `${camelCase(f.name as string)} == other.${camelCase(f.name as string)}`,
54
+ )
55
+ .join(" &&\n ");
56
+
57
+ const hashFields = fields
58
+ .map((f: StructFieldTypeNode) => camelCase(f.name as string))
59
+ .join(", ");
60
+
61
+ const toStringFields = fields
62
+ .map(
63
+ (f: StructFieldTypeNode) =>
64
+ `${camelCase(f.name as string)}: \$${camelCase(f.name as string)}`,
65
+ )
66
+ .join(", ");
67
+
68
+ // Encoder fields
69
+ const encFields = fieldManifests
70
+ .map(({ field: f, manifest }) => {
71
+ return ` ('${f.name as string}', ${manifest.encoder.content}),`;
72
+ })
73
+ .join("\n");
74
+
75
+ const decFields = fieldManifests
76
+ .map(({ field: f, manifest }) => {
77
+ return ` ('${f.name as string}', ${manifest.decoder.content}),`;
78
+ })
79
+ .join("\n");
80
+
81
+ const toMapFields = fields
82
+ .map(
83
+ (f: StructFieldTypeNode) =>
84
+ ` '${f.name as string}': value.${camelCase(f.name as string)},`,
85
+ )
86
+ .join("\n");
87
+
88
+ const fromMapFields = fieldManifests
89
+ .map(({ field: f, manifest }) => {
90
+ const typeStr = manifest.type.content;
91
+ const isNullable = typeStr.endsWith("?");
92
+ const accessor = isNullable ? `map['${f.name as string}']` : `map['${f.name as string}']!`;
93
+ return ` ${camelCase(f.name as string)}: ${accessor} as ${typeStr},`;
94
+ })
95
+ .join("\n");
96
+
97
+ const encoderName = scope.nameApi.encoderFunction(name);
98
+ const decoderName = scope.nameApi.decoderFunction(name);
99
+ const codecName = scope.nameApi.codecFunction(name);
100
+ const decodeFnName = scope.nameApi.accountDecodeFunction(name);
101
+
102
+ // Size
103
+ const sizeFragment = node.size != null
104
+ ? fragment`
105
+ /// The size of the [${fragmentFromString(typeName)}] account data in bytes.
106
+ const int ${fragmentFromString(scope.nameApi.accountSizeConstant(name))} = ${fragmentFromString(String(node.size))};`
107
+ : emptyFragment();
108
+
109
+ // Discriminator
110
+ const discFragment = getDiscriminatorConstantsFragment(node, scope);
111
+
112
+ const parts: Fragment[] = [
113
+ fragment`// Auto-generated. Do not edit.
114
+ // ignore_for_file: type=lint
115
+
116
+ ${use("Uint8List", "dartTypedData")}
117
+ ${use("immutable", "meta")}
118
+ ${use("Encoder", "solanaCodecsCore")}
119
+ ${use("Decoder", "solanaCodecsCore")}
120
+ ${use("Codec", "solanaCodecsCore")}
121
+ ${use("combineCodec", "solanaCodecsCore")}
122
+ ${use("transformEncoder", "solanaCodecsCore")}
123
+ ${use("transformDecoder", "solanaCodecsCore")}
124
+ ${use("getStructEncoder", "solanaCodecsDataStructures")}
125
+ ${use("getStructDecoder", "solanaCodecsDataStructures")}
126
+ ${use("Account", "solanaAccounts")}
127
+ ${use("EncodedAccount", "solanaAccounts")}
128
+ ${use("decodeAccount", "solanaAccounts")}
129
+
130
+ @immutable
131
+ class ${fragmentFromString(typeName)} {
132
+ const ${fragmentFromString(typeName)}({
133
+ ${fragmentFromString(ctorParams)}
134
+ });
135
+
136
+ ${fragmentFromString(fieldDecls)}
137
+
138
+ @override
139
+ bool operator ==(Object other) =>
140
+ identical(this, other) ||
141
+ other is ${fragmentFromString(typeName)} &&
142
+ runtimeType == other.runtimeType &&
143
+ ${fragmentFromString(eqChecks)};
144
+
145
+ @override
146
+ int get hashCode => Object.hash(${fragmentFromString(hashFields)});
147
+
148
+ @override
149
+ String toString() => '${fragmentFromString(typeName)}(${fragmentFromString(toStringFields)})';
150
+ }`,
151
+ ];
152
+
153
+ if (sizeFragment.content) parts.push(sizeFragment);
154
+ if (discFragment.content) parts.push(discFragment);
155
+
156
+ parts.push(fragment`
157
+ Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
158
+ final structEncoder = getStructEncoder(<(String, Encoder<Object?>)>[
159
+ ${fragmentFromString(encFields)}
160
+ ]);
161
+
162
+ return transformEncoder(
163
+ structEncoder,
164
+ (${fragmentFromString(typeName)} value) => <String, Object?>{
165
+ ${fragmentFromString(toMapFields)}
166
+ },
167
+ );
168
+ }
169
+
170
+ Decoder<${fragmentFromString(typeName)}> ${fragmentFromString(decoderName)}() {
171
+ final structDecoder = getStructDecoder(<(String, Decoder<Object?>)>[
172
+ ${fragmentFromString(decFields)}
173
+ ]);
174
+
175
+ return transformDecoder(
176
+ structDecoder,
177
+ (Map<String, Object?> map, Uint8List bytes, int offset) => ${fragmentFromString(typeName)}(
178
+ ${fragmentFromString(fromMapFields)}
179
+ ),
180
+ );
181
+ }
182
+
183
+ Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragmentFromString(codecName)}() {
184
+ return combineCodec(${fragmentFromString(encoderName)}(), ${fragmentFromString(decoderName)}());
185
+ }
186
+
187
+ Account<${fragmentFromString(typeName)}> ${fragmentFromString(decodeFnName)}(EncodedAccount encodedAccount) {
188
+ return decodeAccount(encodedAccount, ${fragmentFromString(decoderName)}());
189
+ }`);
190
+
191
+ const result = mergeFragments(parts, (cs) => cs.join("\n\n"));
192
+
193
+ // Merge field type manifest imports (encoder, decoder, type) into the result
194
+ for (const { manifest } of fieldManifests) {
195
+ result.imports.mergeWith(manifest.encoder.imports);
196
+ result.imports.mergeWith(manifest.decoder.imports);
197
+ result.imports.mergeWith(manifest.type.imports);
198
+ }
199
+
200
+ return result;
201
+ }
@@ -0,0 +1,74 @@
1
+ import type {
2
+ AccountNode,
3
+ ConstantDiscriminatorNode,
4
+ FieldDiscriminatorNode,
5
+ InstructionNode,
6
+ SizeDiscriminatorNode,
7
+ } from "@codama/nodes";
8
+ import { visit } from "@codama/visitors-core";
9
+
10
+ import type { Fragment } from "../utils/fragment.js";
11
+ import { emptyFragment, fragment, fragmentFromString, mergeFragments, use } from "../utils/fragment.js";
12
+ import type { RenderScope } from "../utils/options.js";
13
+ import { bytesToDartHexList } from "../utils/codecs.js";
14
+
15
+ /**
16
+ * Generate discriminator constant declarations for an account or instruction.
17
+ */
18
+ export function getDiscriminatorConstantsFragment(
19
+ node: AccountNode | InstructionNode,
20
+ scope: RenderScope,
21
+ ): Fragment {
22
+ const discriminators = node.discriminators ?? [];
23
+ if (discriminators.length === 0) return emptyFragment();
24
+
25
+ const name = node.name as string;
26
+ const constName = scope.nameApi.discriminatorConstant(name);
27
+
28
+ const fragments: Fragment[] = [];
29
+
30
+ for (const disc of discriminators) {
31
+ switch (disc.kind) {
32
+ case "constantDiscriminatorNode": {
33
+ const constDisc = disc as ConstantDiscriminatorNode;
34
+ if (constDisc.constant.value.kind === "bytesValueNode") {
35
+ const bytes = hexToBytes(constDisc.constant.value.data);
36
+ const hexList = bytesToDartHexList(bytes);
37
+ fragments.push(
38
+ fragment`/// The discriminator bytes for this ${node.kind === "accountNode" ? "account" : "instruction"}.
39
+ final ${constName} = ${use("Uint8List", "dartTypedData")}.fromList(${fragmentFromString(hexList)});`,
40
+ );
41
+ }
42
+ break;
43
+ }
44
+ case "fieldDiscriminatorNode": {
45
+ const fieldDisc = disc as FieldDiscriminatorNode;
46
+ fragments.push(
47
+ fragment`/// The discriminator field name: '${fragmentFromString(fieldDisc.name as string)}'.
48
+ /// Offset: ${fragmentFromString(String(fieldDisc.offset))}.`,
49
+ );
50
+ break;
51
+ }
52
+ case "sizeDiscriminatorNode": {
53
+ const sizeDisc = disc as SizeDiscriminatorNode;
54
+ fragments.push(
55
+ fragment`/// This ${node.kind === "accountNode" ? "account" : "instruction"} has a size discriminator of ${fragmentFromString(String(sizeDisc.size))} bytes.`,
56
+ );
57
+ break;
58
+ }
59
+ }
60
+ }
61
+
62
+ if (fragments.length === 0) return emptyFragment();
63
+
64
+ return mergeFragments(fragments, (cs) => cs.join("\n\n"));
65
+ }
66
+
67
+ function hexToBytes(hex: string): Uint8Array {
68
+ const clean = hex.replace(/^0x/, "");
69
+ const bytes = new Uint8Array(clean.length / 2);
70
+ for (let i = 0; i < clean.length; i += 2) {
71
+ bytes[i / 2] = parseInt(clean.slice(i, i + 2), 16);
72
+ }
73
+ return bytes;
74
+ }