codama-renderers-dart 0.3.1 → 0.4.1
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/package.json +9 -2
- package/readme.md +28 -0
- package/src/fragments/instructionPage.ts +65 -12
- package/src/fragments/pdaPage.ts +33 -7
- package/src/fragments/programPage.ts +27 -5
- package/src/fragments/typePage.ts +77 -31
- package/src/utils/index.ts +1 -0
- package/src/utils/wellKnownAddresses.ts +64 -0
- package/src/visitors/getRenderMapVisitor.ts +54 -69
- package/src/visitors/getTypeManifestVisitor.ts +86 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codama-renderers-dart",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Codama renderer for generating Dart code targeting the solana_kit SDK",
|
|
5
5
|
"exports": {
|
|
6
6
|
"node": {
|
|
@@ -30,6 +30,11 @@
|
|
|
30
30
|
"code-generator"
|
|
31
31
|
],
|
|
32
32
|
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/openbudgetfun/solana_kit.git",
|
|
36
|
+
"directory": "packages/codama-renderers-dart"
|
|
37
|
+
},
|
|
33
38
|
"dependencies": {
|
|
34
39
|
"@codama/errors": "^1.4.4",
|
|
35
40
|
"@codama/nodes": "^1.4.4",
|
|
@@ -38,7 +43,8 @@
|
|
|
38
43
|
},
|
|
39
44
|
"devDependencies": {
|
|
40
45
|
"@codama/nodes-from-anchor": "^1.3.9",
|
|
41
|
-
"@codama/renderers-js": "^2.0.2"
|
|
46
|
+
"@codama/renderers-js": "^2.0.2",
|
|
47
|
+
"@vitest/coverage-v8": "3.2.4"
|
|
42
48
|
},
|
|
43
49
|
"engines": {
|
|
44
50
|
"node": ">=18"
|
|
@@ -50,6 +56,7 @@
|
|
|
50
56
|
"build": "pnpm exec rimraf dist && pnpm exec tsup && pnpm exec tsc -p tsconfig.declarations.json",
|
|
51
57
|
"lint": "eslint --ext .ts src",
|
|
52
58
|
"test": "pnpm exec vitest run",
|
|
59
|
+
"test:coverage": "pnpm exec vitest run --coverage.enabled --coverage.reporter=lcov",
|
|
53
60
|
"test:watch": "pnpm exec vitest",
|
|
54
61
|
"typecheck": "pnpm exec tsc --noEmit"
|
|
55
62
|
}
|
package/readme.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# codama-renderers-dart
|
|
2
2
|
|
|
3
|
+
[](https://pub.dev/packages/codama-renderers-dart)
|
|
4
|
+
[](https://github.com/openbudgetfun/solana_kit/actions/workflows/ci.yml)
|
|
5
|
+
[](https://codecov.io/gh/openbudgetfun/solana_kit?flag=codama-renderers-dart)
|
|
6
|
+
|
|
3
7
|
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
8
|
|
|
5
9
|
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.
|
|
@@ -9,6 +13,10 @@ Given a Codama IDL (Interface Description Language) describing a Solana program,
|
|
|
9
13
|
```bash
|
|
10
14
|
pnpm add codama-renderers-dart
|
|
11
15
|
# or
|
|
16
|
+
|
|
17
|
+
[](https://pub.dev/packages/codama-renderers-dart)
|
|
18
|
+
[](https://github.com/openbudgetfun/solana_kit/actions/workflows/ci.yml)
|
|
19
|
+
[](https://codecov.io/gh/openbudgetfun/solana_kit?flag=codama-renderers-dart)
|
|
12
20
|
npm install codama-renderers-dart
|
|
13
21
|
```
|
|
14
22
|
|
|
@@ -277,18 +285,38 @@ Key source files:
|
|
|
277
285
|
|
|
278
286
|
```bash
|
|
279
287
|
# Install dependencies
|
|
288
|
+
|
|
289
|
+
[](https://pub.dev/packages/codama-renderers-dart)
|
|
290
|
+
[](https://github.com/openbudgetfun/solana_kit/actions/workflows/ci.yml)
|
|
291
|
+
[](https://codecov.io/gh/openbudgetfun/solana_kit?flag=codama-renderers-dart)
|
|
280
292
|
pnpm install
|
|
281
293
|
|
|
282
294
|
# Type check
|
|
295
|
+
|
|
296
|
+
[](https://pub.dev/packages/codama-renderers-dart)
|
|
297
|
+
[](https://github.com/openbudgetfun/solana_kit/actions/workflows/ci.yml)
|
|
298
|
+
[](https://codecov.io/gh/openbudgetfun/solana_kit?flag=codama-renderers-dart)
|
|
283
299
|
npx tsc --noEmit
|
|
284
300
|
|
|
285
301
|
# Run tests
|
|
302
|
+
|
|
303
|
+
[](https://pub.dev/packages/codama-renderers-dart)
|
|
304
|
+
[](https://github.com/openbudgetfun/solana_kit/actions/workflows/ci.yml)
|
|
305
|
+
[](https://codecov.io/gh/openbudgetfun/solana_kit?flag=codama-renderers-dart)
|
|
286
306
|
pnpm test
|
|
287
307
|
|
|
288
308
|
# Watch mode
|
|
309
|
+
|
|
310
|
+
[](https://pub.dev/packages/codama-renderers-dart)
|
|
311
|
+
[](https://github.com/openbudgetfun/solana_kit/actions/workflows/ci.yml)
|
|
312
|
+
[](https://codecov.io/gh/openbudgetfun/solana_kit?flag=codama-renderers-dart)
|
|
289
313
|
pnpm test:watch
|
|
290
314
|
|
|
291
315
|
# Build
|
|
316
|
+
|
|
317
|
+
[](https://pub.dev/packages/codama-renderers-dart)
|
|
318
|
+
[](https://github.com/openbudgetfun/solana_kit/actions/workflows/ci.yml)
|
|
319
|
+
[](https://codecov.io/gh/openbudgetfun/solana_kit?flag=codama-renderers-dart)
|
|
292
320
|
pnpm build
|
|
293
321
|
```
|
|
294
322
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
2
|
InstructionAccountNode,
|
|
3
3
|
InstructionArgumentNode,
|
|
4
4
|
InstructionNode,
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import type { RenderScope } from "../utils/options.js";
|
|
17
17
|
import { camelCase, pascalCase } from "../utils/nameTransformers.js";
|
|
18
18
|
import { getDiscriminatorConstantsFragment } from "./discriminatorConstants.js";
|
|
19
|
+
import { WELL_KNOWN_ADDRESSES } from "../utils/wellKnownAddresses.js";
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Generate a full Dart file for an instruction.
|
|
@@ -53,15 +54,28 @@ export function getInstructionPageFragment(
|
|
|
53
54
|
.join("\n");
|
|
54
55
|
|
|
55
56
|
const dataCtorParams = allArgManifests
|
|
56
|
-
.map(({ arg }) => {
|
|
57
|
+
.map(({ arg, manifest }) => {
|
|
57
58
|
const fieldName = camelCase(arg.name as string);
|
|
58
59
|
if (isDiscriminatorArg(arg, node)) {
|
|
59
|
-
return
|
|
60
|
+
return isConstDefaultValue(arg.defaultValue)
|
|
61
|
+
? ` this.${fieldName} = ${getDiscriminatorDefault(arg, node)},`
|
|
62
|
+
: ` ${manifest.type.content}? ${fieldName},`;
|
|
60
63
|
}
|
|
61
64
|
return ` required this.${fieldName},`;
|
|
62
65
|
})
|
|
63
66
|
.join("\n");
|
|
64
67
|
|
|
68
|
+
const dataCtorInitializers = allArgManifests
|
|
69
|
+
.map(({ arg }) => {
|
|
70
|
+
const fieldName = camelCase(arg.name as string);
|
|
71
|
+
if (isDiscriminatorArg(arg, node) && !isConstDefaultValue(arg.defaultValue)) {
|
|
72
|
+
return ` ${fieldName} = ${fieldName} ?? ${getDiscriminatorDefault(arg, node)}`;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
})
|
|
76
|
+
.filter((value): value is string => value !== null)
|
|
77
|
+
.join(",\n");
|
|
78
|
+
|
|
65
79
|
// Encoder/decoder for instruction data
|
|
66
80
|
const encFields = allArgManifests
|
|
67
81
|
.map(({ arg, manifest }) => {
|
|
@@ -91,10 +105,17 @@ export function getInstructionPageFragment(
|
|
|
91
105
|
})
|
|
92
106
|
.join("\n");
|
|
93
107
|
|
|
108
|
+
const dataCtorKeyword = dataCtorInitializers ? "" : "const ";
|
|
94
109
|
const dataEncoderName = `get${typeName}InstructionDataEncoder`;
|
|
95
110
|
const dataDecoderName = `get${typeName}InstructionDataDecoder`;
|
|
96
111
|
const dataCodecName = `get${typeName}InstructionDataCodec`;
|
|
97
112
|
|
|
113
|
+
// Determine whether any arg or account name collides with 'programAddress'
|
|
114
|
+
const argNames = new Set(args.map((a) => camelCase(a.name as string)));
|
|
115
|
+
const accountNames = new Set(accounts.map((a) => camelCase(a.name as string)));
|
|
116
|
+
const hasProgramAddressCollision = argNames.has('programAddress') || accountNames.has('programAddress');
|
|
117
|
+
const instrProgramParam = hasProgramAddressCollision ? 'instructionProgramAddress' : 'programAddress';
|
|
118
|
+
|
|
98
119
|
// Build the instruction builder function
|
|
99
120
|
const accountParams = accounts
|
|
100
121
|
.map((acc) => {
|
|
@@ -112,11 +133,14 @@ export function getInstructionPageFragment(
|
|
|
112
133
|
.map((arg) => {
|
|
113
134
|
const manifest = argManifestMap.get(arg)!;
|
|
114
135
|
const fieldName = camelCase(arg.name as string);
|
|
136
|
+
const typeStr = manifest.type.content;
|
|
115
137
|
const hasDefault = arg.defaultValue != null;
|
|
116
138
|
if (hasDefault) {
|
|
117
|
-
|
|
139
|
+
// Don't add `?` if the type is already nullable.
|
|
140
|
+
const nullableType = typeStr.endsWith("?") ? typeStr : `${typeStr}?`;
|
|
141
|
+
return ` ${nullableType} ${fieldName},`;
|
|
118
142
|
}
|
|
119
|
-
return ` required ${
|
|
143
|
+
return ` required ${typeStr} ${fieldName},`;
|
|
120
144
|
})
|
|
121
145
|
.join("\n");
|
|
122
146
|
|
|
@@ -140,6 +164,7 @@ export function getInstructionPageFragment(
|
|
|
140
164
|
if (isDiscriminatorArg(arg, node)) {
|
|
141
165
|
return ""; // Use default
|
|
142
166
|
}
|
|
167
|
+
// Use 'arg_<name>' prefix to reference builder params without shadowing.
|
|
143
168
|
return ` ${fieldName}: ${fieldName}${arg.defaultValue != null ? ` ?? ${getDefaultValue(arg)}` : ""},`;
|
|
144
169
|
})
|
|
145
170
|
.filter(Boolean)
|
|
@@ -174,9 +199,9 @@ ${use("AccountRole", "solanaInstructions")}`,
|
|
|
174
199
|
parts.push(fragment`
|
|
175
200
|
@immutable
|
|
176
201
|
class ${fragmentFromString(dataClassName)} {
|
|
177
|
-
|
|
202
|
+
${fragmentFromString(dataCtorKeyword)}${fragmentFromString(dataClassName)}({
|
|
178
203
|
${fragmentFromString(dataCtorParams)}
|
|
179
|
-
});
|
|
204
|
+
})${fragmentFromString(dataCtorInitializers ? ` :\n${dataCtorInitializers}` : "")};
|
|
180
205
|
|
|
181
206
|
${fragmentFromString(dataFieldDecls)}
|
|
182
207
|
}`);
|
|
@@ -217,20 +242,20 @@ Codec<${fragmentFromString(dataClassName)}, ${fragmentFromString(dataClassName)}
|
|
|
217
242
|
parts.push(fragment`
|
|
218
243
|
/// Creates a [${fragmentFromString(typeName)}] instruction.
|
|
219
244
|
Instruction ${fragmentFromString(instrFnName)}({
|
|
220
|
-
required Address
|
|
245
|
+
required Address ${fragmentFromString(instrProgramParam)},
|
|
221
246
|
${fragmentFromString(accountParams)}
|
|
222
247
|
${fragmentFromString(argParams)}
|
|
223
248
|
}) {
|
|
224
|
-
final
|
|
249
|
+
final instructionData = ${fragmentFromString(dataClassName)}(
|
|
225
250
|
${fragmentFromString(dataConstruction)}
|
|
226
251
|
);
|
|
227
252
|
|
|
228
253
|
return Instruction(
|
|
229
|
-
programAddress:
|
|
254
|
+
programAddress: ${fragmentFromString(instrProgramParam)},
|
|
230
255
|
accounts: [
|
|
231
256
|
${fragmentFromString(accountMetas)}
|
|
232
257
|
],
|
|
233
|
-
data: ${fragmentFromString(dataEncoderName)}().encode(
|
|
258
|
+
data: ${fragmentFromString(dataEncoderName)}().encode(instructionData),
|
|
234
259
|
);
|
|
235
260
|
}`);
|
|
236
261
|
|
|
@@ -282,10 +307,28 @@ function getDiscriminatorDefault(
|
|
|
282
307
|
if (arg.defaultValue.kind === "numberValueNode") {
|
|
283
308
|
return String(arg.defaultValue.number);
|
|
284
309
|
}
|
|
310
|
+
if (arg.defaultValue.kind === "bytesValueNode") {
|
|
311
|
+
const data = arg.defaultValue.data as string;
|
|
312
|
+
const bytes = data.match(/.{1,2}/g)?.map((b) => parseInt(b, 16)) ?? [];
|
|
313
|
+
return `Uint8List.fromList([${bytes.join(", ")}])`;
|
|
314
|
+
}
|
|
285
315
|
}
|
|
286
316
|
return "0";
|
|
287
317
|
}
|
|
288
318
|
|
|
319
|
+
function isConstDefaultValue(defaultValue: InstructionArgumentNode["defaultValue"]): boolean {
|
|
320
|
+
if (!defaultValue) return false;
|
|
321
|
+
switch (defaultValue.kind) {
|
|
322
|
+
case "numberValueNode":
|
|
323
|
+
case "booleanValueNode":
|
|
324
|
+
case "stringValueNode":
|
|
325
|
+
case "noneValueNode":
|
|
326
|
+
return true;
|
|
327
|
+
default:
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
289
332
|
function getDefaultValue(arg: InstructionArgumentNode): string {
|
|
290
333
|
if (!arg.defaultValue) return "null";
|
|
291
334
|
const dv = arg.defaultValue;
|
|
@@ -296,10 +339,20 @@ function getDefaultValue(arg: InstructionArgumentNode): string {
|
|
|
296
339
|
return String(dv.boolean);
|
|
297
340
|
case "stringValueNode":
|
|
298
341
|
return `'${dv.string}'`;
|
|
299
|
-
case "publicKeyValueNode":
|
|
342
|
+
case "publicKeyValueNode": {
|
|
343
|
+
const wellKnownName = WELL_KNOWN_ADDRESSES.get(dv.publicKey);
|
|
344
|
+
if (wellKnownName) {
|
|
345
|
+
use(wellKnownName, "solanaAddresses");
|
|
346
|
+
return wellKnownName;
|
|
347
|
+
}
|
|
300
348
|
return `Address('${dv.publicKey}')`;
|
|
349
|
+
}
|
|
301
350
|
case "noneValueNode":
|
|
302
351
|
return "null";
|
|
352
|
+
case "bytesValueNode": {
|
|
353
|
+
const bytes = dv.data.match(/.{1,2}/g)?.map((b) => parseInt(b, 16)) ?? [];
|
|
354
|
+
return `Uint8List.fromList([${bytes.join(", ")}])`;
|
|
355
|
+
}
|
|
303
356
|
default:
|
|
304
357
|
return "null";
|
|
305
358
|
}
|
package/src/fragments/pdaPage.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "../utils/fragment.js";
|
|
11
11
|
import type { RenderScope } from "../utils/options.js";
|
|
12
12
|
import { camelCase, pascalCase } from "../utils/nameTransformers.js";
|
|
13
|
+
import { WELL_KNOWN_ADDRESSES } from "../utils/wellKnownAddresses.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Generate a full Dart file for a PDA.
|
|
@@ -23,6 +24,11 @@ export function getPdaPageFragment(
|
|
|
23
24
|
const seedsClassName = `${pascalCase(name)}Seeds`;
|
|
24
25
|
|
|
25
26
|
const seeds = node.seeds ?? [];
|
|
27
|
+
const hasByteSeeds = seeds.some(
|
|
28
|
+
(seed) =>
|
|
29
|
+
seed.kind === "constantPdaSeedNode" &&
|
|
30
|
+
seed.value.kind === "bytesValueNode",
|
|
31
|
+
);
|
|
26
32
|
|
|
27
33
|
// Separate variable seeds from constant seeds
|
|
28
34
|
const variableSeeds = seeds.filter(
|
|
@@ -59,9 +65,17 @@ export function getPdaPageFragment(
|
|
|
59
65
|
} else if (seed.value.kind === "stringValueNode") {
|
|
60
66
|
seedValues.push(` '${seed.value.string}',`);
|
|
61
67
|
} else if (seed.value.kind === "publicKeyValueNode") {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
const wellKnownSeedName = WELL_KNOWN_ADDRESSES.get(seed.value.publicKey);
|
|
69
|
+
if (wellKnownSeedName) {
|
|
70
|
+
use(wellKnownSeedName, "solanaAddresses");
|
|
71
|
+
seedValues.push(
|
|
72
|
+
` getAddressEncoder().encode(${wellKnownSeedName}),`,
|
|
73
|
+
);
|
|
74
|
+
} else {
|
|
75
|
+
seedValues.push(
|
|
76
|
+
` getAddressEncoder().encode(Address('${seed.value.publicKey}')),`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
65
79
|
} else if (seed.value.kind === "numberValueNode") {
|
|
66
80
|
const manifest = visit(seed.type, scope.typeManifestVisitor);
|
|
67
81
|
seedValues.push(` ${manifest.encoder.content}.encode(${seed.value.number}),`);
|
|
@@ -75,15 +89,27 @@ export function getPdaPageFragment(
|
|
|
75
89
|
}
|
|
76
90
|
|
|
77
91
|
// Program address parameter
|
|
92
|
+
const wellKnownProgramName = node.programId
|
|
93
|
+
? WELL_KNOWN_ADDRESSES.get(node.programId)
|
|
94
|
+
: undefined;
|
|
95
|
+
if (wellKnownProgramName) {
|
|
96
|
+
use(wellKnownProgramName, "solanaAddresses");
|
|
97
|
+
}
|
|
78
98
|
const programIdParam = node.programId
|
|
79
|
-
?
|
|
99
|
+
? wellKnownProgramName
|
|
100
|
+
? `Address programAddress = ${wellKnownProgramName}`
|
|
101
|
+
: `Address programAddress = const Address('${node.programId}')`
|
|
80
102
|
: "required Address programAddress";
|
|
81
103
|
|
|
104
|
+
const typedDataImport = hasByteSeeds
|
|
105
|
+
? use("Uint8List", "dartTypedData")
|
|
106
|
+
: fragmentFromString("");
|
|
107
|
+
|
|
82
108
|
const parts: Fragment[] = [
|
|
83
109
|
fragment`// Auto-generated. Do not edit.
|
|
84
110
|
// ignore_for_file: type=lint
|
|
85
111
|
|
|
86
|
-
${
|
|
112
|
+
${typedDataImport}
|
|
87
113
|
${use("immutable", "meta")}
|
|
88
114
|
${use("Address", "solanaAddresses")}
|
|
89
115
|
${use("getAddressEncoder", "solanaAddresses")}
|
|
@@ -110,13 +136,13 @@ Future<(Address, int)> ${fragmentFromString(findFnName)}({
|
|
|
110
136
|
${fragmentFromString(seedsParam)}
|
|
111
137
|
${fragmentFromString(programIdParam)},
|
|
112
138
|
}) async {
|
|
113
|
-
final
|
|
139
|
+
final seedValues = <Object>[
|
|
114
140
|
${fragmentFromString(seedValues.join("\n"))}
|
|
115
141
|
];
|
|
116
142
|
|
|
117
143
|
return getProgramDerivedAddress(
|
|
118
144
|
programAddress: programAddress,
|
|
119
|
-
seeds:
|
|
145
|
+
seeds: seedValues,
|
|
120
146
|
);
|
|
121
147
|
}`);
|
|
122
148
|
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "../utils/fragment.js";
|
|
11
11
|
import type { RenderScope } from "../utils/options.js";
|
|
12
12
|
import { camelCase, pascalCase } from "../utils/nameTransformers.js";
|
|
13
|
+
import { WELL_KNOWN_ADDRESSES } from "../utils/wellKnownAddresses.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Generate a full Dart file for a program.
|
|
@@ -21,15 +22,36 @@ export function getProgramPageFragment(
|
|
|
21
22
|
const name = node.name as string;
|
|
22
23
|
const addressConstName = scope.nameApi.programAddressConstant(name);
|
|
23
24
|
|
|
24
|
-
const
|
|
25
|
-
|
|
25
|
+
const wellKnownName = WELL_KNOWN_ADDRESSES.get(node.publicKey);
|
|
26
|
+
|
|
27
|
+
// For well-known addresses, export and alias the canonical constant from
|
|
28
|
+
// solana_kit_addresses instead of hardcoding the address string.
|
|
29
|
+
// When the generated name matches the canonical name, a simple export suffices.
|
|
30
|
+
// When they differ, we also need a local const alias.
|
|
31
|
+
// For unknown addresses, fall back to the original Address('...') pattern.
|
|
32
|
+
const addressDeclaration: Fragment = wellKnownName
|
|
33
|
+
? addressConstName === wellKnownName
|
|
34
|
+
? fragment`// Auto-generated. Do not edit.
|
|
35
|
+
// ignore_for_file: type=lint
|
|
36
|
+
|
|
37
|
+
/// The address of the ${fragmentFromString(pascalCase(name))} program.
|
|
38
|
+
export 'package:solana_kit_addresses/solana_kit_addresses.dart' show ${fragmentFromString(wellKnownName)};`
|
|
39
|
+
: fragment`${use(wellKnownName, "solanaAddresses")}
|
|
40
|
+
|
|
41
|
+
// Auto-generated. Do not edit.
|
|
42
|
+
// ignore_for_file: type=lint
|
|
43
|
+
|
|
44
|
+
/// The address of the ${fragmentFromString(pascalCase(name))} program.
|
|
45
|
+
const ${fragmentFromString(addressConstName)} = ${fragmentFromString(wellKnownName)};`
|
|
46
|
+
: fragment`// Auto-generated. Do not edit.
|
|
26
47
|
// ignore_for_file: type=lint
|
|
27
48
|
|
|
28
49
|
${use("Address", "solanaAddresses")}
|
|
29
50
|
|
|
30
51
|
/// The address of the ${fragmentFromString(pascalCase(name))} program.
|
|
31
|
-
const ${fragmentFromString(addressConstName)} = Address('${fragmentFromString(node.publicKey)}')
|
|
32
|
-
|
|
52
|
+
const ${fragmentFromString(addressConstName)} = Address('${fragmentFromString(node.publicKey)}');`;
|
|
53
|
+
|
|
54
|
+
const parts: Fragment[] = [addressDeclaration];
|
|
33
55
|
|
|
34
56
|
// Account identifier enum
|
|
35
57
|
const accounts = node.accounts ?? [];
|
|
@@ -60,4 +82,4 @@ ${fragmentFromString(instrVariants)}
|
|
|
60
82
|
}
|
|
61
83
|
|
|
62
84
|
return mergeFragments(parts, (cs) => cs.join("\n"));
|
|
63
|
-
}
|
|
85
|
+
}
|
|
@@ -125,6 +125,8 @@ function getDataEnumPageFragment(
|
|
|
125
125
|
const variantClasses: string[] = [];
|
|
126
126
|
const encoderVariants: string[] = [];
|
|
127
127
|
const decoderVariants: string[] = [];
|
|
128
|
+
const encodeCases: string[] = [];
|
|
129
|
+
const decodeCases: string[] = [];
|
|
128
130
|
// Collect all field type manifests from variants so we can merge their imports
|
|
129
131
|
const allVariantManifests: { encoder: Fragment; decoder: Fragment; type: Fragment }[] = [];
|
|
130
132
|
|
|
@@ -154,8 +156,10 @@ function getDataEnumPageFragment(
|
|
|
154
156
|
`(${i}, getStructEncoder(<(String, Encoder<Object?>)>[]))`,
|
|
155
157
|
);
|
|
156
158
|
decoderVariants.push(
|
|
157
|
-
`(${i}, getStructDecoder(<(String, Decoder<Object?>)>[])
|
|
159
|
+
`(${i}, transformDecoder<Map<String, Object?>, Map<String, Object?>>(getStructDecoder(<(String, Decoder<Object?>)>[]), (Map<String, Object?> map, Uint8List bytes, int offset) => <String, Object?>{}))`,
|
|
158
160
|
);
|
|
161
|
+
encodeCases.push(`${variantClassName}() => <String, Object?>{'__kind': ${i}},`);
|
|
162
|
+
decodeCases.push(`case ${i}: return const ${variantClassName}();`);
|
|
159
163
|
} else if (variant.kind === "enumStructVariantTypeNode") {
|
|
160
164
|
const resolvedStruct = resolveNestedTypeNode(variant.struct);
|
|
161
165
|
const fields = resolvedStruct.fields;
|
|
@@ -178,23 +182,28 @@ function getDataEnumPageFragment(
|
|
|
178
182
|
const ctorParams = fields
|
|
179
183
|
.map((f: StructFieldTypeNode) => ` required this.${camelCase(f.name as string)},`)
|
|
180
184
|
.join("\n");
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
.
|
|
188
|
-
|
|
185
|
+
const ctorSignature =
|
|
186
|
+
fields.length === 0
|
|
187
|
+
? ` const ${variantClassName}();`
|
|
188
|
+
: ` const ${variantClassName}({\n${ctorParams}\n });`;
|
|
189
|
+
|
|
190
|
+
const eqChecks =
|
|
191
|
+
fields.length === 0
|
|
192
|
+
? "true"
|
|
193
|
+
: fields
|
|
194
|
+
.map((f: StructFieldTypeNode) => `${camelCase(f.name as string)} == other.${camelCase(f.name as string)}`)
|
|
195
|
+
.join(" &&\n ");
|
|
196
|
+
|
|
197
|
+
const hashExpression = getHashExpression(
|
|
198
|
+
fields.map((f: StructFieldTypeNode) => camelCase(f.name as string)),
|
|
199
|
+
);
|
|
189
200
|
|
|
190
201
|
const toStringFields = fields
|
|
191
202
|
.map((f: StructFieldTypeNode) => `${camelCase(f.name as string)}: $${camelCase(f.name as string)}`)
|
|
192
203
|
.join(", ");
|
|
193
204
|
|
|
194
205
|
variantClasses.push(`final class ${variantClassName} extends ${typeName} {
|
|
195
|
-
|
|
196
|
-
${ctorParams}
|
|
197
|
-
});
|
|
206
|
+
${ctorSignature}
|
|
198
207
|
|
|
199
208
|
${fieldDecls}
|
|
200
209
|
|
|
@@ -205,7 +214,7 @@ ${fieldDecls}
|
|
|
205
214
|
${eqChecks};
|
|
206
215
|
|
|
207
216
|
@override
|
|
208
|
-
int get hashCode =>
|
|
217
|
+
int get hashCode => ${hashExpression};
|
|
209
218
|
|
|
210
219
|
@override
|
|
211
220
|
String toString() => '${typeName}.${variantClassName}(${toStringFields})';
|
|
@@ -242,11 +251,23 @@ ${fieldDecls}
|
|
|
242
251
|
.join(", ");
|
|
243
252
|
|
|
244
253
|
encoderVariants.push(
|
|
245
|
-
`(${i},
|
|
254
|
+
`(${i}, getStructEncoder([${encFields}]))`,
|
|
246
255
|
);
|
|
247
256
|
decoderVariants.push(
|
|
248
|
-
`(${i}, transformDecoder(getStructDecoder([${decFields}]), (Map<String, Object?> map, Uint8List bytes, int offset) =>
|
|
257
|
+
`(${i}, transformDecoder<Map<String, Object?>, Map<String, Object?>>(getStructDecoder([${decFields}]), (Map<String, Object?> map, Uint8List bytes, int offset) => map))`,
|
|
249
258
|
);
|
|
259
|
+
const structPattern =
|
|
260
|
+
fields.length === 0
|
|
261
|
+
? ''
|
|
262
|
+
: fields
|
|
263
|
+
.map((f: StructFieldTypeNode) => `${camelCase(f.name as string)}: final ${camelCase(f.name as string)}`)
|
|
264
|
+
.join(', ');
|
|
265
|
+
const structMapEntries =
|
|
266
|
+
fields.length === 0
|
|
267
|
+
? ''
|
|
268
|
+
: `, ${fields.map((f: StructFieldTypeNode)=>`'${f.name as string}': ${camelCase(f.name as string)}`).join(', ')}`;
|
|
269
|
+
encodeCases.push(`${variantClassName}(${structPattern}) => <String, Object?>{'__kind': ${i}${structMapEntries}},`);
|
|
270
|
+
decodeCases.push(`case ${i}: return ${variantClassName}(${fromMapFields.replace(/\n/g, ' ').replace(/,$/, '')});`);
|
|
250
271
|
} else if (variant.kind === "enumTupleVariantTypeNode") {
|
|
251
272
|
const resolvedTuple = resolveNestedTypeNode(variant.tuple);
|
|
252
273
|
const items = resolvedTuple.items;
|
|
@@ -270,11 +291,13 @@ ${fieldDecls}
|
|
|
270
291
|
String toString() => '${typeName}.${variantClassName}(\$value)';
|
|
271
292
|
}`);
|
|
272
293
|
encoderVariants.push(
|
|
273
|
-
`(${i}, transformEncoder
|
|
294
|
+
`(${i}, transformEncoder<${manifest.type.content}, Map<String, Object?>>(${manifest.encoder.content}, (Map<String, Object?> map) => map['value']! as ${manifest.type.content}))`,
|
|
274
295
|
);
|
|
275
296
|
decoderVariants.push(
|
|
276
|
-
`(${i}, transformDecoder(${manifest.decoder.content}, (${manifest.type.content} value, Uint8List bytes, int offset) =>
|
|
297
|
+
`(${i}, transformDecoder<${manifest.type.content}, Map<String, Object?>>(${manifest.decoder.content}, (${manifest.type.content} value, Uint8List bytes, int offset) => <String, Object?>{'value': value}))`,
|
|
277
298
|
);
|
|
299
|
+
encodeCases.push(`${variantClassName}(value: final value) => <String, Object?>{'__kind': ${i}, 'value': value},`);
|
|
300
|
+
decodeCases.push(`case ${i}: return ${variantClassName}(map['value']! as ${manifest.type.content});`);
|
|
278
301
|
}
|
|
279
302
|
}
|
|
280
303
|
}
|
|
@@ -290,7 +313,6 @@ ${fieldDecls}
|
|
|
290
313
|
// ignore_for_file: type=lint
|
|
291
314
|
|
|
292
315
|
${use("Uint8List", "dartTypedData")}
|
|
293
|
-
${use("immutable", "meta")}
|
|
294
316
|
${use("Encoder", "solanaCodecsCore")}
|
|
295
317
|
${use("Decoder", "solanaCodecsCore")}
|
|
296
318
|
${use("Codec", "solanaCodecsCore")}
|
|
@@ -311,15 +333,28 @@ sealed class ${fragmentFromString(typeName)} {
|
|
|
311
333
|
${fragmentFromString(variantClasses.join("\n\n"))}
|
|
312
334
|
|
|
313
335
|
Encoder<${fragmentFromString(typeName)}> ${fragmentFromString(encoderName)}() {
|
|
314
|
-
return
|
|
315
|
-
|
|
316
|
-
|
|
336
|
+
return transformEncoder<Map<String, Object?>, ${fragmentFromString(typeName)}>(
|
|
337
|
+
getDiscriminatedUnionEncoder([
|
|
338
|
+
${fragmentFromString(encoderVariants.join(",\n "))},
|
|
339
|
+
], size: get${fragmentFromString(sizeCodecName)}Encoder()),
|
|
340
|
+
(${fragmentFromString(typeName)} value) => switch (value) {
|
|
341
|
+
${fragmentFromString(encodeCases.join("\n "))}
|
|
342
|
+
},
|
|
343
|
+
);
|
|
317
344
|
}
|
|
318
345
|
|
|
319
346
|
Decoder<${fragmentFromString(typeName)}> ${fragmentFromString(decoderName)}() {
|
|
320
|
-
return
|
|
321
|
-
|
|
322
|
-
|
|
347
|
+
return transformDecoder<Map<String, Object?>, ${fragmentFromString(typeName)}>(
|
|
348
|
+
getDiscriminatedUnionDecoder([
|
|
349
|
+
${fragmentFromString(decoderVariants.join(",\n "))},
|
|
350
|
+
], size: get${fragmentFromString(sizeCodecName)}Decoder()),
|
|
351
|
+
(Map<String, Object?> map, Uint8List bytes, int offset) {
|
|
352
|
+
switch (map['__kind']) {
|
|
353
|
+
${fragmentFromString(decodeCases.join("\n "))}
|
|
354
|
+
}
|
|
355
|
+
throw StateError('Unsupported ${typeName} discriminator: \${map['__kind']}');
|
|
356
|
+
},
|
|
357
|
+
);
|
|
323
358
|
}
|
|
324
359
|
|
|
325
360
|
Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragmentFromString(codecName)}() {
|
|
@@ -365,6 +400,10 @@ function getStructPageFragment(
|
|
|
365
400
|
const ctorParams = fields
|
|
366
401
|
.map((f: StructFieldTypeNode) => ` required this.${camelCase(f.name as string)},`)
|
|
367
402
|
.join("\n");
|
|
403
|
+
const ctorSignature =
|
|
404
|
+
fields.length === 0
|
|
405
|
+
? ` const ${typeName}();`
|
|
406
|
+
: ` const ${typeName}({\n${ctorParams}\n });`;
|
|
368
407
|
|
|
369
408
|
const eqChecks =
|
|
370
409
|
fields.length === 0
|
|
@@ -376,9 +415,12 @@ function getStructPageFragment(
|
|
|
376
415
|
)
|
|
377
416
|
.join(" &&\n ");
|
|
378
417
|
|
|
379
|
-
const hashFields =
|
|
380
|
-
.
|
|
381
|
-
|
|
418
|
+
const hashFields =
|
|
419
|
+
fields.length === 0
|
|
420
|
+
? "runtimeType"
|
|
421
|
+
: fields
|
|
422
|
+
.map((f: StructFieldTypeNode) => camelCase(f.name as string))
|
|
423
|
+
.join(", ");
|
|
382
424
|
|
|
383
425
|
const toStringFields = fields
|
|
384
426
|
.map(
|
|
@@ -436,9 +478,7 @@ ${use("getStructDecoder", "solanaCodecsDataStructures")}
|
|
|
436
478
|
|
|
437
479
|
@immutable
|
|
438
480
|
class ${fragmentFromString(typeName)} {
|
|
439
|
-
|
|
440
|
-
${fragmentFromString(ctorParams)}
|
|
441
|
-
});
|
|
481
|
+
${fragmentFromString(ctorSignature)}
|
|
442
482
|
|
|
443
483
|
${fragmentFromString(fieldDecls)}
|
|
444
484
|
|
|
@@ -450,7 +490,7 @@ ${fragmentFromString(fieldDecls)}
|
|
|
450
490
|
${fragmentFromString(eqChecks)};
|
|
451
491
|
|
|
452
492
|
@override
|
|
453
|
-
int get hashCode =>
|
|
493
|
+
int get hashCode => ${fragmentFromString(getHashExpression(fields.map((f: StructFieldTypeNode) => camelCase(f.name as string))))};
|
|
454
494
|
|
|
455
495
|
@override
|
|
456
496
|
String toString() => '${fragmentFromString(typeName)}(${fragmentFromString(toStringFields)})';
|
|
@@ -534,6 +574,12 @@ Codec<${fragmentFromString(typeName)}, ${fragmentFromString(typeName)}> ${fragme
|
|
|
534
574
|
}`;
|
|
535
575
|
}
|
|
536
576
|
|
|
577
|
+
function getHashExpression(fields: string[]): string {
|
|
578
|
+
if (fields.length === 0) return "runtimeType.hashCode";
|
|
579
|
+
if (fields.length === 1) return `${fields[0]}.hashCode`;
|
|
580
|
+
return `Object.hash(${fields.join(", ")})`;
|
|
581
|
+
}
|
|
582
|
+
|
|
537
583
|
function getNumberCodecName(format: string): string {
|
|
538
584
|
switch (format) {
|
|
539
585
|
case "u8": return "U8";
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps well-known Solana public keys to their canonical Dart constant names
|
|
3
|
+
* in `package:solana_kit_addresses`. When the renderer encounters a known
|
|
4
|
+
* address, it emits a re-export from that package instead of hardcoding the
|
|
5
|
+
* address string.
|
|
6
|
+
*
|
|
7
|
+
* The keys are the base-58 public key strings as they appear in IDL/Codama nodes.
|
|
8
|
+
* The values are the Dart constant names exported by `solana_kit_addresses`.
|
|
9
|
+
*/
|
|
10
|
+
export const WELL_KNOWN_ADDRESSES: ReadonlyMap<string, string> = new Map([
|
|
11
|
+
// Agave / Solana native programs
|
|
12
|
+
['11111111111111111111111111111111', 'systemProgramAddress'],
|
|
13
|
+
['AddressLookupTab1e1111111111111111111111111', 'addressLookupTableProgramAddress'],
|
|
14
|
+
['BPFLoader2111111111111111111111111111111111', 'bpfLoaderProgramAddress'],
|
|
15
|
+
['BPFLoader1111111111111111111111111111111111', 'bpfLoaderDeprecatedProgramAddress'],
|
|
16
|
+
['BPFLoaderUpgradeab1e11111111111111111111111', 'bpfLoaderUpgradeableProgramAddress'],
|
|
17
|
+
['ComputeBudget111111111111111111111111111111', 'computeBudgetProgramAddress'],
|
|
18
|
+
['Config1111111111111111111111111111111111111', 'configProgramAddress'],
|
|
19
|
+
['Ed25519SigVerify111111111111111111111111111', 'ed25519ProgramAddress'],
|
|
20
|
+
['Feature111111111111111111111111111111111111', 'featureProgramAddress'],
|
|
21
|
+
['1nc1nerator11111111111111111111111111111111', 'incineratorAddress'],
|
|
22
|
+
['LoaderV411111111111111111111111111111111111', 'loaderV4ProgramAddress'],
|
|
23
|
+
['NativeLoader1111111111111111111111111111', 'nativeLoaderProgramAddress'],
|
|
24
|
+
['KeccakSecp256k11111111111111111111111111111', 'secp256k1ProgramAddress'],
|
|
25
|
+
['Secp256r1SigVerify1111111111111111111111111', 'secp256r1ProgramAddress'],
|
|
26
|
+
['Stake11111111111111111111111111111111111111', 'stakeProgramAddress'],
|
|
27
|
+
['StakeConfig1111111111111111111111111111', 'stakeConfigAddress'],
|
|
28
|
+
['Vote111111111111111111111111111111111111111', 'voteProgramAddress'],
|
|
29
|
+
['ZkTokenProof1111111111111111111111111111111', 'zkTokenProofProgramAddress'],
|
|
30
|
+
['ZkE1Gama1Proof11111111111111111111111111111', 'zkElgamalProofProgramAddress'],
|
|
31
|
+
|
|
32
|
+
// Sysvars
|
|
33
|
+
['SysvarC1ock11111111111111111111111111111111', 'sysvarClockAddress'],
|
|
34
|
+
['Rent11111111111111111111111111111111111111', 'sysvarRentAddress'],
|
|
35
|
+
['11111111111111111111111111111111111111111111', 'sysvarRecentBlockhashesAddress'], // note: 44 chars
|
|
36
|
+
['SysvarFees1111111111111111111111111111111111', 'sysvarFeesAddress'],
|
|
37
|
+
['Sysvar1nstruct1ons11111111111111111111111111', 'sysvarInstructionsAddress'],
|
|
38
|
+
['SysvarEpochSchedu1e111111111111111111111111', 'sysvarEpochScheduleAddress'],
|
|
39
|
+
['SysvarRewards111111111111111111111111111111', 'sysvarRewardsAddress'],
|
|
40
|
+
['SysvarStakeHistory1111111111111111111111111', 'sysvarStakeHistoryAddress'],
|
|
41
|
+
['SysvarS1otHashes111111111111111111111111111', 'sysvarSlotHashesAddress'],
|
|
42
|
+
['SysvarS1otHistory11111111111111111111111111', 'sysvarSlotHistoryAddress'],
|
|
43
|
+
['Sysvar1111111111111111111111111111111111111', 'sysvarOwnerAddress'],
|
|
44
|
+
|
|
45
|
+
// SPL programs
|
|
46
|
+
['TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', 'tokenProgramAddress'],
|
|
47
|
+
['TokenzQdBNb4qyze1S1U9AHB8MGXmNK1REkTPT5Z3Y', 'token2022ProgramAddress'],
|
|
48
|
+
['ATokenGPvbdGVxr1b2hvZbsiqW5xWH25ef7s3c8BnQKu', 'associatedTokenProgramAddress'],
|
|
49
|
+
['Memo1UhkJRfRhVq1sR7Y7Nto1s3J2mXTT3L6Wk4K2m6', 'memoProgramAddress'],
|
|
50
|
+
['MemoSq4gqABXKKYWVqKkV3m3kb1cTR7F2UMy9r6kFNK', 'memoLegacyProgramAddress'],
|
|
51
|
+
|
|
52
|
+
// Metaplex programs
|
|
53
|
+
['metaqbxxU9qX8rZXkmUJ7haCrXXz2W1PSUq1Rn3E1od', 'tokenMetadataProgramAddress'],
|
|
54
|
+
['BGUMAp9V1mQ2KV8t34L5u3gZsXYZaYDBdKkc1xYk1qA1', 'mplBubblegumProgramAddress'],
|
|
55
|
+
['auth9SigNpDKz4s8cHr3MFBPS1phbRKLSCN3aBe9sS', 'authRulesProgramAddress'],
|
|
56
|
+
['CoREENxT6tW1Ho6BW6W5Z4N41ci5Xz2mmrR2kErmM5TR', 'metaplexCoreProgramAddress'],
|
|
57
|
+
['cmtMqR4bPM9sEV3bPnJQq5s2EnYw4N5vQ8hWhEd1xzS', 'splAccountCompressionProgramAddress'],
|
|
58
|
+
['noopb9skMVngR5Qi4sczwFr1jU4V4F7nm8xzFQK9C', 'noopProgramAddress'],
|
|
59
|
+
|
|
60
|
+
// Well-known mints
|
|
61
|
+
['So11111111111111111111111111111111111111112', 'wrappedSolAddress'],
|
|
62
|
+
['EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', 'usdcAddress'],
|
|
63
|
+
['Es9vMFrzaCERm6YKXx6bqKr1Rmd9b5v3J8rM5e4rfF4', 'usdtAddress'],
|
|
64
|
+
]);
|
|
@@ -89,7 +89,10 @@ export function getRenderMapVisitor(
|
|
|
89
89
|
...node.additionalPrograms.map((p) => visit(p, self)),
|
|
90
90
|
];
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
const mergedProgramMap = mergeRenderMaps(programMaps);
|
|
93
|
+
const categoryMaps = getSharedCategoryIndexMaps(mergedProgramMap);
|
|
94
|
+
|
|
95
|
+
return mergeRenderMaps([mergedProgramMap, ...categoryMaps]);
|
|
93
96
|
},
|
|
94
97
|
|
|
95
98
|
visitProgram(node: ProgramNode, { self }) {
|
|
@@ -151,76 +154,15 @@ export function getRenderMapVisitor(
|
|
|
151
154
|
maps.push(errorMap);
|
|
152
155
|
}
|
|
153
156
|
|
|
154
|
-
//
|
|
157
|
+
// Root barrel
|
|
155
158
|
const categories: string[] = [];
|
|
159
|
+
if (accountFiles.length > 0) categories.push("accounts");
|
|
160
|
+
if (instructionFiles.length > 0) categories.push("instructions");
|
|
161
|
+
if (typeFiles.length > 0) categories.push("types");
|
|
162
|
+
if (pdaFiles.length > 0) categories.push("pdas");
|
|
163
|
+
if (errorFiles.length > 0) categories.push("errors");
|
|
164
|
+
if (programFiles.length > 0) categories.push("programs");
|
|
156
165
|
|
|
157
|
-
if (accountFiles.length > 0) {
|
|
158
|
-
categories.push("accounts");
|
|
159
|
-
let indexMap = createRenderMap<Fragment>();
|
|
160
|
-
indexMap = addToRenderMap(
|
|
161
|
-
indexMap,
|
|
162
|
-
"accounts/accounts.dart",
|
|
163
|
-
getIndexPageFragment(accountFiles),
|
|
164
|
-
);
|
|
165
|
-
maps.push(indexMap);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (instructionFiles.length > 0) {
|
|
169
|
-
categories.push("instructions");
|
|
170
|
-
let indexMap = createRenderMap<Fragment>();
|
|
171
|
-
indexMap = addToRenderMap(
|
|
172
|
-
indexMap,
|
|
173
|
-
"instructions/instructions.dart",
|
|
174
|
-
getIndexPageFragment(instructionFiles),
|
|
175
|
-
);
|
|
176
|
-
maps.push(indexMap);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (typeFiles.length > 0) {
|
|
180
|
-
categories.push("types");
|
|
181
|
-
let indexMap = createRenderMap<Fragment>();
|
|
182
|
-
indexMap = addToRenderMap(
|
|
183
|
-
indexMap,
|
|
184
|
-
"types/types.dart",
|
|
185
|
-
getIndexPageFragment(typeFiles),
|
|
186
|
-
);
|
|
187
|
-
maps.push(indexMap);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (pdaFiles.length > 0) {
|
|
191
|
-
categories.push("pdas");
|
|
192
|
-
let indexMap = createRenderMap<Fragment>();
|
|
193
|
-
indexMap = addToRenderMap(
|
|
194
|
-
indexMap,
|
|
195
|
-
"pdas/pdas.dart",
|
|
196
|
-
getIndexPageFragment(pdaFiles),
|
|
197
|
-
);
|
|
198
|
-
maps.push(indexMap);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (errorFiles.length > 0) {
|
|
202
|
-
categories.push("errors");
|
|
203
|
-
let indexMap = createRenderMap<Fragment>();
|
|
204
|
-
indexMap = addToRenderMap(
|
|
205
|
-
indexMap,
|
|
206
|
-
"errors/errors.dart",
|
|
207
|
-
getIndexPageFragment(errorFiles),
|
|
208
|
-
);
|
|
209
|
-
maps.push(indexMap);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (programFiles.length > 0) {
|
|
213
|
-
categories.push("programs");
|
|
214
|
-
let indexMap = createRenderMap<Fragment>();
|
|
215
|
-
indexMap = addToRenderMap(
|
|
216
|
-
indexMap,
|
|
217
|
-
"programs/programs.dart",
|
|
218
|
-
getIndexPageFragment(programFiles),
|
|
219
|
-
);
|
|
220
|
-
maps.push(indexMap);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Root barrel
|
|
224
166
|
if (categories.length > 0) {
|
|
225
167
|
let rootMap = createRenderMap<Fragment>();
|
|
226
168
|
rootMap = addToRenderMap(
|
|
@@ -270,3 +212,46 @@ export function getRenderMapVisitor(
|
|
|
270
212
|
(v) => recordLinkablesOnFirstVisitVisitor(v, linkables),
|
|
271
213
|
);
|
|
272
214
|
}
|
|
215
|
+
|
|
216
|
+
function getSharedCategoryIndexMaps(
|
|
217
|
+
renderMap: RenderMap<Fragment>,
|
|
218
|
+
): RenderMap<Fragment>[] {
|
|
219
|
+
const categories = new Map<string, Set<string>>();
|
|
220
|
+
const categoryIndexNames: Record<string, string> = {
|
|
221
|
+
accounts: "accounts.dart",
|
|
222
|
+
instructions: "instructions.dart",
|
|
223
|
+
types: "types.dart",
|
|
224
|
+
pdas: "pdas.dart",
|
|
225
|
+
errors: "errors.dart",
|
|
226
|
+
programs: "programs.dart",
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
for (const renderPath of renderMap.keys()) {
|
|
230
|
+
const match = renderPath.match(
|
|
231
|
+
/^(accounts|instructions|types|pdas|errors|programs)\/([^/]+\.dart)$/,
|
|
232
|
+
);
|
|
233
|
+
if (!match) continue;
|
|
234
|
+
|
|
235
|
+
const [, category, fileName] = match;
|
|
236
|
+
if (fileName === categoryIndexNames[category]) continue;
|
|
237
|
+
|
|
238
|
+
if (!categories.has(category)) {
|
|
239
|
+
categories.set(category, new Set());
|
|
240
|
+
}
|
|
241
|
+
categories.get(category)!.add(fileName);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const maps: RenderMap<Fragment>[] = [];
|
|
245
|
+
for (const [category, files] of categories.entries()) {
|
|
246
|
+
const sortedFiles = [...files].sort();
|
|
247
|
+
let map = createRenderMap<Fragment>();
|
|
248
|
+
map = addToRenderMap(
|
|
249
|
+
map,
|
|
250
|
+
`${category}/${categoryIndexNames[category]}`,
|
|
251
|
+
getIndexPageFragment(sortedFiles),
|
|
252
|
+
);
|
|
253
|
+
maps.push(map);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return maps;
|
|
257
|
+
}
|
|
@@ -417,6 +417,7 @@ export function getTypeManifestVisitor(input: {
|
|
|
417
417
|
|
|
418
418
|
visitOptionType(node: OptionTypeNode, { self }) {
|
|
419
419
|
const itemManifest = visit(node.item, self);
|
|
420
|
+
const itemTypeStr = itemManifest.type.content;
|
|
420
421
|
const resolvedPrefix = resolveNestedTypeNode(node.prefix);
|
|
421
422
|
const prefixExpr = resolvedPrefix.format !== "u8"
|
|
422
423
|
? `, prefix: ${getNumberCodecExpression(resolvedPrefix.format, "Encoder")}`
|
|
@@ -424,17 +425,20 @@ export function getTypeManifestVisitor(input: {
|
|
|
424
425
|
const prefixDecoderExpr = resolvedPrefix.format !== "u8"
|
|
425
426
|
? `, prefix: ${getNumberCodecExpression(resolvedPrefix.format, "Decoder")}`
|
|
426
427
|
: "";
|
|
428
|
+
const noneValueExpr = node.fixed
|
|
429
|
+
? ", noneValue: const ZeroesNoneValue()"
|
|
430
|
+
: "";
|
|
427
431
|
|
|
428
432
|
return {
|
|
429
433
|
type: fragment`${itemManifest.type}?`,
|
|
430
434
|
encoder: fragment`${use(
|
|
431
435
|
"getNullableEncoder",
|
|
432
436
|
"solanaCodecsDataStructures",
|
|
433
|
-
)}(${itemManifest.encoder}${fragmentFromString(prefixExpr)})`,
|
|
437
|
+
)}<${fragmentFromString(itemTypeStr)}>(${itemManifest.encoder}${fragmentFromString(prefixExpr)}${fragmentFromString(noneValueExpr)})`,
|
|
434
438
|
decoder: fragment`${use(
|
|
435
439
|
"getNullableDecoder",
|
|
436
440
|
"solanaCodecsDataStructures",
|
|
437
|
-
)}(${itemManifest.decoder}${fragmentFromString(prefixDecoderExpr)})`,
|
|
441
|
+
)}<${fragmentFromString(itemTypeStr)}>(${itemManifest.decoder}${fragmentFromString(prefixDecoderExpr)}${fragmentFromString(noneValueExpr)})`,
|
|
438
442
|
value: emptyTypeManifest().value,
|
|
439
443
|
isEnum: false,
|
|
440
444
|
};
|
|
@@ -445,17 +449,18 @@ export function getTypeManifestVisitor(input: {
|
|
|
445
449
|
{ self },
|
|
446
450
|
) {
|
|
447
451
|
const itemManifest = visit(node.item, self);
|
|
452
|
+
const itemTypeStr = itemManifest.type.content;
|
|
448
453
|
|
|
449
454
|
return {
|
|
450
455
|
type: fragment`${itemManifest.type}?`,
|
|
451
456
|
encoder: fragment`${use(
|
|
452
457
|
"getNullableEncoder",
|
|
453
458
|
"solanaCodecsDataStructures",
|
|
454
|
-
)}(${itemManifest.encoder}, hasPrefix: false)`,
|
|
459
|
+
)}<${fragmentFromString(itemTypeStr)}>(${itemManifest.encoder}, hasPrefix: false)`,
|
|
455
460
|
decoder: fragment`${use(
|
|
456
461
|
"getNullableDecoder",
|
|
457
462
|
"solanaCodecsDataStructures",
|
|
458
|
-
)}(${itemManifest.decoder}, hasPrefix: false)`,
|
|
463
|
+
)}<${fragmentFromString(itemTypeStr)}>(${itemManifest.decoder}, hasPrefix: false)`,
|
|
459
464
|
value: emptyTypeManifest().value,
|
|
460
465
|
isEnum: false,
|
|
461
466
|
};
|
|
@@ -466,17 +471,18 @@ export function getTypeManifestVisitor(input: {
|
|
|
466
471
|
{ self },
|
|
467
472
|
) {
|
|
468
473
|
const itemManifest = visit(node.item, self);
|
|
474
|
+
const itemTypeStr = itemManifest.type.content;
|
|
469
475
|
|
|
470
476
|
return {
|
|
471
477
|
type: fragment`${itemManifest.type}?`,
|
|
472
478
|
encoder: fragment`${use(
|
|
473
479
|
"getNullableEncoder",
|
|
474
480
|
"solanaCodecsDataStructures",
|
|
475
|
-
)}(${itemManifest.encoder}, hasPrefix: false, noneValue: const ZeroesNoneValue())`,
|
|
481
|
+
)}<${fragmentFromString(itemTypeStr)}>(${itemManifest.encoder}, hasPrefix: false, noneValue: const ZeroesNoneValue())`,
|
|
476
482
|
decoder: fragment`${use(
|
|
477
483
|
"getNullableDecoder",
|
|
478
484
|
"solanaCodecsDataStructures",
|
|
479
|
-
)}(${itemManifest.decoder}, hasPrefix: false, noneValue: const ZeroesNoneValue())`,
|
|
485
|
+
)}<${fragmentFromString(itemTypeStr)}>(${itemManifest.decoder}, hasPrefix: false, noneValue: const ZeroesNoneValue())`,
|
|
480
486
|
value: emptyTypeManifest().value,
|
|
481
487
|
isEnum: false,
|
|
482
488
|
};
|
|
@@ -502,7 +508,26 @@ export function getTypeManifestVisitor(input: {
|
|
|
502
508
|
|
|
503
509
|
visitSizePrefixType(node: SizePrefixTypeNode, { self }) {
|
|
504
510
|
const innerManifest = visit(node.type, self);
|
|
505
|
-
|
|
511
|
+
|
|
512
|
+
// The Dart addEncoderSizePrefix / addDecoderSizePrefix APIs require
|
|
513
|
+
// Encoder<num> / Decoder<num> for the prefix. BigInt-width number types
|
|
514
|
+
// (u64, u128, i64, i128) return Encoder<BigInt> which is incompatible.
|
|
515
|
+
// When the IDL specifies such a prefix, substitute u32 which is wide
|
|
516
|
+
// enough for any realistic size prefix and satisfies the num constraint.
|
|
517
|
+
const bigIntFormats = new Set(["u64", "u128", "i64", "i128"]);
|
|
518
|
+
let prefixManifest;
|
|
519
|
+
if (
|
|
520
|
+
node.prefix.kind === "numberTypeNode" &&
|
|
521
|
+
bigIntFormats.has((node.prefix as any).format)
|
|
522
|
+
) {
|
|
523
|
+
// Use u32 encoder/decoder instead of the oversized type.
|
|
524
|
+
prefixManifest = {
|
|
525
|
+
encoder: fragment`${use("getU32Encoder", "solanaCodecsNumbers")}()`,
|
|
526
|
+
decoder: fragment`${use("getU32Decoder", "solanaCodecsNumbers")}()`,
|
|
527
|
+
};
|
|
528
|
+
} else {
|
|
529
|
+
prefixManifest = visit(node.prefix, self);
|
|
530
|
+
}
|
|
506
531
|
|
|
507
532
|
return {
|
|
508
533
|
type: innerManifest.type,
|
|
@@ -521,7 +546,7 @@ export function getTypeManifestVisitor(input: {
|
|
|
521
546
|
|
|
522
547
|
visitHiddenPrefixType(node: HiddenPrefixTypeNode, { self }) {
|
|
523
548
|
const innerManifest = visit(node.type, self);
|
|
524
|
-
const prefixes = node.prefix.map((p) =>
|
|
549
|
+
const prefixes = node.prefix.map((p) => getHiddenAffixManifest(p, self));
|
|
525
550
|
const prefixEncoders = prefixes
|
|
526
551
|
.map((p) => p.encoder.content)
|
|
527
552
|
.join(", ");
|
|
@@ -556,7 +581,7 @@ export function getTypeManifestVisitor(input: {
|
|
|
556
581
|
|
|
557
582
|
visitHiddenSuffixType(node: HiddenSuffixTypeNode, { self }) {
|
|
558
583
|
const innerManifest = visit(node.type, self);
|
|
559
|
-
const suffixes = node.suffix.map((s) =>
|
|
584
|
+
const suffixes = node.suffix.map((s) => getHiddenAffixManifest(s, self));
|
|
560
585
|
const suffixEncoders = suffixes
|
|
561
586
|
.map((s) => s.encoder.content)
|
|
562
587
|
.join(", ");
|
|
@@ -792,3 +817,55 @@ function getSetSizeExpression(node: SetTypeNode): string {
|
|
|
792
817
|
return "";
|
|
793
818
|
}
|
|
794
819
|
}
|
|
820
|
+
|
|
821
|
+
type HiddenAffixManifest = {
|
|
822
|
+
encoder: Fragment;
|
|
823
|
+
decoder: Fragment;
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
function getHiddenAffixManifest(
|
|
827
|
+
node: any,
|
|
828
|
+
self: Visitor<TypeManifest>,
|
|
829
|
+
): HiddenAffixManifest {
|
|
830
|
+
if (node?.kind !== "constantValueNode") {
|
|
831
|
+
const manifest = visit(node, self);
|
|
832
|
+
return { encoder: manifest.encoder, decoder: manifest.decoder };
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const constantBytes = getConstantBytesExpression(node);
|
|
836
|
+
return {
|
|
837
|
+
encoder: fragment`${use("getConstantEncoder", "solanaCodecsDataStructures")}(${constantBytes})`,
|
|
838
|
+
decoder: fragment`${use("getConstantDecoder", "solanaCodecsDataStructures")}(${constantBytes})`,
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function getConstantBytesExpression(node: any): Fragment {
|
|
843
|
+
const valueNode = node.value;
|
|
844
|
+
const typeNode = node.type;
|
|
845
|
+
|
|
846
|
+
if (valueNode?.kind !== "numberValueNode") {
|
|
847
|
+
throw new Error(
|
|
848
|
+
`Unsupported hidden affix constant value kind: ${valueNode?.kind}`,
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const value = Number(valueNode.number);
|
|
853
|
+
|
|
854
|
+
if (typeNode?.kind === "numberTypeNode") {
|
|
855
|
+
return fragment`${use(`get${getNumberCodecName(typeNode.format)}Encoder`, "solanaCodecsNumbers")}().encode(${value})`;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (typeNode?.kind === "preOffsetTypeNode") {
|
|
859
|
+
const inner = typeNode.type;
|
|
860
|
+
if (inner?.kind !== "numberTypeNode") {
|
|
861
|
+
throw new Error(
|
|
862
|
+
`Unsupported hidden affix pre-offset inner type kind: ${inner?.kind}`,
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
return fragment`${use("padLeftEncoder", "solanaCodecsCore")}(${use(`get${getNumberCodecName(inner.format)}Encoder`, "solanaCodecsNumbers")}(), ${typeNode.offset}).encode(${value})`;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
throw new Error(
|
|
869
|
+
`Unsupported hidden affix constant type kind: ${typeNode?.kind}`,
|
|
870
|
+
);
|
|
871
|
+
}
|