@secondlayer/cli 1.2.0 → 1.2.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/dist/cli.js +25662 -17324
- package/dist/cli.js.map +75 -21
- package/dist/index.d.ts +5 -4
- package/dist/index.js +778 -1036
- package/dist/index.js.map +18 -13
- package/dist/plugin-manager.d.ts +5 -4
- package/dist/plugin-manager.js +30 -17
- package/dist/plugin-manager.js.map +5 -4
- package/package.json +10 -7
package/dist/index.js
CHANGED
|
@@ -1,20 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
4
2
|
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
-
for (let key of __getOwnPropNames(mod))
|
|
11
|
-
if (!__hasOwnProp.call(to, key))
|
|
12
|
-
__defProp(to, key, {
|
|
13
|
-
get: () => mod[key],
|
|
14
|
-
enumerable: true
|
|
15
|
-
});
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
3
|
var __export = (target, all) => {
|
|
19
4
|
for (var name in all)
|
|
20
5
|
__defProp(target, name, {
|
|
@@ -81,7 +66,19 @@ var init_format = () => {};
|
|
|
81
66
|
// src/core/plugin-manager.ts
|
|
82
67
|
import { promises as fs } from "fs";
|
|
83
68
|
import path from "path";
|
|
84
|
-
import {
|
|
69
|
+
import { isValidAddress as _validateStacksAddress } from "@secondlayer/stacks";
|
|
70
|
+
|
|
71
|
+
// src/utils/contract-id.ts
|
|
72
|
+
function parseContractId(contractId) {
|
|
73
|
+
const dotIndex = contractId.indexOf(".");
|
|
74
|
+
if (dotIndex === -1) {
|
|
75
|
+
throw new Error(`Invalid contract ID: "${contractId}" (expected "address.contractName")`);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
address: contractId.slice(0, dotIndex),
|
|
79
|
+
contractName: contractId.slice(dotIndex + 1)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
85
82
|
|
|
86
83
|
// src/types/plugin.ts
|
|
87
84
|
function isClarinetContract(c) {
|
|
@@ -92,6 +89,8 @@ function isDirectFileContract(c) {
|
|
|
92
89
|
}
|
|
93
90
|
|
|
94
91
|
// src/core/plugin-manager.ts
|
|
92
|
+
var validateStacksAddress = _validateStacksAddress;
|
|
93
|
+
|
|
95
94
|
class PluginManager {
|
|
96
95
|
plugins = [];
|
|
97
96
|
logger;
|
|
@@ -153,11 +152,11 @@ class PluginManager {
|
|
|
153
152
|
for (let contract of contracts) {
|
|
154
153
|
if (isClarinetContract(contract) && contract.abi) {
|
|
155
154
|
const address = typeof contract.address === "string" ? contract.address : "";
|
|
156
|
-
const
|
|
155
|
+
const parsed = parseContractId(address);
|
|
157
156
|
const processed = {
|
|
158
|
-
name: contract.name || contractName,
|
|
159
|
-
address:
|
|
160
|
-
contractName,
|
|
157
|
+
name: contract.name || parsed.contractName,
|
|
158
|
+
address: parsed.address,
|
|
159
|
+
contractName: parsed.contractName,
|
|
161
160
|
abi: contract.abi,
|
|
162
161
|
source: "local",
|
|
163
162
|
metadata: { source: "clarinet" }
|
|
@@ -167,11 +166,11 @@ class PluginManager {
|
|
|
167
166
|
}
|
|
168
167
|
if (isDirectFileContract(contract) && contract.abi) {
|
|
169
168
|
const address = typeof contract.address === "string" ? contract.address : "";
|
|
170
|
-
const
|
|
169
|
+
const parsed = parseContractId(address);
|
|
171
170
|
const processed = {
|
|
172
|
-
name: contract.name || contractName,
|
|
173
|
-
address:
|
|
174
|
-
contractName,
|
|
171
|
+
name: contract.name || parsed.contractName,
|
|
172
|
+
address: parsed.address,
|
|
173
|
+
contractName: parsed.contractName,
|
|
175
174
|
abi: contract.abi,
|
|
176
175
|
source: "local",
|
|
177
176
|
metadata: { source: "direct" }
|
|
@@ -199,11 +198,11 @@ class PluginManager {
|
|
|
199
198
|
}
|
|
200
199
|
if (contract.abi) {
|
|
201
200
|
const addressStr = typeof contract.address === "string" ? contract.address : "";
|
|
202
|
-
const
|
|
201
|
+
const parsed = parseContractId(addressStr);
|
|
203
202
|
const processed = {
|
|
204
|
-
name: contract.name ||
|
|
205
|
-
address:
|
|
206
|
-
contractName:
|
|
203
|
+
name: contract.name || parsed.contractName || "unknown",
|
|
204
|
+
address: parsed.address || "unknown",
|
|
205
|
+
contractName: parsed.contractName || contract.name || "unknown",
|
|
207
206
|
abi: contract.abi,
|
|
208
207
|
source: "api",
|
|
209
208
|
metadata: contract.metadata
|
|
@@ -344,11 +343,10 @@ ${JSON.stringify(content, null, 2)}`;
|
|
|
344
343
|
return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
345
344
|
},
|
|
346
345
|
validateAddress: (address) => {
|
|
347
|
-
return validateStacksAddress(address.
|
|
346
|
+
return validateStacksAddress(parseContractId(address).address);
|
|
348
347
|
},
|
|
349
348
|
parseContractId: (contractId) => {
|
|
350
|
-
|
|
351
|
-
return { address, contractName };
|
|
349
|
+
return parseContractId(contractId);
|
|
352
350
|
},
|
|
353
351
|
formatCode: async (code) => {
|
|
354
352
|
const { formatCode: formatCode2 } = await Promise.resolve().then(() => (init_format(), exports_format));
|
|
@@ -379,25 +377,25 @@ ${JSON.stringify(content, null, 2)}`;
|
|
|
379
377
|
}
|
|
380
378
|
// src/plugins/clarinet/index.ts
|
|
381
379
|
import { initSimnet } from "@hirosystems/clarinet-sdk";
|
|
382
|
-
import { toCamelCase as
|
|
380
|
+
import { toCamelCase as toCamelCase4 } from "@secondlayer/stacks/clarity";
|
|
383
381
|
|
|
384
382
|
// src/generators/contract.ts
|
|
385
383
|
init_format();
|
|
386
384
|
import {
|
|
387
|
-
toCamelCase as
|
|
388
|
-
} from "@secondlayer/clarity
|
|
385
|
+
toCamelCase as toCamelCase3
|
|
386
|
+
} from "@secondlayer/stacks/clarity";
|
|
389
387
|
|
|
390
388
|
// src/utils/type-mapping.ts
|
|
391
389
|
import {
|
|
392
390
|
toCamelCase,
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
} from "@secondlayer/clarity
|
|
391
|
+
isAbiList,
|
|
392
|
+
isAbiTuple,
|
|
393
|
+
isAbiOptional,
|
|
394
|
+
isAbiResponse,
|
|
395
|
+
isAbiBuffer,
|
|
396
|
+
isAbiStringAscii,
|
|
397
|
+
isAbiStringUtf8
|
|
398
|
+
} from "@secondlayer/stacks/clarity";
|
|
401
399
|
function clarityTypeToTS(type) {
|
|
402
400
|
if (typeof type === "string") {
|
|
403
401
|
switch (type) {
|
|
@@ -427,31 +425,31 @@ function clarityTypeToTS(type) {
|
|
|
427
425
|
}
|
|
428
426
|
}
|
|
429
427
|
}
|
|
430
|
-
if (
|
|
428
|
+
if (isAbiBuffer(type)) {
|
|
431
429
|
return "Uint8Array | string | { type: 'ascii' | 'utf8' | 'hex'; value: string }";
|
|
432
430
|
}
|
|
433
|
-
if (
|
|
431
|
+
if (isAbiStringAscii(type) || isAbiStringUtf8(type)) {
|
|
434
432
|
return "string";
|
|
435
433
|
}
|
|
436
|
-
if (
|
|
434
|
+
if (isAbiOptional(type)) {
|
|
437
435
|
const innerType = clarityTypeToTS(type.optional);
|
|
438
436
|
if (innerType.includes(" | ") && !innerType.startsWith("(")) {
|
|
439
437
|
return `(${innerType}) | null`;
|
|
440
438
|
}
|
|
441
439
|
return `${innerType} | null`;
|
|
442
440
|
}
|
|
443
|
-
if (
|
|
441
|
+
if (isAbiList(type)) {
|
|
444
442
|
const innerType = clarityTypeToTS(type.list.type);
|
|
445
443
|
if (innerType.includes(" | ") && !innerType.startsWith("(")) {
|
|
446
444
|
return `(${innerType})[]`;
|
|
447
445
|
}
|
|
448
446
|
return `${innerType}[]`;
|
|
449
447
|
}
|
|
450
|
-
if (
|
|
448
|
+
if (isAbiTuple(type)) {
|
|
451
449
|
const fields = type.tuple.map((field) => `${toCamelCase(field.name)}: ${clarityTypeToTS(field.type)}`).join("; ");
|
|
452
450
|
return `{ ${fields} }`;
|
|
453
451
|
}
|
|
454
|
-
if (
|
|
452
|
+
if (isAbiResponse(type)) {
|
|
455
453
|
const okType = clarityTypeToTS(type.response.ok);
|
|
456
454
|
const errType = clarityTypeToTS(type.response.error);
|
|
457
455
|
return `{ ok: ${okType} } | { err: ${errType} }`;
|
|
@@ -462,6 +460,145 @@ function getTypeForArg(arg) {
|
|
|
462
460
|
return clarityTypeToTS(arg.type);
|
|
463
461
|
}
|
|
464
462
|
|
|
463
|
+
// src/utils/clarity-conversion.ts
|
|
464
|
+
import {
|
|
465
|
+
toCamelCase as toCamelCase2,
|
|
466
|
+
isAbiStringAscii as isAbiStringAscii2,
|
|
467
|
+
isAbiStringUtf8 as isAbiStringUtf82,
|
|
468
|
+
isAbiBuffer as isAbiBuffer2,
|
|
469
|
+
isAbiOptional as isAbiOptional2,
|
|
470
|
+
isAbiList as isAbiList2,
|
|
471
|
+
isAbiTuple as isAbiTuple2,
|
|
472
|
+
isAbiResponse as isAbiResponse2
|
|
473
|
+
} from "@secondlayer/stacks/clarity";
|
|
474
|
+
function generateClarityConversion(argName, argType) {
|
|
475
|
+
const type = argType.type;
|
|
476
|
+
if (typeof type === "string") {
|
|
477
|
+
switch (type) {
|
|
478
|
+
case "uint128":
|
|
479
|
+
return `Cl.uint(${argName})`;
|
|
480
|
+
case "int128":
|
|
481
|
+
return `Cl.int(${argName})`;
|
|
482
|
+
case "bool":
|
|
483
|
+
return `Cl.bool(${argName})`;
|
|
484
|
+
case "principal":
|
|
485
|
+
case "trait_reference":
|
|
486
|
+
return `(() => {
|
|
487
|
+
const [address, contractName] = ${argName}.split(".") as [string, string | undefined];
|
|
488
|
+
if (!validateStacksAddress(address)) {
|
|
489
|
+
throw new Error("Invalid Stacks address format");
|
|
490
|
+
}
|
|
491
|
+
if (contractName !== undefined) {
|
|
492
|
+
if (!CONTRACT_NAME_REGEX.test(contractName)) {
|
|
493
|
+
throw new Error("Invalid contract name format: must start with letter and contain only letters, numbers, and hyphens");
|
|
494
|
+
}
|
|
495
|
+
return Cl.contractPrincipal(address, contractName);
|
|
496
|
+
}
|
|
497
|
+
return Cl.standardPrincipal(${argName});
|
|
498
|
+
})()`;
|
|
499
|
+
default:
|
|
500
|
+
return `${argName}`;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (isAbiStringAscii2(type)) {
|
|
504
|
+
return `Cl.stringAscii(${argName})`;
|
|
505
|
+
}
|
|
506
|
+
if (isAbiStringUtf82(type)) {
|
|
507
|
+
return `Cl.stringUtf8(${argName})`;
|
|
508
|
+
}
|
|
509
|
+
if (isAbiBuffer2(type)) {
|
|
510
|
+
return `(() => {
|
|
511
|
+
const value = ${argName};
|
|
512
|
+
if (value instanceof Uint8Array) {
|
|
513
|
+
return Cl.buffer(value);
|
|
514
|
+
}
|
|
515
|
+
if (typeof value === 'object' && value !== null && value.type && value.value) {
|
|
516
|
+
switch (value.type) {
|
|
517
|
+
case 'ascii':
|
|
518
|
+
return Cl.bufferFromAscii(value.value);
|
|
519
|
+
case 'utf8':
|
|
520
|
+
return Cl.bufferFromUtf8(value.value);
|
|
521
|
+
case 'hex':
|
|
522
|
+
return Cl.bufferFromHex(value.value);
|
|
523
|
+
default:
|
|
524
|
+
throw new Error(\`Unsupported buffer type: \${value.type}\`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (typeof value === 'string') {
|
|
528
|
+
if (value.startsWith('0x') || /^[0-9a-fA-F]+$/.test(value)) {
|
|
529
|
+
return Cl.bufferFromHex(value);
|
|
530
|
+
}
|
|
531
|
+
const hasNonAscii = value.split('').some(char => char.charCodeAt(0) > 127);
|
|
532
|
+
if (hasNonAscii) {
|
|
533
|
+
return Cl.bufferFromUtf8(value);
|
|
534
|
+
}
|
|
535
|
+
return Cl.bufferFromAscii(value);
|
|
536
|
+
}
|
|
537
|
+
throw new Error(\`Invalid buffer value: \${value}\`);
|
|
538
|
+
})()`;
|
|
539
|
+
}
|
|
540
|
+
if (isAbiOptional2(type)) {
|
|
541
|
+
const innerConversion = generateClarityConversion(argName, {
|
|
542
|
+
type: type.optional
|
|
543
|
+
});
|
|
544
|
+
return `${argName} !== null ? Cl.some(${innerConversion.replace(argName, `${argName}`)}) : Cl.none()`;
|
|
545
|
+
}
|
|
546
|
+
if (isAbiList2(type)) {
|
|
547
|
+
const innerConversion = generateClarityConversion("item", {
|
|
548
|
+
type: type.list.type
|
|
549
|
+
});
|
|
550
|
+
const maxLength = type.list.length || 100;
|
|
551
|
+
return `(() => {
|
|
552
|
+
const listValue = ${argName};
|
|
553
|
+
if (listValue.length > ${maxLength}) {
|
|
554
|
+
throw new Error(\`List length \${listValue.length} exceeds max ${maxLength}\`);
|
|
555
|
+
}
|
|
556
|
+
return Cl.list(listValue.map(item => ${innerConversion}));
|
|
557
|
+
})()`;
|
|
558
|
+
}
|
|
559
|
+
if (isAbiTuple2(type)) {
|
|
560
|
+
const requiredFields = type.tuple.map((f) => f.name);
|
|
561
|
+
const fieldNames = JSON.stringify(requiredFields);
|
|
562
|
+
const fields = type.tuple.map((field) => {
|
|
563
|
+
const camelFieldName = toCamelCase2(field.name);
|
|
564
|
+
const fieldConversion = generateClarityConversion(`tupleValue.${camelFieldName}`, { type: field.type });
|
|
565
|
+
return `"${field.name}": ${fieldConversion}`;
|
|
566
|
+
}).join(", ");
|
|
567
|
+
return `(() => {
|
|
568
|
+
const tupleValue = ${argName};
|
|
569
|
+
const requiredFields = ${fieldNames};
|
|
570
|
+
for (const fieldName of requiredFields) {
|
|
571
|
+
const camelName = fieldName.replace(/-([a-z])/g, (_: string, l: string) => l.toUpperCase());
|
|
572
|
+
if (!(fieldName in tupleValue) && !(camelName in tupleValue)) {
|
|
573
|
+
throw new Error(\`Missing tuple field: \${fieldName}\`);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return Cl.tuple({ ${fields} });
|
|
577
|
+
})()`;
|
|
578
|
+
}
|
|
579
|
+
if (isAbiResponse2(type)) {
|
|
580
|
+
const okConversion = generateClarityConversion(`responseValue.ok`, {
|
|
581
|
+
type: type.response.ok
|
|
582
|
+
});
|
|
583
|
+
const errConversion = generateClarityConversion(`responseValue.err`, {
|
|
584
|
+
type: type.response.error
|
|
585
|
+
});
|
|
586
|
+
return `(() => {
|
|
587
|
+
const responseValue = ${argName};
|
|
588
|
+
const hasOk = 'ok' in responseValue;
|
|
589
|
+
const hasErr = 'err' in responseValue;
|
|
590
|
+
if (hasOk && !hasErr) {
|
|
591
|
+
return Cl.ok(${okConversion});
|
|
592
|
+
}
|
|
593
|
+
if (hasErr && !hasOk) {
|
|
594
|
+
return Cl.error(${errConversion});
|
|
595
|
+
}
|
|
596
|
+
throw new Error("Response must have exactly 'ok' or 'err' property");
|
|
597
|
+
})()`;
|
|
598
|
+
}
|
|
599
|
+
return `${argName}`;
|
|
600
|
+
}
|
|
601
|
+
|
|
465
602
|
// src/generators/contract.ts
|
|
466
603
|
function generateNetworkUtils() {
|
|
467
604
|
return `/**
|
|
@@ -502,12 +639,12 @@ function generateValidationUtils() {
|
|
|
502
639
|
const CONTRACT_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9\\-]{0,127}$/;`;
|
|
503
640
|
}
|
|
504
641
|
async function generateContractInterface(contracts) {
|
|
505
|
-
const imports = `import { Cl, validateStacksAddress } from '@stacks
|
|
642
|
+
const imports = `import { Cl, validateStacksAddress } from '@secondlayer/stacks'`;
|
|
506
643
|
const header = `/**
|
|
507
644
|
* Generated by @secondlayer/cli
|
|
508
645
|
* DO NOT EDIT MANUALLY
|
|
509
646
|
*
|
|
510
|
-
* @requires @stacks
|
|
647
|
+
* @requires @secondlayer/stacks - Install with: npm install @secondlayer/stacks
|
|
511
648
|
*/`;
|
|
512
649
|
const validationUtils = generateValidationUtils();
|
|
513
650
|
const networkUtils = generateNetworkUtils();
|
|
@@ -553,7 +690,7 @@ function generateAbiConstant(name, abi) {
|
|
|
553
690
|
return `export const ${name}Abi = ${abiJson} as const`;
|
|
554
691
|
}
|
|
555
692
|
function generateMethod(func, address, contractName) {
|
|
556
|
-
const methodName =
|
|
693
|
+
const methodName = toCamelCase3(func.name);
|
|
557
694
|
if (func.args.length === 0) {
|
|
558
695
|
return `${methodName}() {
|
|
559
696
|
return {
|
|
@@ -566,7 +703,7 @@ function generateMethod(func, address, contractName) {
|
|
|
566
703
|
}
|
|
567
704
|
if (func.args.length === 1) {
|
|
568
705
|
const originalArgName = func.args[0].name;
|
|
569
|
-
const argName =
|
|
706
|
+
const argName = toCamelCase3(originalArgName);
|
|
570
707
|
const argType = getTypeForArg(func.args[0]);
|
|
571
708
|
const clarityConversion = generateClarityConversion(argName, func.args[0]);
|
|
572
709
|
return `${methodName}(...args: [{ ${argName}: ${argType} }] | [${argType}]) {
|
|
@@ -582,17 +719,17 @@ function generateMethod(func, address, contractName) {
|
|
|
582
719
|
}
|
|
583
720
|
}`;
|
|
584
721
|
}
|
|
585
|
-
const argsList = func.args.map((arg) =>
|
|
722
|
+
const argsList = func.args.map((arg) => toCamelCase3(arg.name)).join(", ");
|
|
586
723
|
const argsTypes = func.args.map((arg) => {
|
|
587
|
-
const camelName =
|
|
724
|
+
const camelName = toCamelCase3(arg.name);
|
|
588
725
|
return `${camelName}: ${getTypeForArg(arg)}`;
|
|
589
726
|
}).join("; ");
|
|
590
727
|
const argsArray = func.args.map((arg) => {
|
|
591
|
-
const argName =
|
|
728
|
+
const argName = toCamelCase3(arg.name);
|
|
592
729
|
return generateClarityConversion(argName, arg);
|
|
593
730
|
}).join(", ");
|
|
594
731
|
const objectAccess = func.args.map((arg) => {
|
|
595
|
-
const camelName =
|
|
732
|
+
const camelName = toCamelCase3(arg.name);
|
|
596
733
|
return `args[0].${camelName}`;
|
|
597
734
|
}).join(", ");
|
|
598
735
|
const positionTypes = func.args.map((arg) => getTypeForArg(arg)).join(", ");
|
|
@@ -609,152 +746,19 @@ function generateMethod(func, address, contractName) {
|
|
|
609
746
|
}
|
|
610
747
|
}`;
|
|
611
748
|
}
|
|
612
|
-
function generateClarityConversion(argName, argType) {
|
|
613
|
-
const type = argType.type;
|
|
614
|
-
if (typeof type === "string") {
|
|
615
|
-
switch (type) {
|
|
616
|
-
case "uint128":
|
|
617
|
-
return `Cl.uint(${argName})`;
|
|
618
|
-
case "int128":
|
|
619
|
-
return `Cl.int(${argName})`;
|
|
620
|
-
case "bool":
|
|
621
|
-
return `Cl.bool(${argName})`;
|
|
622
|
-
case "principal":
|
|
623
|
-
case "trait_reference":
|
|
624
|
-
return `(() => {
|
|
625
|
-
const [address, contractName] = ${argName}.split(".") as [string, string | undefined];
|
|
626
|
-
if (!validateStacksAddress(address)) {
|
|
627
|
-
throw new Error("Invalid Stacks address format");
|
|
628
|
-
}
|
|
629
|
-
if (contractName !== undefined) {
|
|
630
|
-
if (!CONTRACT_NAME_REGEX.test(contractName)) {
|
|
631
|
-
throw new Error("Invalid contract name format: must start with letter and contain only letters, numbers, and hyphens");
|
|
632
|
-
}
|
|
633
|
-
return Cl.contractPrincipal(address, contractName);
|
|
634
|
-
}
|
|
635
|
-
return Cl.standardPrincipal(${argName});
|
|
636
|
-
})()`;
|
|
637
|
-
default:
|
|
638
|
-
return `${argName}`;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
if (type["string-ascii"]) {
|
|
642
|
-
return `Cl.stringAscii(${argName})`;
|
|
643
|
-
}
|
|
644
|
-
if (type["string-utf8"]) {
|
|
645
|
-
return `Cl.stringUtf8(${argName})`;
|
|
646
|
-
}
|
|
647
|
-
if (type.buff) {
|
|
648
|
-
return `(() => {
|
|
649
|
-
const value = ${argName};
|
|
650
|
-
// Direct Uint8Array
|
|
651
|
-
if (value instanceof Uint8Array) {
|
|
652
|
-
return Cl.buffer(value);
|
|
653
|
-
}
|
|
654
|
-
// Object notation with explicit type
|
|
655
|
-
if (typeof value === 'object' && value !== null && value.type && value.value) {
|
|
656
|
-
switch (value.type) {
|
|
657
|
-
case 'ascii':
|
|
658
|
-
return Cl.bufferFromAscii(value.value);
|
|
659
|
-
case 'utf8':
|
|
660
|
-
return Cl.bufferFromUtf8(value.value);
|
|
661
|
-
case 'hex':
|
|
662
|
-
return Cl.bufferFromHex(value.value);
|
|
663
|
-
default:
|
|
664
|
-
throw new Error(\`Unsupported buffer type: \${value.type}\`);
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
// Auto-detect string type
|
|
668
|
-
if (typeof value === 'string') {
|
|
669
|
-
// Check for hex (0x prefix or pure hex pattern)
|
|
670
|
-
if (value.startsWith('0x') || /^[0-9a-fA-F]+$/.test(value)) {
|
|
671
|
-
return Cl.bufferFromHex(value);
|
|
672
|
-
}
|
|
673
|
-
// Check for non-ASCII characters (UTF-8) using char code comparison
|
|
674
|
-
const hasNonAscii = value.split('').some(char => char.charCodeAt(0) > 127);
|
|
675
|
-
if (hasNonAscii) {
|
|
676
|
-
return Cl.bufferFromUtf8(value);
|
|
677
|
-
}
|
|
678
|
-
// Default to ASCII for simple ASCII strings
|
|
679
|
-
return Cl.bufferFromAscii(value);
|
|
680
|
-
}
|
|
681
|
-
throw new Error(\`Invalid buffer value: \${value}\`);
|
|
682
|
-
})()`;
|
|
683
|
-
}
|
|
684
|
-
if (type.optional) {
|
|
685
|
-
const innerConversion = generateClarityConversion(argName, {
|
|
686
|
-
type: type.optional
|
|
687
|
-
});
|
|
688
|
-
return `${argName} !== null ? Cl.some(${innerConversion.replace(argName, `${argName}`)}) : Cl.none()`;
|
|
689
|
-
}
|
|
690
|
-
if (type.list) {
|
|
691
|
-
const innerConversion = generateClarityConversion("item", {
|
|
692
|
-
type: type.list.type
|
|
693
|
-
});
|
|
694
|
-
const maxLength = type.list.length || 100;
|
|
695
|
-
return `(() => {
|
|
696
|
-
const listValue = ${argName};
|
|
697
|
-
if (listValue.length > ${maxLength}) {
|
|
698
|
-
throw new Error(\`List length \${listValue.length} exceeds max ${maxLength}\`);
|
|
699
|
-
}
|
|
700
|
-
return Cl.list(listValue.map(item => ${innerConversion}));
|
|
701
|
-
})()`;
|
|
702
|
-
}
|
|
703
|
-
if (type.tuple) {
|
|
704
|
-
const requiredFields = type.tuple.map((f) => f.name);
|
|
705
|
-
const fieldNames = JSON.stringify(requiredFields);
|
|
706
|
-
const fields = type.tuple.map((field) => {
|
|
707
|
-
const camelFieldName = toCamelCase2(field.name);
|
|
708
|
-
const fieldConversion = generateClarityConversion(`tupleValue.${camelFieldName}`, { type: field.type });
|
|
709
|
-
return `"${field.name}": ${fieldConversion}`;
|
|
710
|
-
}).join(", ");
|
|
711
|
-
return `(() => {
|
|
712
|
-
const tupleValue = ${argName};
|
|
713
|
-
const requiredFields = ${fieldNames};
|
|
714
|
-
for (const fieldName of requiredFields) {
|
|
715
|
-
const camelName = fieldName.replace(/-([a-z])/g, (_: string, l: string) => l.toUpperCase());
|
|
716
|
-
if (!(fieldName in tupleValue) && !(camelName in tupleValue)) {
|
|
717
|
-
throw new Error(\`Missing tuple field: \${fieldName}\`);
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
return Cl.tuple({ ${fields} });
|
|
721
|
-
})()`;
|
|
722
|
-
}
|
|
723
|
-
if (type.response) {
|
|
724
|
-
const okConversion = generateClarityConversion(`responseValue.ok`, {
|
|
725
|
-
type: type.response.ok
|
|
726
|
-
});
|
|
727
|
-
const errConversion = generateClarityConversion(`responseValue.err`, {
|
|
728
|
-
type: type.response.error
|
|
729
|
-
});
|
|
730
|
-
return `(() => {
|
|
731
|
-
const responseValue = ${argName};
|
|
732
|
-
const hasOk = 'ok' in responseValue;
|
|
733
|
-
const hasErr = 'err' in responseValue;
|
|
734
|
-
if (hasOk && !hasErr) {
|
|
735
|
-
return Cl.ok(${okConversion});
|
|
736
|
-
}
|
|
737
|
-
if (hasErr && !hasOk) {
|
|
738
|
-
return Cl.error(${errConversion});
|
|
739
|
-
}
|
|
740
|
-
throw new Error("Response must have exactly 'ok' or 'err' property");
|
|
741
|
-
})()`;
|
|
742
|
-
}
|
|
743
|
-
return `${argName}`;
|
|
744
|
-
}
|
|
745
749
|
function generateMapsObject(maps, address, contractName) {
|
|
746
750
|
if (!maps || maps.length === 0) {
|
|
747
751
|
return "";
|
|
748
752
|
}
|
|
749
753
|
const mapMethods = maps.map((map) => {
|
|
750
|
-
const methodName =
|
|
754
|
+
const methodName = toCamelCase3(map.name);
|
|
751
755
|
const keyType = getTypeForArg({ type: map.key });
|
|
752
756
|
const valueType = getTypeForArg({ type: map.value });
|
|
753
757
|
const keyConversion = generateMapKeyConversion(map.key);
|
|
754
758
|
return `${methodName}: {
|
|
755
759
|
async get(key: ${keyType}, options?: { network?: 'mainnet' | 'testnet' | 'devnet' }): Promise<${valueType} | null> {
|
|
756
760
|
try {
|
|
757
|
-
const { cvToJSON, serializeCV } = await import('@stacks/
|
|
761
|
+
const { cvToJSON, serializeCV } = await import('@secondlayer/stacks/clarity');
|
|
758
762
|
const baseUrl = getApiUrl('${address}', options?.network);
|
|
759
763
|
const mapKey = ${keyConversion};
|
|
760
764
|
const keyHex = serializeCV(mapKey).toString('hex');
|
|
@@ -777,7 +781,7 @@ function generateMapsObject(maps, address, contractName) {
|
|
|
777
781
|
return null; // none value
|
|
778
782
|
}
|
|
779
783
|
|
|
780
|
-
const { deserializeCV } = await import('@stacks/
|
|
784
|
+
const { deserializeCV } = await import('@secondlayer/stacks/clarity');
|
|
781
785
|
const cv = deserializeCV(result.data);
|
|
782
786
|
const parsed = cvToJSON(cv);
|
|
783
787
|
// Unwrap the (some ...) wrapper
|
|
@@ -808,12 +812,12 @@ function generateVarsObject(variables, address, contractName) {
|
|
|
808
812
|
return "";
|
|
809
813
|
}
|
|
810
814
|
const varMethods = dataVars.map((variable) => {
|
|
811
|
-
const methodName =
|
|
815
|
+
const methodName = toCamelCase3(variable.name);
|
|
812
816
|
const valueType = getTypeForArg({ type: variable.type });
|
|
813
817
|
return `${methodName}: {
|
|
814
818
|
async get(options?: { network?: 'mainnet' | 'testnet' | 'devnet' }): Promise<${valueType}> {
|
|
815
819
|
try {
|
|
816
|
-
const { cvToJSON, deserializeCV } = await import('@stacks/
|
|
820
|
+
const { cvToJSON, deserializeCV } = await import('@secondlayer/stacks/clarity');
|
|
817
821
|
const baseUrl = getApiUrl('${address}', options?.network);
|
|
818
822
|
|
|
819
823
|
const response = await fetch(
|
|
@@ -853,12 +857,12 @@ function generateConstantsObject(variables, address, contractName) {
|
|
|
853
857
|
return "";
|
|
854
858
|
}
|
|
855
859
|
const constMethods = constants.map((constant) => {
|
|
856
|
-
const methodName =
|
|
860
|
+
const methodName = toCamelCase3(constant.name);
|
|
857
861
|
const valueType = getTypeForArg({ type: constant.type });
|
|
858
862
|
return `${methodName}: {
|
|
859
863
|
async get(options?: { network?: 'mainnet' | 'testnet' | 'devnet' }): Promise<${valueType}> {
|
|
860
864
|
try {
|
|
861
|
-
const { cvToJSON, deserializeCV } = await import('@stacks/
|
|
865
|
+
const { cvToJSON, deserializeCV } = await import('@secondlayer/stacks/clarity');
|
|
862
866
|
const baseUrl = getApiUrl('${address}', options?.network);
|
|
863
867
|
|
|
864
868
|
const response = await fetch(
|
|
@@ -892,7 +896,7 @@ function generateConstantsObject(variables, address, contractName) {
|
|
|
892
896
|
function generateMapKeyConversion(keyType) {
|
|
893
897
|
if (keyType.tuple) {
|
|
894
898
|
const fields = keyType.tuple.map((field) => {
|
|
895
|
-
const camelFieldName =
|
|
899
|
+
const camelFieldName = toCamelCase3(field.name);
|
|
896
900
|
const fieldConversion = generateClarityConversion(`key.${camelFieldName}`, { type: field.type });
|
|
897
901
|
return `"${field.name}": ${fieldConversion}`;
|
|
898
902
|
}).join(", ");
|
|
@@ -901,14 +905,167 @@ function generateMapKeyConversion(keyType) {
|
|
|
901
905
|
return generateClarityConversion("key", { type: keyType });
|
|
902
906
|
}
|
|
903
907
|
|
|
904
|
-
// src/
|
|
905
|
-
function
|
|
906
|
-
|
|
908
|
+
// src/utils/abi-compat.ts
|
|
909
|
+
function normalizeAccess(access) {
|
|
910
|
+
if (access === "read_only")
|
|
911
|
+
return "read-only";
|
|
912
|
+
return access;
|
|
907
913
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
914
|
+
function normalizeType(type) {
|
|
915
|
+
if (typeof type === "string") {
|
|
916
|
+
switch (type) {
|
|
917
|
+
case "uint128":
|
|
918
|
+
case "int128":
|
|
919
|
+
case "bool":
|
|
920
|
+
case "principal":
|
|
921
|
+
case "trait_reference":
|
|
922
|
+
return type;
|
|
923
|
+
default:
|
|
924
|
+
return type;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
if (typeof type !== "object" || type === null) {
|
|
928
|
+
throw new Error(`Invalid ABI type: expected object, got ${typeof type}`);
|
|
929
|
+
}
|
|
930
|
+
const typeObj = type;
|
|
931
|
+
if ("buffer" in typeObj) {
|
|
932
|
+
const buffer = typeObj.buffer;
|
|
933
|
+
return {
|
|
934
|
+
buff: {
|
|
935
|
+
length: buffer?.length ?? 32
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
if ("buff" in typeObj) {
|
|
940
|
+
const buff = typeObj.buff;
|
|
941
|
+
return {
|
|
942
|
+
buff: {
|
|
943
|
+
length: buff?.length ?? 32
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
if ("string-ascii" in typeObj) {
|
|
948
|
+
const strAscii = typeObj["string-ascii"];
|
|
949
|
+
return {
|
|
950
|
+
"string-ascii": {
|
|
951
|
+
length: strAscii?.length ?? 256
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
if ("string-utf8" in typeObj) {
|
|
956
|
+
const strUtf8 = typeObj["string-utf8"];
|
|
957
|
+
return {
|
|
958
|
+
"string-utf8": {
|
|
959
|
+
length: strUtf8?.length ?? 256
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
if ("response" in typeObj) {
|
|
964
|
+
const response = typeObj.response;
|
|
965
|
+
return {
|
|
966
|
+
response: {
|
|
967
|
+
ok: normalizeType(response?.ok ?? "bool"),
|
|
968
|
+
error: normalizeType(response?.error ?? "uint128")
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
if ("optional" in typeObj) {
|
|
973
|
+
return {
|
|
974
|
+
optional: normalizeType(typeObj.optional)
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
if ("list" in typeObj) {
|
|
978
|
+
const list = typeObj.list;
|
|
979
|
+
return {
|
|
980
|
+
list: {
|
|
981
|
+
type: normalizeType(list?.type ?? "uint128"),
|
|
982
|
+
length: list?.length ?? 100
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
if ("tuple" in typeObj) {
|
|
987
|
+
const tuple = typeObj.tuple;
|
|
988
|
+
return {
|
|
989
|
+
tuple: tuple.map((field) => ({
|
|
990
|
+
name: field.name,
|
|
991
|
+
type: normalizeType(field.type)
|
|
992
|
+
}))
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
throw new Error(`Unknown ABI type structure: ${JSON.stringify(type)}`);
|
|
996
|
+
}
|
|
997
|
+
function normalizeFunction(func) {
|
|
998
|
+
const access = normalizeAccess(func.access);
|
|
999
|
+
const args = func.args ?? [];
|
|
1000
|
+
const outputs = func.outputs;
|
|
1001
|
+
return {
|
|
1002
|
+
name: func.name,
|
|
1003
|
+
access,
|
|
1004
|
+
args: args.map((arg) => ({
|
|
1005
|
+
name: arg.name,
|
|
1006
|
+
type: normalizeType(arg.type)
|
|
1007
|
+
})),
|
|
1008
|
+
outputs: normalizeType(typeof outputs === "object" && outputs !== null && "type" in outputs ? outputs.type : outputs)
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
function normalizeMap(map) {
|
|
1012
|
+
return {
|
|
1013
|
+
name: map.name,
|
|
1014
|
+
key: normalizeType(map.key),
|
|
1015
|
+
value: normalizeType(map.value)
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
function normalizeVariable(variable) {
|
|
1019
|
+
return {
|
|
1020
|
+
name: variable.name,
|
|
1021
|
+
type: normalizeType(variable.type),
|
|
1022
|
+
access: variable.access
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
function normalizeAbi(abi) {
|
|
1026
|
+
if (typeof abi !== "object" || abi === null) {
|
|
1027
|
+
return { functions: [] };
|
|
1028
|
+
}
|
|
1029
|
+
const abiObj = abi;
|
|
1030
|
+
const functions = [];
|
|
1031
|
+
const maps = [];
|
|
1032
|
+
const variables = [];
|
|
1033
|
+
if (Array.isArray(abiObj.functions)) {
|
|
1034
|
+
for (const func of abiObj.functions) {
|
|
1035
|
+
if (typeof func === "object" && func !== null) {
|
|
1036
|
+
functions.push(normalizeFunction(func));
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
if (Array.isArray(abiObj.maps)) {
|
|
1041
|
+
for (const map of abiObj.maps) {
|
|
1042
|
+
if (typeof map === "object" && map !== null) {
|
|
1043
|
+
maps.push(normalizeMap(map));
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
if (Array.isArray(abiObj.variables)) {
|
|
1048
|
+
for (const variable of abiObj.variables) {
|
|
1049
|
+
if (typeof variable === "object" && variable !== null) {
|
|
1050
|
+
variables.push(normalizeVariable(variable));
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
return {
|
|
1055
|
+
functions,
|
|
1056
|
+
maps: maps.length > 0 ? maps : undefined,
|
|
1057
|
+
variables: variables.length > 0 ? variables : undefined
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// src/plugins/clarinet/index.ts
|
|
1062
|
+
function sanitizeContractName(name) {
|
|
1063
|
+
return toCamelCase4(name);
|
|
1064
|
+
}
|
|
1065
|
+
async function isUserDefinedContract(contractId, manifestPath) {
|
|
1066
|
+
const { address, contractName } = parseContractId(contractId);
|
|
1067
|
+
try {
|
|
1068
|
+
const { promises: fs2 } = await import("fs");
|
|
912
1069
|
const tomlContent = await fs2.readFile(manifestPath, "utf-8");
|
|
913
1070
|
const contractSectionRegex = /^\[contracts\.([^\]]+)\]/gm;
|
|
914
1071
|
const userContracts = new Set;
|
|
@@ -950,7 +1107,7 @@ var clarinet = (options = {}) => {
|
|
|
950
1107
|
const contractInterfaces = simnet.getContractsInterfaces();
|
|
951
1108
|
const contracts = [];
|
|
952
1109
|
for (const [contractId, abi] of contractInterfaces) {
|
|
953
|
-
const
|
|
1110
|
+
const { contractName } = parseContractId(contractId);
|
|
954
1111
|
if (!await isUserDefinedContract(contractId, manifestPath)) {
|
|
955
1112
|
if (options.debug) {
|
|
956
1113
|
console.log(`\uD83D\uDEAB Skipping system contract: ${contractId}`);
|
|
@@ -967,7 +1124,7 @@ var clarinet = (options = {}) => {
|
|
|
967
1124
|
contracts.push({
|
|
968
1125
|
name: sanitizedName,
|
|
969
1126
|
address: contractId,
|
|
970
|
-
abi,
|
|
1127
|
+
abi: normalizeAbi(abi),
|
|
971
1128
|
_clarinetSource: true
|
|
972
1129
|
});
|
|
973
1130
|
}
|
|
@@ -1013,155 +1170,33 @@ async function hasClarinetProject(path2 = "./Clarinet.toml") {
|
|
|
1013
1170
|
}
|
|
1014
1171
|
}
|
|
1015
1172
|
// src/plugins/actions/generators.ts
|
|
1016
|
-
import { toCamelCase as
|
|
1173
|
+
import { toCamelCase as toCamelCase6 } from "@secondlayer/stacks/clarity";
|
|
1174
|
+
|
|
1175
|
+
// src/utils/generator-helpers.ts
|
|
1176
|
+
import { toCamelCase as toCamelCase5 } from "@secondlayer/stacks/clarity";
|
|
1017
1177
|
function generateArgsSignature(args) {
|
|
1018
1178
|
if (args.length === 0)
|
|
1019
1179
|
return "";
|
|
1020
1180
|
const argsTypes = args.map((arg) => {
|
|
1021
|
-
const camelName =
|
|
1181
|
+
const camelName = toCamelCase5(arg.name);
|
|
1022
1182
|
return `${camelName}: ${getTypeForArg(arg)}`;
|
|
1023
1183
|
}).join("; ");
|
|
1024
1184
|
return `args: { ${argsTypes} }, `;
|
|
1025
1185
|
}
|
|
1026
|
-
function generateClarityArgs(args
|
|
1186
|
+
function generateClarityArgs(args) {
|
|
1027
1187
|
if (args.length === 0)
|
|
1028
1188
|
return "";
|
|
1029
1189
|
return args.map((arg) => {
|
|
1030
|
-
const argName = `args.${
|
|
1031
|
-
return
|
|
1190
|
+
const argName = `args.${toCamelCase5(arg.name)}`;
|
|
1191
|
+
return generateClarityConversion(argName, arg);
|
|
1032
1192
|
}).join(", ");
|
|
1033
1193
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
if (typeof type === "string") {
|
|
1037
|
-
switch (type) {
|
|
1038
|
-
case "uint128":
|
|
1039
|
-
return `Cl.uint(${argName})`;
|
|
1040
|
-
case "int128":
|
|
1041
|
-
return `Cl.int(${argName})`;
|
|
1042
|
-
case "bool":
|
|
1043
|
-
return `Cl.bool(${argName})`;
|
|
1044
|
-
case "principal":
|
|
1045
|
-
case "trait_reference":
|
|
1046
|
-
return `(() => {
|
|
1047
|
-
const [address, contractName] = ${argName}.split(".") as [string, string | undefined];
|
|
1048
|
-
if (!validateStacksAddress(address)) {
|
|
1049
|
-
throw new Error("Invalid Stacks address format");
|
|
1050
|
-
}
|
|
1051
|
-
if (contractName !== undefined) {
|
|
1052
|
-
if (!CONTRACT_NAME_REGEX.test(contractName)) {
|
|
1053
|
-
throw new Error("Invalid contract name format: must start with letter and contain only letters, numbers, and hyphens");
|
|
1054
|
-
}
|
|
1055
|
-
return Cl.contractPrincipal(address, contractName);
|
|
1056
|
-
}
|
|
1057
|
-
return Cl.standardPrincipal(${argName});
|
|
1058
|
-
})()`;
|
|
1059
|
-
default:
|
|
1060
|
-
return `${argName}`;
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
if (type["string-ascii"]) {
|
|
1064
|
-
return `Cl.stringAscii(${argName})`;
|
|
1065
|
-
}
|
|
1066
|
-
if (type["string-utf8"]) {
|
|
1067
|
-
return `Cl.stringUtf8(${argName})`;
|
|
1068
|
-
}
|
|
1069
|
-
if (type.buff) {
|
|
1070
|
-
return `(() => {
|
|
1071
|
-
const value = ${argName};
|
|
1072
|
-
if (value instanceof Uint8Array) {
|
|
1073
|
-
return Cl.buffer(value);
|
|
1074
|
-
}
|
|
1075
|
-
if (typeof value === 'object' && value !== null && value.type && value.value) {
|
|
1076
|
-
switch (value.type) {
|
|
1077
|
-
case 'ascii':
|
|
1078
|
-
return Cl.bufferFromAscii(value.value);
|
|
1079
|
-
case 'utf8':
|
|
1080
|
-
return Cl.bufferFromUtf8(value.value);
|
|
1081
|
-
case 'hex':
|
|
1082
|
-
return Cl.bufferFromHex(value.value);
|
|
1083
|
-
default:
|
|
1084
|
-
throw new Error(\`Unsupported buffer type: \${value.type}\`);
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
if (typeof value === 'string') {
|
|
1088
|
-
if (value.startsWith('0x') || /^[0-9a-fA-F]+$/.test(value)) {
|
|
1089
|
-
return Cl.bufferFromHex(value);
|
|
1090
|
-
}
|
|
1091
|
-
const hasNonAscii = value.split('').some(char => char.charCodeAt(0) > 127);
|
|
1092
|
-
if (hasNonAscii) {
|
|
1093
|
-
return Cl.bufferFromUtf8(value);
|
|
1094
|
-
}
|
|
1095
|
-
return Cl.bufferFromAscii(value);
|
|
1096
|
-
}
|
|
1097
|
-
throw new Error(\`Invalid buffer value: \${value}\`);
|
|
1098
|
-
})()`;
|
|
1099
|
-
}
|
|
1100
|
-
if (type.optional) {
|
|
1101
|
-
const innerConversion = generateClarityConversion2(argName, {
|
|
1102
|
-
type: type.optional
|
|
1103
|
-
});
|
|
1104
|
-
return `${argName} !== null ? Cl.some(${innerConversion.replace(argName, `${argName}`)}) : Cl.none()`;
|
|
1105
|
-
}
|
|
1106
|
-
if (type.list) {
|
|
1107
|
-
const innerConversion = generateClarityConversion2("item", {
|
|
1108
|
-
type: type.list.type
|
|
1109
|
-
});
|
|
1110
|
-
const maxLength = type.list.length || 100;
|
|
1111
|
-
return `(() => {
|
|
1112
|
-
const listValue = ${argName};
|
|
1113
|
-
if (listValue.length > ${maxLength}) {
|
|
1114
|
-
throw new Error(\`List length \${listValue.length} exceeds max ${maxLength}\`);
|
|
1115
|
-
}
|
|
1116
|
-
return Cl.list(listValue.map(item => ${innerConversion}));
|
|
1117
|
-
})()`;
|
|
1118
|
-
}
|
|
1119
|
-
if (type.tuple) {
|
|
1120
|
-
const requiredFields = type.tuple.map((f) => f.name);
|
|
1121
|
-
const fieldNames = JSON.stringify(requiredFields);
|
|
1122
|
-
const fields = type.tuple.map((field) => {
|
|
1123
|
-
const camelFieldName = toCamelCase4(field.name);
|
|
1124
|
-
const fieldConversion = generateClarityConversion2(`tupleValue.${camelFieldName}`, { type: field.type });
|
|
1125
|
-
return `"${field.name}": ${fieldConversion}`;
|
|
1126
|
-
}).join(", ");
|
|
1127
|
-
return `(() => {
|
|
1128
|
-
const tupleValue = ${argName};
|
|
1129
|
-
const requiredFields = ${fieldNames};
|
|
1130
|
-
for (const fieldName of requiredFields) {
|
|
1131
|
-
const camelName = fieldName.replace(/-([a-z])/g, (_: string, l: string) => l.toUpperCase());
|
|
1132
|
-
if (!(fieldName in tupleValue) && !(camelName in tupleValue)) {
|
|
1133
|
-
throw new Error(\`Missing tuple field: \${fieldName}\`);
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
return Cl.tuple({ ${fields} });
|
|
1137
|
-
})()`;
|
|
1138
|
-
}
|
|
1139
|
-
if (type.response) {
|
|
1140
|
-
const okConversion = generateClarityConversion2(`responseValue.ok`, {
|
|
1141
|
-
type: type.response.ok
|
|
1142
|
-
});
|
|
1143
|
-
const errConversion = generateClarityConversion2(`responseValue.err`, {
|
|
1144
|
-
type: type.response.error
|
|
1145
|
-
});
|
|
1146
|
-
return `(() => {
|
|
1147
|
-
const responseValue = ${argName};
|
|
1148
|
-
const hasOk = 'ok' in responseValue;
|
|
1149
|
-
const hasErr = 'err' in responseValue;
|
|
1150
|
-
if (hasOk && !hasErr) {
|
|
1151
|
-
return Cl.ok(${okConversion});
|
|
1152
|
-
}
|
|
1153
|
-
if (hasErr && !hasOk) {
|
|
1154
|
-
return Cl.error(${errConversion});
|
|
1155
|
-
}
|
|
1156
|
-
throw new Error("Response must have exactly 'ok' or 'err' property");
|
|
1157
|
-
})()`;
|
|
1158
|
-
}
|
|
1159
|
-
return `${argName}`;
|
|
1160
|
-
}
|
|
1194
|
+
|
|
1195
|
+
// src/plugins/actions/generators.ts
|
|
1161
1196
|
function generateReadHelpers(contract, options) {
|
|
1162
|
-
const { abi
|
|
1197
|
+
const { abi } = contract;
|
|
1163
1198
|
const functions = abi.functions || [];
|
|
1164
|
-
const readOnlyFunctions = functions.filter((f) => f.access === "
|
|
1199
|
+
const readOnlyFunctions = functions.filter((f) => f.access === "read-only");
|
|
1165
1200
|
if (readOnlyFunctions.length === 0) {
|
|
1166
1201
|
return "";
|
|
1167
1202
|
}
|
|
@@ -1178,9 +1213,9 @@ function generateReadHelpers(contract, options) {
|
|
|
1178
1213
|
return "";
|
|
1179
1214
|
}
|
|
1180
1215
|
const helpers = filteredFunctions.map((func) => {
|
|
1181
|
-
const methodName =
|
|
1216
|
+
const methodName = toCamelCase6(func.name);
|
|
1182
1217
|
const argsSignature = generateArgsSignature(func.args);
|
|
1183
|
-
const clarityArgs = generateClarityArgs(func.args
|
|
1218
|
+
const clarityArgs = generateClarityArgs(func.args);
|
|
1184
1219
|
return `async ${methodName}(${argsSignature}options?: {
|
|
1185
1220
|
network?: 'mainnet' | 'testnet' | 'devnet';
|
|
1186
1221
|
senderAddress?: string;
|
|
@@ -1202,7 +1237,7 @@ function generateReadHelpers(contract, options) {
|
|
|
1202
1237
|
}`;
|
|
1203
1238
|
}
|
|
1204
1239
|
function generateWriteHelpers(contract, options) {
|
|
1205
|
-
const { abi
|
|
1240
|
+
const { abi } = contract;
|
|
1206
1241
|
const functions = abi.functions || [];
|
|
1207
1242
|
const envVarName = options.senderKeyEnv ?? "STX_SENDER_KEY";
|
|
1208
1243
|
const publicFunctions = functions.filter((f) => f.access === "public");
|
|
@@ -1222,9 +1257,9 @@ function generateWriteHelpers(contract, options) {
|
|
|
1222
1257
|
return "";
|
|
1223
1258
|
}
|
|
1224
1259
|
const helpers = filteredFunctions.map((func) => {
|
|
1225
|
-
const methodName =
|
|
1260
|
+
const methodName = toCamelCase6(func.name);
|
|
1226
1261
|
const argsSignature = generateArgsSignature(func.args);
|
|
1227
|
-
const clarityArgs = generateClarityArgs(func.args
|
|
1262
|
+
const clarityArgs = generateClarityArgs(func.args);
|
|
1228
1263
|
return `async ${methodName}(${argsSignature}senderKey?: string, options?: {
|
|
1229
1264
|
network?: 'mainnet' | 'testnet' | 'devnet';
|
|
1230
1265
|
fee?: string | number | undefined;
|
|
@@ -1318,28 +1353,19 @@ var actions = (options = {}) => {
|
|
|
1318
1353
|
};
|
|
1319
1354
|
};
|
|
1320
1355
|
function addRequiredImports(content) {
|
|
1321
|
-
const
|
|
1322
|
-
const match = content.match(
|
|
1356
|
+
const stacksImportRegex = /import\s+\{([^}]+)\}\s+from\s+['"]@secondlayer\/stacks['"];/;
|
|
1357
|
+
const match = content.match(stacksImportRegex);
|
|
1323
1358
|
if (match) {
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
];
|
|
1329
|
-
const newImports = requiredImports.filter((imp) => !existingImports.includes(imp)).join(", ");
|
|
1330
|
-
if (newImports) {
|
|
1331
|
-
const updatedImport = `import { ${existingImports}, ${newImports} } from '@stacks/transactions';`;
|
|
1332
|
-
let updatedContent = content.replace(transactionsImportRegex, updatedImport);
|
|
1333
|
-
if (!updatedContent.includes("type PostCondition")) {
|
|
1334
|
-
updatedContent = updatedContent.replace(updatedImport, `${updatedImport}
|
|
1335
|
-
import type { PostCondition } from '@stacks/transactions';`);
|
|
1336
|
-
}
|
|
1337
|
-
return updatedContent;
|
|
1359
|
+
let updatedContent = content;
|
|
1360
|
+
if (!updatedContent.includes("fetchCallReadOnlyFunction")) {
|
|
1361
|
+
updatedContent = updatedContent.replace(stacksImportRegex, `${match[0]}
|
|
1362
|
+
import { fetchCallReadOnlyFunction, makeContractCall } from '@secondlayer/stacks/clarity';`);
|
|
1338
1363
|
}
|
|
1339
|
-
if (!
|
|
1340
|
-
|
|
1341
|
-
import type { PostCondition } from '@stacks
|
|
1364
|
+
if (!updatedContent.includes("type PostCondition")) {
|
|
1365
|
+
updatedContent = updatedContent.replace(stacksImportRegex, `${match[0]}
|
|
1366
|
+
import type { PostCondition } from '@secondlayer/stacks';`);
|
|
1342
1367
|
}
|
|
1368
|
+
return updatedContent;
|
|
1343
1369
|
}
|
|
1344
1370
|
return content;
|
|
1345
1371
|
}
|
|
@@ -1455,268 +1481,32 @@ export function useSecondLayerConfig(): SecondLayerReactConfig {
|
|
|
1455
1481
|
|
|
1456
1482
|
// src/plugins/react/generators/generic.ts
|
|
1457
1483
|
init_format();
|
|
1458
|
-
var GENERIC_HOOKS = [
|
|
1459
|
-
"useAccount",
|
|
1460
|
-
"useConnect",
|
|
1461
|
-
"useDisconnect",
|
|
1462
|
-
"useNetwork",
|
|
1463
|
-
"useContract",
|
|
1464
|
-
"useOpenSTXTransfer",
|
|
1465
|
-
"useSignMessage",
|
|
1466
|
-
"useDeployContract",
|
|
1467
|
-
"useReadContract",
|
|
1468
|
-
"useTransaction",
|
|
1469
|
-
"useBlock",
|
|
1470
|
-
"useAccountTransactions",
|
|
1471
|
-
"useWaitForTransaction"
|
|
1472
|
-
];
|
|
1473
|
-
async function generateGenericHooks(excludeList = []) {
|
|
1474
|
-
const hooksToGenerate = GENERIC_HOOKS.filter((hookName) => !excludeList.includes(hookName));
|
|
1475
|
-
const imports = `import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
1476
|
-
import { useState, useCallback } from 'react'
|
|
1477
|
-
import { useSecondLayerConfig } from './provider'
|
|
1478
|
-
import { connect, disconnect, isConnected, request, openContractCall as stacksOpenContractCall, openSTXTransfer, openSignatureRequestPopup, openContractDeploy } from '@stacks/connect'
|
|
1479
|
-
import { Cl, validateStacksAddress } from '@stacks/transactions'
|
|
1480
|
-
import type { PostCondition } from '@stacks/transactions'
|
|
1481
|
-
import type { ExtractFunctionArgs, ExtractFunctionNames, ClarityContract } from '@secondlayer/clarity-types'
|
|
1482
1484
|
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1485
|
+
// src/generators/templates/use-contract.ts
|
|
1486
|
+
var USE_CONTRACT_TEMPLATE = `export function useContract() {
|
|
1487
|
+
const config = useSecondLayerConfig()
|
|
1488
|
+
const queryClient = useQueryClient()
|
|
1489
|
+
const [isRequestPending, setIsRequestPending] = useState(false)
|
|
1488
1490
|
|
|
1489
|
-
|
|
1490
|
-
const
|
|
1491
|
-
|
|
1492
|
-
if (!response.ok) throw new Error(\`Failed to fetch transaction: \${response.statusText}\`)
|
|
1493
|
-
return response.json()
|
|
1494
|
-
}
|
|
1491
|
+
// Helper function to convert JS values to Clarity values based on ABI
|
|
1492
|
+
const convertArgsWithAbi = (args: any, abiArgs: any[]): any[] => {
|
|
1493
|
+
if (!abiArgs || abiArgs.length === 0) return []
|
|
1495
1494
|
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
}
|
|
1495
|
+
return abiArgs.map((abiArg, index) => {
|
|
1496
|
+
const argValue = Array.isArray(args)
|
|
1497
|
+
? args[index]
|
|
1498
|
+
: args[abiArg.name] || args[abiArg.name.replace(/-/g, '').replace(/_/g, '')]
|
|
1499
|
+
return convertJSValueToClarityValue(argValue, abiArg.type)
|
|
1500
|
+
})
|
|
1501
|
+
}
|
|
1502
1502
|
|
|
1503
|
-
|
|
1504
|
-
const
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
}
|
|
1509
|
-
const header = `/**
|
|
1510
|
-
* Generated generic Stacks React hooks
|
|
1511
|
-
* DO NOT EDIT MANUALLY
|
|
1512
|
-
*/`;
|
|
1513
|
-
const hooksCode = hooksToGenerate.map((hookName) => generateGenericHook(hookName)).filter(Boolean).join(`
|
|
1503
|
+
// Helper function to convert buffer values with auto-detection
|
|
1504
|
+
const convertBufferValue = (value: any): any => {
|
|
1505
|
+
// Direct Uint8Array
|
|
1506
|
+
if (value instanceof Uint8Array) {
|
|
1507
|
+
return Cl.buffer(value)
|
|
1508
|
+
}
|
|
1514
1509
|
|
|
1515
|
-
`);
|
|
1516
|
-
const code = `${imports}
|
|
1517
|
-
|
|
1518
|
-
${header}
|
|
1519
|
-
|
|
1520
|
-
${hooksCode}`;
|
|
1521
|
-
return formatCode(code);
|
|
1522
|
-
}
|
|
1523
|
-
function generateGenericHook(hookName) {
|
|
1524
|
-
switch (hookName) {
|
|
1525
|
-
case "useAccount":
|
|
1526
|
-
return `export function useAccount() {
|
|
1527
|
-
const config = useSecondLayerConfig()
|
|
1528
|
-
|
|
1529
|
-
return useQuery({
|
|
1530
|
-
queryKey: ['stacks-account', config.network],
|
|
1531
|
-
queryFn: async () => {
|
|
1532
|
-
try {
|
|
1533
|
-
// Check if already connected using @stacks/connect v8
|
|
1534
|
-
const connected = isConnected()
|
|
1535
|
-
|
|
1536
|
-
if (!connected) {
|
|
1537
|
-
return {
|
|
1538
|
-
address: undefined,
|
|
1539
|
-
addresses: undefined,
|
|
1540
|
-
isConnected: false,
|
|
1541
|
-
isConnecting: false,
|
|
1542
|
-
isDisconnected: true,
|
|
1543
|
-
status: 'disconnected' as const
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
// Get addresses using @stacks/connect v8 request method (SIP-030)
|
|
1548
|
-
const result = await request('stx_getAddresses')
|
|
1549
|
-
|
|
1550
|
-
if (!result || !result.addresses || result.addresses.length === 0) {
|
|
1551
|
-
return {
|
|
1552
|
-
address: undefined,
|
|
1553
|
-
addresses: undefined,
|
|
1554
|
-
isConnected: false,
|
|
1555
|
-
isConnecting: false,
|
|
1556
|
-
isDisconnected: true,
|
|
1557
|
-
status: 'disconnected' as const
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
// Extract STX addresses from the response
|
|
1562
|
-
const stxAddresses = result.addresses
|
|
1563
|
-
.filter((addr: any) => addr.address.startsWith('SP') || addr.address.startsWith('ST'))
|
|
1564
|
-
.map((addr: any) => addr.address)
|
|
1565
|
-
|
|
1566
|
-
return {
|
|
1567
|
-
address: stxAddresses[0] || undefined,
|
|
1568
|
-
addresses: stxAddresses,
|
|
1569
|
-
isConnected: true,
|
|
1570
|
-
isConnecting: false,
|
|
1571
|
-
isDisconnected: false,
|
|
1572
|
-
status: 'connected' as const
|
|
1573
|
-
}
|
|
1574
|
-
} catch (error) {
|
|
1575
|
-
// Handle case where wallet is not available or user rejected
|
|
1576
|
-
return {
|
|
1577
|
-
address: undefined,
|
|
1578
|
-
addresses: undefined,
|
|
1579
|
-
isConnected: false,
|
|
1580
|
-
isConnecting: false,
|
|
1581
|
-
isDisconnected: true,
|
|
1582
|
-
status: 'disconnected' as const
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
},
|
|
1586
|
-
refetchOnWindowFocus: false,
|
|
1587
|
-
retry: false,
|
|
1588
|
-
staleTime: 1000 * 60 * 5, // 5 minutes
|
|
1589
|
-
refetchInterval: 1000 * 30, // Refetch every 30 seconds to detect wallet changes
|
|
1590
|
-
})
|
|
1591
|
-
}`;
|
|
1592
|
-
case "useConnect":
|
|
1593
|
-
return `export function useConnect() {
|
|
1594
|
-
const queryClient = useQueryClient()
|
|
1595
|
-
|
|
1596
|
-
const mutation = useMutation({
|
|
1597
|
-
mutationFn: async (options: { forceWalletSelect?: boolean } = {}) => {
|
|
1598
|
-
// Use @stacks/connect v8 connect method
|
|
1599
|
-
return await connect(options)
|
|
1600
|
-
},
|
|
1601
|
-
onSuccess: () => {
|
|
1602
|
-
// Invalidate account queries to refetch connection state
|
|
1603
|
-
queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
|
|
1604
|
-
},
|
|
1605
|
-
onError: (error) => {
|
|
1606
|
-
console.error('Connection failed:', error)
|
|
1607
|
-
}
|
|
1608
|
-
})
|
|
1609
|
-
|
|
1610
|
-
return {
|
|
1611
|
-
// Custom connect function that works without arguments
|
|
1612
|
-
connect: (options?: { forceWalletSelect?: boolean }) => {
|
|
1613
|
-
return mutation.mutate(options || {})
|
|
1614
|
-
},
|
|
1615
|
-
connectAsync: async (options?: { forceWalletSelect?: boolean }) => {
|
|
1616
|
-
return mutation.mutateAsync(options || {})
|
|
1617
|
-
},
|
|
1618
|
-
// Expose all the mutation state
|
|
1619
|
-
isPending: mutation.isPending,
|
|
1620
|
-
isError: mutation.isError,
|
|
1621
|
-
isSuccess: mutation.isSuccess,
|
|
1622
|
-
error: mutation.error,
|
|
1623
|
-
data: mutation.data,
|
|
1624
|
-
reset: mutation.reset,
|
|
1625
|
-
// Keep the original mutate/mutateAsync for advanced users
|
|
1626
|
-
mutate: mutation.mutate,
|
|
1627
|
-
mutateAsync: mutation.mutateAsync
|
|
1628
|
-
}
|
|
1629
|
-
}`;
|
|
1630
|
-
case "useDisconnect":
|
|
1631
|
-
return `export function useDisconnect() {
|
|
1632
|
-
const queryClient = useQueryClient()
|
|
1633
|
-
|
|
1634
|
-
const mutation = useMutation({
|
|
1635
|
-
mutationFn: async () => {
|
|
1636
|
-
// Use @stacks/connect v8 disconnect method
|
|
1637
|
-
return await disconnect()
|
|
1638
|
-
},
|
|
1639
|
-
onSuccess: () => {
|
|
1640
|
-
// Clear all cached data on disconnect
|
|
1641
|
-
queryClient.clear()
|
|
1642
|
-
},
|
|
1643
|
-
onError: (error) => {
|
|
1644
|
-
console.error('Disconnect failed:', error)
|
|
1645
|
-
}
|
|
1646
|
-
})
|
|
1647
|
-
|
|
1648
|
-
return {
|
|
1649
|
-
// Custom disconnect function
|
|
1650
|
-
disconnect: () => {
|
|
1651
|
-
return mutation.mutate()
|
|
1652
|
-
},
|
|
1653
|
-
disconnectAsync: async () => {
|
|
1654
|
-
return mutation.mutateAsync()
|
|
1655
|
-
},
|
|
1656
|
-
// Expose all the mutation state
|
|
1657
|
-
isPending: mutation.isPending,
|
|
1658
|
-
isError: mutation.isError,
|
|
1659
|
-
isSuccess: mutation.isSuccess,
|
|
1660
|
-
error: mutation.error,
|
|
1661
|
-
data: mutation.data,
|
|
1662
|
-
reset: mutation.reset,
|
|
1663
|
-
// Keep the original mutate/mutateAsync for advanced users
|
|
1664
|
-
mutate: mutation.mutate,
|
|
1665
|
-
mutateAsync: mutation.mutateAsync
|
|
1666
|
-
}
|
|
1667
|
-
}`;
|
|
1668
|
-
case "useNetwork":
|
|
1669
|
-
return `export function useNetwork() {
|
|
1670
|
-
const config = useSecondLayerConfig()
|
|
1671
|
-
|
|
1672
|
-
return useQuery({
|
|
1673
|
-
queryKey: ['stacks-network', config.network],
|
|
1674
|
-
queryFn: async () => {
|
|
1675
|
-
// Currently read-only from config
|
|
1676
|
-
// Future: Use request('stx_getNetworks') when wallet support improves
|
|
1677
|
-
const network = config.network
|
|
1678
|
-
|
|
1679
|
-
return {
|
|
1680
|
-
network,
|
|
1681
|
-
isMainnet: network === 'mainnet',
|
|
1682
|
-
isTestnet: network === 'testnet',
|
|
1683
|
-
isDevnet: network === 'devnet',
|
|
1684
|
-
// Future: Add switchNetwork when wallets support stx_networkChange
|
|
1685
|
-
// switchNetwork: async (newNetwork: string) => {
|
|
1686
|
-
// return await request('wallet_changeNetwork', { network: newNetwork })
|
|
1687
|
-
// }
|
|
1688
|
-
}
|
|
1689
|
-
},
|
|
1690
|
-
staleTime: Infinity, // Network config rarely changes
|
|
1691
|
-
refetchOnWindowFocus: false,
|
|
1692
|
-
retry: false
|
|
1693
|
-
})
|
|
1694
|
-
}`;
|
|
1695
|
-
case "useContract":
|
|
1696
|
-
return `export function useContract() {
|
|
1697
|
-
const config = useSecondLayerConfig()
|
|
1698
|
-
const queryClient = useQueryClient()
|
|
1699
|
-
const [isRequestPending, setIsRequestPending] = useState(false)
|
|
1700
|
-
|
|
1701
|
-
// Helper function to convert JS values to Clarity values based on ABI
|
|
1702
|
-
const convertArgsWithAbi = (args: any, abiArgs: any[]): any[] => {
|
|
1703
|
-
if (!abiArgs || abiArgs.length === 0) return []
|
|
1704
|
-
|
|
1705
|
-
return abiArgs.map((abiArg, index) => {
|
|
1706
|
-
const argValue = Array.isArray(args)
|
|
1707
|
-
? args[index]
|
|
1708
|
-
: args[abiArg.name] || args[abiArg.name.replace(/-/g, '').replace(/_/g, '')]
|
|
1709
|
-
return convertJSValueToClarityValue(argValue, abiArg.type)
|
|
1710
|
-
})
|
|
1711
|
-
}
|
|
1712
|
-
|
|
1713
|
-
// Helper function to convert buffer values with auto-detection
|
|
1714
|
-
const convertBufferValue = (value: any): any => {
|
|
1715
|
-
// Direct Uint8Array
|
|
1716
|
-
if (value instanceof Uint8Array) {
|
|
1717
|
-
return Cl.buffer(value)
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
1510
|
// Object notation with explicit type
|
|
1721
1511
|
if (typeof value === 'object' && value !== null && value.type && value.value) {
|
|
1722
1512
|
switch (value.type) {
|
|
@@ -1730,23 +1520,23 @@ function generateGenericHook(hookName) {
|
|
|
1730
1520
|
throw new Error(\`Unsupported buffer type: \${value.type}\`)
|
|
1731
1521
|
}
|
|
1732
1522
|
}
|
|
1733
|
-
|
|
1523
|
+
|
|
1734
1524
|
// Auto-detect string type
|
|
1735
1525
|
if (typeof value === 'string') {
|
|
1736
1526
|
// 1. Check for hex (0x prefix or pure hex pattern)
|
|
1737
1527
|
if (value.startsWith('0x') || /^[0-9a-fA-F]+$/.test(value)) {
|
|
1738
1528
|
return Cl.bufferFromHex(value)
|
|
1739
1529
|
}
|
|
1740
|
-
|
|
1530
|
+
|
|
1741
1531
|
// 2. Check for non-ASCII characters (UTF-8)
|
|
1742
1532
|
if (!/^[\\x00-\\x7F]*$/.test(value)) {
|
|
1743
1533
|
return Cl.bufferFromUtf8(value)
|
|
1744
1534
|
}
|
|
1745
|
-
|
|
1535
|
+
|
|
1746
1536
|
// 3. Default to ASCII for simple ASCII strings
|
|
1747
1537
|
return Cl.bufferFromAscii(value)
|
|
1748
1538
|
}
|
|
1749
|
-
|
|
1539
|
+
|
|
1750
1540
|
throw new Error(\`Invalid buffer value: \${value}\`)
|
|
1751
1541
|
}
|
|
1752
1542
|
|
|
@@ -1804,7 +1594,7 @@ function generateGenericHook(hookName) {
|
|
|
1804
1594
|
}
|
|
1805
1595
|
|
|
1806
1596
|
if (type.response) {
|
|
1807
|
-
return 'ok' in value
|
|
1597
|
+
return 'ok' in value
|
|
1808
1598
|
? Cl.ok(convertJSValueToClarityValue(value.ok, type.response.ok))
|
|
1809
1599
|
: Cl.error(convertJSValueToClarityValue(value.err, type.response.error))
|
|
1810
1600
|
}
|
|
@@ -1817,13 +1607,13 @@ function generateGenericHook(hookName) {
|
|
|
1817
1607
|
if (!abi || !abi.functions) return null
|
|
1818
1608
|
return abi.functions.find((func: any) => func.name === functionName)
|
|
1819
1609
|
}
|
|
1820
|
-
|
|
1821
|
-
// Legacy function -
|
|
1610
|
+
|
|
1611
|
+
// Legacy function - pre-converted Clarity values
|
|
1822
1612
|
const legacyOpenContractCall = useCallback(async (params: {
|
|
1823
1613
|
contractAddress: string;
|
|
1824
1614
|
contractName: string;
|
|
1825
1615
|
functionName: string;
|
|
1826
|
-
functionArgs: any[];
|
|
1616
|
+
functionArgs: any[];
|
|
1827
1617
|
network?: string;
|
|
1828
1618
|
postConditions?: PostCondition[];
|
|
1829
1619
|
attachment?: string;
|
|
@@ -1831,57 +1621,23 @@ function generateGenericHook(hookName) {
|
|
|
1831
1621
|
onCancel?: () => void;
|
|
1832
1622
|
}) => {
|
|
1833
1623
|
setIsRequestPending(true)
|
|
1834
|
-
|
|
1624
|
+
|
|
1835
1625
|
try {
|
|
1836
1626
|
const { contractAddress, contractName, functionName, functionArgs, onFinish, onCancel, ...options } = params
|
|
1837
1627
|
const network = params.network || config.network || 'mainnet'
|
|
1838
1628
|
const contract = \`\${contractAddress}.\${contractName}\`
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
queryClient.invalidateQueries({
|
|
1852
|
-
queryKey: ['stacks-account']
|
|
1853
|
-
})
|
|
1854
|
-
|
|
1855
|
-
onFinish?.(result)
|
|
1856
|
-
return result
|
|
1857
|
-
} catch (connectError) {
|
|
1858
|
-
// Fallback to openContractCall for broader wallet compatibility
|
|
1859
|
-
console.warn('stx_callContract not supported, falling back to openContractCall:', connectError)
|
|
1860
|
-
|
|
1861
|
-
return new Promise((resolve, reject) => {
|
|
1862
|
-
stacksOpenContractCall({
|
|
1863
|
-
contractAddress,
|
|
1864
|
-
contractName,
|
|
1865
|
-
functionName,
|
|
1866
|
-
functionArgs,
|
|
1867
|
-
network,
|
|
1868
|
-
...options,
|
|
1869
|
-
onFinish: (data: any) => {
|
|
1870
|
-
// Invalidate relevant queries on success
|
|
1871
|
-
queryClient.invalidateQueries({
|
|
1872
|
-
queryKey: ['stacks-account']
|
|
1873
|
-
})
|
|
1874
|
-
|
|
1875
|
-
onFinish?.(data)
|
|
1876
|
-
resolve(data)
|
|
1877
|
-
},
|
|
1878
|
-
onCancel: () => {
|
|
1879
|
-
onCancel?.()
|
|
1880
|
-
reject(new Error('User cancelled transaction'))
|
|
1881
|
-
}
|
|
1882
|
-
})
|
|
1883
|
-
})
|
|
1884
|
-
}
|
|
1629
|
+
|
|
1630
|
+
const result = await request('stx_callContract', {
|
|
1631
|
+
contract,
|
|
1632
|
+
functionName,
|
|
1633
|
+
functionArgs,
|
|
1634
|
+
network,
|
|
1635
|
+
...options
|
|
1636
|
+
})
|
|
1637
|
+
|
|
1638
|
+
queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
|
|
1639
|
+
onFinish?.(result)
|
|
1640
|
+
return result
|
|
1885
1641
|
} catch (error) {
|
|
1886
1642
|
console.error('Contract call failed:', error)
|
|
1887
1643
|
throw error instanceof Error ? error : new Error('Contract call failed')
|
|
@@ -1892,7 +1648,7 @@ function generateGenericHook(hookName) {
|
|
|
1892
1648
|
|
|
1893
1649
|
// Enhanced function - requires ABI, auto-converts JS values
|
|
1894
1650
|
const openContractCall = useCallback(async <
|
|
1895
|
-
T extends
|
|
1651
|
+
T extends AbiContract,
|
|
1896
1652
|
FN extends ExtractFunctionNames<T>
|
|
1897
1653
|
>(params: {
|
|
1898
1654
|
contractAddress: string;
|
|
@@ -1907,79 +1663,285 @@ function generateGenericHook(hookName) {
|
|
|
1907
1663
|
onCancel?: () => void;
|
|
1908
1664
|
}) => {
|
|
1909
1665
|
setIsRequestPending(true)
|
|
1910
|
-
|
|
1666
|
+
|
|
1911
1667
|
try {
|
|
1912
1668
|
const { contractAddress, contractName, functionName, functionArgs, abi, onFinish, onCancel, ...options } = params
|
|
1913
1669
|
const network = params.network || config.network || 'mainnet'
|
|
1914
1670
|
const contract = \`\${contractAddress}.\${contractName}\`
|
|
1915
|
-
|
|
1916
|
-
// Find the function in the ABI and convert args
|
|
1671
|
+
|
|
1917
1672
|
const abiFunction = findFunctionInAbi(abi, functionName)
|
|
1918
1673
|
if (!abiFunction) {
|
|
1919
1674
|
throw new Error(\`Function '\${functionName}' not found in ABI\`)
|
|
1920
1675
|
}
|
|
1921
|
-
|
|
1676
|
+
|
|
1922
1677
|
const processedArgs = convertArgsWithAbi(functionArgs, abiFunction.args || [])
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
queryClient.invalidateQueries({
|
|
1936
|
-
queryKey: ['stacks-account']
|
|
1937
|
-
})
|
|
1938
|
-
|
|
1939
|
-
onFinish?.(result)
|
|
1940
|
-
return result
|
|
1941
|
-
} catch (connectError) {
|
|
1942
|
-
// Fallback to openContractCall for broader wallet compatibility
|
|
1943
|
-
console.warn('stx_callContract not supported, falling back to openContractCall:', connectError)
|
|
1944
|
-
|
|
1945
|
-
return new Promise((resolve, reject) => {
|
|
1946
|
-
stacksOpenContractCall({
|
|
1947
|
-
contractAddress,
|
|
1948
|
-
contractName,
|
|
1949
|
-
functionName,
|
|
1950
|
-
functionArgs: processedArgs,
|
|
1951
|
-
network,
|
|
1952
|
-
...options,
|
|
1953
|
-
onFinish: (data: any) => {
|
|
1954
|
-
// Invalidate relevant queries on success
|
|
1955
|
-
queryClient.invalidateQueries({
|
|
1956
|
-
queryKey: ['stacks-account']
|
|
1957
|
-
})
|
|
1958
|
-
|
|
1959
|
-
onFinish?.(data)
|
|
1960
|
-
resolve(data)
|
|
1961
|
-
},
|
|
1962
|
-
onCancel: () => {
|
|
1963
|
-
onCancel?.()
|
|
1964
|
-
reject(new Error('User cancelled transaction'))
|
|
1965
|
-
}
|
|
1966
|
-
})
|
|
1967
|
-
})
|
|
1968
|
-
}
|
|
1678
|
+
|
|
1679
|
+
const result = await request('stx_callContract', {
|
|
1680
|
+
contract,
|
|
1681
|
+
functionName,
|
|
1682
|
+
functionArgs: processedArgs,
|
|
1683
|
+
network,
|
|
1684
|
+
...options
|
|
1685
|
+
})
|
|
1686
|
+
|
|
1687
|
+
queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
|
|
1688
|
+
onFinish?.(result)
|
|
1689
|
+
return result
|
|
1969
1690
|
} catch (error) {
|
|
1970
1691
|
console.error('Contract call failed:', error)
|
|
1971
1692
|
throw error instanceof Error ? error : new Error('Contract call failed')
|
|
1972
1693
|
} finally {
|
|
1973
1694
|
setIsRequestPending(false)
|
|
1974
1695
|
}
|
|
1975
|
-
}, [config.network, queryClient])
|
|
1696
|
+
}, [config.network, queryClient])
|
|
1697
|
+
|
|
1698
|
+
return {
|
|
1699
|
+
legacyOpenContractCall,
|
|
1700
|
+
openContractCall,
|
|
1701
|
+
isRequestPending
|
|
1702
|
+
}
|
|
1703
|
+
}`;
|
|
1704
|
+
|
|
1705
|
+
// src/plugins/react/generators/generic.ts
|
|
1706
|
+
var GENERIC_HOOKS = [
|
|
1707
|
+
"useAccount",
|
|
1708
|
+
"useConnect",
|
|
1709
|
+
"useDisconnect",
|
|
1710
|
+
"useNetwork",
|
|
1711
|
+
"useContract",
|
|
1712
|
+
"useOpenSTXTransfer",
|
|
1713
|
+
"useSignMessage",
|
|
1714
|
+
"useDeployContract",
|
|
1715
|
+
"useReadContract",
|
|
1716
|
+
"useTransaction",
|
|
1717
|
+
"useBlock",
|
|
1718
|
+
"useAccountTransactions",
|
|
1719
|
+
"useWaitForTransaction"
|
|
1720
|
+
];
|
|
1721
|
+
async function generateGenericHooks(excludeList = []) {
|
|
1722
|
+
const hooksToGenerate = GENERIC_HOOKS.filter((hookName) => !excludeList.includes(hookName));
|
|
1723
|
+
const imports = `import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
1724
|
+
import { useState, useCallback } from 'react'
|
|
1725
|
+
import { useSecondLayerConfig } from './provider'
|
|
1726
|
+
import { connect, disconnect, isConnected, request } from '@secondlayer/stacks/connect'
|
|
1727
|
+
import { Cl, validateStacksAddress } from '@secondlayer/stacks'
|
|
1728
|
+
import type { PostCondition } from '@secondlayer/stacks'
|
|
1729
|
+
import type { ExtractFunctionArgs, ExtractFunctionNames, AbiContract } from '@secondlayer/stacks/clarity'
|
|
1730
|
+
|
|
1731
|
+
const API_URLS: Record<string, string> = {
|
|
1732
|
+
mainnet: 'https://api.hiro.so',
|
|
1733
|
+
testnet: 'https://api.testnet.hiro.so',
|
|
1734
|
+
devnet: 'http://localhost:3999'
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
async function fetchTransaction({ txId, network, apiUrl }: { txId: string; network?: string; apiUrl?: string }): Promise<any> {
|
|
1738
|
+
const baseUrl = apiUrl || API_URLS[network || 'mainnet']
|
|
1739
|
+
const response = await fetch(\`\${baseUrl}/extended/v1/tx/\${txId}\`)
|
|
1740
|
+
if (!response.ok) throw new Error(\`Failed to fetch transaction: \${response.statusText}\`)
|
|
1741
|
+
return response.json()
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
async function fetchBlock({ height, network, apiUrl }: { height: number; network?: string; apiUrl?: string }): Promise<any> {
|
|
1745
|
+
const baseUrl = apiUrl || API_URLS[network || 'mainnet']
|
|
1746
|
+
const response = await fetch(\`\${baseUrl}/extended/v1/block/by_height/\${height}\`)
|
|
1747
|
+
if (!response.ok) throw new Error(\`Failed to fetch block: \${response.statusText}\`)
|
|
1748
|
+
return response.json()
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
async function fetchAccountTransactions({ address, network, apiUrl }: { address: string; network?: string; apiUrl?: string }): Promise<any> {
|
|
1752
|
+
const baseUrl = apiUrl || API_URLS[network || 'mainnet']
|
|
1753
|
+
const response = await fetch(\`\${baseUrl}/extended/v1/address/\${address}/transactions\`)
|
|
1754
|
+
if (!response.ok) throw new Error(\`Failed to fetch transactions: \${response.statusText}\`)
|
|
1755
|
+
return response.json()
|
|
1756
|
+
}`;
|
|
1757
|
+
const header = `/**
|
|
1758
|
+
* Generated generic Stacks React hooks
|
|
1759
|
+
* DO NOT EDIT MANUALLY
|
|
1760
|
+
*/`;
|
|
1761
|
+
const hooksCode = hooksToGenerate.map((hookName) => generateGenericHook(hookName)).filter(Boolean).join(`
|
|
1762
|
+
|
|
1763
|
+
`);
|
|
1764
|
+
const code = `${imports}
|
|
1765
|
+
|
|
1766
|
+
${header}
|
|
1767
|
+
|
|
1768
|
+
${hooksCode}`;
|
|
1769
|
+
return formatCode(code);
|
|
1770
|
+
}
|
|
1771
|
+
function generateGenericHook(hookName) {
|
|
1772
|
+
switch (hookName) {
|
|
1773
|
+
case "useAccount":
|
|
1774
|
+
return `export function useAccount() {
|
|
1775
|
+
const config = useSecondLayerConfig()
|
|
1776
|
+
|
|
1777
|
+
return useQuery({
|
|
1778
|
+
queryKey: ['stacks-account', config.network],
|
|
1779
|
+
queryFn: async () => {
|
|
1780
|
+
try {
|
|
1781
|
+
// Check if already connected
|
|
1782
|
+
const connected = isConnected()
|
|
1783
|
+
|
|
1784
|
+
if (!connected) {
|
|
1785
|
+
return {
|
|
1786
|
+
address: undefined,
|
|
1787
|
+
addresses: undefined,
|
|
1788
|
+
isConnected: false,
|
|
1789
|
+
isConnecting: false,
|
|
1790
|
+
isDisconnected: true,
|
|
1791
|
+
status: 'disconnected' as const
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// Get addresses via SIP-030
|
|
1796
|
+
const result = await request('stx_getAddresses')
|
|
1797
|
+
|
|
1798
|
+
if (!result || !result.addresses || result.addresses.length === 0) {
|
|
1799
|
+
return {
|
|
1800
|
+
address: undefined,
|
|
1801
|
+
addresses: undefined,
|
|
1802
|
+
isConnected: false,
|
|
1803
|
+
isConnecting: false,
|
|
1804
|
+
isDisconnected: true,
|
|
1805
|
+
status: 'disconnected' as const
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
// Extract STX addresses from the response
|
|
1810
|
+
const stxAddresses = result.addresses
|
|
1811
|
+
.filter((addr: any) => addr.address.startsWith('SP') || addr.address.startsWith('ST'))
|
|
1812
|
+
.map((addr: any) => addr.address)
|
|
1813
|
+
|
|
1814
|
+
return {
|
|
1815
|
+
address: stxAddresses[0] || undefined,
|
|
1816
|
+
addresses: stxAddresses,
|
|
1817
|
+
isConnected: true,
|
|
1818
|
+
isConnecting: false,
|
|
1819
|
+
isDisconnected: false,
|
|
1820
|
+
status: 'connected' as const
|
|
1821
|
+
}
|
|
1822
|
+
} catch (error) {
|
|
1823
|
+
// Handle case where wallet is not available or user rejected
|
|
1824
|
+
return {
|
|
1825
|
+
address: undefined,
|
|
1826
|
+
addresses: undefined,
|
|
1827
|
+
isConnected: false,
|
|
1828
|
+
isConnecting: false,
|
|
1829
|
+
isDisconnected: true,
|
|
1830
|
+
status: 'disconnected' as const
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
},
|
|
1834
|
+
refetchOnWindowFocus: false,
|
|
1835
|
+
retry: false,
|
|
1836
|
+
staleTime: 1000 * 60 * 5, // 5 minutes
|
|
1837
|
+
refetchInterval: 1000 * 30, // Refetch every 30 seconds to detect wallet changes
|
|
1838
|
+
})
|
|
1839
|
+
}`;
|
|
1840
|
+
case "useConnect":
|
|
1841
|
+
return `export function useConnect() {
|
|
1842
|
+
const queryClient = useQueryClient()
|
|
1843
|
+
|
|
1844
|
+
const mutation = useMutation({
|
|
1845
|
+
mutationFn: async (options: { forceWalletSelect?: boolean } = {}) => {
|
|
1846
|
+
// SIP-030 connect
|
|
1847
|
+
return await connect(options)
|
|
1848
|
+
},
|
|
1849
|
+
onSuccess: () => {
|
|
1850
|
+
// Invalidate account queries to refetch connection state
|
|
1851
|
+
queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
|
|
1852
|
+
},
|
|
1853
|
+
onError: (error) => {
|
|
1854
|
+
console.error('Connection failed:', error)
|
|
1855
|
+
}
|
|
1856
|
+
})
|
|
1976
1857
|
|
|
1977
1858
|
return {
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1859
|
+
// Custom connect function that works without arguments
|
|
1860
|
+
connect: (options?: { forceWalletSelect?: boolean }) => {
|
|
1861
|
+
return mutation.mutate(options || {})
|
|
1862
|
+
},
|
|
1863
|
+
connectAsync: async (options?: { forceWalletSelect?: boolean }) => {
|
|
1864
|
+
return mutation.mutateAsync(options || {})
|
|
1865
|
+
},
|
|
1866
|
+
// Expose all the mutation state
|
|
1867
|
+
isPending: mutation.isPending,
|
|
1868
|
+
isError: mutation.isError,
|
|
1869
|
+
isSuccess: mutation.isSuccess,
|
|
1870
|
+
error: mutation.error,
|
|
1871
|
+
data: mutation.data,
|
|
1872
|
+
reset: mutation.reset,
|
|
1873
|
+
// Keep the original mutate/mutateAsync for advanced users
|
|
1874
|
+
mutate: mutation.mutate,
|
|
1875
|
+
mutateAsync: mutation.mutateAsync
|
|
1876
|
+
}
|
|
1877
|
+
}`;
|
|
1878
|
+
case "useDisconnect":
|
|
1879
|
+
return `export function useDisconnect() {
|
|
1880
|
+
const queryClient = useQueryClient()
|
|
1881
|
+
|
|
1882
|
+
const mutation = useMutation({
|
|
1883
|
+
mutationFn: async () => {
|
|
1884
|
+
// SIP-030 disconnect
|
|
1885
|
+
return await disconnect()
|
|
1886
|
+
},
|
|
1887
|
+
onSuccess: () => {
|
|
1888
|
+
// Clear all cached data on disconnect
|
|
1889
|
+
queryClient.clear()
|
|
1890
|
+
},
|
|
1891
|
+
onError: (error) => {
|
|
1892
|
+
console.error('Disconnect failed:', error)
|
|
1893
|
+
}
|
|
1894
|
+
})
|
|
1895
|
+
|
|
1896
|
+
return {
|
|
1897
|
+
// Custom disconnect function
|
|
1898
|
+
disconnect: () => {
|
|
1899
|
+
return mutation.mutate()
|
|
1900
|
+
},
|
|
1901
|
+
disconnectAsync: async () => {
|
|
1902
|
+
return mutation.mutateAsync()
|
|
1903
|
+
},
|
|
1904
|
+
// Expose all the mutation state
|
|
1905
|
+
isPending: mutation.isPending,
|
|
1906
|
+
isError: mutation.isError,
|
|
1907
|
+
isSuccess: mutation.isSuccess,
|
|
1908
|
+
error: mutation.error,
|
|
1909
|
+
data: mutation.data,
|
|
1910
|
+
reset: mutation.reset,
|
|
1911
|
+
// Keep the original mutate/mutateAsync for advanced users
|
|
1912
|
+
mutate: mutation.mutate,
|
|
1913
|
+
mutateAsync: mutation.mutateAsync
|
|
1981
1914
|
}
|
|
1982
1915
|
}`;
|
|
1916
|
+
case "useNetwork":
|
|
1917
|
+
return `export function useNetwork() {
|
|
1918
|
+
const config = useSecondLayerConfig()
|
|
1919
|
+
|
|
1920
|
+
return useQuery({
|
|
1921
|
+
queryKey: ['stacks-network', config.network],
|
|
1922
|
+
queryFn: async () => {
|
|
1923
|
+
// Currently read-only from config
|
|
1924
|
+
// Future: Use request('stx_getNetworks') when wallet support improves
|
|
1925
|
+
const network = config.network
|
|
1926
|
+
|
|
1927
|
+
return {
|
|
1928
|
+
network,
|
|
1929
|
+
isMainnet: network === 'mainnet',
|
|
1930
|
+
isTestnet: network === 'testnet',
|
|
1931
|
+
isDevnet: network === 'devnet',
|
|
1932
|
+
// Future: Add switchNetwork when wallets support stx_networkChange
|
|
1933
|
+
// switchNetwork: async (newNetwork: string) => {
|
|
1934
|
+
// return await request('wallet_changeNetwork', { network: newNetwork })
|
|
1935
|
+
// }
|
|
1936
|
+
}
|
|
1937
|
+
},
|
|
1938
|
+
staleTime: Infinity, // Network config rarely changes
|
|
1939
|
+
refetchOnWindowFocus: false,
|
|
1940
|
+
retry: false
|
|
1941
|
+
})
|
|
1942
|
+
}`;
|
|
1943
|
+
case "useContract":
|
|
1944
|
+
return USE_CONTRACT_TEMPLATE;
|
|
1983
1945
|
case "useReadContract":
|
|
1984
1946
|
return `export function useReadContract<TArgs = any, TResult = any>(params: {
|
|
1985
1947
|
contractAddress: string;
|
|
@@ -1994,7 +1956,7 @@ function generateGenericHook(hookName) {
|
|
|
1994
1956
|
return useQuery<TResult>({
|
|
1995
1957
|
queryKey: ['read-contract', params.contractAddress, params.contractName, params.functionName, params.args, params.network || config.network],
|
|
1996
1958
|
queryFn: async () => {
|
|
1997
|
-
const { fetchCallReadOnlyFunction } = await import('@stacks/
|
|
1959
|
+
const { fetchCallReadOnlyFunction } = await import('@secondlayer/stacks/clarity')
|
|
1998
1960
|
|
|
1999
1961
|
// For now, we'll need to handle the args conversion here
|
|
2000
1962
|
// In the future, we could integrate with the contract interface for automatic conversion
|
|
@@ -2095,39 +2057,25 @@ function generateGenericHook(hookName) {
|
|
|
2095
2057
|
return `export function useOpenSTXTransfer() {
|
|
2096
2058
|
const config = useSecondLayerConfig()
|
|
2097
2059
|
const queryClient = useQueryClient()
|
|
2098
|
-
|
|
2060
|
+
|
|
2099
2061
|
const mutation = useMutation({
|
|
2100
2062
|
mutationFn: async (params: {
|
|
2101
2063
|
recipient: string;
|
|
2102
2064
|
amount: string | number;
|
|
2103
2065
|
memo?: string;
|
|
2104
2066
|
network?: string;
|
|
2105
|
-
onFinish?: (data: any) => void;
|
|
2106
|
-
onCancel?: () => void;
|
|
2107
2067
|
}) => {
|
|
2108
|
-
const { recipient, amount, memo
|
|
2068
|
+
const { recipient, amount, memo } = params
|
|
2109
2069
|
const network = params.network || config.network || 'mainnet'
|
|
2110
|
-
|
|
2111
|
-
return
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
network,
|
|
2117
|
-
...options,
|
|
2118
|
-
onFinish: (data: any) => {
|
|
2119
|
-
onFinish?.(data)
|
|
2120
|
-
resolve(data)
|
|
2121
|
-
},
|
|
2122
|
-
onCancel: () => {
|
|
2123
|
-
onCancel?.()
|
|
2124
|
-
reject(new Error('User cancelled transaction'))
|
|
2125
|
-
}
|
|
2126
|
-
})
|
|
2070
|
+
|
|
2071
|
+
return await request('stx_transferStx', {
|
|
2072
|
+
recipient,
|
|
2073
|
+
amount: amount.toString(),
|
|
2074
|
+
memo,
|
|
2075
|
+
network,
|
|
2127
2076
|
})
|
|
2128
2077
|
},
|
|
2129
2078
|
onSuccess: () => {
|
|
2130
|
-
// Invalidate relevant queries on success
|
|
2131
2079
|
queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
|
|
2132
2080
|
},
|
|
2133
2081
|
onError: (error) => {
|
|
@@ -2140,15 +2088,12 @@ function generateGenericHook(hookName) {
|
|
|
2140
2088
|
amount: string | number;
|
|
2141
2089
|
memo?: string;
|
|
2142
2090
|
network?: string;
|
|
2143
|
-
onFinish?: (data: any) => void;
|
|
2144
|
-
onCancel?: () => void;
|
|
2145
2091
|
}) => {
|
|
2146
2092
|
return mutation.mutateAsync(params)
|
|
2147
2093
|
}, [mutation])
|
|
2148
2094
|
|
|
2149
2095
|
return {
|
|
2150
2096
|
openSTXTransfer,
|
|
2151
|
-
// Expose mutation state
|
|
2152
2097
|
isPending: mutation.isPending,
|
|
2153
2098
|
isError: mutation.isError,
|
|
2154
2099
|
isSuccess: mutation.isSuccess,
|
|
@@ -2160,31 +2105,18 @@ function generateGenericHook(hookName) {
|
|
|
2160
2105
|
case "useSignMessage":
|
|
2161
2106
|
return `export function useSignMessage() {
|
|
2162
2107
|
const config = useSecondLayerConfig()
|
|
2163
|
-
|
|
2108
|
+
|
|
2164
2109
|
const mutation = useMutation({
|
|
2165
2110
|
mutationFn: async (params: {
|
|
2166
2111
|
message: string;
|
|
2167
2112
|
network?: string;
|
|
2168
|
-
onFinish?: (data: any) => void;
|
|
2169
|
-
onCancel?: () => void;
|
|
2170
2113
|
}) => {
|
|
2171
|
-
const { message
|
|
2114
|
+
const { message } = params
|
|
2172
2115
|
const network = params.network || config.network || 'mainnet'
|
|
2173
|
-
|
|
2174
|
-
return
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
network,
|
|
2178
|
-
...options,
|
|
2179
|
-
onFinish: (data: any) => {
|
|
2180
|
-
onFinish?.(data)
|
|
2181
|
-
resolve(data)
|
|
2182
|
-
},
|
|
2183
|
-
onCancel: () => {
|
|
2184
|
-
onCancel?.()
|
|
2185
|
-
reject(new Error('User cancelled message signing'))
|
|
2186
|
-
}
|
|
2187
|
-
})
|
|
2116
|
+
|
|
2117
|
+
return await request('stx_signMessage', {
|
|
2118
|
+
message,
|
|
2119
|
+
network,
|
|
2188
2120
|
})
|
|
2189
2121
|
},
|
|
2190
2122
|
onError: (error) => {
|
|
@@ -2195,15 +2127,12 @@ function generateGenericHook(hookName) {
|
|
|
2195
2127
|
const signMessage = useCallback(async (params: {
|
|
2196
2128
|
message: string;
|
|
2197
2129
|
network?: string;
|
|
2198
|
-
onFinish?: (data: any) => void;
|
|
2199
|
-
onCancel?: () => void;
|
|
2200
2130
|
}) => {
|
|
2201
2131
|
return mutation.mutateAsync(params)
|
|
2202
2132
|
}, [mutation])
|
|
2203
2133
|
|
|
2204
2134
|
return {
|
|
2205
2135
|
signMessage,
|
|
2206
|
-
// Expose mutation state
|
|
2207
2136
|
isPending: mutation.isPending,
|
|
2208
2137
|
isError: mutation.isError,
|
|
2209
2138
|
isSuccess: mutation.isSuccess,
|
|
@@ -2216,38 +2145,23 @@ function generateGenericHook(hookName) {
|
|
|
2216
2145
|
return `export function useDeployContract() {
|
|
2217
2146
|
const config = useSecondLayerConfig()
|
|
2218
2147
|
const queryClient = useQueryClient()
|
|
2219
|
-
|
|
2148
|
+
|
|
2220
2149
|
const mutation = useMutation({
|
|
2221
2150
|
mutationFn: async (params: {
|
|
2222
2151
|
contractName: string;
|
|
2223
2152
|
codeBody: string;
|
|
2224
2153
|
network?: string;
|
|
2225
|
-
postConditions?: PostCondition[];
|
|
2226
|
-
onFinish?: (data: any) => void;
|
|
2227
|
-
onCancel?: () => void;
|
|
2228
2154
|
}) => {
|
|
2229
|
-
const { contractName, codeBody
|
|
2155
|
+
const { contractName, codeBody } = params
|
|
2230
2156
|
const network = params.network || config.network || 'mainnet'
|
|
2231
|
-
|
|
2232
|
-
return
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
network,
|
|
2237
|
-
...options,
|
|
2238
|
-
onFinish: (data: any) => {
|
|
2239
|
-
onFinish?.(data)
|
|
2240
|
-
resolve(data)
|
|
2241
|
-
},
|
|
2242
|
-
onCancel: () => {
|
|
2243
|
-
onCancel?.()
|
|
2244
|
-
reject(new Error('User cancelled contract deployment'))
|
|
2245
|
-
}
|
|
2246
|
-
})
|
|
2157
|
+
|
|
2158
|
+
return await request('stx_deployContract', {
|
|
2159
|
+
name: contractName,
|
|
2160
|
+
clarityCode: codeBody,
|
|
2161
|
+
network,
|
|
2247
2162
|
})
|
|
2248
2163
|
},
|
|
2249
2164
|
onSuccess: () => {
|
|
2250
|
-
// Invalidate relevant queries on success
|
|
2251
2165
|
queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
|
|
2252
2166
|
},
|
|
2253
2167
|
onError: (error) => {
|
|
@@ -2259,16 +2173,12 @@ function generateGenericHook(hookName) {
|
|
|
2259
2173
|
contractName: string;
|
|
2260
2174
|
codeBody: string;
|
|
2261
2175
|
network?: string;
|
|
2262
|
-
postConditions?: PostCondition[];
|
|
2263
|
-
onFinish?: (data: any) => void;
|
|
2264
|
-
onCancel?: () => void;
|
|
2265
2176
|
}) => {
|
|
2266
2177
|
return mutation.mutateAsync(params)
|
|
2267
2178
|
}, [mutation])
|
|
2268
2179
|
|
|
2269
2180
|
return {
|
|
2270
2181
|
deployContract,
|
|
2271
|
-
// Expose mutation state
|
|
2272
2182
|
isPending: mutation.isPending,
|
|
2273
2183
|
isError: mutation.isError,
|
|
2274
2184
|
isSuccess: mutation.isSuccess,
|
|
@@ -2286,35 +2196,30 @@ function generateGenericHook(hookName) {
|
|
|
2286
2196
|
init_format();
|
|
2287
2197
|
|
|
2288
2198
|
// src/plugins/react/generators/utils.ts
|
|
2289
|
-
import { toCamelCase as
|
|
2199
|
+
import { toCamelCase as toCamelCase7 } from "@secondlayer/stacks/clarity";
|
|
2290
2200
|
function capitalize(str) {
|
|
2291
2201
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
2292
2202
|
}
|
|
2293
2203
|
function generateHookArgsSignature(args) {
|
|
2294
2204
|
if (args.length === 0)
|
|
2295
2205
|
return "";
|
|
2296
|
-
const argsList = args.map((arg) => `${
|
|
2206
|
+
const argsList = args.map((arg) => `${toCamelCase7(arg.name)}: ${clarityTypeToTS(arg.type)}`).join(", ");
|
|
2297
2207
|
return `${argsList}`;
|
|
2298
2208
|
}
|
|
2299
2209
|
function generateArgsType(args) {
|
|
2300
2210
|
if (args.length === 0)
|
|
2301
2211
|
return "void";
|
|
2302
|
-
const argsList = args.map((arg) => `${
|
|
2212
|
+
const argsList = args.map((arg) => `${toCamelCase7(arg.name)}: ${clarityTypeToTS(arg.type)}`).join("; ");
|
|
2303
2213
|
return `{ ${argsList} }`;
|
|
2304
2214
|
}
|
|
2305
|
-
function
|
|
2215
|
+
function generateArgNames(args) {
|
|
2306
2216
|
if (args.length === 0)
|
|
2307
2217
|
return "";
|
|
2308
|
-
return args.map((arg) =>
|
|
2309
|
-
}
|
|
2310
|
-
function generateFunctionCallArgs(args) {
|
|
2311
|
-
if (args.length === 0)
|
|
2312
|
-
return "";
|
|
2313
|
-
return args.map((arg) => toCamelCase5(arg.name)).join(", ");
|
|
2218
|
+
return args.map((arg) => toCamelCase7(arg.name)).join(", ");
|
|
2314
2219
|
}
|
|
2315
2220
|
function generateEnabledCondition(args) {
|
|
2316
2221
|
return args.map((arg) => {
|
|
2317
|
-
const camelName =
|
|
2222
|
+
const camelName = toCamelCase7(arg.name);
|
|
2318
2223
|
const type = clarityTypeToTS(arg.type);
|
|
2319
2224
|
if (type === "string")
|
|
2320
2225
|
return `!!${camelName}`;
|
|
@@ -2326,7 +2231,7 @@ function generateEnabledCondition(args) {
|
|
|
2326
2231
|
function generateObjectArgs(args) {
|
|
2327
2232
|
if (args.length === 0)
|
|
2328
2233
|
return "";
|
|
2329
|
-
return args.map((arg) => `${arg.name}: ${
|
|
2234
|
+
return args.map((arg) => `${arg.name}: ${toCamelCase7(arg.name)}`).join(", ");
|
|
2330
2235
|
}
|
|
2331
2236
|
|
|
2332
2237
|
// src/plugins/react/generators/contract.ts
|
|
@@ -2334,8 +2239,8 @@ async function generateContractHooks(contracts, excludeList = []) {
|
|
|
2334
2239
|
const imports = `import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
2335
2240
|
import { useCallback } from 'react'
|
|
2336
2241
|
import { useSecondLayerConfig } from './provider'
|
|
2337
|
-
import { request
|
|
2338
|
-
import type { PostCondition } from '@stacks
|
|
2242
|
+
import { request } from '@secondlayer/stacks/connect'
|
|
2243
|
+
import type { PostCondition } from '@secondlayer/stacks'
|
|
2339
2244
|
import { ${contracts.map((c) => c.name).join(", ")} } from './contracts'`;
|
|
2340
2245
|
const header = `/**
|
|
2341
2246
|
* Generated contract-specific React hooks
|
|
@@ -2356,24 +2261,24 @@ function generateContractHookMethods(contract, excludeList) {
|
|
|
2356
2261
|
const functions = abi.functions || [];
|
|
2357
2262
|
const maps = abi.maps || [];
|
|
2358
2263
|
const variables = abi.variables || [];
|
|
2359
|
-
const readOnlyFunctions = functions.filter((f) => f.access === "
|
|
2264
|
+
const readOnlyFunctions = functions.filter((f) => f.access === "read-only");
|
|
2360
2265
|
const publicFunctions = functions.filter((f) => f.access === "public");
|
|
2361
2266
|
const readHooks = readOnlyFunctions.map((func) => {
|
|
2362
|
-
const hookName = `use${capitalize(name)}${capitalize(
|
|
2267
|
+
const hookName = `use${capitalize(name)}${capitalize(toCamelCase7(func.name))}`;
|
|
2363
2268
|
if (excludeList.includes(hookName)) {
|
|
2364
2269
|
return null;
|
|
2365
2270
|
}
|
|
2366
2271
|
return generateReadHook(func, name);
|
|
2367
2272
|
}).filter(Boolean);
|
|
2368
2273
|
const writeHooks = publicFunctions.map((func) => {
|
|
2369
|
-
const hookName = `use${capitalize(name)}${capitalize(
|
|
2274
|
+
const hookName = `use${capitalize(name)}${capitalize(toCamelCase7(func.name))}`;
|
|
2370
2275
|
if (excludeList.includes(hookName)) {
|
|
2371
2276
|
return null;
|
|
2372
2277
|
}
|
|
2373
2278
|
return generateWriteHook(func, name);
|
|
2374
2279
|
}).filter(Boolean);
|
|
2375
2280
|
const mapHooks = maps.map((map) => {
|
|
2376
|
-
const hookName = `use${capitalize(name)}${capitalize(
|
|
2281
|
+
const hookName = `use${capitalize(name)}${capitalize(toCamelCase7(map.name))}`;
|
|
2377
2282
|
if (excludeList.includes(hookName)) {
|
|
2378
2283
|
return null;
|
|
2379
2284
|
}
|
|
@@ -2381,7 +2286,7 @@ function generateContractHookMethods(contract, excludeList) {
|
|
|
2381
2286
|
}).filter(Boolean);
|
|
2382
2287
|
const dataVars = variables.filter((v) => v.access === "variable");
|
|
2383
2288
|
const varHooks = dataVars.map((variable) => {
|
|
2384
|
-
const hookName = `use${capitalize(name)}${capitalize(
|
|
2289
|
+
const hookName = `use${capitalize(name)}${capitalize(toCamelCase7(variable.name))}`;
|
|
2385
2290
|
if (excludeList.includes(hookName)) {
|
|
2386
2291
|
return null;
|
|
2387
2292
|
}
|
|
@@ -2389,7 +2294,7 @@ function generateContractHookMethods(contract, excludeList) {
|
|
|
2389
2294
|
}).filter(Boolean);
|
|
2390
2295
|
const constants = variables.filter((v) => v.access === "constant");
|
|
2391
2296
|
const constantHooks = constants.map((constant) => {
|
|
2392
|
-
const hookName = `use${capitalize(name)}${capitalize(
|
|
2297
|
+
const hookName = `use${capitalize(name)}${capitalize(toCamelCase7(constant.name))}`;
|
|
2393
2298
|
if (excludeList.includes(hookName)) {
|
|
2394
2299
|
return null;
|
|
2395
2300
|
}
|
|
@@ -2404,7 +2309,7 @@ function generateContractHookMethods(contract, excludeList) {
|
|
|
2404
2309
|
`);
|
|
2405
2310
|
}
|
|
2406
2311
|
function generateReadHook(func, contractName) {
|
|
2407
|
-
const hookName = `use${capitalize(contractName)}${capitalize(
|
|
2312
|
+
const hookName = `use${capitalize(contractName)}${capitalize(toCamelCase7(func.name))}`;
|
|
2408
2313
|
const argsSignature = generateHookArgsSignature(func.args);
|
|
2409
2314
|
const enabledParam = func.args.length > 0 ? ", options?: { enabled?: boolean }" : "options?: { enabled?: boolean }";
|
|
2410
2315
|
const returnType = clarityTypeToTS(func.outputs);
|
|
@@ -2412,8 +2317,8 @@ function generateReadHook(func, contractName) {
|
|
|
2412
2317
|
const config = useSecondLayerConfig()
|
|
2413
2318
|
|
|
2414
2319
|
return useQuery<${returnType}>({
|
|
2415
|
-
queryKey: ['${func.name}', ${contractName}.address, ${
|
|
2416
|
-
queryFn: () => ${contractName}.read.${
|
|
2320
|
+
queryKey: ['${func.name}', ${contractName}.address, ${generateArgNames(func.args)}],
|
|
2321
|
+
queryFn: () => ${contractName}.read.${toCamelCase7(func.name)}(${generateArgNames(func.args) ? `{ ${generateObjectArgs(func.args)} }, ` : ""}{
|
|
2417
2322
|
network: config.network,
|
|
2418
2323
|
senderAddress: config.senderAddress || 'SP000000000000000000002Q6VF78'
|
|
2419
2324
|
}),
|
|
@@ -2423,7 +2328,7 @@ function generateReadHook(func, contractName) {
|
|
|
2423
2328
|
}`;
|
|
2424
2329
|
}
|
|
2425
2330
|
function generateWriteHook(func, contractName) {
|
|
2426
|
-
const hookName = `use${capitalize(contractName)}${capitalize(
|
|
2331
|
+
const hookName = `use${capitalize(contractName)}${capitalize(toCamelCase7(func.name))}`;
|
|
2427
2332
|
const argsType = generateArgsType(func.args);
|
|
2428
2333
|
return `export function ${hookName}() {
|
|
2429
2334
|
const config = useSecondLayerConfig()
|
|
@@ -2440,46 +2345,21 @@ function generateWriteHook(func, contractName) {
|
|
|
2440
2345
|
};
|
|
2441
2346
|
}) => {
|
|
2442
2347
|
const { args, options = {} } = params
|
|
2443
|
-
const contractCallData = ${contractName}.${
|
|
2348
|
+
const contractCallData = ${contractName}.${toCamelCase7(func.name)}(args)
|
|
2444
2349
|
const { contractAddress, contractName: name, functionName, functionArgs } = contractCallData
|
|
2445
2350
|
const network = config.network || 'mainnet'
|
|
2446
2351
|
const contract = \`\${contractAddress}.\${name}\`
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
options.onFinish?.(result)
|
|
2459
|
-
return result
|
|
2460
|
-
} catch (connectError) {
|
|
2461
|
-
// Fallback to openContractCall for broader wallet compatibility
|
|
2462
|
-
console.warn('stx_callContract not supported, falling back to openContractCall:', connectError)
|
|
2463
|
-
|
|
2464
|
-
return new Promise((resolve, reject) => {
|
|
2465
|
-
stacksOpenContractCall({
|
|
2466
|
-
contractAddress,
|
|
2467
|
-
contractName: name,
|
|
2468
|
-
functionName,
|
|
2469
|
-
functionArgs,
|
|
2470
|
-
network,
|
|
2471
|
-
...options,
|
|
2472
|
-
onFinish: (data: any) => {
|
|
2473
|
-
options.onFinish?.(data)
|
|
2474
|
-
resolve(data)
|
|
2475
|
-
},
|
|
2476
|
-
onCancel: () => {
|
|
2477
|
-
options.onCancel?.()
|
|
2478
|
-
reject(new Error('User cancelled transaction'))
|
|
2479
|
-
}
|
|
2480
|
-
})
|
|
2481
|
-
})
|
|
2482
|
-
}
|
|
2352
|
+
|
|
2353
|
+
const result = await request('stx_callContract', {
|
|
2354
|
+
contract,
|
|
2355
|
+
functionName,
|
|
2356
|
+
functionArgs,
|
|
2357
|
+
network,
|
|
2358
|
+
...options
|
|
2359
|
+
})
|
|
2360
|
+
|
|
2361
|
+
options.onFinish?.(result)
|
|
2362
|
+
return result
|
|
2483
2363
|
},
|
|
2484
2364
|
onSuccess: () => {
|
|
2485
2365
|
// Invalidate relevant queries on success
|
|
@@ -2490,7 +2370,7 @@ function generateWriteHook(func, contractName) {
|
|
|
2490
2370
|
}
|
|
2491
2371
|
})
|
|
2492
2372
|
|
|
2493
|
-
const ${
|
|
2373
|
+
const ${toCamelCase7(func.name)} = useCallback(async (
|
|
2494
2374
|
args: ${argsType},
|
|
2495
2375
|
options?: {
|
|
2496
2376
|
postConditions?: PostCondition[];
|
|
@@ -2503,7 +2383,7 @@ function generateWriteHook(func, contractName) {
|
|
|
2503
2383
|
}, [mutation])
|
|
2504
2384
|
|
|
2505
2385
|
return {
|
|
2506
|
-
${
|
|
2386
|
+
${toCamelCase7(func.name)},
|
|
2507
2387
|
// Expose mutation state
|
|
2508
2388
|
isPending: mutation.isPending,
|
|
2509
2389
|
isError: mutation.isError,
|
|
@@ -2515,7 +2395,7 @@ function generateWriteHook(func, contractName) {
|
|
|
2515
2395
|
}`;
|
|
2516
2396
|
}
|
|
2517
2397
|
function generateMapHook(map, contractVarName, _address, _contractName) {
|
|
2518
|
-
const hookName = `use${capitalize(contractVarName)}${capitalize(
|
|
2398
|
+
const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase7(map.name))}`;
|
|
2519
2399
|
const keyType = clarityTypeToTS(map.key);
|
|
2520
2400
|
const valueType = clarityTypeToTS(map.value);
|
|
2521
2401
|
return `export function ${hookName}(key: ${keyType}, options?: { enabled?: boolean }) {
|
|
@@ -2524,14 +2404,14 @@ function generateMapHook(map, contractVarName, _address, _contractName) {
|
|
|
2524
2404
|
return useQuery<${valueType} | null>({
|
|
2525
2405
|
queryKey: ['${contractVarName}', '${map.name}', 'map', key, config.network],
|
|
2526
2406
|
queryFn: async () => {
|
|
2527
|
-
return ${contractVarName}.maps.${
|
|
2407
|
+
return ${contractVarName}.maps.${toCamelCase7(map.name)}.get(key, { network: config.network })
|
|
2528
2408
|
},
|
|
2529
2409
|
enabled: options?.enabled ?? true
|
|
2530
2410
|
})
|
|
2531
2411
|
}`;
|
|
2532
2412
|
}
|
|
2533
2413
|
function generateVarHook(variable, contractVarName, _address, _contractName) {
|
|
2534
|
-
const hookName = `use${capitalize(contractVarName)}${capitalize(
|
|
2414
|
+
const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase7(variable.name))}`;
|
|
2535
2415
|
const valueType = clarityTypeToTS(variable.type);
|
|
2536
2416
|
return `export function ${hookName}(options?: { enabled?: boolean }) {
|
|
2537
2417
|
const config = useSecondLayerConfig()
|
|
@@ -2539,14 +2419,14 @@ function generateVarHook(variable, contractVarName, _address, _contractName) {
|
|
|
2539
2419
|
return useQuery<${valueType}>({
|
|
2540
2420
|
queryKey: ['${contractVarName}', '${variable.name}', 'var', config.network],
|
|
2541
2421
|
queryFn: async () => {
|
|
2542
|
-
return ${contractVarName}.vars.${
|
|
2422
|
+
return ${contractVarName}.vars.${toCamelCase7(variable.name)}.get({ network: config.network })
|
|
2543
2423
|
},
|
|
2544
2424
|
enabled: options?.enabled ?? true
|
|
2545
2425
|
})
|
|
2546
2426
|
}`;
|
|
2547
2427
|
}
|
|
2548
2428
|
function generateConstantHook(constant, contractVarName, _address, _contractName) {
|
|
2549
|
-
const hookName = `use${capitalize(contractVarName)}${capitalize(
|
|
2429
|
+
const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase7(constant.name))}`;
|
|
2550
2430
|
const valueType = clarityTypeToTS(constant.type);
|
|
2551
2431
|
return `export function ${hookName}(options?: { enabled?: boolean }) {
|
|
2552
2432
|
const config = useSecondLayerConfig()
|
|
@@ -2554,7 +2434,7 @@ function generateConstantHook(constant, contractVarName, _address, _contractName
|
|
|
2554
2434
|
return useQuery<${valueType}>({
|
|
2555
2435
|
queryKey: ['${contractVarName}', '${constant.name}', 'constant', config.network],
|
|
2556
2436
|
queryFn: async () => {
|
|
2557
|
-
return ${contractVarName}.constants.${
|
|
2437
|
+
return ${contractVarName}.constants.${toCamelCase7(constant.name)}.get({ network: config.network })
|
|
2558
2438
|
},
|
|
2559
2439
|
enabled: options?.enabled ?? true,
|
|
2560
2440
|
staleTime: Infinity // Constants never change
|
|
@@ -2602,155 +2482,17 @@ var react = (options = {}) => {
|
|
|
2602
2482
|
};
|
|
2603
2483
|
// src/plugins/testing/generators.ts
|
|
2604
2484
|
import {
|
|
2605
|
-
toCamelCase as
|
|
2606
|
-
|
|
2485
|
+
toCamelCase as toCamelCase8,
|
|
2486
|
+
isAbiTuple as isAbiTuple3
|
|
2487
|
+
} from "@secondlayer/stacks/clarity";
|
|
2607
2488
|
function toPascalCase(str) {
|
|
2608
|
-
const camel =
|
|
2489
|
+
const camel = toCamelCase8(str);
|
|
2609
2490
|
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
2610
2491
|
}
|
|
2611
|
-
function generateArgsSignature2(args) {
|
|
2612
|
-
if (args.length === 0)
|
|
2613
|
-
return "";
|
|
2614
|
-
const argsTypes = args.map((arg) => {
|
|
2615
|
-
const camelName = toCamelCase6(arg.name);
|
|
2616
|
-
return `${camelName}: ${getTypeForArg(arg)}`;
|
|
2617
|
-
}).join("; ");
|
|
2618
|
-
return `args: { ${argsTypes} }, `;
|
|
2619
|
-
}
|
|
2620
|
-
function generateClarityConversion3(argName, argType) {
|
|
2621
|
-
const type = argType.type;
|
|
2622
|
-
if (typeof type === "string") {
|
|
2623
|
-
switch (type) {
|
|
2624
|
-
case "uint128":
|
|
2625
|
-
return `Cl.uint(${argName})`;
|
|
2626
|
-
case "int128":
|
|
2627
|
-
return `Cl.int(${argName})`;
|
|
2628
|
-
case "bool":
|
|
2629
|
-
return `Cl.bool(${argName})`;
|
|
2630
|
-
case "principal":
|
|
2631
|
-
case "trait_reference":
|
|
2632
|
-
return `(() => {
|
|
2633
|
-
const principal = ${argName};
|
|
2634
|
-
if (principal.includes(".")) {
|
|
2635
|
-
const [address, contractName] = principal.split(".") as [string, string];
|
|
2636
|
-
return Cl.contractPrincipal(address, contractName);
|
|
2637
|
-
} else {
|
|
2638
|
-
return Cl.standardPrincipal(principal);
|
|
2639
|
-
}
|
|
2640
|
-
})()`;
|
|
2641
|
-
default:
|
|
2642
|
-
return `${argName}`;
|
|
2643
|
-
}
|
|
2644
|
-
}
|
|
2645
|
-
if (type["string-ascii"]) {
|
|
2646
|
-
return `Cl.stringAscii(${argName})`;
|
|
2647
|
-
}
|
|
2648
|
-
if (type["string-utf8"]) {
|
|
2649
|
-
return `Cl.stringUtf8(${argName})`;
|
|
2650
|
-
}
|
|
2651
|
-
if (type.buff) {
|
|
2652
|
-
return `(() => {
|
|
2653
|
-
const value = ${argName};
|
|
2654
|
-
if (value instanceof Uint8Array) {
|
|
2655
|
-
return Cl.buffer(value);
|
|
2656
|
-
}
|
|
2657
|
-
if (typeof value === 'object' && value !== null && 'type' in value && 'value' in value) {
|
|
2658
|
-
switch (value.type) {
|
|
2659
|
-
case 'ascii':
|
|
2660
|
-
return Cl.bufferFromAscii(value.value);
|
|
2661
|
-
case 'utf8':
|
|
2662
|
-
return Cl.bufferFromUtf8(value.value);
|
|
2663
|
-
case 'hex':
|
|
2664
|
-
return Cl.bufferFromHex(value.value);
|
|
2665
|
-
default:
|
|
2666
|
-
throw new Error(\`Unsupported buffer type: \${value.type}\`);
|
|
2667
|
-
}
|
|
2668
|
-
}
|
|
2669
|
-
if (typeof value === 'string') {
|
|
2670
|
-
if (value.startsWith('0x') || /^[0-9a-fA-F]+$/.test(value)) {
|
|
2671
|
-
return Cl.bufferFromHex(value);
|
|
2672
|
-
}
|
|
2673
|
-
if (!/^[\\x00-\\x7F]*$/.test(value)) {
|
|
2674
|
-
return Cl.bufferFromUtf8(value);
|
|
2675
|
-
}
|
|
2676
|
-
return Cl.bufferFromAscii(value);
|
|
2677
|
-
}
|
|
2678
|
-
throw new Error(\`Invalid buffer value: \${value}\`);
|
|
2679
|
-
})()`;
|
|
2680
|
-
}
|
|
2681
|
-
if (type.optional) {
|
|
2682
|
-
const innerConversion = generateClarityConversion3("inner", {
|
|
2683
|
-
type: type.optional
|
|
2684
|
-
});
|
|
2685
|
-
return `${argName} !== null ? Cl.some((() => { const inner = ${argName}; return ${innerConversion}; })()) : Cl.none()`;
|
|
2686
|
-
}
|
|
2687
|
-
if (type.list) {
|
|
2688
|
-
const innerConversion = generateClarityConversion3("item", {
|
|
2689
|
-
type: type.list.type
|
|
2690
|
-
});
|
|
2691
|
-
const maxLength = type.list.length || 100;
|
|
2692
|
-
return `(() => {
|
|
2693
|
-
const listValue = ${argName};
|
|
2694
|
-
if (listValue.length > ${maxLength}) {
|
|
2695
|
-
throw new Error(\`List length \${listValue.length} exceeds max ${maxLength}\`);
|
|
2696
|
-
}
|
|
2697
|
-
return Cl.list(listValue.map(item => ${innerConversion}));
|
|
2698
|
-
})()`;
|
|
2699
|
-
}
|
|
2700
|
-
if (type.tuple) {
|
|
2701
|
-
const requiredFields = type.tuple.map((f) => f.name);
|
|
2702
|
-
const fieldNames = JSON.stringify(requiredFields);
|
|
2703
|
-
const fields = type.tuple.map((field) => {
|
|
2704
|
-
const camelFieldName = toCamelCase6(field.name);
|
|
2705
|
-
const fieldConversion = generateClarityConversion3(`tupleValue.${camelFieldName}`, { type: field.type });
|
|
2706
|
-
return `"${field.name}": ${fieldConversion}`;
|
|
2707
|
-
}).join(", ");
|
|
2708
|
-
return `(() => {
|
|
2709
|
-
const tupleValue = ${argName};
|
|
2710
|
-
const requiredFields = ${fieldNames};
|
|
2711
|
-
for (const fieldName of requiredFields) {
|
|
2712
|
-
const camelName = fieldName.replace(/-([a-z])/g, (_: string, l: string) => l.toUpperCase());
|
|
2713
|
-
if (!(fieldName in tupleValue) && !(camelName in tupleValue)) {
|
|
2714
|
-
throw new Error(\`Missing tuple field: \${fieldName}\`);
|
|
2715
|
-
}
|
|
2716
|
-
}
|
|
2717
|
-
return Cl.tuple({ ${fields} });
|
|
2718
|
-
})()`;
|
|
2719
|
-
}
|
|
2720
|
-
if (type.response) {
|
|
2721
|
-
const okConversion = generateClarityConversion3(`responseValue.ok`, {
|
|
2722
|
-
type: type.response.ok
|
|
2723
|
-
});
|
|
2724
|
-
const errConversion = generateClarityConversion3(`responseValue.err`, {
|
|
2725
|
-
type: type.response.error
|
|
2726
|
-
});
|
|
2727
|
-
return `(() => {
|
|
2728
|
-
const responseValue = ${argName};
|
|
2729
|
-
const hasOk = 'ok' in responseValue;
|
|
2730
|
-
const hasErr = 'err' in responseValue;
|
|
2731
|
-
if (hasOk && !hasErr) {
|
|
2732
|
-
return Cl.ok(${okConversion});
|
|
2733
|
-
}
|
|
2734
|
-
if (hasErr && !hasOk) {
|
|
2735
|
-
return Cl.error(${errConversion});
|
|
2736
|
-
}
|
|
2737
|
-
throw new Error("Response must have exactly 'ok' or 'err' property");
|
|
2738
|
-
})()`;
|
|
2739
|
-
}
|
|
2740
|
-
return `${argName}`;
|
|
2741
|
-
}
|
|
2742
|
-
function generateClarityArgs2(args) {
|
|
2743
|
-
if (args.length === 0)
|
|
2744
|
-
return "";
|
|
2745
|
-
return args.map((arg) => {
|
|
2746
|
-
const argName = `args.${toCamelCase6(arg.name)}`;
|
|
2747
|
-
return generateClarityConversion3(argName, arg);
|
|
2748
|
-
}).join(", ");
|
|
2749
|
-
}
|
|
2750
2492
|
function generatePublicFunction(func, contractId) {
|
|
2751
|
-
const methodName =
|
|
2752
|
-
const argsSignature =
|
|
2753
|
-
const clarityArgs =
|
|
2493
|
+
const methodName = toCamelCase8(func.name);
|
|
2494
|
+
const argsSignature = generateArgsSignature(func.args);
|
|
2495
|
+
const clarityArgs = generateClarityArgs(func.args);
|
|
2754
2496
|
return `${methodName}: (${argsSignature}caller: string) => {
|
|
2755
2497
|
const callerAddr = accounts.get(caller) ?? caller;
|
|
2756
2498
|
return simnet.callPublicFn(
|
|
@@ -2762,9 +2504,9 @@ function generatePublicFunction(func, contractId) {
|
|
|
2762
2504
|
}`;
|
|
2763
2505
|
}
|
|
2764
2506
|
function generateReadOnlyFunction(func, contractId) {
|
|
2765
|
-
const methodName =
|
|
2766
|
-
const argsSignature =
|
|
2767
|
-
const clarityArgs =
|
|
2507
|
+
const methodName = toCamelCase8(func.name);
|
|
2508
|
+
const argsSignature = generateArgsSignature(func.args);
|
|
2509
|
+
const clarityArgs = generateClarityArgs(func.args);
|
|
2768
2510
|
const hasArgs = func.args.length > 0;
|
|
2769
2511
|
const argsParam = hasArgs ? argsSignature : "";
|
|
2770
2512
|
return `${methodName}: (${argsParam}) => {
|
|
@@ -2777,9 +2519,9 @@ function generateReadOnlyFunction(func, contractId) {
|
|
|
2777
2519
|
}`;
|
|
2778
2520
|
}
|
|
2779
2521
|
function generatePrivateFunction(func, contractId) {
|
|
2780
|
-
const methodName =
|
|
2781
|
-
const argsSignature =
|
|
2782
|
-
const clarityArgs =
|
|
2522
|
+
const methodName = toCamelCase8(func.name);
|
|
2523
|
+
const argsSignature = generateArgsSignature(func.args);
|
|
2524
|
+
const clarityArgs = generateClarityArgs(func.args);
|
|
2783
2525
|
return `${methodName}: (${argsSignature}caller: string) => {
|
|
2784
2526
|
const callerAddr = accounts.get(caller) ?? caller;
|
|
2785
2527
|
return simnet.callPrivateFn(
|
|
@@ -2791,31 +2533,31 @@ function generatePrivateFunction(func, contractId) {
|
|
|
2791
2533
|
}`;
|
|
2792
2534
|
}
|
|
2793
2535
|
function generateDataVarHelper(variable, contractId) {
|
|
2794
|
-
const methodName =
|
|
2536
|
+
const methodName = toCamelCase8(variable.name);
|
|
2795
2537
|
return `${methodName}: () => {
|
|
2796
2538
|
return simnet.getDataVar('${contractId}', '${variable.name}');
|
|
2797
2539
|
}`;
|
|
2798
2540
|
}
|
|
2799
2541
|
function getMapKeyType(keyType) {
|
|
2800
|
-
if (keyType
|
|
2801
|
-
const fields = keyType.tuple.map((field) => `${
|
|
2542
|
+
if (isAbiTuple3(keyType)) {
|
|
2543
|
+
const fields = keyType.tuple.map((field) => `${toCamelCase8(field.name)}: ${getTypeForArg({ type: field.type })}`).join("; ");
|
|
2802
2544
|
return `{ ${fields} }`;
|
|
2803
2545
|
}
|
|
2804
2546
|
return getTypeForArg({ type: keyType });
|
|
2805
2547
|
}
|
|
2806
2548
|
function generateMapKeyConversion2(keyType) {
|
|
2807
|
-
if (keyType
|
|
2549
|
+
if (isAbiTuple3(keyType)) {
|
|
2808
2550
|
const fields = keyType.tuple.map((field) => {
|
|
2809
|
-
const camelFieldName =
|
|
2810
|
-
const fieldConversion =
|
|
2551
|
+
const camelFieldName = toCamelCase8(field.name);
|
|
2552
|
+
const fieldConversion = generateClarityConversion(`key.${camelFieldName}`, { type: field.type });
|
|
2811
2553
|
return `"${field.name}": ${fieldConversion}`;
|
|
2812
2554
|
}).join(", ");
|
|
2813
2555
|
return `Cl.tuple({ ${fields} })`;
|
|
2814
2556
|
}
|
|
2815
|
-
return
|
|
2557
|
+
return generateClarityConversion("key", { type: keyType });
|
|
2816
2558
|
}
|
|
2817
2559
|
function generateMapEntryHelper(map, contractId) {
|
|
2818
|
-
const methodName =
|
|
2560
|
+
const methodName = toCamelCase8(map.name);
|
|
2819
2561
|
const keyType = getMapKeyType(map.key);
|
|
2820
2562
|
const keyConversion = generateMapKeyConversion2(map.key);
|
|
2821
2563
|
return `${methodName}: (key: ${keyType}) => {
|
|
@@ -2856,7 +2598,7 @@ function generateContractHelper(contract, options) {
|
|
|
2856
2598
|
const maps = abi.maps || [];
|
|
2857
2599
|
const pascalName = toPascalCase(name);
|
|
2858
2600
|
const publicFns = functions.filter((f) => f.access === "public");
|
|
2859
|
-
const readOnlyFns = functions.filter((f) => f.access === "
|
|
2601
|
+
const readOnlyFns = functions.filter((f) => f.access === "read-only");
|
|
2860
2602
|
const privateFns = options.includePrivate ? functions.filter((f) => f.access === "private") : [];
|
|
2861
2603
|
const publicHelpers = publicFns.map((f) => generatePublicFunction(f, address));
|
|
2862
2604
|
const readOnlyHelpers = readOnlyFns.map((f) => generateReadOnlyFunction(f, address));
|
|
@@ -2885,7 +2627,7 @@ function generateContractHelper(contract, options) {
|
|
|
2885
2627
|
}
|
|
2886
2628
|
function generateGetContracts(contracts) {
|
|
2887
2629
|
const contractEntries = contracts.map((contract) => {
|
|
2888
|
-
const camelName =
|
|
2630
|
+
const camelName = toCamelCase8(contract.name);
|
|
2889
2631
|
const pascalName = toPascalCase(contract.name);
|
|
2890
2632
|
return `${camelName}: get${pascalName}(simnet)`;
|
|
2891
2633
|
}).join(`,
|
|
@@ -3013,5 +2755,5 @@ export {
|
|
|
3013
2755
|
PluginManager
|
|
3014
2756
|
};
|
|
3015
2757
|
|
|
3016
|
-
//# debugId=
|
|
2758
|
+
//# debugId=217DF2A543BBE57564756E2164756E21
|
|
3017
2759
|
//# sourceMappingURL=index.js.map
|