gen-typescript-from-tolk-dev 0.1.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/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # Tolk-to-TypeScript wrappers generator (alpha)
2
+
3
+ Currently, it uses a patched LSP to parse and Tolk source code (for MVP). Later, the compiler will be able
4
+ to emit ABI of a contract, and LSP won't be used in this process.
5
+
6
+ ### Main CLI
7
+
8
+ The main entrypoint accepts contract ABI as a JSON string and prints the generated TypeScript wrapper to stdout:
9
+
10
+ ```bash
11
+ npx gen-typescript-from-tolk '{"contractName":"MyContract", ... }'
12
+ ```
13
+
14
+ This package no longer bundles a Tolk compiler. It only supports `ABI JSON -> TypeScript wrapper`.
15
+
16
+
17
+ ### Configuring TypeScript output
18
+
19
+ 1. All structs/aliases/enums are generated to the output — it's unconfigurable currently.
20
+ If a struct can't be serialized (contains `int` for example, not int32/uint64),
21
+ its `fromSlice` and `store` just contain `throw new Error` with a description.
22
+
23
+ 2. Default values of all structs are preserved:
24
+ ```
25
+ struct WalletStorage {
26
+ jettonBalance: coins = 0 // <----
27
+ ownerAddress: address
28
+ minterAddress: address
29
+ }
30
+ ```
31
+
32
+ Then, `jettonBalance` key in `WalletStorage.create()` will be optional. If it's a storage or a message, it will also be optional in `fromStorage` and `sendXXX`.
33
+
34
+ 3. If your storage change shape after initialization (the case of NFT), you can specify an "uninited storage" shape:
35
+ ```
36
+ type NOT_INITIALIZED_STORAGE = StorageStructNotInitialized
37
+ ```
38
+
39
+ Then, `fromStorage` will accept fields from this struct, not from `STORAGE`.
40
+
41
+
42
+ ### Known issues
43
+
44
+ 1. Generics behave incorrectly when `T` is used inside unions (because generics are not monomorphic, they are true TypeScript generics). For example, you have `struct A<T> { v: T | int8 }` and instantiate `A<int2 | int4>`, then an actual prefix tree will be '00/01/10', but a TS wrapper serializes '0+T/1'; or, for `A<int8>` actually will be no union (`int8 | int8` = `int8`), but a TS wrapper will still serialize as '0+int8/1'. Similar corner cases appear in get methods that work via the stack. For example, `struct MyNullable<T> { v: T? }`, instantiated as `MyNullable<Point>` has no information about stack layout and type-id.
45
+ 2. Non-standard keys for maps are unsupported, like `map<Point, ...>`. Only intN/uintN/address are supported (99% use cases). Others can not be represented via `@ton/core` library at all.
46
+ 3. Custom serializers for generic structs `fun Some<T>.packToBuilder` not supported (works perfectly for non-generic structs and aliases, but not for generic).
47
+ 4. Default values for struct fields, if a field contains a union, not supported.
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+
3
+ const path = require('path');
4
+ require('ts-node').register({
5
+ transpileOnly: true,
6
+ project: path.join(__dirname, '..', 'tsconfig.json'),
7
+ });
8
+
9
+ require('../src/cli-generate-from-abi-json')
10
+ .runGeneratorFromAbiCli(process.argv.slice(2))
11
+ .catch((error) => {
12
+ const message = error instanceof Error ? error.message : String(error);
13
+ process.stderr.write(`${message}\n`);
14
+ process.exitCode = 1;
15
+ });
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "gen-typescript-from-tolk-dev",
3
+ "version": "0.1.0",
4
+ "author": "TON Core",
5
+ "files": [
6
+ "README.md",
7
+ "bin",
8
+ "src",
9
+ "tsconfig.json"
10
+ ],
11
+ "bin": {
12
+ "gen-typescript-from-tolk": "./bin/generator.js"
13
+ },
14
+ "scripts": {
15
+ "build:standalone": "node scripts/build-standalone-cli.js",
16
+ "test": "jest --verbose"
17
+ },
18
+ "dependencies": {
19
+ "@ton/core": "~0",
20
+ "ts-node": "^10.9.2",
21
+ "typescript": "^5.9.2"
22
+ },
23
+ "devDependencies": {
24
+ "@ton/crypto": "^3.3.0",
25
+ "@ton/sandbox": ">=0.37.0",
26
+ "@ton/ton": ">=15.3.1 <16.0.0",
27
+ "@types/jest": "^30.0.0",
28
+ "@types/node": "^22.17.2",
29
+ "jest": "^30.0.5",
30
+ "ts-jest": "^29.4.1"
31
+ }
32
+ }
@@ -0,0 +1,157 @@
1
+ /*
2
+ ABI Types — describes the type system and symbols definitions in the Tolk language.
3
+ The goal is to have a full bidirectional mapping from any Tolk struct to ABI:
4
+ all tricky types like generics, aliases, inline unions, etc. should be supported.
5
+ Then, any input could be packed to a cell and unpacked back — exactly as it's done by the compiler.
6
+ */
7
+
8
+ // Since ABI should be JSON-serialized, all 'bigint' numbers are stored as strings.
9
+ type bigint_as_string = string
10
+
11
+ // Ty is an "ABI Type" that fully reflects the type system in Tolk.
12
+ export type Ty =
13
+ | { kind: 'int' }
14
+ | { kind: 'intN'; n: number }
15
+ | { kind: 'uintN'; n: number }
16
+ | { kind: 'varintN', n: number }
17
+ | { kind: 'varuintN', n: number }
18
+ | { kind: 'coins' }
19
+ | { kind: 'bool' }
20
+ | { kind: 'cell' }
21
+ | { kind: 'builder' }
22
+ | { kind: 'slice' }
23
+ | { kind: 'string' }
24
+ | { kind: 'remaining' }
25
+ | { kind: 'address' }
26
+ | { kind: 'addressOpt' }
27
+ | { kind: 'addressExt' }
28
+ | { kind: 'addressAny' }
29
+ | { kind: 'bitsN'; n: number }
30
+ | { kind: 'nullLiteral' }
31
+ | { kind: 'callable' }
32
+ | { kind: 'void' }
33
+ | { kind: 'unknown' }
34
+ | { kind: 'nullable'; inner: Ty; stackTypeId?: number; stackWidth?: number }
35
+ | { kind: 'cellOf'; inner: Ty }
36
+ | { kind: 'arrayOf'; inner: Ty }
37
+ | { kind: 'lispListOf'; inner: Ty }
38
+ | { kind: 'tensor'; items: Ty[] }
39
+ | { kind: 'shapedTuple'; items: Ty[] }
40
+ | { kind: 'mapKV'; k: Ty; v: Ty }
41
+ | { kind: 'EnumRef'; enumName: string }
42
+ | { kind: 'StructRef'; structName: string; typeArgs?: Ty[] }
43
+ | { kind: 'AliasRef'; aliasName: string; typeArgs?: Ty[] }
44
+ | { kind: 'genericT'; nameT: string }
45
+ | { kind: 'union'; variants: UnionVariant[]; stackWidth: number }
46
+
47
+ // UnionVariant exists for every `T_i` in a union type `T1 | T2 | ...`.
48
+ // For binary serialization, a union should have a prefix tree,
49
+ // which is either defined explicitly with struct prefixes: `struct (0x12345678) CounterIncrement`,
50
+ // or auto-generated (implicit), e.g. `int8 | int16 | int32` is serialized as '00'+int8 / '01'+int16 / '10'+int32.
51
+ export interface UnionVariant {
52
+ variantTy: Ty
53
+ prefixStr: string
54
+ prefixLen: number
55
+ isPrefixImplicit?: boolean
56
+ stackTypeId: number
57
+ stackWidth: number
58
+ }
59
+
60
+ // ABIConstExpression is "an expression" that occurs in fields defaults.
61
+ // Example:
62
+ // > struct Demo {
63
+ // > initial: int8 = 123
64
+ // > owner: address = address("UQ...")
65
+ // > }
66
+ // Then both fields have a default value:
67
+ // initial: { kind: 'int', v: '123' }
68
+ // owner: { kind: 'address', addr: 'UQ...' }
69
+ export type ABIConstExpression =
70
+ | { kind: 'int'; v: bigint_as_string }
71
+ | { kind: 'bool'; v: boolean }
72
+ | { kind: 'slice'; hex: string }
73
+ | { kind: 'string'; str: string }
74
+ | { kind: 'address'; addr: string }
75
+ | { kind: 'tensor'; items: ABIConstExpression[] }
76
+ | { kind: 'shapedTuple'; items: ABIConstExpression[] }
77
+ | { kind: 'object'; structName: string; fields: ABIConstExpression[] }
78
+ | { kind: 'castTo'; inner: ABIConstExpression; castTo: Ty }
79
+ | { kind: 'null' }
80
+
81
+ // ABICustomSerializers is present in a struct/alias if that type has custom serializers in Tolk code:
82
+ //
83
+ // fun SomeType.packToBuilder(self, mutate b: builder) { ... }
84
+ // fun SomeType.unpackFromSlice(mutate s: slice): SomeAlias { ... }
85
+ //
86
+ // Body of these functions is not a part of ABI (it's arbitrary Tolk code).
87
+ // To make serialization work (e.g., in TypeScript wrappers),
88
+ // one should provide equivalent implementations in a language ABI is applied to.
89
+ export interface ABICustomSerializers {
90
+ packToBuilder: boolean
91
+ unpackFromSlice: boolean
92
+ }
93
+
94
+ // ABIStruct represents a Tolk struct.
95
+ // Examples:
96
+ //
97
+ // > struct Point { x: int, y: int }
98
+ // A simple struct, not serializable (because 'int', not 'int8' or similar)
99
+ //
100
+ // > struct Wrapper<TItem> { item: TItem }
101
+ // A generic struct (has typeParams), fields[0].ty = { kind: 'genericT', nameT: 'TItem' }
102
+ //
103
+ // > struct (0x12345678) Increment { ... }
104
+ // Has a serialization prefix: prefixStr = '0x12345678', prefixLen = 32
105
+ export interface ABIStruct {
106
+ kind: 'Struct'
107
+ name: string
108
+ typeParams?: string[]
109
+ prefix?: {
110
+ prefixStr: string
111
+ prefixLen: number
112
+ }
113
+ fields: {
114
+ name: string
115
+ ty: Ty
116
+ defaultValue?: ABIConstExpression
117
+ description?: string
118
+ }[]
119
+ customPackUnpack?: ABICustomSerializers
120
+ }
121
+
122
+ // ABIAlias represents a Tolk type alias.
123
+ // Examples:
124
+ //
125
+ // > type UserId = int32
126
+ // A simple alias, targetTy = { kind: 'intN', n: 32 }
127
+ //
128
+ // > type Maybe<T> = MaybeNothing | MaybeJust<T>
129
+ // A generic alias (has typeParams), targetTy = { kind: 'union', variants: ... }
130
+ //
131
+ // An alias is serialized as its target, unless it has custom serializers in Tolk code.
132
+ export interface ABIAlias {
133
+ kind: 'Alias'
134
+ name: string
135
+ targetTy: Ty
136
+ typeParams?: string[]
137
+ customPackUnpack?: ABICustomSerializers
138
+ }
139
+
140
+ // ABIEnum represents a Tolk enum.
141
+ // Examples:
142
+ //
143
+ // > enum Color { Red, Green, Blue }
144
+ // Has 3 members (values '0', '1', '2'), encoded as 'uint2' (auto-calculated by the compiler).
145
+ //
146
+ // > enum Mode: int8 { User = 0, Admin = 127 }
147
+ // Has 2 members, encoded as 'int8' (specified manually).
148
+ export interface ABIEnum {
149
+ kind: 'Enum'
150
+ name: string
151
+ encodedAs: Ty
152
+ members: {
153
+ name: string
154
+ value: bigint_as_string
155
+ }[]
156
+ customPackUnpack?: ABICustomSerializers
157
+ }
package/src/abi.ts ADDED
@@ -0,0 +1,132 @@
1
+ /*
2
+ ABI of a contract describes how the contract is "seen" by external observers:
3
+ its get methods, incoming messages, etc.
4
+ Given an ABI, it becomes possible to
5
+ - post messages from a client, since ABI provides sufficient info for serialization
6
+ - generate wrappers for TypeScript and other languages
7
+ - render its storage in the explorer
8
+ - create a UI to interact with a contract from Web/IDE
9
+ - etc.
10
+ The ABI is emitted by the Tolk compiler, along with bytecode.
11
+ */
12
+
13
+ import { Ty, ABIStruct, ABIAlias, ABIEnum, ABIConstExpression } from './abi-types'
14
+
15
+ // ABIGetMethod is a "get method" (aka "contract getter").
16
+ // In Tolk code, getters are created with `get fun`.
17
+ // Example:
18
+ // > get fun calcData(owner: address): SomeStruct { ... }
19
+ // It has one parameter with ty = { kind: 'address' },
20
+ // its returnTy is { kind: 'StructRef', structName: 'SomeStruct' }.
21
+ // Note, that getters are called off-chain — via the TVM stack, not via serialization.
22
+ // (For instance, they can return 'int' or 'slice', although they are not serializable)
23
+ export interface ABIGetMethod {
24
+ tvmMethodId: number
25
+ name: string
26
+ parameters: {
27
+ name: string
28
+ ty: Ty
29
+ description?: string
30
+ defaultValue?: ABIConstExpression
31
+ }[]
32
+ returnTy: Ty
33
+ description?: string
34
+ }
35
+
36
+ // ABIInternalMessage is "an incoming internal message" (handled by `onInternalMessage`).
37
+ // In practice, a user describes each message as a struct:
38
+ // > struct (0x12345678) Increment { ... }
39
+ // > struct (0x23456789) Reset { ... }
40
+ // Then, ABI of a contract will contain those two messages:
41
+ // * bodyTy = { kind: 'StructRef', structName: 'Increment' }
42
+ // * bodyTy = { kind: 'StructRef', structName: 'Reset' }
43
+ // Theoretically, bodyTy can be something else: e.g., instantiation `Transfer<ForwardPayload>`.
44
+ // Description, minimalMsgValue, and other properties may be set with an `@abi` attribute:
45
+ // > @abi(...)
46
+ // > struct (0x12345678) Increment { ... }
47
+ export interface ABIInternalMessage {
48
+ bodyTy: Ty
49
+ description?: string
50
+ minimalMsgValue?: number
51
+ preferredSendMode?: number
52
+ }
53
+
54
+ // ABIExternalMessage is "an incoming external message" (handled by `onExternalMessage`).
55
+ // It's either a 'slice' or some struct.
56
+ export interface ABIExternalMessage {
57
+ bodyTy: Ty
58
+ description?: string
59
+ }
60
+
61
+ // ABIOutgoingMessage is "an outgoing internal/external message".
62
+ // In Tolk code, those are calls to `createMessage`.
63
+ export interface ABIOutgoingMessage {
64
+ bodyTy: Ty
65
+ description?: string
66
+ // todo need BounceMode? if yes, what if 2 messages of type T sent with different modes?
67
+ }
68
+
69
+ // ABIStorage defines shape of a storage.
70
+ // Most often, it's a regular struct, serializable into a cell.
71
+ // In the case of NFT, when a storage changes its shape (several fields appear after deployment),
72
+ // the "initial storage" can also be expressed: it's called "storage at deployment".
73
+ // The storage is used to visualize current contract state and to calculate its address.
74
+ export interface ABIStorage {
75
+ storageTy?: Ty
76
+ storageAtDeploymentTy?: Ty
77
+ }
78
+
79
+ // ABIThrownError is an errCode fired by `throw` or `assert` in Tolk code.
80
+ export interface ABIThrownError {
81
+ kind: 'plainInt' | 'constant' | 'enumMember'
82
+ name?: string
83
+ errCode: number
84
+ }
85
+
86
+ // ContractABI is a final result — the ABI of a TON smart contract.
87
+ //
88
+ // Partially, its properties may be specified by a user manually:
89
+ // > contract MyName {
90
+ // > author: "Dima"
91
+ // > incomingMessages: SomeUnion
92
+ // > }
93
+ //
94
+ // Partially, its properties are automatically calculated by the compiler:
95
+ // - outgoingMessages, via the calls to `createMessage`
96
+ // - thrownErrors, via `throw` and `assert` statements
97
+ // - getMethods, essentially `get fun`
98
+ //
99
+ // Auto-calculated properties may be manually enriched via the `@abi` annotation:
100
+ // > @abi({
101
+ // > description: "...",
102
+ // > minimalMsgValue: ton("0.05"),
103
+ // > })
104
+ // > struct SomeOutgoingMessage { ... }
105
+ //
106
+ // todo opened questions:
107
+ // - clientTy for struct fields
108
+ // - potentially missing struct fields, especially for describing FunC
109
+ // - union types are not supported in get methods now, they have complicated stack layout
110
+ // - TEPs and namespaces?
111
+ export interface ContractABI {
112
+ contractName: string
113
+ author?: string
114
+ version?: string
115
+ description?: string
116
+
117
+ declarations: (ABIStruct | ABIAlias | ABIEnum)[]
118
+
119
+ storage: ABIStorage
120
+ incomingMessages: ABIInternalMessage[]
121
+ incomingExternal: ABIExternalMessage[]
122
+ outgoingMessages: ABIOutgoingMessage[]
123
+ emittedEvents: ABIOutgoingMessage[]
124
+ getMethods: ABIGetMethod[]
125
+ thrownErrors: ABIThrownError[]
126
+
127
+ compilerName: string
128
+ compilerVersion: string
129
+
130
+ // todo is it a part of ABI? the same question for source maps in the future
131
+ codeBoc64: string
132
+ }
@@ -0,0 +1,21 @@
1
+ import { generateTypeScriptFromAbiJson } from './generate-from-abi-json';
2
+
3
+ const HELP_TEXT = `Usage:
4
+ generator '<contract-abi-json>'
5
+
6
+ Prints the generated TypeScript wrapper to stdout.`;
7
+
8
+ function parseCliArgs(argv: string[]): string {
9
+ if (argv.length !== 1) {
10
+ throw new Error(`Expected a single positional ABI JSON argument.\n\n${HELP_TEXT}`);
11
+ }
12
+ const abiJson = argv[0];
13
+ if (abiJson.length === 0) {
14
+ throw new Error(`ABI JSON argument is required.\n\n${HELP_TEXT}`);
15
+ }
16
+ return abiJson;
17
+ }
18
+
19
+ export async function runGeneratorFromAbiCli(argv: string[]): Promise<void> {
20
+ process.stdout.write(generateTypeScriptFromAbiJson(parseCliArgs(argv)));
21
+ }
@@ -0,0 +1,115 @@
1
+ import type { Ty, ABIStruct, ABIAlias, ABIEnum } from './abi-types';
2
+ import { SymbolNotFound } from './unsupported-errors';
3
+
4
+ // Functions implemented in an output template, used to detect name shadowing.
5
+ export const RUNTIME = {
6
+ beginCell: 'beginCell',
7
+ lookupPrefix: 'lookupPrefix',
8
+ lookupPrefixAndEat: 'lookupPrefixAndEat',
9
+ throwNonePrefixMatch: 'throwNonePrefixMatch',
10
+ loadAndCheckPrefix32: 'loadAndCheckPrefix32',
11
+ loadAndCheckPrefix: 'loadAndCheckPrefix',
12
+ loadTolkBitsN: 'loadTolkBitsN',
13
+ storeTolkBitsN: 'storeTolkBitsN',
14
+ loadTolkAddressAny: 'loadTolkAddressAny',
15
+ storeTolkAddressAny: 'storeTolkAddressAny',
16
+ loadTolkRemaining: 'loadTolkRemaining',
17
+ storeTolkRemaining: 'storeTolkRemaining',
18
+ loadCellRef: 'loadCellRef',
19
+ storeCellRef: 'storeCellRef',
20
+ loadArrayOf: 'loadArrayOf',
21
+ storeArrayOf: 'storeArrayOf',
22
+ loadLispListOf: 'loadLispListOf',
23
+ storeLispListOf: 'storeLispListOf',
24
+ storeTolkNullable: 'storeTolkNullable',
25
+ makeCellFrom: 'makeCellFrom',
26
+ createDictionaryValue: 'createDictionaryValue',
27
+ registerCustomPackUnpack: 'registerCustomPackUnpack',
28
+ ExtraSendOptions: 'ExtraSendOptions',
29
+ StackReader: 'StackReader',
30
+ };
31
+
32
+ // SymTable is an index of top-level declarations.
33
+ export class SymTable {
34
+ private structs = new Map<string, ABIStruct>()
35
+ private aliases = new Map<string, ABIAlias>()
36
+ private enums = new Map<string, ABIEnum>()
37
+
38
+ constructor(declarations: (ABIStruct | ABIAlias | ABIEnum)[]) {
39
+ for (const n of declarations) {
40
+ if (n.kind === 'Struct') this.structs.set(n.name, n);
41
+ else if (n.kind === 'Alias') this.aliases.set(n.name, n);
42
+ else if (n.kind === 'Enum') this.enums.set(n.name, n);
43
+ }
44
+ }
45
+
46
+ getStruct(structName: string): ABIStruct {
47
+ const s = this.structs.get(structName);
48
+ if (s === undefined)
49
+ throw new SymbolNotFound('struct', structName);
50
+ return s;
51
+ }
52
+
53
+ getAlias(aliasName: string): ABIAlias {
54
+ const a = this.aliases.get(aliasName);
55
+ if (a === undefined)
56
+ throw new SymbolNotFound('alias', aliasName);
57
+ return a;
58
+ }
59
+
60
+ getEnum(enumName: string): ABIEnum {
61
+ const e = this.enums.get(enumName);
62
+ if (e === undefined)
63
+ throw new SymbolNotFound('enum', enumName);
64
+ return e;
65
+ }
66
+
67
+ getAliasTarget(aliasName: string): Ty {
68
+ return this.getAlias(aliasName).targetTy;
69
+ }
70
+ }
71
+
72
+ // CodegenCtx represents current state of TypeScript code generation.
73
+ // It contains properties that affect final output and help resolve symbols.
74
+ // For example, we want Tolk `int64` not be just TS `bigint`, but
75
+ // to export `type int64 = bigint` and use `int64` directly,
76
+ // that's why we track all occurred intN.
77
+ // Same for other public properties, they are modified while iterating through declarations.
78
+ export class CodegenCtx {
79
+ readonly symbols: SymTable
80
+
81
+ intNOccurred = new Set<string>(['int8', 'int16', 'int32', 'int256'])
82
+ uintNOccurred = new Set<string>(['uint8', 'uint16', 'uint32', 'uint256'])
83
+ varIntNOccurred = new Set<string>()
84
+ bitsNOccurred = new Set<string>()
85
+
86
+ has_RemainingBitsAndRefs = false
87
+ has_customPackUnpack = false
88
+ has_customDictV = false
89
+ has_implicitUnionPrefix = false
90
+ has_non32Prefixes = false
91
+ has_addressAny = false
92
+ has_arrayOf = false
93
+ has_lispListOf = false
94
+
95
+ stackReadsUnknown = false
96
+ stackReadsArrayOf = false
97
+ stackReadsLispListOf = false
98
+ stackReadsSnakeString = false
99
+ stackReadsTuple = false
100
+ stackReadsMapKV = false
101
+ stackReadsBuilder = false
102
+ stackReadsNullable = false
103
+ stackReadsWideNullable = false
104
+ stackReadsUnionType = false
105
+ stackReadsCellRef = false
106
+ stackReadsNullLiteral = false
107
+
108
+ constructor(declarations: (ABIStruct | ABIAlias | ABIEnum)[]) {
109
+ this.symbols = new SymTable(declarations);
110
+ }
111
+
112
+ sortOccurred(occurred: Set<string>): string[] {
113
+ return [...occurred].sort((a, b) => a.length - b.length || a.localeCompare(b));
114
+ }
115
+ }
@@ -0,0 +1,55 @@
1
+ import * as c from '@ton/core'
2
+ import { SymTable } from './codegen-ctx'
3
+ import { ABIGetMethod, ContractABI } from './abi'
4
+ import { CantPackDynamic, CantUnpackDynamic } from './unsupported-errors'
5
+
6
+ type CustomPackToBuilderFn<T> = (self: T, b: c.Builder) => void
7
+ type CustomUnpackFromSliceFn<T> = (s: c.Slice) => T
8
+
9
+ export class DynamicCtx {
10
+ readonly contractName: string;
11
+ readonly getMethods: ABIGetMethod[];
12
+ readonly symbols: SymTable;
13
+
14
+ private customSerializersRegistry: Map<string, [CustomPackToBuilderFn<any> | null, CustomUnpackFromSliceFn<any> | null]> = new Map;
15
+
16
+ constructor(contract: ContractABI) {
17
+ this.contractName = contract.contractName;
18
+ this.getMethods = contract.getMethods;
19
+ this.symbols = new SymTable(contract.declarations);
20
+ }
21
+
22
+ registerCustomPackUnpack<T>(
23
+ typeName: string,
24
+ packToBuilderFn: CustomPackToBuilderFn<T> | null,
25
+ unpackFromSliceFn: CustomUnpackFromSliceFn<T> | null,
26
+ ) {
27
+ if (this.customSerializersRegistry.has(typeName)) {
28
+ throw new Error(`Custom pack/unpack for '${typeName}' already registered`);
29
+ }
30
+ this.customSerializersRegistry.set(typeName, [packToBuilderFn, unpackFromSliceFn]);
31
+ }
32
+
33
+ getCustomPackFnOrThrow(typeName: string, fieldPath: string): CustomPackToBuilderFn<any> {
34
+ let packUnpack = this.customSerializersRegistry.get(typeName);
35
+ let packFn = packUnpack ? packUnpack[0] : null;
36
+ if (!packFn) {
37
+ throw new CantPackDynamic(fieldPath, `custom packToBuilder was not registered for '${typeName}'`);
38
+ }
39
+ return packFn;
40
+ }
41
+
42
+ getCustomUnpackFnOrThrow(typeName: string, fieldPath: string): CustomUnpackFromSliceFn<any> {
43
+ let packUnpack = this.customSerializersRegistry.get(typeName);
44
+ let unpackFn = packUnpack ? packUnpack[1] : null;
45
+ if (!unpackFn) {
46
+ throw new CantUnpackDynamic(fieldPath, `custom unpackFromSlice was not registered for '${typeName}'`);
47
+ }
48
+ return unpackFn;
49
+ }
50
+
51
+ findGetMethod(getMethodName: string): ABIGetMethod | undefined {
52
+ return this.getMethods.find(m => m.name === getMethodName);
53
+ }
54
+ }
55
+