@movebridge/testing 0.0.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/LICENSE +0 -0
- package/dist/index.d.mts +451 -0
- package/dist/index.d.ts +451 -0
- package/dist/index.js +988 -0
- package/dist/index.mjs +953 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,988 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
PREDEFINED_SCHEMAS: () => PREDEFINED_SCHEMAS,
|
|
24
|
+
createCallTracker: () => createCallTracker,
|
|
25
|
+
createFaker: () => createFaker,
|
|
26
|
+
createIntegrationUtils: () => createIntegrationUtils,
|
|
27
|
+
createMockClient: () => createMockClient,
|
|
28
|
+
createNetworkSimulator: () => createNetworkSimulator,
|
|
29
|
+
createSnapshotUtils: () => createSnapshotUtils,
|
|
30
|
+
createTestHarness: () => createTestHarness,
|
|
31
|
+
getAddressValidationDetails: () => getAddressValidationDetails,
|
|
32
|
+
getValidationErrors: () => getValidationErrors,
|
|
33
|
+
hasSchema: () => hasSchema,
|
|
34
|
+
isValidAddress: () => isValidAddress,
|
|
35
|
+
normalizeAddress: () => normalizeAddress,
|
|
36
|
+
registerSchema: () => registerSchema,
|
|
37
|
+
validateAddress: () => validateAddress,
|
|
38
|
+
validateEntryFunctionPayload: () => validateEntryFunctionPayload,
|
|
39
|
+
validatePayload: () => validatePayload,
|
|
40
|
+
validateSchema: () => validateSchema,
|
|
41
|
+
validateTransferPayload: () => validateTransferPayload
|
|
42
|
+
});
|
|
43
|
+
module.exports = __toCommonJS(index_exports);
|
|
44
|
+
|
|
45
|
+
// src/faker.ts
|
|
46
|
+
var SeededRandom = class {
|
|
47
|
+
state;
|
|
48
|
+
constructor(seed) {
|
|
49
|
+
this.state = seed;
|
|
50
|
+
}
|
|
51
|
+
next() {
|
|
52
|
+
let t = this.state += 1831565813;
|
|
53
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
54
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
55
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
56
|
+
}
|
|
57
|
+
nextInt(min, max) {
|
|
58
|
+
return Math.floor(this.next() * (max - min + 1)) + min;
|
|
59
|
+
}
|
|
60
|
+
nextBigInt(min, max) {
|
|
61
|
+
const range = max - min + 1n;
|
|
62
|
+
const randomFraction = this.next();
|
|
63
|
+
return min + BigInt(Math.floor(Number(range) * randomFraction));
|
|
64
|
+
}
|
|
65
|
+
pick(array2) {
|
|
66
|
+
return array2[this.nextInt(0, array2.length - 1)];
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
function createFaker(options) {
|
|
70
|
+
const seed = options?.seed ?? Date.now();
|
|
71
|
+
const random = new SeededRandom(seed);
|
|
72
|
+
function randomHex(length) {
|
|
73
|
+
const chars = "0123456789abcdef";
|
|
74
|
+
let result = "";
|
|
75
|
+
for (let i = 0; i < length; i++) {
|
|
76
|
+
result += chars[random.nextInt(0, 15)];
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
function randomTimestamp() {
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
const dayAgo = now - 24 * 60 * 60 * 1e3;
|
|
83
|
+
return String(random.nextInt(dayAgo, now) * 1e3);
|
|
84
|
+
}
|
|
85
|
+
function randomSequenceNumber() {
|
|
86
|
+
return String(random.nextInt(0, 1e6));
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
/**
|
|
90
|
+
* Generates a valid random Movement address
|
|
91
|
+
*/
|
|
92
|
+
fakeAddress() {
|
|
93
|
+
return `0x${randomHex(64)}`;
|
|
94
|
+
},
|
|
95
|
+
/**
|
|
96
|
+
* Generates a random balance within optional bounds
|
|
97
|
+
*/
|
|
98
|
+
fakeBalance(options2) {
|
|
99
|
+
const min = options2?.min ? BigInt(options2.min) : 0n;
|
|
100
|
+
const max = options2?.max ? BigInt(options2.max) : 10000000000n;
|
|
101
|
+
return random.nextBigInt(min, max).toString();
|
|
102
|
+
},
|
|
103
|
+
/**
|
|
104
|
+
* Generates a complete fake transaction
|
|
105
|
+
*/
|
|
106
|
+
fakeTransaction() {
|
|
107
|
+
return {
|
|
108
|
+
hash: `0x${randomHex(64)}`,
|
|
109
|
+
sender: `0x${randomHex(64)}`,
|
|
110
|
+
sequenceNumber: randomSequenceNumber(),
|
|
111
|
+
payload: {
|
|
112
|
+
type: "entry_function_payload",
|
|
113
|
+
function: `0x${randomHex(64)}::module::function`,
|
|
114
|
+
typeArguments: [],
|
|
115
|
+
arguments: []
|
|
116
|
+
},
|
|
117
|
+
timestamp: randomTimestamp()
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
/**
|
|
121
|
+
* Generates a fake transaction response
|
|
122
|
+
*/
|
|
123
|
+
fakeTransactionResponse(success = true) {
|
|
124
|
+
return {
|
|
125
|
+
hash: `0x${randomHex(64)}`,
|
|
126
|
+
success,
|
|
127
|
+
vmStatus: success ? "Executed successfully" : "Move abort",
|
|
128
|
+
gasUsed: String(random.nextInt(100, 1e4)),
|
|
129
|
+
events: []
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
/**
|
|
133
|
+
* Generates a fake resource with specified type
|
|
134
|
+
*/
|
|
135
|
+
fakeResource(type) {
|
|
136
|
+
return {
|
|
137
|
+
type,
|
|
138
|
+
data: {
|
|
139
|
+
value: String(random.nextInt(0, 1e6))
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
/**
|
|
144
|
+
* Generates a fake contract event with specified type
|
|
145
|
+
*/
|
|
146
|
+
fakeEvent(type) {
|
|
147
|
+
return {
|
|
148
|
+
type,
|
|
149
|
+
sequenceNumber: randomSequenceNumber(),
|
|
150
|
+
data: {
|
|
151
|
+
value: String(random.nextInt(0, 1e6))
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
/**
|
|
156
|
+
* Generates a fake wallet state
|
|
157
|
+
*/
|
|
158
|
+
fakeWalletState(connected = false) {
|
|
159
|
+
if (connected) {
|
|
160
|
+
return {
|
|
161
|
+
connected: true,
|
|
162
|
+
address: `0x${randomHex(64)}`,
|
|
163
|
+
publicKey: `0x${randomHex(64)}`
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
connected: false,
|
|
168
|
+
address: null,
|
|
169
|
+
publicKey: null
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
/**
|
|
173
|
+
* Generates a fake transaction hash
|
|
174
|
+
*/
|
|
175
|
+
fakeTransactionHash() {
|
|
176
|
+
return `0x${randomHex(64)}`;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/tracker.ts
|
|
182
|
+
var import_core = require("@movebridge/core");
|
|
183
|
+
function createCallTracker() {
|
|
184
|
+
const calls = [];
|
|
185
|
+
function deepEqual(a, b) {
|
|
186
|
+
if (a === b) return true;
|
|
187
|
+
if (a === null || b === null) return false;
|
|
188
|
+
if (typeof a !== typeof b) return false;
|
|
189
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
190
|
+
if (a.length !== b.length) return false;
|
|
191
|
+
return a.every((item, index) => deepEqual(item, b[index]));
|
|
192
|
+
}
|
|
193
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
194
|
+
const aKeys = Object.keys(a);
|
|
195
|
+
const bKeys = Object.keys(b);
|
|
196
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
197
|
+
return aKeys.every(
|
|
198
|
+
(key) => deepEqual(
|
|
199
|
+
a[key],
|
|
200
|
+
b[key]
|
|
201
|
+
)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
/**
|
|
208
|
+
* Records a method call
|
|
209
|
+
*/
|
|
210
|
+
recordCall(method, args, result, error) {
|
|
211
|
+
calls.push({
|
|
212
|
+
method,
|
|
213
|
+
arguments: args,
|
|
214
|
+
timestamp: Date.now(),
|
|
215
|
+
result,
|
|
216
|
+
error
|
|
217
|
+
});
|
|
218
|
+
},
|
|
219
|
+
/**
|
|
220
|
+
* Gets all calls to a specific method
|
|
221
|
+
*/
|
|
222
|
+
getCalls(method) {
|
|
223
|
+
return calls.filter((call) => call.method === method);
|
|
224
|
+
},
|
|
225
|
+
/**
|
|
226
|
+
* Gets the count of calls to a specific method
|
|
227
|
+
*/
|
|
228
|
+
getCallCount(method) {
|
|
229
|
+
return calls.filter((call) => call.method === method).length;
|
|
230
|
+
},
|
|
231
|
+
/**
|
|
232
|
+
* Gets all recorded calls
|
|
233
|
+
*/
|
|
234
|
+
getAllCalls() {
|
|
235
|
+
return [...calls];
|
|
236
|
+
},
|
|
237
|
+
/**
|
|
238
|
+
* Asserts that a method was called at least once
|
|
239
|
+
*/
|
|
240
|
+
assertCalled(method) {
|
|
241
|
+
const count = this.getCallCount(method);
|
|
242
|
+
if (count === 0) {
|
|
243
|
+
throw new import_core.MovementError(
|
|
244
|
+
`Expected method "${method}" to be called, but it was never called`,
|
|
245
|
+
"INVALID_ARGUMENT",
|
|
246
|
+
{ method, expected: "at least 1 call", actual: 0 }
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
/**
|
|
251
|
+
* Asserts that a method was called exactly N times
|
|
252
|
+
*/
|
|
253
|
+
assertCalledTimes(method, times) {
|
|
254
|
+
const count = this.getCallCount(method);
|
|
255
|
+
if (count !== times) {
|
|
256
|
+
throw new import_core.MovementError(
|
|
257
|
+
`Expected method "${method}" to be called ${times} times, but it was called ${count} times`,
|
|
258
|
+
"INVALID_ARGUMENT",
|
|
259
|
+
{ method, expected: times, actual: count }
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
/**
|
|
264
|
+
* Asserts that a method was called with specific arguments
|
|
265
|
+
*/
|
|
266
|
+
assertCalledWith(method, ...expectedArgs) {
|
|
267
|
+
const methodCalls = this.getCalls(method);
|
|
268
|
+
if (methodCalls.length === 0) {
|
|
269
|
+
throw new import_core.MovementError(
|
|
270
|
+
`Expected method "${method}" to be called with arguments, but it was never called`,
|
|
271
|
+
"INVALID_ARGUMENT",
|
|
272
|
+
{ method, expectedArgs, actualCalls: [] }
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
const hasMatch = methodCalls.some(
|
|
276
|
+
(call) => deepEqual(call.arguments, expectedArgs)
|
|
277
|
+
);
|
|
278
|
+
if (!hasMatch) {
|
|
279
|
+
throw new import_core.MovementError(
|
|
280
|
+
`Expected method "${method}" to be called with specific arguments, but no matching call found`,
|
|
281
|
+
"INVALID_ARGUMENT",
|
|
282
|
+
{
|
|
283
|
+
method,
|
|
284
|
+
expectedArgs,
|
|
285
|
+
actualCalls: methodCalls.map((c) => c.arguments)
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
/**
|
|
291
|
+
* Asserts that a method was never called
|
|
292
|
+
*/
|
|
293
|
+
assertNotCalled(method) {
|
|
294
|
+
const count = this.getCallCount(method);
|
|
295
|
+
if (count > 0) {
|
|
296
|
+
throw new import_core.MovementError(
|
|
297
|
+
`Expected method "${method}" to not be called, but it was called ${count} times`,
|
|
298
|
+
"INVALID_ARGUMENT",
|
|
299
|
+
{ method, expected: 0, actual: count }
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
/**
|
|
304
|
+
* Clears all recorded calls
|
|
305
|
+
*/
|
|
306
|
+
clearCalls() {
|
|
307
|
+
calls.length = 0;
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/simulator.ts
|
|
313
|
+
function createNetworkSimulator() {
|
|
314
|
+
let latency = 0;
|
|
315
|
+
let networkErrorEnabled = false;
|
|
316
|
+
let rateLimitMax = null;
|
|
317
|
+
let rateLimitCalls = 0;
|
|
318
|
+
const timeoutMethods = /* @__PURE__ */ new Set();
|
|
319
|
+
return {
|
|
320
|
+
/**
|
|
321
|
+
* Sets the latency for all mock responses
|
|
322
|
+
*/
|
|
323
|
+
simulateLatency(ms) {
|
|
324
|
+
latency = ms;
|
|
325
|
+
},
|
|
326
|
+
/**
|
|
327
|
+
* Causes a specific method to timeout
|
|
328
|
+
*/
|
|
329
|
+
simulateTimeout(method) {
|
|
330
|
+
timeoutMethods.add(method);
|
|
331
|
+
},
|
|
332
|
+
/**
|
|
333
|
+
* Enables network error simulation for all calls
|
|
334
|
+
*/
|
|
335
|
+
simulateNetworkError() {
|
|
336
|
+
networkErrorEnabled = true;
|
|
337
|
+
},
|
|
338
|
+
/**
|
|
339
|
+
* Sets up rate limiting after N calls
|
|
340
|
+
*/
|
|
341
|
+
simulateRateLimit(maxCalls) {
|
|
342
|
+
rateLimitMax = maxCalls;
|
|
343
|
+
rateLimitCalls = 0;
|
|
344
|
+
},
|
|
345
|
+
/**
|
|
346
|
+
* Resets all simulation settings
|
|
347
|
+
*/
|
|
348
|
+
resetSimulation() {
|
|
349
|
+
latency = 0;
|
|
350
|
+
networkErrorEnabled = false;
|
|
351
|
+
rateLimitMax = null;
|
|
352
|
+
rateLimitCalls = 0;
|
|
353
|
+
timeoutMethods.clear();
|
|
354
|
+
},
|
|
355
|
+
/**
|
|
356
|
+
* Gets the current latency setting
|
|
357
|
+
*/
|
|
358
|
+
getLatency() {
|
|
359
|
+
return latency;
|
|
360
|
+
},
|
|
361
|
+
/**
|
|
362
|
+
* Checks if network error is enabled
|
|
363
|
+
*/
|
|
364
|
+
isNetworkErrorEnabled() {
|
|
365
|
+
return networkErrorEnabled;
|
|
366
|
+
},
|
|
367
|
+
/**
|
|
368
|
+
* Checks if a method is set to timeout
|
|
369
|
+
*/
|
|
370
|
+
isMethodTimedOut(method) {
|
|
371
|
+
return timeoutMethods.has(method);
|
|
372
|
+
},
|
|
373
|
+
/**
|
|
374
|
+
* Gets remaining rate limit calls (null if no limit)
|
|
375
|
+
*/
|
|
376
|
+
getRateLimitRemaining() {
|
|
377
|
+
if (rateLimitMax === null) return null;
|
|
378
|
+
return Math.max(0, rateLimitMax - rateLimitCalls);
|
|
379
|
+
},
|
|
380
|
+
/**
|
|
381
|
+
* Increments the rate limit call counter
|
|
382
|
+
*/
|
|
383
|
+
incrementRateLimitCalls() {
|
|
384
|
+
rateLimitCalls++;
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
async function applySimulation(simulator, method, fn) {
|
|
389
|
+
if (simulator.isNetworkErrorEnabled()) {
|
|
390
|
+
throw new Error("Network error: Connection failed");
|
|
391
|
+
}
|
|
392
|
+
if (simulator.isMethodTimedOut(method)) {
|
|
393
|
+
throw new Error(`Timeout: Method "${method}" timed out`);
|
|
394
|
+
}
|
|
395
|
+
const remaining = simulator.getRateLimitRemaining();
|
|
396
|
+
if (remaining !== null && remaining <= 0) {
|
|
397
|
+
throw new Error("Rate limited: Too many requests");
|
|
398
|
+
}
|
|
399
|
+
if (remaining !== null) {
|
|
400
|
+
simulator.incrementRateLimitCalls();
|
|
401
|
+
}
|
|
402
|
+
const latency = simulator.getLatency();
|
|
403
|
+
if (latency > 0) {
|
|
404
|
+
await new Promise((resolve) => setTimeout(resolve, latency));
|
|
405
|
+
}
|
|
406
|
+
return fn();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// src/mock-client.ts
|
|
410
|
+
function createMockClient(deps) {
|
|
411
|
+
const { tracker, simulator, faker } = deps;
|
|
412
|
+
const mocks = /* @__PURE__ */ new Map();
|
|
413
|
+
const onceMocks = /* @__PURE__ */ new Map();
|
|
414
|
+
async function getResponse(method, defaultFn) {
|
|
415
|
+
const onceMock = onceMocks.get(method);
|
|
416
|
+
if (onceMock) {
|
|
417
|
+
onceMocks.delete(method);
|
|
418
|
+
if (onceMock.error) {
|
|
419
|
+
throw onceMock.error;
|
|
420
|
+
}
|
|
421
|
+
return onceMock.response;
|
|
422
|
+
}
|
|
423
|
+
const mock = mocks.get(method);
|
|
424
|
+
if (mock) {
|
|
425
|
+
if (mock.error) {
|
|
426
|
+
throw mock.error;
|
|
427
|
+
}
|
|
428
|
+
return mock.response;
|
|
429
|
+
}
|
|
430
|
+
return defaultFn();
|
|
431
|
+
}
|
|
432
|
+
async function wrapCall(method, args, defaultFn) {
|
|
433
|
+
try {
|
|
434
|
+
const result = await applySimulation(simulator, method, async () => {
|
|
435
|
+
return getResponse(method, defaultFn);
|
|
436
|
+
});
|
|
437
|
+
tracker.recordCall(method, args, result);
|
|
438
|
+
return result;
|
|
439
|
+
} catch (error) {
|
|
440
|
+
tracker.recordCall(method, args, void 0, error);
|
|
441
|
+
throw error;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
/**
|
|
446
|
+
* Configures a persistent mock response for a method
|
|
447
|
+
*/
|
|
448
|
+
mockResponse(method, response) {
|
|
449
|
+
mocks.set(method, { response });
|
|
450
|
+
},
|
|
451
|
+
/**
|
|
452
|
+
* Configures a mock error for a method
|
|
453
|
+
*/
|
|
454
|
+
mockError(method, error) {
|
|
455
|
+
mocks.set(method, { error });
|
|
456
|
+
},
|
|
457
|
+
/**
|
|
458
|
+
* Configures a one-time mock response for a method
|
|
459
|
+
*/
|
|
460
|
+
mockResponseOnce(method, response) {
|
|
461
|
+
onceMocks.set(method, { response });
|
|
462
|
+
},
|
|
463
|
+
/**
|
|
464
|
+
* Clears all mock configurations
|
|
465
|
+
*/
|
|
466
|
+
clearMocks() {
|
|
467
|
+
mocks.clear();
|
|
468
|
+
onceMocks.clear();
|
|
469
|
+
},
|
|
470
|
+
/**
|
|
471
|
+
* Gets account balance (mocked)
|
|
472
|
+
*/
|
|
473
|
+
async getAccountBalance(address) {
|
|
474
|
+
return wrapCall(
|
|
475
|
+
"getAccountBalance",
|
|
476
|
+
[address],
|
|
477
|
+
() => faker.fakeBalance()
|
|
478
|
+
);
|
|
479
|
+
},
|
|
480
|
+
/**
|
|
481
|
+
* Gets account resources (mocked)
|
|
482
|
+
*/
|
|
483
|
+
async getAccountResources(address) {
|
|
484
|
+
return wrapCall("getAccountResources", [address], () => [
|
|
485
|
+
faker.fakeResource("0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>")
|
|
486
|
+
]);
|
|
487
|
+
},
|
|
488
|
+
/**
|
|
489
|
+
* Gets transaction by hash (mocked)
|
|
490
|
+
*/
|
|
491
|
+
async getTransaction(hash) {
|
|
492
|
+
return wrapCall("getTransaction", [hash], () => {
|
|
493
|
+
const tx = faker.fakeTransaction();
|
|
494
|
+
return { ...tx, hash };
|
|
495
|
+
});
|
|
496
|
+
},
|
|
497
|
+
/**
|
|
498
|
+
* Waits for transaction confirmation (mocked)
|
|
499
|
+
*/
|
|
500
|
+
async waitForTransaction(hash) {
|
|
501
|
+
return wrapCall("waitForTransaction", [hash], () => {
|
|
502
|
+
const response = faker.fakeTransactionResponse(true);
|
|
503
|
+
return { ...response, hash };
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// src/harness.ts
|
|
510
|
+
function createTestHarness(config) {
|
|
511
|
+
const seed = config?.seed ?? Date.now();
|
|
512
|
+
const defaultLatency = config?.defaultLatency ?? 0;
|
|
513
|
+
const faker = createFaker({ seed });
|
|
514
|
+
const tracker = createCallTracker();
|
|
515
|
+
const simulator = createNetworkSimulator();
|
|
516
|
+
if (defaultLatency > 0) {
|
|
517
|
+
simulator.simulateLatency(defaultLatency);
|
|
518
|
+
}
|
|
519
|
+
const client = createMockClient({ tracker, simulator, faker });
|
|
520
|
+
return {
|
|
521
|
+
client,
|
|
522
|
+
tracker,
|
|
523
|
+
simulator,
|
|
524
|
+
faker,
|
|
525
|
+
/**
|
|
526
|
+
* Cleans up all state - call after each test
|
|
527
|
+
*/
|
|
528
|
+
cleanup() {
|
|
529
|
+
client.clearMocks();
|
|
530
|
+
tracker.clearCalls();
|
|
531
|
+
simulator.resetSimulation();
|
|
532
|
+
},
|
|
533
|
+
/**
|
|
534
|
+
* Resets to initial state while keeping configuration
|
|
535
|
+
*/
|
|
536
|
+
reset() {
|
|
537
|
+
client.clearMocks();
|
|
538
|
+
tracker.clearCalls();
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/validators/address.ts
|
|
544
|
+
var import_core2 = require("@movebridge/core");
|
|
545
|
+
var ADDRESS_REGEX = /^(0[xX])?[a-fA-F0-9]{1,64}$/;
|
|
546
|
+
function isValidAddress(address) {
|
|
547
|
+
if (typeof address !== "string") {
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
return ADDRESS_REGEX.test(address);
|
|
551
|
+
}
|
|
552
|
+
function validateAddress(address) {
|
|
553
|
+
const result = getAddressValidationDetails(address);
|
|
554
|
+
if (!result.valid) {
|
|
555
|
+
throw new import_core2.MovementError(
|
|
556
|
+
`Invalid address: ${address} - ${result.error}`,
|
|
557
|
+
"INVALID_ADDRESS",
|
|
558
|
+
{ address, reason: result.error }
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function normalizeAddress(address) {
|
|
563
|
+
validateAddress(address);
|
|
564
|
+
const withoutPrefix = address.startsWith("0x") || address.startsWith("0X") ? address.slice(2) : address;
|
|
565
|
+
const padded = withoutPrefix.padStart(64, "0");
|
|
566
|
+
return `0x${padded.toLowerCase()}`;
|
|
567
|
+
}
|
|
568
|
+
function getAddressValidationDetails(address) {
|
|
569
|
+
if (typeof address !== "string") {
|
|
570
|
+
return {
|
|
571
|
+
valid: false,
|
|
572
|
+
error: "Address must be a string"
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
if (address.length === 0) {
|
|
576
|
+
return {
|
|
577
|
+
valid: false,
|
|
578
|
+
error: "Address cannot be empty"
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
const hasPrefix = address.startsWith("0x") || address.startsWith("0X");
|
|
582
|
+
const hexPart = hasPrefix ? address.slice(2) : address;
|
|
583
|
+
if (hexPart.length === 0) {
|
|
584
|
+
return {
|
|
585
|
+
valid: false,
|
|
586
|
+
error: "Address must contain hex characters after 0x prefix"
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
if (hexPart.length > 64) {
|
|
590
|
+
return {
|
|
591
|
+
valid: false,
|
|
592
|
+
error: `Address too long: ${hexPart.length} hex characters (max 64)`
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
if (!/^[a-fA-F0-9]+$/.test(hexPart)) {
|
|
596
|
+
return {
|
|
597
|
+
valid: false,
|
|
598
|
+
error: "Address contains invalid characters (must be hex: 0-9, a-f, A-F)"
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
const normalized = `0x${hexPart.padStart(64, "0").toLowerCase()}`;
|
|
602
|
+
return {
|
|
603
|
+
valid: true,
|
|
604
|
+
normalized
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// src/validators/transaction.ts
|
|
609
|
+
var import_core3 = require("@movebridge/core");
|
|
610
|
+
var FUNCTION_REGEX = /^0x[a-fA-F0-9]{1,64}::[a-zA-Z_][a-zA-Z0-9_]*::[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
611
|
+
function validateTransferPayload(payload) {
|
|
612
|
+
if (!isValidAddress(payload.to)) {
|
|
613
|
+
throw new import_core3.MovementError(
|
|
614
|
+
`Invalid recipient address: ${payload.to}`,
|
|
615
|
+
"INVALID_ADDRESS",
|
|
616
|
+
{ address: payload.to, field: "to" }
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
validateAmount(payload.amount);
|
|
620
|
+
if (payload.coinType !== void 0) {
|
|
621
|
+
if (typeof payload.coinType !== "string" || payload.coinType.length === 0) {
|
|
622
|
+
throw new import_core3.MovementError(
|
|
623
|
+
"Invalid coin type: must be a non-empty string",
|
|
624
|
+
"INVALID_ARGUMENT",
|
|
625
|
+
{ argument: "coinType", value: payload.coinType }
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
function validateEntryFunctionPayload(payload) {
|
|
632
|
+
if (!FUNCTION_REGEX.test(payload.function)) {
|
|
633
|
+
throw new import_core3.MovementError(
|
|
634
|
+
`Invalid function identifier: ${payload.function}`,
|
|
635
|
+
"INVALID_ARGUMENT",
|
|
636
|
+
{
|
|
637
|
+
argument: "function",
|
|
638
|
+
value: payload.function,
|
|
639
|
+
expectedFormat: "0xADDRESS::module::function"
|
|
640
|
+
}
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
if (!Array.isArray(payload.typeArguments)) {
|
|
644
|
+
throw new import_core3.MovementError(
|
|
645
|
+
"typeArguments must be an array",
|
|
646
|
+
"INVALID_ARGUMENT",
|
|
647
|
+
{ argument: "typeArguments", value: payload.typeArguments }
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
for (let i = 0; i < payload.typeArguments.length; i++) {
|
|
651
|
+
if (typeof payload.typeArguments[i] !== "string") {
|
|
652
|
+
throw new import_core3.MovementError(
|
|
653
|
+
`typeArguments[${i}] must be a string`,
|
|
654
|
+
"INVALID_ARGUMENT",
|
|
655
|
+
{ argument: `typeArguments[${i}]`, value: payload.typeArguments[i] }
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (!Array.isArray(payload.arguments)) {
|
|
660
|
+
throw new import_core3.MovementError(
|
|
661
|
+
"arguments must be an array",
|
|
662
|
+
"INVALID_ARGUMENT",
|
|
663
|
+
{ argument: "arguments", value: payload.arguments }
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
function validatePayload(payload) {
|
|
669
|
+
if (!payload || typeof payload !== "object") {
|
|
670
|
+
throw new import_core3.MovementError(
|
|
671
|
+
"Payload must be an object",
|
|
672
|
+
"INVALID_ARGUMENT",
|
|
673
|
+
{ argument: "payload", value: payload }
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
if (payload.type !== "entry_function_payload") {
|
|
677
|
+
throw new import_core3.MovementError(
|
|
678
|
+
`Unsupported payload type: ${payload.type}`,
|
|
679
|
+
"INVALID_ARGUMENT",
|
|
680
|
+
{ argument: "type", value: payload.type, supported: ["entry_function_payload"] }
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
return validateEntryFunctionPayload({
|
|
684
|
+
function: payload.function,
|
|
685
|
+
typeArguments: payload.typeArguments,
|
|
686
|
+
arguments: payload.arguments
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
function validateAmount(amount) {
|
|
690
|
+
if (typeof amount !== "string") {
|
|
691
|
+
throw new import_core3.MovementError(
|
|
692
|
+
"Amount must be a string",
|
|
693
|
+
"INVALID_ARGUMENT",
|
|
694
|
+
{ argument: "amount", value: amount }
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
const numValue = Number(amount);
|
|
698
|
+
if (isNaN(numValue)) {
|
|
699
|
+
throw new import_core3.MovementError(
|
|
700
|
+
`Invalid amount: "${amount}" is not a valid number`,
|
|
701
|
+
"INVALID_ARGUMENT",
|
|
702
|
+
{ argument: "amount", value: amount, reason: "not a number" }
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
if (numValue < 0) {
|
|
706
|
+
throw new import_core3.MovementError(
|
|
707
|
+
`Invalid amount: "${amount}" cannot be negative`,
|
|
708
|
+
"INVALID_ARGUMENT",
|
|
709
|
+
{ argument: "amount", value: amount, reason: "negative value" }
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
if (numValue === 0) {
|
|
713
|
+
throw new import_core3.MovementError(
|
|
714
|
+
`Invalid amount: "${amount}" cannot be zero`,
|
|
715
|
+
"INVALID_ARGUMENT",
|
|
716
|
+
{ argument: "amount", value: amount, reason: "zero value" }
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
if (!Number.isInteger(numValue)) {
|
|
720
|
+
throw new import_core3.MovementError(
|
|
721
|
+
`Invalid amount: "${amount}" must be a whole number (no decimals)`,
|
|
722
|
+
"INVALID_ARGUMENT",
|
|
723
|
+
{ argument: "amount", value: amount, reason: "contains decimals" }
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/validators/schema.ts
|
|
729
|
+
var import_superstruct = require("superstruct");
|
|
730
|
+
var import_core4 = require("@movebridge/core");
|
|
731
|
+
var PREDEFINED_SCHEMAS = {
|
|
732
|
+
Resource: (0, import_superstruct.object)({
|
|
733
|
+
type: (0, import_superstruct.string)(),
|
|
734
|
+
data: (0, import_superstruct.record)((0, import_superstruct.string)(), (0, import_superstruct.unknown)())
|
|
735
|
+
}),
|
|
736
|
+
Transaction: (0, import_superstruct.object)({
|
|
737
|
+
hash: (0, import_superstruct.string)(),
|
|
738
|
+
sender: (0, import_superstruct.string)(),
|
|
739
|
+
sequenceNumber: (0, import_superstruct.string)(),
|
|
740
|
+
payload: (0, import_superstruct.object)({
|
|
741
|
+
type: (0, import_superstruct.literal)("entry_function_payload"),
|
|
742
|
+
function: (0, import_superstruct.string)(),
|
|
743
|
+
typeArguments: (0, import_superstruct.array)((0, import_superstruct.string)()),
|
|
744
|
+
arguments: (0, import_superstruct.array)((0, import_superstruct.unknown)())
|
|
745
|
+
}),
|
|
746
|
+
timestamp: (0, import_superstruct.string)()
|
|
747
|
+
}),
|
|
748
|
+
TransactionResponse: (0, import_superstruct.object)({
|
|
749
|
+
hash: (0, import_superstruct.string)(),
|
|
750
|
+
success: (0, import_superstruct.boolean)(),
|
|
751
|
+
vmStatus: (0, import_superstruct.string)(),
|
|
752
|
+
gasUsed: (0, import_superstruct.string)(),
|
|
753
|
+
events: (0, import_superstruct.array)(
|
|
754
|
+
(0, import_superstruct.object)({
|
|
755
|
+
type: (0, import_superstruct.string)(),
|
|
756
|
+
sequenceNumber: (0, import_superstruct.string)(),
|
|
757
|
+
data: (0, import_superstruct.record)((0, import_superstruct.string)(), (0, import_superstruct.unknown)())
|
|
758
|
+
})
|
|
759
|
+
)
|
|
760
|
+
}),
|
|
761
|
+
WalletState: (0, import_superstruct.object)({
|
|
762
|
+
connected: (0, import_superstruct.boolean)(),
|
|
763
|
+
address: (0, import_superstruct.nullable)((0, import_superstruct.string)()),
|
|
764
|
+
publicKey: (0, import_superstruct.nullable)((0, import_superstruct.string)())
|
|
765
|
+
}),
|
|
766
|
+
ContractEvent: (0, import_superstruct.object)({
|
|
767
|
+
type: (0, import_superstruct.string)(),
|
|
768
|
+
sequenceNumber: (0, import_superstruct.string)(),
|
|
769
|
+
data: (0, import_superstruct.record)((0, import_superstruct.string)(), (0, import_superstruct.unknown)())
|
|
770
|
+
})
|
|
771
|
+
};
|
|
772
|
+
var customSchemas = /* @__PURE__ */ new Map();
|
|
773
|
+
function getSchema(name) {
|
|
774
|
+
if (name in PREDEFINED_SCHEMAS) {
|
|
775
|
+
return PREDEFINED_SCHEMAS[name];
|
|
776
|
+
}
|
|
777
|
+
const customSchema = customSchemas.get(name);
|
|
778
|
+
if (customSchema) {
|
|
779
|
+
return customSchema;
|
|
780
|
+
}
|
|
781
|
+
throw new import_core4.MovementError(
|
|
782
|
+
`Unknown schema: ${name}`,
|
|
783
|
+
"INVALID_ARGUMENT",
|
|
784
|
+
{
|
|
785
|
+
schemaName: name,
|
|
786
|
+
availableSchemas: [
|
|
787
|
+
...Object.keys(PREDEFINED_SCHEMAS),
|
|
788
|
+
...customSchemas.keys()
|
|
789
|
+
]
|
|
790
|
+
}
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
function validateSchema(data, schemaName) {
|
|
794
|
+
const schema = getSchema(schemaName);
|
|
795
|
+
const [error] = (0, import_superstruct.validate)(data, schema);
|
|
796
|
+
return error === void 0;
|
|
797
|
+
}
|
|
798
|
+
function getValidationErrors(data, schemaName) {
|
|
799
|
+
const schema = getSchema(schemaName);
|
|
800
|
+
const [error] = (0, import_superstruct.validate)(data, schema);
|
|
801
|
+
if (!error) {
|
|
802
|
+
return [];
|
|
803
|
+
}
|
|
804
|
+
return convertStructError(error);
|
|
805
|
+
}
|
|
806
|
+
function registerSchema(name, schema) {
|
|
807
|
+
if (name in PREDEFINED_SCHEMAS) {
|
|
808
|
+
throw new import_core4.MovementError(
|
|
809
|
+
`Cannot override predefined schema: ${name}`,
|
|
810
|
+
"INVALID_ARGUMENT",
|
|
811
|
+
{ schemaName: name }
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
customSchemas.set(name, schema);
|
|
815
|
+
}
|
|
816
|
+
function hasSchema(name) {
|
|
817
|
+
return name in PREDEFINED_SCHEMAS || customSchemas.has(name);
|
|
818
|
+
}
|
|
819
|
+
function convertStructError(error) {
|
|
820
|
+
const errors = [];
|
|
821
|
+
for (const failure of error.failures()) {
|
|
822
|
+
errors.push({
|
|
823
|
+
path: failure.path.join(".") || "(root)",
|
|
824
|
+
expected: failure.type,
|
|
825
|
+
received: getTypeOf(failure.value),
|
|
826
|
+
message: failure.message
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
return errors;
|
|
830
|
+
}
|
|
831
|
+
function getTypeOf(value) {
|
|
832
|
+
if (value === null) return "null";
|
|
833
|
+
if (value === void 0) return "undefined";
|
|
834
|
+
if (Array.isArray(value)) return "array";
|
|
835
|
+
return typeof value;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// src/snapshots.ts
|
|
839
|
+
function createSnapshotUtils() {
|
|
840
|
+
const snapshots = /* @__PURE__ */ new Map();
|
|
841
|
+
function serialize(data) {
|
|
842
|
+
return JSON.stringify(data, null, 2);
|
|
843
|
+
}
|
|
844
|
+
function generateDiff(expected, actual) {
|
|
845
|
+
const expectedLines = expected.split("\n");
|
|
846
|
+
const actualLines = actual.split("\n");
|
|
847
|
+
const diff = [];
|
|
848
|
+
const maxLines = Math.max(expectedLines.length, actualLines.length);
|
|
849
|
+
for (let i = 0; i < maxLines; i++) {
|
|
850
|
+
const expectedLine = expectedLines[i];
|
|
851
|
+
const actualLine = actualLines[i];
|
|
852
|
+
if (expectedLine === actualLine) {
|
|
853
|
+
diff.push(` ${expectedLine ?? ""}`);
|
|
854
|
+
} else {
|
|
855
|
+
if (expectedLine !== void 0) {
|
|
856
|
+
diff.push(`- ${expectedLine}`);
|
|
857
|
+
}
|
|
858
|
+
if (actualLine !== void 0) {
|
|
859
|
+
diff.push(`+ ${actualLine}`);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return diff.join("\n");
|
|
864
|
+
}
|
|
865
|
+
return {
|
|
866
|
+
/**
|
|
867
|
+
* Creates a new snapshot
|
|
868
|
+
*/
|
|
869
|
+
createSnapshot(data, name) {
|
|
870
|
+
snapshots.set(name, serialize(data));
|
|
871
|
+
},
|
|
872
|
+
/**
|
|
873
|
+
* Matches data against an existing snapshot
|
|
874
|
+
* Creates a new snapshot if one doesn't exist
|
|
875
|
+
*/
|
|
876
|
+
matchSnapshot(data, name) {
|
|
877
|
+
const serialized = serialize(data);
|
|
878
|
+
const existing = snapshots.get(name);
|
|
879
|
+
if (existing === void 0) {
|
|
880
|
+
snapshots.set(name, serialized);
|
|
881
|
+
return { match: true };
|
|
882
|
+
}
|
|
883
|
+
if (existing === serialized) {
|
|
884
|
+
return { match: true };
|
|
885
|
+
}
|
|
886
|
+
return {
|
|
887
|
+
match: false,
|
|
888
|
+
diff: generateDiff(existing, serialized)
|
|
889
|
+
};
|
|
890
|
+
},
|
|
891
|
+
/**
|
|
892
|
+
* Updates an existing snapshot with new data
|
|
893
|
+
*/
|
|
894
|
+
updateSnapshot(data, name) {
|
|
895
|
+
snapshots.set(name, serialize(data));
|
|
896
|
+
},
|
|
897
|
+
/**
|
|
898
|
+
* Deletes a snapshot
|
|
899
|
+
*/
|
|
900
|
+
deleteSnapshot(name) {
|
|
901
|
+
snapshots.delete(name);
|
|
902
|
+
},
|
|
903
|
+
/**
|
|
904
|
+
* Lists all snapshot names
|
|
905
|
+
*/
|
|
906
|
+
listSnapshots() {
|
|
907
|
+
return Array.from(snapshots.keys());
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// src/integration.ts
|
|
913
|
+
var import_core5 = require("@movebridge/core");
|
|
914
|
+
function createIntegrationUtils(network) {
|
|
915
|
+
if (network !== "testnet") {
|
|
916
|
+
throw new import_core5.MovementError(
|
|
917
|
+
"Integration tests can only be run on testnet to prevent accidental mainnet usage",
|
|
918
|
+
"INVALID_ARGUMENT",
|
|
919
|
+
{ network, allowed: ["testnet"] }
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
return {
|
|
923
|
+
/**
|
|
924
|
+
* Creates a new test account and funds it from the faucet
|
|
925
|
+
* Note: This requires network access and should only be used in integration tests
|
|
926
|
+
*/
|
|
927
|
+
async createTestAccount() {
|
|
928
|
+
throw new import_core5.MovementError(
|
|
929
|
+
"createTestAccount requires network access. Use in integration tests only.",
|
|
930
|
+
"NETWORK_ERROR",
|
|
931
|
+
{ reason: "Not implemented for unit tests" }
|
|
932
|
+
);
|
|
933
|
+
},
|
|
934
|
+
/**
|
|
935
|
+
* Waits for an account to be funded
|
|
936
|
+
*/
|
|
937
|
+
async waitForFunding(address, timeout = 3e4) {
|
|
938
|
+
throw new import_core5.MovementError(
|
|
939
|
+
"waitForFunding requires network access. Use in integration tests only.",
|
|
940
|
+
"NETWORK_ERROR",
|
|
941
|
+
{ address, timeout, reason: "Not implemented for unit tests" }
|
|
942
|
+
);
|
|
943
|
+
},
|
|
944
|
+
/**
|
|
945
|
+
* Cleans up a test account by transferring remaining balance
|
|
946
|
+
*/
|
|
947
|
+
async cleanupTestAccount(account) {
|
|
948
|
+
throw new import_core5.MovementError(
|
|
949
|
+
"cleanupTestAccount requires network access. Use in integration tests only.",
|
|
950
|
+
"NETWORK_ERROR",
|
|
951
|
+
{ address: account.address, reason: "Not implemented for unit tests" }
|
|
952
|
+
);
|
|
953
|
+
},
|
|
954
|
+
/**
|
|
955
|
+
* Executes a callback with a temporary test account
|
|
956
|
+
*/
|
|
957
|
+
async withTestAccount(callback) {
|
|
958
|
+
const account = await this.createTestAccount();
|
|
959
|
+
try {
|
|
960
|
+
return await callback(account);
|
|
961
|
+
} finally {
|
|
962
|
+
await this.cleanupTestAccount(account);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
968
|
+
0 && (module.exports = {
|
|
969
|
+
PREDEFINED_SCHEMAS,
|
|
970
|
+
createCallTracker,
|
|
971
|
+
createFaker,
|
|
972
|
+
createIntegrationUtils,
|
|
973
|
+
createMockClient,
|
|
974
|
+
createNetworkSimulator,
|
|
975
|
+
createSnapshotUtils,
|
|
976
|
+
createTestHarness,
|
|
977
|
+
getAddressValidationDetails,
|
|
978
|
+
getValidationErrors,
|
|
979
|
+
hasSchema,
|
|
980
|
+
isValidAddress,
|
|
981
|
+
normalizeAddress,
|
|
982
|
+
registerSchema,
|
|
983
|
+
validateAddress,
|
|
984
|
+
validateEntryFunctionPayload,
|
|
985
|
+
validatePayload,
|
|
986
|
+
validateSchema,
|
|
987
|
+
validateTransferPayload
|
|
988
|
+
});
|