@xelis/sdk 0.11.32 → 0.11.34
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/dist/cjs/contract/contract.js +126 -84
- package/dist/cjs/contract/typed_contract.js +134 -88
- package/dist/cjs/contract/xvm_serializer.js +252 -29
- package/dist/cjs/rpc/websocket.js +10 -4
- package/dist/esm/contract/contract.js +125 -83
- package/dist/esm/contract/typed_contract.js +131 -85
- package/dist/esm/contract/xvm_serializer.js +244 -27
- package/dist/esm/rpc/websocket.js +10 -4
- package/dist/types/contract/contract.d.ts +46 -10
- package/dist/types/contract/typed_contract.d.ts +50 -15
- package/dist/types/contract/xvm_serializer.d.ts +68 -4
- package/dist/types/daemon/types.d.ts +17 -8
- package/dist/types/wallet/rpc.d.ts +1 -1
- package/dist/types/wallet/types.d.ts +34 -17
- package/dist/types/wallet/websocket.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,35 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
// Convert ABI type to validator type
|
|
3
|
-
function normalizeType(abiType) {
|
|
4
|
-
const typeMap = {
|
|
5
|
-
'Hash': 'Hash',
|
|
6
|
-
'Address': 'Address',
|
|
7
|
-
'PublicKey': 'PublicKey',
|
|
8
|
-
'Blob': 'Blob',
|
|
9
|
-
'u256': 'u256',
|
|
10
|
-
'u128': 'u128',
|
|
11
|
-
'u64': 'u64',
|
|
12
|
-
'u32': 'u32',
|
|
13
|
-
'u16': 'u16',
|
|
14
|
-
'u8': 'u8',
|
|
15
|
-
'boolean': 'boolean',
|
|
16
|
-
'bool': 'boolean',
|
|
17
|
-
'string': 'string',
|
|
18
|
-
'String': 'string',
|
|
19
|
-
'Boolean': 'boolean',
|
|
20
|
-
'U256': 'u256',
|
|
21
|
-
'U128': 'u128',
|
|
22
|
-
'U64': 'u64',
|
|
23
|
-
'U32': 'u32',
|
|
24
|
-
'U16': 'u16',
|
|
25
|
-
'U8': 'u8'
|
|
26
|
-
};
|
|
27
|
-
const normalized = typeMap[abiType];
|
|
28
|
-
if (!normalized) {
|
|
29
|
-
throw new Error(`Unknown ABI type: ${abiType}`);
|
|
30
|
-
}
|
|
31
|
-
return normalized;
|
|
32
|
-
}
|
|
1
|
+
import { createContractInvocation, typeRegistry, defineEnum, defineStruct, createVMParameter } from './xvm_serializer.js';
|
|
33
2
|
/**
|
|
34
3
|
* Strongly typed contract class
|
|
35
4
|
*/
|
|
@@ -38,21 +7,17 @@ export class TypedContract {
|
|
|
38
7
|
this.address = address;
|
|
39
8
|
this.abi = abi;
|
|
40
9
|
this.methods = new Map();
|
|
41
|
-
|
|
10
|
+
this.register_internal_types();
|
|
42
11
|
for (const entry of abi.data) {
|
|
43
12
|
if (entry.type === 'entry') {
|
|
44
|
-
console.log("new method", entry.name);
|
|
45
13
|
this.methods.set(entry.name, entry);
|
|
46
14
|
}
|
|
47
15
|
}
|
|
48
|
-
// Return a Proxy to handle dynamic method calls
|
|
49
16
|
return new Proxy(this, {
|
|
50
17
|
get(target, prop, receiver) {
|
|
51
|
-
// If it's a known property/method, return it
|
|
52
18
|
if (prop in target) {
|
|
53
19
|
return Reflect.get(target, prop, receiver);
|
|
54
20
|
}
|
|
55
|
-
// If it's a string property that matches a method name, create dynamic method
|
|
56
21
|
if (typeof prop === 'string' && target.methods.has(prop)) {
|
|
57
22
|
return (params) => target.invokeUnsafe(prop, params);
|
|
58
23
|
}
|
|
@@ -60,81 +25,159 @@ export class TypedContract {
|
|
|
60
25
|
}
|
|
61
26
|
});
|
|
62
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Register all custom types from ABI
|
|
30
|
+
*/
|
|
31
|
+
register_internal_types() {
|
|
32
|
+
if (!this.abi.internal_types)
|
|
33
|
+
return;
|
|
34
|
+
for (const type_def of this.abi.internal_types) {
|
|
35
|
+
if (type_def.kind === 'enum' && type_def.variants) {
|
|
36
|
+
typeRegistry.register(defineEnum(type_def.name, type_def.variants));
|
|
37
|
+
}
|
|
38
|
+
else if (type_def.kind === 'struct' && type_def.fields) {
|
|
39
|
+
typeRegistry.register(defineStruct(type_def.name, type_def.fields));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Helper to create struct values with positional arguments
|
|
45
|
+
* Validates field types immediately
|
|
46
|
+
* @param type_name - Name of the struct type
|
|
47
|
+
* @param field_values - Field values in the order defined in ABI
|
|
48
|
+
*/
|
|
49
|
+
struct(type_name, ...field_values) {
|
|
50
|
+
const type_def = this.abi.internal_types?.find(t => t.name === type_name && t.kind === 'struct');
|
|
51
|
+
if (!type_def || !type_def.fields) {
|
|
52
|
+
throw new Error(`Struct type '${type_name}' not found in contract ABI`);
|
|
53
|
+
}
|
|
54
|
+
if (field_values.length !== type_def.fields.length) {
|
|
55
|
+
throw new Error(`Struct '${type_name}' expects ${type_def.fields.length} fields ` +
|
|
56
|
+
`(${type_def.fields.map(f => f.name).join(', ')}), ` +
|
|
57
|
+
`but got ${field_values.length}`);
|
|
58
|
+
}
|
|
59
|
+
const result = {};
|
|
60
|
+
for (let i = 0; i < type_def.fields.length; i++) {
|
|
61
|
+
const field = type_def.fields[i];
|
|
62
|
+
const value = field_values[i];
|
|
63
|
+
try {
|
|
64
|
+
createVMParameter(value, field.type);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
throw new Error(`Invalid value for field '${field.name}' (position ${i}) of struct '${type_name}': ${error}`);
|
|
68
|
+
}
|
|
69
|
+
result[field.name] = value;
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Helper to create enum values with positional arguments
|
|
75
|
+
* Validates field types immediately
|
|
76
|
+
* @param type_name - Name of the enum type
|
|
77
|
+
* @param variant_name - Name of the variant
|
|
78
|
+
* @param field_values - Field values in the order defined in ABI
|
|
79
|
+
*/
|
|
80
|
+
enum(type_name, variant_name, ...field_values) {
|
|
81
|
+
const type_def = this.abi.internal_types?.find(t => t.name === type_name && t.kind === 'enum');
|
|
82
|
+
if (!type_def || !type_def.variants) {
|
|
83
|
+
throw new Error(`Enum type '${type_name}' not found in contract ABI`);
|
|
84
|
+
}
|
|
85
|
+
const variant = type_def.variants.find(v => v.name === variant_name);
|
|
86
|
+
if (!variant) {
|
|
87
|
+
const available = type_def.variants.map(v => v.name).join(', ');
|
|
88
|
+
throw new Error(`Unknown variant '${variant_name}' for enum '${type_name}'. ` +
|
|
89
|
+
`Available variants: ${available}`);
|
|
90
|
+
}
|
|
91
|
+
if (field_values.length !== variant.fields.length) {
|
|
92
|
+
throw new Error(`Variant '${variant_name}' of enum '${type_name}' expects ${variant.fields.length} fields ` +
|
|
93
|
+
`(${variant.fields.map(f => f.name).join(', ')}), ` +
|
|
94
|
+
`but got ${field_values.length}`);
|
|
95
|
+
}
|
|
96
|
+
const result = { type: variant_name };
|
|
97
|
+
for (let i = 0; i < variant.fields.length; i++) {
|
|
98
|
+
const field = variant.fields[i];
|
|
99
|
+
const value = field_values[i];
|
|
100
|
+
try {
|
|
101
|
+
createVMParameter(value, field.type);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
throw new Error(`Invalid value for field '${field.name}' (position ${i}) of variant '${variant_name}' in enum '${type_name}': ${error}`);
|
|
105
|
+
}
|
|
106
|
+
result[field.name] = value;
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
63
110
|
/**
|
|
64
111
|
* Internal method to invoke contract functions
|
|
65
112
|
*/
|
|
66
|
-
invokeUnsafe(
|
|
67
|
-
const entry = this.methods.get(
|
|
113
|
+
invokeUnsafe(method_name, params = {}) {
|
|
114
|
+
const entry = this.methods.get(method_name);
|
|
68
115
|
if (!entry) {
|
|
69
|
-
throw new Error(`Method '${
|
|
116
|
+
throw new Error(`Method '${method_name}' not found in contract ABI`);
|
|
70
117
|
}
|
|
71
|
-
|
|
72
|
-
const { maxGas, deposits, ...methodParams } = params;
|
|
73
|
-
// Build parameter list according to ABI
|
|
118
|
+
const { maxGas, deposits, permission, ...method_params } = params;
|
|
74
119
|
const parameters = [];
|
|
75
|
-
for (const
|
|
76
|
-
const value =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
methodParams[abiParam.name] = abiParam.default;
|
|
120
|
+
for (const abi_param of entry.params) {
|
|
121
|
+
const value = method_params[abi_param.name];
|
|
122
|
+
if (value === undefined && !abi_param.optional) {
|
|
123
|
+
if (abi_param.default !== undefined) {
|
|
124
|
+
method_params[abi_param.name] = abi_param.default;
|
|
81
125
|
}
|
|
82
126
|
else {
|
|
83
|
-
throw new Error(`Missing required parameter '${
|
|
127
|
+
throw new Error(`Missing required parameter '${abi_param.name}' for method '${method_name}'`);
|
|
84
128
|
}
|
|
85
129
|
}
|
|
86
130
|
if (value !== undefined) {
|
|
87
131
|
try {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
parameters.push(vmParam);
|
|
132
|
+
const VMParam = createVMParameter(value, abi_param.type);
|
|
133
|
+
parameters.push(VMParam);
|
|
91
134
|
}
|
|
92
135
|
catch (error) {
|
|
93
|
-
throw new Error(`Invalid parameter '${
|
|
136
|
+
throw new Error(`Invalid parameter '${abi_param.name}' for method '${method_name}': ${error}`);
|
|
94
137
|
}
|
|
95
138
|
}
|
|
96
139
|
}
|
|
97
|
-
|
|
98
|
-
const invocationParams = {
|
|
140
|
+
const invocation_params = {
|
|
99
141
|
contract: this.address,
|
|
100
|
-
|
|
142
|
+
chunk_id: entry.chunk_id,
|
|
101
143
|
parameters,
|
|
102
|
-
|
|
144
|
+
permission,
|
|
145
|
+
maxGas: maxGas || 50000000
|
|
103
146
|
};
|
|
104
147
|
if (deposits && Object.keys(deposits).length > 0) {
|
|
105
|
-
|
|
148
|
+
invocation_params.deposits = deposits;
|
|
106
149
|
}
|
|
107
|
-
return createContractInvocation(
|
|
150
|
+
return createContractInvocation(invocation_params);
|
|
108
151
|
}
|
|
109
152
|
/**
|
|
110
153
|
* Type-safe invoke method
|
|
111
154
|
*/
|
|
112
|
-
invoke(
|
|
113
|
-
return this.invokeUnsafe(
|
|
155
|
+
invoke(method_name, params) {
|
|
156
|
+
return this.invokeUnsafe(method_name, params);
|
|
114
157
|
}
|
|
115
158
|
/**
|
|
116
159
|
* Get list of available methods
|
|
117
160
|
*/
|
|
118
|
-
|
|
161
|
+
get_methods() {
|
|
119
162
|
return Array.from(this.methods.keys());
|
|
120
163
|
}
|
|
121
164
|
/**
|
|
122
165
|
* Get method signature information
|
|
123
166
|
*/
|
|
124
|
-
|
|
125
|
-
return this.methods.get(
|
|
167
|
+
get_method_signature(method_name) {
|
|
168
|
+
return this.methods.get(method_name);
|
|
126
169
|
}
|
|
127
170
|
/**
|
|
128
171
|
* Generate TypeScript interface for the contract
|
|
129
172
|
*/
|
|
130
|
-
|
|
173
|
+
generate_interface() {
|
|
131
174
|
const lines = [
|
|
132
175
|
`interface ${this.constructor.name}Methods {`
|
|
133
176
|
];
|
|
134
177
|
for (const [name, entry] of this.methods) {
|
|
135
178
|
const params = entry.params.map(p => {
|
|
136
179
|
const optional = p.optional ? '?' : '';
|
|
137
|
-
return ` ${p.name}${optional}: ${this.
|
|
180
|
+
return ` ${p.name}${optional}: ${this.get_typescript_type(p.type)};`;
|
|
138
181
|
}).join('\n');
|
|
139
182
|
lines.push(` ${name}(params: {`);
|
|
140
183
|
lines.push(params);
|
|
@@ -146,8 +189,8 @@ export class TypedContract {
|
|
|
146
189
|
lines.push('}');
|
|
147
190
|
return lines.join('\n');
|
|
148
191
|
}
|
|
149
|
-
|
|
150
|
-
const
|
|
192
|
+
get_typescript_type(abi_type) {
|
|
193
|
+
const type_map = {
|
|
151
194
|
'Hash': 'string',
|
|
152
195
|
'Address': 'string',
|
|
153
196
|
'PublicKey': 'string',
|
|
@@ -170,51 +213,54 @@ export class TypedContract {
|
|
|
170
213
|
'U8': 'number',
|
|
171
214
|
'u8': 'number'
|
|
172
215
|
};
|
|
173
|
-
return
|
|
216
|
+
return type_map[abi_type] || 'any';
|
|
174
217
|
}
|
|
175
218
|
}
|
|
176
219
|
/**
|
|
177
220
|
* Create a typed contract instance with full TypeScript support
|
|
178
221
|
*/
|
|
179
|
-
export function
|
|
222
|
+
export function create_typed_contract(address, abi) {
|
|
180
223
|
return new TypedContract(address, abi);
|
|
181
224
|
}
|
|
182
225
|
/**
|
|
183
226
|
* Contract factory with strong typing
|
|
184
227
|
*/
|
|
185
228
|
export class TypedContractFactory {
|
|
186
|
-
constructor(abi,
|
|
229
|
+
constructor(abi, contract_name = 'Contract') {
|
|
187
230
|
this.abi = abi;
|
|
188
|
-
this.
|
|
231
|
+
this.contract_name = contract_name;
|
|
189
232
|
}
|
|
190
233
|
/**
|
|
191
234
|
* Create a new contract instance at the specified address
|
|
192
235
|
*/
|
|
193
236
|
at(address) {
|
|
194
|
-
return
|
|
237
|
+
return create_typed_contract(address, this.abi);
|
|
195
238
|
}
|
|
196
239
|
/**
|
|
197
240
|
* Get the ABI
|
|
198
241
|
*/
|
|
199
|
-
|
|
242
|
+
get_abi() {
|
|
200
243
|
return this.abi;
|
|
201
244
|
}
|
|
202
245
|
/**
|
|
203
246
|
* Generate TypeScript definitions for this contract
|
|
204
247
|
*/
|
|
205
|
-
|
|
248
|
+
generate_type_definitions() {
|
|
206
249
|
const contract = new TypedContract('0x0', this.abi);
|
|
207
|
-
return contract.
|
|
250
|
+
return contract.generate_interface();
|
|
208
251
|
}
|
|
209
252
|
}
|
|
210
253
|
/**
|
|
211
254
|
* Utility to validate ABI structure
|
|
212
255
|
*/
|
|
213
|
-
export function
|
|
214
|
-
if (!
|
|
256
|
+
export function validate_abi(abi) {
|
|
257
|
+
if (!abi || typeof abi !== 'object') {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
if (!Array.isArray(abi.data)) {
|
|
215
261
|
return false;
|
|
216
262
|
}
|
|
217
|
-
for (const entry of abi) {
|
|
263
|
+
for (const entry of abi.data) {
|
|
218
264
|
if (typeof entry !== 'object' || !entry) {
|
|
219
265
|
return false;
|
|
220
266
|
}
|
|
@@ -227,7 +273,7 @@ export function validateABI(abi) {
|
|
|
227
273
|
if (!Array.isArray(entry.params)) {
|
|
228
274
|
return false;
|
|
229
275
|
}
|
|
230
|
-
if (entry.type !== 'entry'
|
|
276
|
+
if (entry.type !== 'entry') {
|
|
231
277
|
return false;
|
|
232
278
|
}
|
|
233
279
|
for (const param of entry.params) {
|
|
@@ -241,11 +287,11 @@ export function validateABI(abi) {
|
|
|
241
287
|
/**
|
|
242
288
|
* Helper to create a contract from JSON ABI
|
|
243
289
|
*/
|
|
244
|
-
export async function
|
|
245
|
-
const response = await fetch(
|
|
290
|
+
export async function create_contract_from_json(address, abi_path) {
|
|
291
|
+
const response = await fetch(abi_path);
|
|
246
292
|
const abi = await response.json();
|
|
247
|
-
if (!
|
|
293
|
+
if (!validate_abi(abi)) {
|
|
248
294
|
throw new Error('Invalid ABI structure');
|
|
249
295
|
}
|
|
250
|
-
return
|
|
296
|
+
return create_typed_contract(address, abi);
|
|
251
297
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Known opaque types that need special wrapping
|
|
2
|
-
const OPAQUE_TYPES = new Set(['Hash', 'Address', 'PublicKey', 'Blob'
|
|
2
|
+
const OPAQUE_TYPES = new Set(['Hash', 'Address', 'PublicKey', 'Blob']);
|
|
3
3
|
// Type validation and conversion helpers
|
|
4
4
|
const TYPE_VALIDATORS = {
|
|
5
5
|
'u256': (v) => {
|
|
@@ -73,12 +73,12 @@ const TYPE_VALIDATORS = {
|
|
|
73
73
|
* @param type - The type string (e.g., 'u64', 'Hash', 'string')
|
|
74
74
|
* @param validate - Whether to validate and convert the value (default: true)
|
|
75
75
|
*/
|
|
76
|
-
export function
|
|
77
|
-
let
|
|
76
|
+
export function createVMPrimitive(value, type, validate = true) {
|
|
77
|
+
let processed_value = value;
|
|
78
78
|
// Validate and convert value if requested
|
|
79
79
|
if (validate && TYPE_VALIDATORS[type]) {
|
|
80
80
|
try {
|
|
81
|
-
|
|
81
|
+
processed_value = TYPE_VALIDATORS[type](value);
|
|
82
82
|
}
|
|
83
83
|
catch (error) {
|
|
84
84
|
throw new Error(`Failed to create VM parameter for type ${type}: ${error}`);
|
|
@@ -87,62 +87,279 @@ export function createVMParameter(value, type, validate = true) {
|
|
|
87
87
|
// Handle opaque types (Hash, Address, PublicKey)
|
|
88
88
|
if (OPAQUE_TYPES.has(type)) {
|
|
89
89
|
return {
|
|
90
|
-
type: "
|
|
90
|
+
type: "primitive",
|
|
91
91
|
value: {
|
|
92
92
|
type: "opaque",
|
|
93
93
|
value: {
|
|
94
94
|
type: type,
|
|
95
|
-
value:
|
|
95
|
+
value: processed_value
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
// Handle regular types
|
|
101
101
|
return {
|
|
102
|
-
type: "
|
|
102
|
+
type: "primitive",
|
|
103
103
|
value: {
|
|
104
104
|
type: type,
|
|
105
|
-
value:
|
|
105
|
+
value: processed_value
|
|
106
106
|
}
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Serialize an array of values
|
|
111
|
+
*/
|
|
112
|
+
export function serialize_array(items, item_type) {
|
|
113
|
+
const serialized_items = items.map(item => createVMParameter(item, item_type));
|
|
114
|
+
return {
|
|
115
|
+
type: "array",
|
|
116
|
+
value: serialized_items
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Serialize a map
|
|
121
|
+
*/
|
|
122
|
+
export function serialize_map(map, keyType, valueType) {
|
|
123
|
+
const entries = [];
|
|
124
|
+
for (const [key, value] of Object.entries(map)) {
|
|
125
|
+
const serialized_key = createVMParameter(key, keyType);
|
|
126
|
+
const serialized_value = createVMParameter(value, valueType);
|
|
127
|
+
entries.push([serialized_key, serialized_value]);
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
type: "map",
|
|
131
|
+
value: entries
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Serialize an optional value
|
|
136
|
+
*/
|
|
137
|
+
export function serialize_optional(value, inner_type) {
|
|
138
|
+
if (value === null || value === undefined) {
|
|
139
|
+
return {
|
|
140
|
+
type: "option",
|
|
141
|
+
value: null
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
type: "option",
|
|
146
|
+
value: createVMParameter(value, inner_type)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
109
149
|
/**
|
|
110
150
|
* Convenience functions for common types
|
|
111
151
|
*/
|
|
112
|
-
export const
|
|
113
|
-
hash: (value) =>
|
|
114
|
-
address: (value) =>
|
|
115
|
-
|
|
116
|
-
blob: (value) =>
|
|
117
|
-
u64: (value) =>
|
|
118
|
-
u32: (value) =>
|
|
119
|
-
u16: (value) =>
|
|
120
|
-
u8: (value) =>
|
|
121
|
-
string: (value) =>
|
|
122
|
-
boolean: (value) =>
|
|
152
|
+
export const VMParam = {
|
|
153
|
+
hash: (value) => createVMPrimitive(value, 'Hash'),
|
|
154
|
+
address: (value) => createVMPrimitive(value, 'Address'),
|
|
155
|
+
public_key: (value) => createVMPrimitive(value, 'PublicKey'),
|
|
156
|
+
blob: (value) => createVMPrimitive(value, 'Blob'),
|
|
157
|
+
u64: (value) => createVMPrimitive(value, 'u64'),
|
|
158
|
+
u32: (value) => createVMPrimitive(value, 'u32'),
|
|
159
|
+
u16: (value) => createVMPrimitive(value, 'u16'),
|
|
160
|
+
u8: (value) => createVMPrimitive(value, 'u8'),
|
|
161
|
+
string: (value) => createVMPrimitive(value, 'string'),
|
|
162
|
+
boolean: (value) => createVMPrimitive(value, 'boolean'),
|
|
123
163
|
};
|
|
164
|
+
/**
|
|
165
|
+
* Define an enum type from ABI schema
|
|
166
|
+
*/
|
|
167
|
+
export function defineEnum(name, variants) {
|
|
168
|
+
const variantNames = variants.map(v => v.name);
|
|
169
|
+
return {
|
|
170
|
+
name,
|
|
171
|
+
// NEW: mark kind & expose a matcher
|
|
172
|
+
// (safe to add; SerializableType is duck-typed)
|
|
173
|
+
// @ts-ignore - widen at runtime
|
|
174
|
+
kind: 'enum',
|
|
175
|
+
// @ts-ignore
|
|
176
|
+
hasVariant: (vn) => variantNames.includes(vn),
|
|
177
|
+
to_VMParameter(value) {
|
|
178
|
+
const variant_index = variants.findIndex(v => v.name === value.type);
|
|
179
|
+
if (variant_index === -1) {
|
|
180
|
+
throw new Error(`Unknown variant '${value.type}' for enum '${name}'`);
|
|
181
|
+
}
|
|
182
|
+
const variant = variants[variant_index];
|
|
183
|
+
const expected_fields = new Set(variant.fields.map(f => f.name));
|
|
184
|
+
expected_fields.add('type');
|
|
185
|
+
for (const key of Object.keys(value)) {
|
|
186
|
+
if (!expected_fields.has(key)) {
|
|
187
|
+
throw new Error(`Unknown field '${key}' for variant '${value.type}' of enum '${name}'. ` +
|
|
188
|
+
`Expected fields: ${Array.from(expected_fields).filter(f => f !== 'type').join(', ')}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const params = [
|
|
192
|
+
VMParam.u8(variant_index)
|
|
193
|
+
];
|
|
194
|
+
for (const field_schema of variant.fields) {
|
|
195
|
+
const field_value = value[field_schema.name];
|
|
196
|
+
if (field_value === undefined) {
|
|
197
|
+
throw new Error(`Missing field '${field_schema.name}' for variant '${value.type}' of enum '${name}'`);
|
|
198
|
+
}
|
|
199
|
+
params.push(createVMParameter(field_value, field_schema.type));
|
|
200
|
+
}
|
|
201
|
+
return { type: "object", value: params };
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Define a struct type from ABI schema
|
|
207
|
+
*/
|
|
208
|
+
export function defineStruct(name, fields) {
|
|
209
|
+
return {
|
|
210
|
+
name,
|
|
211
|
+
// NEW: mark kind for structs
|
|
212
|
+
// @ts-ignore
|
|
213
|
+
kind: 'struct',
|
|
214
|
+
to_VMParameter(value) {
|
|
215
|
+
const expected_fields = new Set(fields.map(f => f.name));
|
|
216
|
+
for (const key of Object.keys(value)) {
|
|
217
|
+
if (!expected_fields.has(key)) {
|
|
218
|
+
throw new Error(`Unknown field '${key}' for struct '${name}'. ` +
|
|
219
|
+
`Expected fields: ${Array.from(expected_fields).join(', ')}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const params = [];
|
|
223
|
+
for (const field_schema of fields) {
|
|
224
|
+
const field_value = value[field_schema.name];
|
|
225
|
+
if (field_value === undefined) {
|
|
226
|
+
throw new Error(`Missing field '${field_schema.name}' for struct '${name}'`);
|
|
227
|
+
}
|
|
228
|
+
params.push(createVMParameter(field_value, field_schema.type));
|
|
229
|
+
}
|
|
230
|
+
return { type: "object", value: params };
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Type registry - simple Map for custom types
|
|
236
|
+
*/
|
|
237
|
+
class TypeRegistry {
|
|
238
|
+
constructor() {
|
|
239
|
+
this.types = new Map();
|
|
240
|
+
}
|
|
241
|
+
register(definition) {
|
|
242
|
+
this.types.set(definition.name, definition);
|
|
243
|
+
return definition;
|
|
244
|
+
}
|
|
245
|
+
get(name) {
|
|
246
|
+
return this.types.get(name);
|
|
247
|
+
}
|
|
248
|
+
has(name) {
|
|
249
|
+
return this.types.has(name);
|
|
250
|
+
}
|
|
251
|
+
clear() {
|
|
252
|
+
this.types.clear();
|
|
253
|
+
}
|
|
254
|
+
all() {
|
|
255
|
+
return this.types.values();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
export const typeRegistry = new TypeRegistry();
|
|
259
|
+
/**
|
|
260
|
+
* Enhanced parameter creation that handles both primitive and custom types
|
|
261
|
+
* @param value - The value to serialize
|
|
262
|
+
* @param type - The type string (primitive or custom type name)
|
|
263
|
+
* @param validate - Whether to validate primitive values (default: true)
|
|
264
|
+
*/
|
|
265
|
+
export function createVMParameter(value, type, validate = true) {
|
|
266
|
+
// Pass-through already-serialized parameters
|
|
267
|
+
if (value && typeof value === 'object' && 'type' in value && 'value' in value) {
|
|
268
|
+
return value;
|
|
269
|
+
}
|
|
270
|
+
// Arrays
|
|
271
|
+
if (type.endsWith('[]')) {
|
|
272
|
+
const inner_type = type.slice(0, -2);
|
|
273
|
+
if (!Array.isArray(value)) {
|
|
274
|
+
throw new Error(`Expected array for type ${type}, got ${typeof value}`);
|
|
275
|
+
}
|
|
276
|
+
return serialize_array(value, inner_type);
|
|
277
|
+
}
|
|
278
|
+
// Optionals
|
|
279
|
+
if (type.startsWith('optional<') && type.endsWith('>')) {
|
|
280
|
+
const inner_type = type.slice(9, -1);
|
|
281
|
+
return serialize_optional(value, inner_type);
|
|
282
|
+
}
|
|
283
|
+
// Maps
|
|
284
|
+
const map_match = type.match(/^map<(.+),\s*(.+)>$/);
|
|
285
|
+
if (map_match) {
|
|
286
|
+
const [, keyType, valueType] = map_match;
|
|
287
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
288
|
+
throw new Error(`Expected object for type ${type}, got ${typeof value}`);
|
|
289
|
+
}
|
|
290
|
+
return serialize_map(value, keyType.trim(), valueType.trim());
|
|
291
|
+
}
|
|
292
|
+
// Generic enum/struct notations: enum<Foo>, struct<Bar>
|
|
293
|
+
const enumGeneric = type.match(/^enum<\s*([^>]+)\s*>$/i);
|
|
294
|
+
if (enumGeneric) {
|
|
295
|
+
const realType = enumGeneric[1].trim();
|
|
296
|
+
const custom = typeRegistry.get(realType);
|
|
297
|
+
if (!custom)
|
|
298
|
+
throw new Error(`Unregistered enum type: ${realType}`);
|
|
299
|
+
return custom.to_VMParameter(value);
|
|
300
|
+
}
|
|
301
|
+
const structGeneric = type.match(/^struct<\s*([^>]+)\s*>$/i);
|
|
302
|
+
if (structGeneric) {
|
|
303
|
+
const realType = structGeneric[1].trim();
|
|
304
|
+
const custom = typeRegistry.get(realType);
|
|
305
|
+
if (!custom)
|
|
306
|
+
throw new Error(`Unregistered struct type: ${realType}`);
|
|
307
|
+
return custom.to_VMParameter(value);
|
|
308
|
+
}
|
|
309
|
+
// Preferred: named custom type
|
|
310
|
+
const custom_type = typeRegistry.get(type);
|
|
311
|
+
if (custom_type) {
|
|
312
|
+
return custom_type.to_VMParameter(value);
|
|
313
|
+
}
|
|
314
|
+
// **Fix for ABI that says literally "enum"/"struct"**
|
|
315
|
+
if (type === 'enum' && value && typeof value === 'object' && 'type' in value) {
|
|
316
|
+
const variantName = value.type;
|
|
317
|
+
for (const t of typeRegistry.all()) {
|
|
318
|
+
const anyT = t;
|
|
319
|
+
if (anyT?.kind === 'enum' && typeof anyT.hasVariant === 'function' && anyT.hasVariant(variantName)) {
|
|
320
|
+
return t.to_VMParameter(value);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
throw new Error(`Cannot resolve enum type for variant '${variantName}'. Is it registered?`);
|
|
324
|
+
}
|
|
325
|
+
if (type === 'struct') {
|
|
326
|
+
// Best-effort: if your ABI truly says "struct" w/o a name, you’ll need a hint.
|
|
327
|
+
// You can add your own resolution heuristic here if you have one.
|
|
328
|
+
throw new Error(`Unknown struct subtype; ABI must specify struct<Foo> or a named type`);
|
|
329
|
+
}
|
|
330
|
+
// Primitive fallback
|
|
331
|
+
if (type in TYPE_VALIDATORS) {
|
|
332
|
+
return createVMPrimitive(value, type, validate);
|
|
333
|
+
}
|
|
334
|
+
console.error("[VMParam] unknown type", type, { value });
|
|
335
|
+
throw new Error(`Unknown type: ${type}`);
|
|
336
|
+
}
|
|
337
|
+
// ============================================================================
|
|
338
|
+
// Contract Invocation Helpers
|
|
339
|
+
// ============================================================================
|
|
124
340
|
/**
|
|
125
341
|
* Creates a deposits object for contract calls
|
|
126
342
|
* @param deposits - Object mapping token hashes to amounts
|
|
127
343
|
*/
|
|
128
344
|
export function createDeposits(deposits) {
|
|
129
345
|
const result = {};
|
|
130
|
-
for (const [
|
|
346
|
+
for (const [token_hash, amount] of Object.entries(deposits)) {
|
|
131
347
|
// Validate hash format
|
|
132
|
-
if (!/^[0-9a-fA-F]{64}$/.test(
|
|
133
|
-
throw new Error(`Invalid token hash format: ${
|
|
348
|
+
if (!/^[0-9a-fA-F]{64}$/.test(token_hash)) {
|
|
349
|
+
throw new Error(`Invalid token hash format: ${token_hash}`);
|
|
134
350
|
}
|
|
135
|
-
result[
|
|
351
|
+
result[token_hash] = { amount };
|
|
136
352
|
}
|
|
137
353
|
return result;
|
|
138
354
|
}
|
|
139
355
|
export function createContractInvocation(params) {
|
|
140
|
-
const { contract,
|
|
356
|
+
const { contract, chunk_id, parameters = [], deposits, permission, maxGas = 50000000 } = params;
|
|
141
357
|
const result = {
|
|
142
358
|
invoke_contract: {
|
|
143
359
|
contract,
|
|
144
360
|
max_gas: maxGas,
|
|
145
|
-
|
|
361
|
+
entry_id: chunk_id,
|
|
362
|
+
permission,
|
|
146
363
|
parameters
|
|
147
364
|
}
|
|
148
365
|
};
|
|
@@ -152,11 +369,11 @@ export function createContractInvocation(params) {
|
|
|
152
369
|
return result;
|
|
153
370
|
}
|
|
154
371
|
export function createContractDeployment(params) {
|
|
155
|
-
const { bytecode, hasConstructor = false, maxGas =
|
|
372
|
+
const { bytecode, hasConstructor = false, maxGas = 50000000 } = params;
|
|
156
373
|
const result = {
|
|
157
374
|
deploy_contract: {
|
|
158
375
|
module: bytecode,
|
|
159
|
-
...(hasConstructor && { invoke: {
|
|
376
|
+
...(hasConstructor && { invoke: { maxGas } })
|
|
160
377
|
}
|
|
161
378
|
};
|
|
162
379
|
return result;
|