@secondlayer/cli 3.3.0 → 3.3.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 +2 -2
- package/dist/cli.js.map +3 -3
- package/dist/index.d.ts +20 -83
- package/dist/index.js +13 -2374
- package/dist/index.js.map +5 -22
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14,7 +14,6 @@ var __export = (target, all) => {
|
|
|
14
14
|
});
|
|
15
15
|
};
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
18
17
|
|
|
19
18
|
// src/utils/format.ts
|
|
20
19
|
var exports_format = {};
|
|
@@ -67,6 +66,14 @@ async function formatCode(code) {
|
|
|
67
66
|
var biome = null;
|
|
68
67
|
var init_format = () => {};
|
|
69
68
|
|
|
69
|
+
// src/utils/config.ts
|
|
70
|
+
import { randomBytes } from "crypto";
|
|
71
|
+
import { promises as fs2 } from "fs";
|
|
72
|
+
import { createRequire as createRequire2 } from "module";
|
|
73
|
+
import { tmpdir } from "os";
|
|
74
|
+
import path2 from "path";
|
|
75
|
+
import { pathToFileURL } from "url";
|
|
76
|
+
|
|
70
77
|
// src/core/plugin-manager.ts
|
|
71
78
|
import { promises as fs } from "fs";
|
|
72
79
|
import path from "path";
|
|
@@ -356,2383 +363,15 @@ ${JSON.stringify(content, null, 2)}`;
|
|
|
356
363
|
};
|
|
357
364
|
}
|
|
358
365
|
}
|
|
359
|
-
// src/plugins/clarinet/index.ts
|
|
360
|
-
import { initSimnet } from "@hirosystems/clarinet-sdk";
|
|
361
|
-
import { toCamelCase as toCamelCase6 } from "@secondlayer/stacks/clarity";
|
|
362
|
-
|
|
363
|
-
// src/generators/contract.ts
|
|
364
|
-
import {
|
|
365
|
-
toCamelCase as toCamelCase5
|
|
366
|
-
} from "@secondlayer/stacks/clarity";
|
|
367
|
-
|
|
368
|
-
// src/utils/clarity-conversion.ts
|
|
369
|
-
import {
|
|
370
|
-
isAbiBuffer,
|
|
371
|
-
isAbiList,
|
|
372
|
-
isAbiOptional,
|
|
373
|
-
isAbiResponse,
|
|
374
|
-
isAbiStringAscii,
|
|
375
|
-
isAbiStringUtf8,
|
|
376
|
-
isAbiTuple,
|
|
377
|
-
toCamelCase as toCamelCase2
|
|
378
|
-
} from "@secondlayer/stacks/clarity";
|
|
379
|
-
function generateClarityConversion(argName, argType) {
|
|
380
|
-
const type = argType.type;
|
|
381
|
-
if (typeof type === "string") {
|
|
382
|
-
switch (type) {
|
|
383
|
-
case "uint128":
|
|
384
|
-
return `Cl.uint(${argName})`;
|
|
385
|
-
case "int128":
|
|
386
|
-
return `Cl.int(${argName})`;
|
|
387
|
-
case "bool":
|
|
388
|
-
return `Cl.bool(${argName})`;
|
|
389
|
-
case "principal":
|
|
390
|
-
case "trait_reference":
|
|
391
|
-
return `(() => {
|
|
392
|
-
const [address, contractName] = ${argName}.split(".") as [string, string | undefined];
|
|
393
|
-
if (!validateStacksAddress(address)) {
|
|
394
|
-
throw new Error("Invalid Stacks address format");
|
|
395
|
-
}
|
|
396
|
-
if (contractName !== undefined) {
|
|
397
|
-
if (!CONTRACT_NAME_REGEX.test(contractName)) {
|
|
398
|
-
throw new Error("Invalid contract name format: must start with letter and contain only letters, numbers, and hyphens");
|
|
399
|
-
}
|
|
400
|
-
return Cl.contractPrincipal(address, contractName);
|
|
401
|
-
}
|
|
402
|
-
return Cl.standardPrincipal(${argName});
|
|
403
|
-
})()`;
|
|
404
|
-
default:
|
|
405
|
-
return `${argName}`;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if (isAbiStringAscii(type)) {
|
|
409
|
-
return `Cl.stringAscii(${argName})`;
|
|
410
|
-
}
|
|
411
|
-
if (isAbiStringUtf8(type)) {
|
|
412
|
-
return `Cl.stringUtf8(${argName})`;
|
|
413
|
-
}
|
|
414
|
-
if (isAbiBuffer(type)) {
|
|
415
|
-
return `(() => {
|
|
416
|
-
const value = ${argName};
|
|
417
|
-
if (value instanceof Uint8Array) {
|
|
418
|
-
return Cl.buffer(value);
|
|
419
|
-
}
|
|
420
|
-
if (typeof value === 'object' && value !== null && value.type && value.value) {
|
|
421
|
-
switch (value.type) {
|
|
422
|
-
case 'ascii':
|
|
423
|
-
return Cl.bufferFromAscii(value.value);
|
|
424
|
-
case 'utf8':
|
|
425
|
-
return Cl.bufferFromUtf8(value.value);
|
|
426
|
-
case 'hex':
|
|
427
|
-
return Cl.bufferFromHex(value.value);
|
|
428
|
-
default:
|
|
429
|
-
throw new Error(\`Unsupported buffer type: \${value.type}\`);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
if (typeof value === 'string') {
|
|
433
|
-
if (value.startsWith('0x') || /^[0-9a-fA-F]+$/.test(value)) {
|
|
434
|
-
return Cl.bufferFromHex(value);
|
|
435
|
-
}
|
|
436
|
-
const hasNonAscii = value.split('').some(char => char.charCodeAt(0) > 127);
|
|
437
|
-
if (hasNonAscii) {
|
|
438
|
-
return Cl.bufferFromUtf8(value);
|
|
439
|
-
}
|
|
440
|
-
return Cl.bufferFromAscii(value);
|
|
441
|
-
}
|
|
442
|
-
throw new Error(\`Invalid buffer value: \${value}\`);
|
|
443
|
-
})()`;
|
|
444
|
-
}
|
|
445
|
-
if (isAbiOptional(type)) {
|
|
446
|
-
const innerConversion = generateClarityConversion(argName, {
|
|
447
|
-
type: type.optional
|
|
448
|
-
});
|
|
449
|
-
return `${argName} !== null ? Cl.some(${innerConversion.replace(argName, `${argName}`)}) : Cl.none()`;
|
|
450
|
-
}
|
|
451
|
-
if (isAbiList(type)) {
|
|
452
|
-
const innerConversion = generateClarityConversion("item", {
|
|
453
|
-
type: type.list.type
|
|
454
|
-
});
|
|
455
|
-
const maxLength = type.list.length || 100;
|
|
456
|
-
return `(() => {
|
|
457
|
-
const listValue = ${argName};
|
|
458
|
-
if (listValue.length > ${maxLength}) {
|
|
459
|
-
throw new Error(\`List length \${listValue.length} exceeds max ${maxLength}\`);
|
|
460
|
-
}
|
|
461
|
-
return Cl.list(listValue.map(item => ${innerConversion}));
|
|
462
|
-
})()`;
|
|
463
|
-
}
|
|
464
|
-
if (isAbiTuple(type)) {
|
|
465
|
-
const requiredFields = type.tuple.map((f) => f.name);
|
|
466
|
-
const fieldNames = JSON.stringify(requiredFields);
|
|
467
|
-
const fields = type.tuple.map((field) => {
|
|
468
|
-
const camelFieldName = toCamelCase2(field.name);
|
|
469
|
-
const fieldConversion = generateClarityConversion(`tupleValue.${camelFieldName}`, { type: field.type });
|
|
470
|
-
return `"${field.name}": ${fieldConversion}`;
|
|
471
|
-
}).join(", ");
|
|
472
|
-
return `(() => {
|
|
473
|
-
const tupleValue = ${argName};
|
|
474
|
-
const requiredFields = ${fieldNames};
|
|
475
|
-
for (const fieldName of requiredFields) {
|
|
476
|
-
const camelName = fieldName.replace(/-([a-z])/g, (_: string, l: string) => l.toUpperCase());
|
|
477
|
-
if (!(fieldName in tupleValue) && !(camelName in tupleValue)) {
|
|
478
|
-
throw new Error(\`Missing tuple field: \${fieldName}\`);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
return Cl.tuple({ ${fields} });
|
|
482
|
-
})()`;
|
|
483
|
-
}
|
|
484
|
-
if (isAbiResponse(type)) {
|
|
485
|
-
const okConversion = generateClarityConversion(`responseValue.ok`, {
|
|
486
|
-
type: type.response.ok
|
|
487
|
-
});
|
|
488
|
-
const errConversion = generateClarityConversion(`responseValue.err`, {
|
|
489
|
-
type: type.response.error
|
|
490
|
-
});
|
|
491
|
-
return `(() => {
|
|
492
|
-
const responseValue = ${argName};
|
|
493
|
-
const hasOk = 'ok' in responseValue;
|
|
494
|
-
const hasErr = 'err' in responseValue;
|
|
495
|
-
if (hasOk && !hasErr) {
|
|
496
|
-
return Cl.ok(${okConversion});
|
|
497
|
-
}
|
|
498
|
-
if (hasErr && !hasOk) {
|
|
499
|
-
return Cl.error(${errConversion});
|
|
500
|
-
}
|
|
501
|
-
throw new Error("Response must have exactly 'ok' or 'err' property");
|
|
502
|
-
})()`;
|
|
503
|
-
}
|
|
504
|
-
return `${argName}`;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// src/generators/contract.ts
|
|
508
|
-
init_format();
|
|
509
|
-
|
|
510
|
-
// src/utils/generator-helpers.ts
|
|
511
|
-
import {
|
|
512
|
-
isAbiTuple as isAbiTuple3,
|
|
513
|
-
toCamelCase as toCamelCase4
|
|
514
|
-
} from "@secondlayer/stacks/clarity";
|
|
515
|
-
|
|
516
|
-
// src/utils/type-mapping.ts
|
|
517
|
-
import {
|
|
518
|
-
isAbiBuffer as isAbiBuffer2,
|
|
519
|
-
isAbiList as isAbiList2,
|
|
520
|
-
isAbiOptional as isAbiOptional2,
|
|
521
|
-
isAbiResponse as isAbiResponse2,
|
|
522
|
-
isAbiStringAscii as isAbiStringAscii2,
|
|
523
|
-
isAbiStringUtf8 as isAbiStringUtf82,
|
|
524
|
-
isAbiTuple as isAbiTuple2,
|
|
525
|
-
toCamelCase as toCamelCase3
|
|
526
|
-
} from "@secondlayer/stacks/clarity";
|
|
527
|
-
function clarityTypeToTS(type) {
|
|
528
|
-
if (typeof type === "string") {
|
|
529
|
-
switch (type) {
|
|
530
|
-
case "uint128":
|
|
531
|
-
case "int128":
|
|
532
|
-
return "bigint";
|
|
533
|
-
case "bool":
|
|
534
|
-
return "boolean";
|
|
535
|
-
case "principal":
|
|
536
|
-
case "trait_reference":
|
|
537
|
-
return "string";
|
|
538
|
-
default: {
|
|
539
|
-
const typeStr = type;
|
|
540
|
-
if (typeStr === "none") {
|
|
541
|
-
return "null";
|
|
542
|
-
}
|
|
543
|
-
if (typeStr.includes("string") || typeStr.includes("ascii") || typeStr.includes("utf8")) {
|
|
544
|
-
return "string";
|
|
545
|
-
}
|
|
546
|
-
if (typeStr.includes("buff")) {
|
|
547
|
-
return "Uint8Array | string | { type: 'ascii' | 'utf8' | 'hex'; value: string }";
|
|
548
|
-
}
|
|
549
|
-
if (typeStr.includes("uint") || typeStr.includes("int")) {
|
|
550
|
-
return "bigint";
|
|
551
|
-
}
|
|
552
|
-
return "any";
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
if (isAbiBuffer2(type)) {
|
|
557
|
-
return "Uint8Array | string | { type: 'ascii' | 'utf8' | 'hex'; value: string }";
|
|
558
|
-
}
|
|
559
|
-
if (isAbiStringAscii2(type) || isAbiStringUtf82(type)) {
|
|
560
|
-
return "string";
|
|
561
|
-
}
|
|
562
|
-
if (isAbiOptional2(type)) {
|
|
563
|
-
const innerType = clarityTypeToTS(type.optional);
|
|
564
|
-
if (innerType.includes(" | ") && !innerType.startsWith("(")) {
|
|
565
|
-
return `(${innerType}) | null`;
|
|
566
|
-
}
|
|
567
|
-
return `${innerType} | null`;
|
|
568
|
-
}
|
|
569
|
-
if (isAbiList2(type)) {
|
|
570
|
-
const innerType = clarityTypeToTS(type.list.type);
|
|
571
|
-
if (innerType.includes(" | ") && !innerType.startsWith("(")) {
|
|
572
|
-
return `(${innerType})[]`;
|
|
573
|
-
}
|
|
574
|
-
return `${innerType}[]`;
|
|
575
|
-
}
|
|
576
|
-
if (isAbiTuple2(type)) {
|
|
577
|
-
const fields = type.tuple.map((field) => `${toCamelCase3(field.name)}: ${clarityTypeToTS(field.type)}`).join("; ");
|
|
578
|
-
return `{ ${fields} }`;
|
|
579
|
-
}
|
|
580
|
-
if (isAbiResponse2(type)) {
|
|
581
|
-
const okType = clarityTypeToTS(type.response.ok);
|
|
582
|
-
const errType = clarityTypeToTS(type.response.error);
|
|
583
|
-
return `{ ok: ${okType} } | { err: ${errType} }`;
|
|
584
|
-
}
|
|
585
|
-
return "any";
|
|
586
|
-
}
|
|
587
|
-
function getTypeForArg(arg) {
|
|
588
|
-
return clarityTypeToTS(arg.type);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// src/utils/generator-helpers.ts
|
|
592
|
-
function generateArgsSignature(args) {
|
|
593
|
-
if (args.length === 0)
|
|
594
|
-
return "";
|
|
595
|
-
const argsTypes = args.map((arg) => {
|
|
596
|
-
const camelName = toCamelCase4(arg.name);
|
|
597
|
-
return `${camelName}: ${getTypeForArg(arg)}`;
|
|
598
|
-
}).join("; ");
|
|
599
|
-
return `args: { ${argsTypes} }, `;
|
|
600
|
-
}
|
|
601
|
-
function generateClarityArgs(args) {
|
|
602
|
-
if (args.length === 0)
|
|
603
|
-
return "";
|
|
604
|
-
return args.map((arg) => {
|
|
605
|
-
const argName = `args.${toCamelCase4(arg.name)}`;
|
|
606
|
-
return generateClarityConversion(argName, arg);
|
|
607
|
-
}).join(", ");
|
|
608
|
-
}
|
|
609
|
-
function generateMapKeyConversion(keyType) {
|
|
610
|
-
if (isAbiTuple3(keyType)) {
|
|
611
|
-
const fields = keyType.tuple.map((field) => {
|
|
612
|
-
const camelFieldName = toCamelCase4(field.name);
|
|
613
|
-
const fieldConversion = generateClarityConversion(`key.${camelFieldName}`, { type: field.type });
|
|
614
|
-
return `"${field.name}": ${fieldConversion}`;
|
|
615
|
-
}).join(", ");
|
|
616
|
-
return `Cl.tuple({ ${fields} })`;
|
|
617
|
-
}
|
|
618
|
-
return generateClarityConversion("key", { type: keyType });
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// src/generators/contract.ts
|
|
622
|
-
function generateNetworkUtils() {
|
|
623
|
-
return `/**
|
|
624
|
-
* API URLs for different networks
|
|
625
|
-
*/
|
|
626
|
-
const API_URLS: Record<'mainnet' | 'testnet' | 'devnet', string> = {
|
|
627
|
-
mainnet: 'https://api.hiro.so',
|
|
628
|
-
testnet: 'https://api.testnet.hiro.so',
|
|
629
|
-
devnet: 'http://localhost:3999'
|
|
630
|
-
};
|
|
631
|
-
|
|
632
|
-
/**
|
|
633
|
-
* Infer network from Stacks address prefix
|
|
634
|
-
* SP/SM = mainnet, ST/SN = testnet
|
|
635
|
-
*/
|
|
636
|
-
function inferNetworkFromAddress(address: string): 'mainnet' | 'testnet' | undefined {
|
|
637
|
-
if (address.startsWith('SP') || address.startsWith('SM')) return 'mainnet';
|
|
638
|
-
if (address.startsWith('ST') || address.startsWith('SN')) return 'testnet';
|
|
639
|
-
return undefined;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
/**
|
|
643
|
-
* Get API URL, inferring network from contract address if not specified
|
|
644
|
-
*/
|
|
645
|
-
function getApiUrl(
|
|
646
|
-
contractAddress: string,
|
|
647
|
-
explicitNetwork?: 'mainnet' | 'testnet' | 'devnet'
|
|
648
|
-
): string {
|
|
649
|
-
const network = explicitNetwork ?? inferNetworkFromAddress(contractAddress) ?? 'mainnet';
|
|
650
|
-
return API_URLS[network];
|
|
651
|
-
}`;
|
|
652
|
-
}
|
|
653
|
-
function generateValidationUtils() {
|
|
654
|
-
return `/**
|
|
655
|
-
* Contract name validation regex
|
|
656
|
-
* Must start with letter, contain only letters, numbers, and hyphens, max 128 chars
|
|
657
|
-
*/
|
|
658
|
-
const CONTRACT_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9\\-]{0,127}$/;`;
|
|
659
|
-
}
|
|
660
|
-
async function generateContractInterface(contracts) {
|
|
661
|
-
const imports = `import { Cl, validateStacksAddress } from '@secondlayer/stacks'`;
|
|
662
|
-
const header = `/**
|
|
663
|
-
* Generated by @secondlayer/cli
|
|
664
|
-
* DO NOT EDIT MANUALLY
|
|
665
|
-
*
|
|
666
|
-
* @requires @secondlayer/stacks - Install with: npm install @secondlayer/stacks
|
|
667
|
-
*/`;
|
|
668
|
-
const validationUtils = generateValidationUtils();
|
|
669
|
-
const networkUtils = generateNetworkUtils();
|
|
670
|
-
const contractsCode = contracts.map((contract) => generateContract(contract)).join(`
|
|
671
|
-
|
|
672
|
-
`);
|
|
673
|
-
const code = `${imports}
|
|
674
|
-
|
|
675
|
-
${header}
|
|
676
|
-
|
|
677
|
-
${validationUtils}
|
|
678
|
-
|
|
679
|
-
${networkUtils}
|
|
680
|
-
|
|
681
|
-
${contractsCode}`;
|
|
682
|
-
return formatCode(code);
|
|
683
|
-
}
|
|
684
|
-
function generateContract(contract) {
|
|
685
|
-
const { name, address, contractName, abi } = contract;
|
|
686
|
-
const abiCode = generateAbiConstant(name, abi);
|
|
687
|
-
const methods = abi.functions.filter((func) => func.access !== "private").map((func) => generateMethod(func, address, contractName)).join(`,
|
|
688
|
-
|
|
689
|
-
`);
|
|
690
|
-
const mapsObject = generateMapsObject(abi.maps || [], address, contractName);
|
|
691
|
-
const varsObject = generateVarsObject(abi.variables || [], address, contractName);
|
|
692
|
-
const constantsObject = generateConstantsObject(abi.variables || [], address, contractName);
|
|
693
|
-
const allMembers = [methods, mapsObject, varsObject, constantsObject].filter(Boolean);
|
|
694
|
-
const contractCode = `export const ${name} = {
|
|
695
|
-
address: '${address}',
|
|
696
|
-
contractAddress: '${address}',
|
|
697
|
-
contractName: '${contractName}',
|
|
698
|
-
|
|
699
|
-
${allMembers.join(`,
|
|
700
|
-
|
|
701
|
-
`)}
|
|
702
|
-
} as const`;
|
|
703
|
-
return `${abiCode}
|
|
704
|
-
|
|
705
|
-
${contractCode}`;
|
|
706
|
-
}
|
|
707
|
-
function generateAbiConstant(name, abi) {
|
|
708
|
-
const abiJson = JSON.stringify(abi, null, 2).replace(/"([a-zA-Z_$][a-zA-Z0-9_$]*)":/g, "$1:").replace(/"/g, "'");
|
|
709
|
-
return `export const ${name}Abi = ${abiJson} as const`;
|
|
710
|
-
}
|
|
711
|
-
function generateMethod(func, address, contractName) {
|
|
712
|
-
const methodName = toCamelCase5(func.name);
|
|
713
|
-
if (func.args.length === 0) {
|
|
714
|
-
return `${methodName}() {
|
|
715
|
-
return {
|
|
716
|
-
contractAddress: '${address}',
|
|
717
|
-
contractName: '${contractName}',
|
|
718
|
-
functionName: '${func.name}',
|
|
719
|
-
functionArgs: []
|
|
720
|
-
}
|
|
721
|
-
}`;
|
|
722
|
-
}
|
|
723
|
-
if (func.args.length === 1) {
|
|
724
|
-
const originalArgName = func.args[0].name;
|
|
725
|
-
const argName = toCamelCase5(originalArgName);
|
|
726
|
-
const argType = getTypeForArg(func.args[0]);
|
|
727
|
-
const clarityConversion = generateClarityConversion(argName, func.args[0]);
|
|
728
|
-
return `${methodName}(...args: [{ ${argName}: ${argType} }] | [${argType}]) {
|
|
729
|
-
const ${argName} = args.length === 1 && typeof args[0] === 'object' && args[0] !== null && '${argName}' in args[0]
|
|
730
|
-
? args[0].${argName}
|
|
731
|
-
: args[0] as ${argType}
|
|
732
|
-
|
|
733
|
-
return {
|
|
734
|
-
contractAddress: '${address}',
|
|
735
|
-
contractName: '${contractName}',
|
|
736
|
-
functionName: '${func.name}',
|
|
737
|
-
functionArgs: [${clarityConversion}]
|
|
738
|
-
}
|
|
739
|
-
}`;
|
|
740
|
-
}
|
|
741
|
-
const argsList = func.args.map((arg) => toCamelCase5(arg.name)).join(", ");
|
|
742
|
-
const argsTypes = func.args.map((arg) => {
|
|
743
|
-
const camelName = toCamelCase5(arg.name);
|
|
744
|
-
return `${camelName}: ${getTypeForArg(arg)}`;
|
|
745
|
-
}).join("; ");
|
|
746
|
-
const argsArray = func.args.map((arg) => {
|
|
747
|
-
const argName = toCamelCase5(arg.name);
|
|
748
|
-
return generateClarityConversion(argName, arg);
|
|
749
|
-
}).join(", ");
|
|
750
|
-
const objectAccess = func.args.map((arg) => {
|
|
751
|
-
const camelName = toCamelCase5(arg.name);
|
|
752
|
-
return `args[0].${camelName}`;
|
|
753
|
-
}).join(", ");
|
|
754
|
-
const positionTypes = func.args.map((arg) => getTypeForArg(arg)).join(", ");
|
|
755
|
-
return `${methodName}(...args: [{ ${argsTypes} }] | [${positionTypes}]) {
|
|
756
|
-
const [${argsList}] = args.length === 1 && typeof args[0] === 'object' && args[0] !== null
|
|
757
|
-
? [${objectAccess}]
|
|
758
|
-
: args as [${positionTypes}]
|
|
759
|
-
|
|
760
|
-
return {
|
|
761
|
-
contractAddress: '${address}',
|
|
762
|
-
contractName: '${contractName}',
|
|
763
|
-
functionName: '${func.name}',
|
|
764
|
-
functionArgs: [${argsArray}]
|
|
765
|
-
}
|
|
766
|
-
}`;
|
|
767
|
-
}
|
|
768
|
-
function generateMapsObject(maps, address, contractName) {
|
|
769
|
-
if (!maps || maps.length === 0) {
|
|
770
|
-
return "";
|
|
771
|
-
}
|
|
772
|
-
const mapMethods = maps.map((map) => {
|
|
773
|
-
const methodName = toCamelCase5(map.name);
|
|
774
|
-
const keyType = getTypeForArg({ type: map.key });
|
|
775
|
-
const valueType = getTypeForArg({ type: map.value });
|
|
776
|
-
const keyConversion = generateMapKeyConversion(map.key);
|
|
777
|
-
return `${methodName}: {
|
|
778
|
-
async get(key: ${keyType}, options?: { network?: 'mainnet' | 'testnet' | 'devnet' }): Promise<${valueType} | null> {
|
|
779
|
-
try {
|
|
780
|
-
const { cvToJSON, serializeCV } = await import('@secondlayer/stacks/clarity');
|
|
781
|
-
const baseUrl = getApiUrl('${address}', options?.network);
|
|
782
|
-
const mapKey = ${keyConversion};
|
|
783
|
-
const keyHex = serializeCV(mapKey).toString('hex');
|
|
784
|
-
|
|
785
|
-
const response = await fetch(
|
|
786
|
-
\`\${baseUrl}/v2/map_entry/${address}/${contractName}/${map.name}\`,
|
|
787
|
-
{
|
|
788
|
-
method: 'POST',
|
|
789
|
-
headers: { 'Content-Type': 'application/json' },
|
|
790
|
-
body: JSON.stringify(keyHex)
|
|
791
|
-
}
|
|
792
|
-
);
|
|
793
|
-
|
|
794
|
-
if (!response.ok) {
|
|
795
|
-
throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
const result = await response.json();
|
|
799
|
-
if (!result.data || result.data === '0x09') {
|
|
800
|
-
return null; // none value
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
const { deserializeCV } = await import('@secondlayer/stacks/clarity');
|
|
804
|
-
const cv = deserializeCV(result.data);
|
|
805
|
-
const parsed = cvToJSON(cv);
|
|
806
|
-
// Unwrap the (some ...) wrapper
|
|
807
|
-
return parsed.value?.value ?? parsed.value ?? null;
|
|
808
|
-
} catch (error) {
|
|
809
|
-
if (error instanceof Error) {
|
|
810
|
-
throw new Error(\`Map access failed for '${map.name}': \${error.message}\`);
|
|
811
|
-
}
|
|
812
|
-
throw error;
|
|
813
|
-
}
|
|
814
|
-
},
|
|
815
|
-
keyType: ${JSON.stringify(map.key)} as const,
|
|
816
|
-
valueType: ${JSON.stringify(map.value)} as const
|
|
817
|
-
}`;
|
|
818
|
-
});
|
|
819
|
-
return `maps: {
|
|
820
|
-
${mapMethods.join(`,
|
|
821
|
-
|
|
822
|
-
`)}
|
|
823
|
-
}`;
|
|
824
|
-
}
|
|
825
|
-
function generateVarsObject(variables, address, contractName) {
|
|
826
|
-
if (!variables || variables.length === 0) {
|
|
827
|
-
return "";
|
|
828
|
-
}
|
|
829
|
-
const dataVars = variables.filter((v) => v.access === "variable");
|
|
830
|
-
if (dataVars.length === 0) {
|
|
831
|
-
return "";
|
|
832
|
-
}
|
|
833
|
-
const varMethods = dataVars.map((variable) => {
|
|
834
|
-
const methodName = toCamelCase5(variable.name);
|
|
835
|
-
const valueType = getTypeForArg({ type: variable.type });
|
|
836
|
-
return `${methodName}: {
|
|
837
|
-
async get(options?: { network?: 'mainnet' | 'testnet' | 'devnet' }): Promise<${valueType}> {
|
|
838
|
-
try {
|
|
839
|
-
const { cvToJSON, deserializeCV } = await import('@secondlayer/stacks/clarity');
|
|
840
|
-
const baseUrl = getApiUrl('${address}', options?.network);
|
|
841
|
-
|
|
842
|
-
const response = await fetch(
|
|
843
|
-
\`\${baseUrl}/v2/data_var/${address}/${contractName}/${variable.name}?proof=0\`
|
|
844
|
-
);
|
|
845
|
-
|
|
846
|
-
if (!response.ok) {
|
|
847
|
-
throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
const result = await response.json();
|
|
851
|
-
const cv = deserializeCV(result.data);
|
|
852
|
-
const parsed = cvToJSON(cv);
|
|
853
|
-
return parsed.value ?? parsed;
|
|
854
|
-
} catch (error) {
|
|
855
|
-
if (error instanceof Error) {
|
|
856
|
-
throw new Error(\`Variable access failed for '${variable.name}': \${error.message}\`);
|
|
857
|
-
}
|
|
858
|
-
throw error;
|
|
859
|
-
}
|
|
860
|
-
},
|
|
861
|
-
type: ${JSON.stringify(variable.type)} as const
|
|
862
|
-
}`;
|
|
863
|
-
});
|
|
864
|
-
return `vars: {
|
|
865
|
-
${varMethods.join(`,
|
|
866
|
-
|
|
867
|
-
`)}
|
|
868
|
-
}`;
|
|
869
|
-
}
|
|
870
|
-
function generateConstantsObject(variables, address, contractName) {
|
|
871
|
-
if (!variables || variables.length === 0) {
|
|
872
|
-
return "";
|
|
873
|
-
}
|
|
874
|
-
const constants = variables.filter((v) => v.access === "constant");
|
|
875
|
-
if (constants.length === 0) {
|
|
876
|
-
return "";
|
|
877
|
-
}
|
|
878
|
-
const constMethods = constants.map((constant) => {
|
|
879
|
-
const methodName = toCamelCase5(constant.name);
|
|
880
|
-
const valueType = getTypeForArg({ type: constant.type });
|
|
881
|
-
return `${methodName}: {
|
|
882
|
-
async get(options?: { network?: 'mainnet' | 'testnet' | 'devnet' }): Promise<${valueType}> {
|
|
883
|
-
try {
|
|
884
|
-
const { cvToJSON, deserializeCV } = await import('@secondlayer/stacks/clarity');
|
|
885
|
-
const baseUrl = getApiUrl('${address}', options?.network);
|
|
886
|
-
|
|
887
|
-
const response = await fetch(
|
|
888
|
-
\`\${baseUrl}/v2/constant_val/${address}/${contractName}/${constant.name}?proof=0\`
|
|
889
|
-
);
|
|
890
|
-
|
|
891
|
-
if (!response.ok) {
|
|
892
|
-
throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
const result = await response.json();
|
|
896
|
-
const cv = deserializeCV(result.data);
|
|
897
|
-
const parsed = cvToJSON(cv);
|
|
898
|
-
return parsed.value ?? parsed;
|
|
899
|
-
} catch (error) {
|
|
900
|
-
if (error instanceof Error) {
|
|
901
|
-
throw new Error(\`Constant access failed for '${constant.name}': \${error.message}\`);
|
|
902
|
-
}
|
|
903
|
-
throw error;
|
|
904
|
-
}
|
|
905
|
-
},
|
|
906
|
-
type: ${JSON.stringify(constant.type)} as const
|
|
907
|
-
}`;
|
|
908
|
-
});
|
|
909
|
-
return `constants: {
|
|
910
|
-
${constMethods.join(`,
|
|
911
|
-
|
|
912
|
-
`)}
|
|
913
|
-
}`;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
// src/utils/abi-compat.ts
|
|
917
|
-
function normalizeAccess(access) {
|
|
918
|
-
if (access === "read_only")
|
|
919
|
-
return "read-only";
|
|
920
|
-
return access;
|
|
921
|
-
}
|
|
922
|
-
function normalizeType(type) {
|
|
923
|
-
if (typeof type === "string") {
|
|
924
|
-
switch (type) {
|
|
925
|
-
case "uint128":
|
|
926
|
-
case "int128":
|
|
927
|
-
case "bool":
|
|
928
|
-
case "principal":
|
|
929
|
-
case "trait_reference":
|
|
930
|
-
return type;
|
|
931
|
-
default:
|
|
932
|
-
return type;
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
if (typeof type !== "object" || type === null) {
|
|
936
|
-
throw new Error(`Invalid ABI type: expected object, got ${typeof type}`);
|
|
937
|
-
}
|
|
938
|
-
const typeObj = type;
|
|
939
|
-
if ("buffer" in typeObj) {
|
|
940
|
-
const buffer = typeObj.buffer;
|
|
941
|
-
return {
|
|
942
|
-
buff: {
|
|
943
|
-
length: buffer?.length ?? 32
|
|
944
|
-
}
|
|
945
|
-
};
|
|
946
|
-
}
|
|
947
|
-
if ("buff" in typeObj) {
|
|
948
|
-
const buff = typeObj.buff;
|
|
949
|
-
return {
|
|
950
|
-
buff: {
|
|
951
|
-
length: buff?.length ?? 32
|
|
952
|
-
}
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
if ("string-ascii" in typeObj) {
|
|
956
|
-
const strAscii = typeObj["string-ascii"];
|
|
957
|
-
return {
|
|
958
|
-
"string-ascii": {
|
|
959
|
-
length: strAscii?.length ?? 256
|
|
960
|
-
}
|
|
961
|
-
};
|
|
962
|
-
}
|
|
963
|
-
if ("string-utf8" in typeObj) {
|
|
964
|
-
const strUtf8 = typeObj["string-utf8"];
|
|
965
|
-
return {
|
|
966
|
-
"string-utf8": {
|
|
967
|
-
length: strUtf8?.length ?? 256
|
|
968
|
-
}
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
|
-
if ("response" in typeObj) {
|
|
972
|
-
const response = typeObj.response;
|
|
973
|
-
return {
|
|
974
|
-
response: {
|
|
975
|
-
ok: normalizeType(response?.ok ?? "bool"),
|
|
976
|
-
error: normalizeType(response?.error ?? "uint128")
|
|
977
|
-
}
|
|
978
|
-
};
|
|
979
|
-
}
|
|
980
|
-
if ("optional" in typeObj) {
|
|
981
|
-
return {
|
|
982
|
-
optional: normalizeType(typeObj.optional)
|
|
983
|
-
};
|
|
984
|
-
}
|
|
985
|
-
if ("list" in typeObj) {
|
|
986
|
-
const list = typeObj.list;
|
|
987
|
-
return {
|
|
988
|
-
list: {
|
|
989
|
-
type: normalizeType(list?.type ?? "uint128"),
|
|
990
|
-
length: list?.length ?? 100
|
|
991
|
-
}
|
|
992
|
-
};
|
|
993
|
-
}
|
|
994
|
-
if ("tuple" in typeObj) {
|
|
995
|
-
const tuple = typeObj.tuple;
|
|
996
|
-
return {
|
|
997
|
-
tuple: tuple.map((field) => ({
|
|
998
|
-
name: field.name,
|
|
999
|
-
type: normalizeType(field.type)
|
|
1000
|
-
}))
|
|
1001
|
-
};
|
|
1002
|
-
}
|
|
1003
|
-
throw new Error(`Unknown ABI type structure: ${JSON.stringify(type)}`);
|
|
1004
|
-
}
|
|
1005
|
-
function normalizeFunction(func) {
|
|
1006
|
-
const access = normalizeAccess(func.access);
|
|
1007
|
-
const args = func.args ?? [];
|
|
1008
|
-
const outputs = func.outputs;
|
|
1009
|
-
return {
|
|
1010
|
-
name: func.name,
|
|
1011
|
-
access,
|
|
1012
|
-
args: args.map((arg) => ({
|
|
1013
|
-
name: arg.name,
|
|
1014
|
-
type: normalizeType(arg.type)
|
|
1015
|
-
})),
|
|
1016
|
-
outputs: normalizeType(typeof outputs === "object" && outputs !== null && "type" in outputs ? outputs.type : outputs)
|
|
1017
|
-
};
|
|
1018
|
-
}
|
|
1019
|
-
function normalizeMap(map) {
|
|
1020
|
-
return {
|
|
1021
|
-
name: map.name,
|
|
1022
|
-
key: normalizeType(map.key),
|
|
1023
|
-
value: normalizeType(map.value)
|
|
1024
|
-
};
|
|
1025
|
-
}
|
|
1026
|
-
function normalizeVariable(variable) {
|
|
1027
|
-
return {
|
|
1028
|
-
name: variable.name,
|
|
1029
|
-
type: normalizeType(variable.type),
|
|
1030
|
-
access: variable.access
|
|
1031
|
-
};
|
|
1032
|
-
}
|
|
1033
|
-
function normalizeAbi(abi) {
|
|
1034
|
-
if (typeof abi !== "object" || abi === null) {
|
|
1035
|
-
return { functions: [] };
|
|
1036
|
-
}
|
|
1037
|
-
const abiObj = abi;
|
|
1038
|
-
const functions = [];
|
|
1039
|
-
const maps = [];
|
|
1040
|
-
const variables = [];
|
|
1041
|
-
if (Array.isArray(abiObj.functions)) {
|
|
1042
|
-
for (const func of abiObj.functions) {
|
|
1043
|
-
if (typeof func === "object" && func !== null) {
|
|
1044
|
-
functions.push(normalizeFunction(func));
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
if (Array.isArray(abiObj.maps)) {
|
|
1049
|
-
for (const map of abiObj.maps) {
|
|
1050
|
-
if (typeof map === "object" && map !== null) {
|
|
1051
|
-
maps.push(normalizeMap(map));
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
if (Array.isArray(abiObj.variables)) {
|
|
1056
|
-
for (const variable of abiObj.variables) {
|
|
1057
|
-
if (typeof variable === "object" && variable !== null) {
|
|
1058
|
-
variables.push(normalizeVariable(variable));
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
return {
|
|
1063
|
-
functions,
|
|
1064
|
-
maps: maps.length > 0 ? maps : undefined,
|
|
1065
|
-
variables: variables.length > 0 ? variables : undefined
|
|
1066
|
-
};
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
// src/plugins/clarinet/index.ts
|
|
1070
|
-
function sanitizeContractName(name) {
|
|
1071
|
-
return toCamelCase6(name);
|
|
1072
|
-
}
|
|
1073
|
-
async function isUserDefinedContract(contractId, manifestPath) {
|
|
1074
|
-
const { address, contractName } = parseContractId(contractId);
|
|
1075
|
-
try {
|
|
1076
|
-
const { promises: fs2 } = await import("fs");
|
|
1077
|
-
const tomlContent = await fs2.readFile(manifestPath, "utf-8");
|
|
1078
|
-
const contractSectionRegex = /^\[contracts\.([^\]]+)\]/gm;
|
|
1079
|
-
const userContracts = new Set;
|
|
1080
|
-
let match;
|
|
1081
|
-
while ((match = contractSectionRegex.exec(tomlContent)) !== null) {
|
|
1082
|
-
userContracts.add(match[1]);
|
|
1083
|
-
}
|
|
1084
|
-
if (userContracts.has(contractName)) {
|
|
1085
|
-
return true;
|
|
1086
|
-
}
|
|
1087
|
-
} catch (error) {}
|
|
1088
|
-
const systemContractPatterns = [
|
|
1089
|
-
/^pox-\d+$/,
|
|
1090
|
-
/^bns$/,
|
|
1091
|
-
/^costs-\d+$/,
|
|
1092
|
-
/^lockup$/
|
|
1093
|
-
];
|
|
1094
|
-
if (systemContractPatterns.some((pattern) => pattern.test(contractName))) {
|
|
1095
|
-
return false;
|
|
1096
|
-
}
|
|
1097
|
-
const systemAddresses = [
|
|
1098
|
-
"SP000000000000000000002Q6VF78",
|
|
1099
|
-
"ST000000000000000000002AMW42H"
|
|
1100
|
-
];
|
|
1101
|
-
if (systemAddresses.includes(address)) {
|
|
1102
|
-
return false;
|
|
1103
|
-
}
|
|
1104
|
-
return true;
|
|
1105
|
-
}
|
|
1106
|
-
var clarinet = (options = {}) => {
|
|
1107
|
-
const manifestPath = options.path || "./Clarinet.toml";
|
|
1108
|
-
let simnet;
|
|
1109
|
-
return {
|
|
1110
|
-
name: "@secondlayer/cli/plugin-clarinet",
|
|
1111
|
-
version: "1.0.0",
|
|
1112
|
-
async transformConfig(config) {
|
|
1113
|
-
try {
|
|
1114
|
-
simnet = await initSimnet(manifestPath);
|
|
1115
|
-
const contractInterfaces = simnet.getContractsInterfaces();
|
|
1116
|
-
const contracts = [];
|
|
1117
|
-
for (const [contractId, abi] of contractInterfaces) {
|
|
1118
|
-
const { contractName } = parseContractId(contractId);
|
|
1119
|
-
if (!await isUserDefinedContract(contractId, manifestPath)) {
|
|
1120
|
-
if (options.debug) {
|
|
1121
|
-
console.log(`\uD83D\uDEAB Skipping system contract: ${contractId}`);
|
|
1122
|
-
}
|
|
1123
|
-
continue;
|
|
1124
|
-
}
|
|
1125
|
-
if (options.include && !options.include.includes(contractName)) {
|
|
1126
|
-
continue;
|
|
1127
|
-
}
|
|
1128
|
-
if (options.exclude && options.exclude.includes(contractName)) {
|
|
1129
|
-
continue;
|
|
1130
|
-
}
|
|
1131
|
-
const sanitizedName = sanitizeContractName(contractName);
|
|
1132
|
-
contracts.push({
|
|
1133
|
-
name: sanitizedName,
|
|
1134
|
-
address: contractId,
|
|
1135
|
-
abi: normalizeAbi(abi),
|
|
1136
|
-
_clarinetSource: true
|
|
1137
|
-
});
|
|
1138
|
-
}
|
|
1139
|
-
if (options.debug) {
|
|
1140
|
-
console.log(`\uD83D\uDD0D Clarinet plugin found ${contracts.length} user-defined contracts`);
|
|
1141
|
-
}
|
|
1142
|
-
return {
|
|
1143
|
-
...config,
|
|
1144
|
-
contracts: [...config.contracts || [], ...contracts]
|
|
1145
|
-
};
|
|
1146
|
-
} catch (error) {
|
|
1147
|
-
const err = error;
|
|
1148
|
-
if (options.debug) {
|
|
1149
|
-
console.warn(`⚠️ Clarinet plugin failed to load contracts: ${err.message}`);
|
|
1150
|
-
}
|
|
1151
|
-
return config;
|
|
1152
|
-
}
|
|
1153
|
-
},
|
|
1154
|
-
async generate(context) {
|
|
1155
|
-
const clarinetContracts = context.contracts.filter((contract) => contract.metadata?.source === "clarinet");
|
|
1156
|
-
if (clarinetContracts.length === 0) {
|
|
1157
|
-
return;
|
|
1158
|
-
}
|
|
1159
|
-
if (options.debug) {
|
|
1160
|
-
context.logger.debug(`Generating interfaces for ${clarinetContracts.length} Clarinet contracts`);
|
|
1161
|
-
}
|
|
1162
|
-
const contractsCode = await generateContractInterface(clarinetContracts);
|
|
1163
|
-
context.addOutput("contracts", {
|
|
1164
|
-
path: context.config.out,
|
|
1165
|
-
content: contractsCode,
|
|
1166
|
-
type: "contracts"
|
|
1167
|
-
});
|
|
1168
|
-
}
|
|
1169
|
-
};
|
|
1170
|
-
};
|
|
1171
|
-
async function hasClarinetProject(path2 = "./Clarinet.toml") {
|
|
1172
|
-
try {
|
|
1173
|
-
const { promises: fs2 } = await import("fs");
|
|
1174
|
-
await fs2.access(path2);
|
|
1175
|
-
return true;
|
|
1176
|
-
} catch {
|
|
1177
|
-
return false;
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
// src/plugins/actions/generators.ts
|
|
1181
|
-
import { toCamelCase as toCamelCase7 } from "@secondlayer/stacks/clarity";
|
|
1182
|
-
function generateReadHelpers(contract, options) {
|
|
1183
|
-
const { abi } = contract;
|
|
1184
|
-
const functions = abi.functions || [];
|
|
1185
|
-
const readOnlyFunctions = functions.filter((f) => f.access === "read-only");
|
|
1186
|
-
if (readOnlyFunctions.length === 0) {
|
|
1187
|
-
return "";
|
|
1188
|
-
}
|
|
1189
|
-
const filteredFunctions = readOnlyFunctions.filter((func) => {
|
|
1190
|
-
if (options.includeFunctions && !options.includeFunctions.includes(func.name)) {
|
|
1191
|
-
return false;
|
|
1192
|
-
}
|
|
1193
|
-
if (options.excludeFunctions && options.excludeFunctions.includes(func.name)) {
|
|
1194
|
-
return false;
|
|
1195
|
-
}
|
|
1196
|
-
return true;
|
|
1197
|
-
});
|
|
1198
|
-
if (filteredFunctions.length === 0) {
|
|
1199
|
-
return "";
|
|
1200
|
-
}
|
|
1201
|
-
const helpers = filteredFunctions.map((func) => {
|
|
1202
|
-
const methodName = toCamelCase7(func.name);
|
|
1203
|
-
const argsSignature = generateArgsSignature(func.args);
|
|
1204
|
-
const clarityArgs = generateClarityArgs(func.args);
|
|
1205
|
-
return `async ${methodName}(${argsSignature}options?: {
|
|
1206
|
-
network?: 'mainnet' | 'testnet' | 'devnet';
|
|
1207
|
-
senderAddress?: string;
|
|
1208
|
-
}) {
|
|
1209
|
-
return await fetchCallReadOnlyFunction({
|
|
1210
|
-
contractAddress: '${contract.address}',
|
|
1211
|
-
contractName: '${contract.contractName}',
|
|
1212
|
-
functionName: '${func.name}',
|
|
1213
|
-
functionArgs: [${clarityArgs}],
|
|
1214
|
-
network: options?.network ?? inferNetworkFromAddress('${contract.address}') ?? 'mainnet',
|
|
1215
|
-
senderAddress: options?.senderAddress || 'SP000000000000000000002Q6VF78'
|
|
1216
|
-
});
|
|
1217
|
-
}`;
|
|
1218
|
-
});
|
|
1219
|
-
return `read: {
|
|
1220
|
-
${helpers.join(`,
|
|
1221
|
-
|
|
1222
|
-
`)}
|
|
1223
|
-
}`;
|
|
1224
|
-
}
|
|
1225
|
-
function generateWriteHelpers(contract, options) {
|
|
1226
|
-
const { abi } = contract;
|
|
1227
|
-
const functions = abi.functions || [];
|
|
1228
|
-
const envVarName = options.senderKeyEnv ?? "STX_SENDER_KEY";
|
|
1229
|
-
const publicFunctions = functions.filter((f) => f.access === "public");
|
|
1230
|
-
if (publicFunctions.length === 0) {
|
|
1231
|
-
return "";
|
|
1232
|
-
}
|
|
1233
|
-
const filteredFunctions = publicFunctions.filter((func) => {
|
|
1234
|
-
if (options.includeFunctions && !options.includeFunctions.includes(func.name)) {
|
|
1235
|
-
return false;
|
|
1236
|
-
}
|
|
1237
|
-
if (options.excludeFunctions && options.excludeFunctions.includes(func.name)) {
|
|
1238
|
-
return false;
|
|
1239
|
-
}
|
|
1240
|
-
return true;
|
|
1241
|
-
});
|
|
1242
|
-
if (filteredFunctions.length === 0) {
|
|
1243
|
-
return "";
|
|
1244
|
-
}
|
|
1245
|
-
const helpers = filteredFunctions.map((func) => {
|
|
1246
|
-
const methodName = toCamelCase7(func.name);
|
|
1247
|
-
const argsSignature = generateArgsSignature(func.args);
|
|
1248
|
-
const clarityArgs = generateClarityArgs(func.args);
|
|
1249
|
-
return `async ${methodName}(${argsSignature}senderKey?: string, options?: {
|
|
1250
|
-
network?: 'mainnet' | 'testnet' | 'devnet';
|
|
1251
|
-
fee?: string | number | undefined;
|
|
1252
|
-
nonce?: bigint;
|
|
1253
|
-
anchorMode?: 1 | 2 | 3; // AnchorMode: OnChainOnly = 1, OffChainOnly = 2, Any = 3
|
|
1254
|
-
postConditions?: PostCondition[];
|
|
1255
|
-
validateWithAbi?: boolean;
|
|
1256
|
-
}) {
|
|
1257
|
-
const resolvedSenderKey = senderKey ?? process.env.${envVarName};
|
|
1258
|
-
if (!resolvedSenderKey) {
|
|
1259
|
-
throw new Error('senderKey required: pass as argument or set ${envVarName} env var');
|
|
1260
|
-
}
|
|
1261
|
-
const { network = 'mainnet', ...txOptions } = options ?? {};
|
|
1262
|
-
|
|
1263
|
-
return await makeContractCall({
|
|
1264
|
-
contractAddress: '${contract.address}',
|
|
1265
|
-
contractName: '${contract.contractName}',
|
|
1266
|
-
functionName: '${func.name}',
|
|
1267
|
-
functionArgs: [${clarityArgs}],
|
|
1268
|
-
senderKey: resolvedSenderKey,
|
|
1269
|
-
network,
|
|
1270
|
-
validateWithAbi: true,
|
|
1271
|
-
...txOptions
|
|
1272
|
-
});
|
|
1273
|
-
}`;
|
|
1274
|
-
});
|
|
1275
|
-
return `write: {
|
|
1276
|
-
${helpers.join(`,
|
|
1277
|
-
|
|
1278
|
-
`)}
|
|
1279
|
-
}`;
|
|
1280
|
-
}
|
|
1281
|
-
async function generateActionHelpers(contract, options) {
|
|
1282
|
-
const readHelpers = generateReadHelpers(contract, options);
|
|
1283
|
-
const writeHelpers = generateWriteHelpers(contract, options);
|
|
1284
|
-
if (!readHelpers && !writeHelpers) {
|
|
1285
|
-
return "";
|
|
1286
|
-
}
|
|
1287
|
-
const helpers = [readHelpers, writeHelpers].filter(Boolean);
|
|
1288
|
-
return helpers.join(`,
|
|
1289
|
-
|
|
1290
|
-
`);
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
// src/plugins/actions/index.ts
|
|
1294
|
-
var actions = (options = {}) => {
|
|
1295
|
-
return {
|
|
1296
|
-
name: "@secondlayer/cli/plugin-actions",
|
|
1297
|
-
version: "1.0.0",
|
|
1298
|
-
async generate(context) {
|
|
1299
|
-
const { contracts } = context;
|
|
1300
|
-
const filteredContracts = contracts.filter((contract) => {
|
|
1301
|
-
if (options.include && !options.include.includes(contract.name)) {
|
|
1302
|
-
return false;
|
|
1303
|
-
}
|
|
1304
|
-
if (options.exclude && options.exclude.includes(contract.name)) {
|
|
1305
|
-
return false;
|
|
1306
|
-
}
|
|
1307
|
-
return true;
|
|
1308
|
-
});
|
|
1309
|
-
if (filteredContracts.length === 0) {
|
|
1310
|
-
if (options.debug) {
|
|
1311
|
-
context.logger.debug("Actions plugin: No contracts to process");
|
|
1312
|
-
}
|
|
1313
|
-
return;
|
|
1314
|
-
}
|
|
1315
|
-
if (options.debug) {
|
|
1316
|
-
context.logger.debug(`Actions plugin: Generating read/write helpers for ${filteredContracts.length} contracts`);
|
|
1317
|
-
}
|
|
1318
|
-
const contractHelpers = new Map;
|
|
1319
|
-
for (const contract of filteredContracts) {
|
|
1320
|
-
const actionsCode = await generateActionHelpers(contract, options);
|
|
1321
|
-
if (actionsCode) {
|
|
1322
|
-
contractHelpers.set(contract.name, actionsCode);
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
if (contractHelpers.size > 0) {
|
|
1326
|
-
const existingOutput = context.outputs.get("contracts");
|
|
1327
|
-
if (existingOutput) {
|
|
1328
|
-
let modifiedContent = addRequiredImports(existingOutput.content);
|
|
1329
|
-
for (const [contractName, helpersCode] of contractHelpers) {
|
|
1330
|
-
modifiedContent = injectHelpersIntoContract(modifiedContent, contractName, helpersCode);
|
|
1331
|
-
}
|
|
1332
|
-
context.outputs.set("contracts", {
|
|
1333
|
-
...existingOutput,
|
|
1334
|
-
content: modifiedContent
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
};
|
|
1340
|
-
};
|
|
1341
|
-
function addRequiredImports(content) {
|
|
1342
|
-
const stacksImportRegex = /import\s+\{([^}]+)\}\s+from\s+['"]@secondlayer\/stacks['"];/;
|
|
1343
|
-
const match = content.match(stacksImportRegex);
|
|
1344
|
-
if (match) {
|
|
1345
|
-
let updatedContent = content;
|
|
1346
|
-
if (!updatedContent.includes("fetchCallReadOnlyFunction")) {
|
|
1347
|
-
updatedContent = updatedContent.replace(stacksImportRegex, `${match[0]}
|
|
1348
|
-
import { fetchCallReadOnlyFunction, makeContractCall } from '@secondlayer/stacks/clarity';`);
|
|
1349
|
-
}
|
|
1350
|
-
if (!updatedContent.includes("type PostCondition")) {
|
|
1351
|
-
updatedContent = updatedContent.replace(stacksImportRegex, `${match[0]}
|
|
1352
|
-
import type { PostCondition } from '@secondlayer/stacks';`);
|
|
1353
|
-
}
|
|
1354
|
-
return updatedContent;
|
|
1355
|
-
}
|
|
1356
|
-
return content;
|
|
1357
|
-
}
|
|
1358
|
-
function injectHelpersIntoContract(content, contractName, helpersCode) {
|
|
1359
|
-
const contractPattern = new RegExp(`(export const ${contractName} = \\{[\\s\\S]*?)\\n\\} as const;`, "g");
|
|
1360
|
-
return content.replace(contractPattern, (_, contractBody) => {
|
|
1361
|
-
const cleanBody = contractBody.replace(/,\s*$/, "");
|
|
1362
|
-
const indentedHelpersCode = helpersCode.split(`
|
|
1363
|
-
`).map((line) => {
|
|
1364
|
-
if (line.match(/^[a-zA-Z_$][a-zA-Z0-9_$]*\s*:/)) {
|
|
1365
|
-
return ` ${line}`;
|
|
1366
|
-
}
|
|
1367
|
-
return line;
|
|
1368
|
-
}).join(`
|
|
1369
|
-
`);
|
|
1370
|
-
return `${cleanBody},
|
|
1371
|
-
|
|
1372
|
-
${indentedHelpersCode}
|
|
1373
|
-
} as const;`;
|
|
1374
|
-
});
|
|
1375
|
-
}
|
|
1376
|
-
// src/plugins/react/generators/contract.ts
|
|
1377
|
-
init_format();
|
|
1378
|
-
|
|
1379
|
-
// src/utils/case-conversion.ts
|
|
1380
|
-
import { toCamelCase as toCamelCase8 } from "@secondlayer/stacks/clarity";
|
|
1381
|
-
function capitalize(str) {
|
|
1382
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1383
|
-
}
|
|
1384
|
-
function toPascalCase(str) {
|
|
1385
|
-
return capitalize(toCamelCase8(str));
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
// src/plugins/react/generators/utils.ts
|
|
1389
|
-
function generateHookArgsSignature(args) {
|
|
1390
|
-
if (args.length === 0)
|
|
1391
|
-
return "";
|
|
1392
|
-
const argsList = args.map((arg) => `${toCamelCase8(arg.name)}: ${clarityTypeToTS(arg.type)}`).join(", ");
|
|
1393
|
-
return `${argsList}`;
|
|
1394
|
-
}
|
|
1395
|
-
function generateArgsType(args) {
|
|
1396
|
-
if (args.length === 0)
|
|
1397
|
-
return "void";
|
|
1398
|
-
const argsList = args.map((arg) => `${toCamelCase8(arg.name)}: ${clarityTypeToTS(arg.type)}`).join("; ");
|
|
1399
|
-
return `{ ${argsList} }`;
|
|
1400
|
-
}
|
|
1401
|
-
function generateArgNames(args) {
|
|
1402
|
-
if (args.length === 0)
|
|
1403
|
-
return "";
|
|
1404
|
-
return args.map((arg) => toCamelCase8(arg.name)).join(", ");
|
|
1405
|
-
}
|
|
1406
|
-
function generateEnabledCondition(args) {
|
|
1407
|
-
return args.map((arg) => {
|
|
1408
|
-
const camelName = toCamelCase8(arg.name);
|
|
1409
|
-
const type = clarityTypeToTS(arg.type);
|
|
1410
|
-
if (type === "string")
|
|
1411
|
-
return `!!${camelName}`;
|
|
1412
|
-
if (type === "bigint")
|
|
1413
|
-
return `${camelName} !== undefined`;
|
|
1414
|
-
return `${camelName} !== undefined`;
|
|
1415
|
-
}).join(" && ");
|
|
1416
|
-
}
|
|
1417
|
-
function generateObjectArgs(args) {
|
|
1418
|
-
if (args.length === 0)
|
|
1419
|
-
return "";
|
|
1420
|
-
return args.map((arg) => `${arg.name}: ${toCamelCase8(arg.name)}`).join(", ");
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
// src/plugins/react/generators/contract.ts
|
|
1424
|
-
async function generateContractHooks(contracts, excludeList = []) {
|
|
1425
|
-
const imports = `import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
1426
|
-
import { useCallback } from 'react'
|
|
1427
|
-
import { useSecondLayerConfig } from './provider'
|
|
1428
|
-
import { request } from '@secondlayer/stacks/connect'
|
|
1429
|
-
import type { PostCondition } from '@secondlayer/stacks'
|
|
1430
|
-
import { ${contracts.map((c) => c.name).join(", ")} } from './contracts'`;
|
|
1431
|
-
const header = `/**
|
|
1432
|
-
* Generated contract-specific React hooks
|
|
1433
|
-
* DO NOT EDIT MANUALLY
|
|
1434
|
-
*/`;
|
|
1435
|
-
const hooksCode = contracts.map((contract) => generateContractHookMethods(contract, excludeList)).filter(Boolean).join(`
|
|
1436
|
-
|
|
1437
|
-
`);
|
|
1438
|
-
const code = `${imports}
|
|
1439
|
-
|
|
1440
|
-
${header}
|
|
1441
|
-
|
|
1442
|
-
${hooksCode}`;
|
|
1443
|
-
return formatCode(code);
|
|
1444
|
-
}
|
|
1445
|
-
function generateContractHookMethods(contract, excludeList) {
|
|
1446
|
-
const { abi, name, address, contractName } = contract;
|
|
1447
|
-
const functions = abi.functions || [];
|
|
1448
|
-
const maps = abi.maps || [];
|
|
1449
|
-
const variables = abi.variables || [];
|
|
1450
|
-
const readOnlyFunctions = functions.filter((f) => f.access === "read-only");
|
|
1451
|
-
const publicFunctions = functions.filter((f) => f.access === "public");
|
|
1452
|
-
const readHooks = readOnlyFunctions.map((func) => {
|
|
1453
|
-
const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(func.name))}`;
|
|
1454
|
-
if (excludeList.includes(hookName)) {
|
|
1455
|
-
return null;
|
|
1456
|
-
}
|
|
1457
|
-
return generateReadHook(func, name);
|
|
1458
|
-
}).filter(Boolean);
|
|
1459
|
-
const writeHooks = publicFunctions.map((func) => {
|
|
1460
|
-
const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(func.name))}`;
|
|
1461
|
-
if (excludeList.includes(hookName)) {
|
|
1462
|
-
return null;
|
|
1463
|
-
}
|
|
1464
|
-
return generateWriteHook(func, name);
|
|
1465
|
-
}).filter(Boolean);
|
|
1466
|
-
const mapHooks = maps.map((map) => {
|
|
1467
|
-
const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(map.name))}`;
|
|
1468
|
-
if (excludeList.includes(hookName)) {
|
|
1469
|
-
return null;
|
|
1470
|
-
}
|
|
1471
|
-
return generateMapHook(map, name, address, contractName);
|
|
1472
|
-
}).filter(Boolean);
|
|
1473
|
-
const dataVars = variables.filter((v) => v.access === "variable");
|
|
1474
|
-
const varHooks = dataVars.map((variable) => {
|
|
1475
|
-
const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(variable.name))}`;
|
|
1476
|
-
if (excludeList.includes(hookName)) {
|
|
1477
|
-
return null;
|
|
1478
|
-
}
|
|
1479
|
-
return generateVarHook(variable, name, address, contractName);
|
|
1480
|
-
}).filter(Boolean);
|
|
1481
|
-
const constants = variables.filter((v) => v.access === "constant");
|
|
1482
|
-
const constantHooks = constants.map((constant) => {
|
|
1483
|
-
const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(constant.name))}`;
|
|
1484
|
-
if (excludeList.includes(hookName)) {
|
|
1485
|
-
return null;
|
|
1486
|
-
}
|
|
1487
|
-
return generateConstantHook(constant, name, address, contractName);
|
|
1488
|
-
}).filter(Boolean);
|
|
1489
|
-
const allHooks = [
|
|
1490
|
-
...readHooks,
|
|
1491
|
-
...writeHooks,
|
|
1492
|
-
...mapHooks,
|
|
1493
|
-
...varHooks,
|
|
1494
|
-
...constantHooks
|
|
1495
|
-
];
|
|
1496
|
-
if (allHooks.length === 0) {
|
|
1497
|
-
return "";
|
|
1498
|
-
}
|
|
1499
|
-
return allHooks.join(`
|
|
1500
|
-
|
|
1501
|
-
`);
|
|
1502
|
-
}
|
|
1503
|
-
function generateReadHook(func, contractName) {
|
|
1504
|
-
const hookName = `use${capitalize(contractName)}${capitalize(toCamelCase8(func.name))}`;
|
|
1505
|
-
const argsSignature = generateHookArgsSignature(func.args);
|
|
1506
|
-
const enabledParam = func.args.length > 0 ? ", options?: { enabled?: boolean }" : "options?: { enabled?: boolean }";
|
|
1507
|
-
const returnType = clarityTypeToTS(func.outputs);
|
|
1508
|
-
return `export function ${hookName}(${argsSignature}${enabledParam}) {
|
|
1509
|
-
const config = useSecondLayerConfig()
|
|
1510
|
-
|
|
1511
|
-
return useQuery<${returnType}>({
|
|
1512
|
-
queryKey: ['${func.name}', ${contractName}.address, ${generateArgNames(func.args)}],
|
|
1513
|
-
queryFn: () => ${contractName}.read.${toCamelCase8(func.name)}(${generateArgNames(func.args) ? `{ ${generateObjectArgs(func.args)} }, ` : ""}{
|
|
1514
|
-
network: config.network,
|
|
1515
|
-
senderAddress: config.senderAddress || 'SP000000000000000000002Q6VF78'
|
|
1516
|
-
}),
|
|
1517
|
-
${func.args.length > 0 ? `enabled: ${generateEnabledCondition(func.args)} && (options?.enabled ?? true),` : ""}
|
|
1518
|
-
...options
|
|
1519
|
-
})
|
|
1520
|
-
}`;
|
|
1521
|
-
}
|
|
1522
|
-
function generateWriteHook(func, contractName) {
|
|
1523
|
-
const hookName = `use${capitalize(contractName)}${capitalize(toCamelCase8(func.name))}`;
|
|
1524
|
-
const argsType = generateArgsType(func.args);
|
|
1525
|
-
return `export function ${hookName}() {
|
|
1526
|
-
const config = useSecondLayerConfig()
|
|
1527
|
-
const queryClient = useQueryClient()
|
|
1528
|
-
|
|
1529
|
-
const mutation = useMutation({
|
|
1530
|
-
mutationFn: async (params: {
|
|
1531
|
-
args: ${argsType};
|
|
1532
|
-
options?: {
|
|
1533
|
-
postConditions?: PostCondition[];
|
|
1534
|
-
attachment?: string;
|
|
1535
|
-
onFinish?: (data: any) => void;
|
|
1536
|
-
onCancel?: () => void;
|
|
1537
|
-
};
|
|
1538
|
-
}) => {
|
|
1539
|
-
const { args, options = {} } = params
|
|
1540
|
-
const contractCallData = ${contractName}.${toCamelCase8(func.name)}(args)
|
|
1541
|
-
const { contractAddress, contractName: name, functionName, functionArgs } = contractCallData
|
|
1542
|
-
const network = config.network || 'mainnet'
|
|
1543
|
-
const contract = \`\${contractAddress}.\${name}\`
|
|
1544
|
-
|
|
1545
|
-
const result = await request('stx_callContract', {
|
|
1546
|
-
contract,
|
|
1547
|
-
functionName,
|
|
1548
|
-
functionArgs,
|
|
1549
|
-
network,
|
|
1550
|
-
...options
|
|
1551
|
-
})
|
|
1552
|
-
|
|
1553
|
-
options.onFinish?.(result)
|
|
1554
|
-
return result
|
|
1555
|
-
},
|
|
1556
|
-
onSuccess: () => {
|
|
1557
|
-
// Invalidate relevant queries on success
|
|
1558
|
-
queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
|
|
1559
|
-
},
|
|
1560
|
-
onError: (error) => {
|
|
1561
|
-
console.error('Contract call failed:', error)
|
|
1562
|
-
}
|
|
1563
|
-
})
|
|
1564
|
-
|
|
1565
|
-
const ${toCamelCase8(func.name)} = useCallback(async (
|
|
1566
|
-
args: ${argsType},
|
|
1567
|
-
options?: {
|
|
1568
|
-
postConditions?: PostCondition[];
|
|
1569
|
-
attachment?: string;
|
|
1570
|
-
onFinish?: (data: any) => void;
|
|
1571
|
-
onCancel?: () => void;
|
|
1572
|
-
}
|
|
1573
|
-
) => {
|
|
1574
|
-
return mutation.mutateAsync({ args, options })
|
|
1575
|
-
}, [mutation])
|
|
1576
|
-
|
|
1577
|
-
return {
|
|
1578
|
-
${toCamelCase8(func.name)},
|
|
1579
|
-
// Expose mutation state
|
|
1580
|
-
isPending: mutation.isPending,
|
|
1581
|
-
isError: mutation.isError,
|
|
1582
|
-
isSuccess: mutation.isSuccess,
|
|
1583
|
-
error: mutation.error,
|
|
1584
|
-
data: mutation.data,
|
|
1585
|
-
reset: mutation.reset
|
|
1586
|
-
}
|
|
1587
|
-
}`;
|
|
1588
|
-
}
|
|
1589
|
-
function generateMapHook(map, contractVarName, _address, _contractName) {
|
|
1590
|
-
const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase8(map.name))}`;
|
|
1591
|
-
const keyType = clarityTypeToTS(map.key);
|
|
1592
|
-
const valueType = clarityTypeToTS(map.value);
|
|
1593
|
-
return `export function ${hookName}(key: ${keyType}, options?: { enabled?: boolean }) {
|
|
1594
|
-
const config = useSecondLayerConfig()
|
|
1595
|
-
|
|
1596
|
-
return useQuery<${valueType} | null>({
|
|
1597
|
-
queryKey: ['${contractVarName}', '${map.name}', 'map', key, config.network],
|
|
1598
|
-
queryFn: async () => {
|
|
1599
|
-
return ${contractVarName}.maps.${toCamelCase8(map.name)}.get(key, { network: config.network })
|
|
1600
|
-
},
|
|
1601
|
-
enabled: options?.enabled ?? true
|
|
1602
|
-
})
|
|
1603
|
-
}`;
|
|
1604
|
-
}
|
|
1605
|
-
function generateVarHook(variable, contractVarName, _address, _contractName) {
|
|
1606
|
-
const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase8(variable.name))}`;
|
|
1607
|
-
const valueType = clarityTypeToTS(variable.type);
|
|
1608
|
-
return `export function ${hookName}(options?: { enabled?: boolean }) {
|
|
1609
|
-
const config = useSecondLayerConfig()
|
|
1610
|
-
|
|
1611
|
-
return useQuery<${valueType}>({
|
|
1612
|
-
queryKey: ['${contractVarName}', '${variable.name}', 'var', config.network],
|
|
1613
|
-
queryFn: async () => {
|
|
1614
|
-
return ${contractVarName}.vars.${toCamelCase8(variable.name)}.get({ network: config.network })
|
|
1615
|
-
},
|
|
1616
|
-
enabled: options?.enabled ?? true
|
|
1617
|
-
})
|
|
1618
|
-
}`;
|
|
1619
|
-
}
|
|
1620
|
-
function generateConstantHook(constant, contractVarName, _address, _contractName) {
|
|
1621
|
-
const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase8(constant.name))}`;
|
|
1622
|
-
const valueType = clarityTypeToTS(constant.type);
|
|
1623
|
-
return `export function ${hookName}(options?: { enabled?: boolean }) {
|
|
1624
|
-
const config = useSecondLayerConfig()
|
|
1625
|
-
|
|
1626
|
-
return useQuery<${valueType}>({
|
|
1627
|
-
queryKey: ['${contractVarName}', '${constant.name}', 'constant', config.network],
|
|
1628
|
-
queryFn: async () => {
|
|
1629
|
-
return ${contractVarName}.constants.${toCamelCase8(constant.name)}.get({ network: config.network })
|
|
1630
|
-
},
|
|
1631
|
-
enabled: options?.enabled ?? true,
|
|
1632
|
-
staleTime: Infinity // Constants never change
|
|
1633
|
-
})
|
|
1634
|
-
}`;
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
|
-
// src/generators/templates/use-contract.ts
|
|
1638
|
-
var USE_CONTRACT_TEMPLATE = `export function useContract() {
|
|
1639
|
-
const config = useSecondLayerConfig()
|
|
1640
|
-
const queryClient = useQueryClient()
|
|
1641
|
-
const [isRequestPending, setIsRequestPending] = useState(false)
|
|
1642
|
-
|
|
1643
|
-
// Helper function to convert JS values to Clarity values based on ABI
|
|
1644
|
-
const convertArgsWithAbi = (args: any, abiArgs: any[]): any[] => {
|
|
1645
|
-
if (!abiArgs || abiArgs.length === 0) return []
|
|
1646
|
-
|
|
1647
|
-
return abiArgs.map((abiArg, index) => {
|
|
1648
|
-
const argValue = Array.isArray(args)
|
|
1649
|
-
? args[index]
|
|
1650
|
-
: args[abiArg.name] || args[abiArg.name.replace(/-/g, '').replace(/_/g, '')]
|
|
1651
|
-
return convertJSValueToClarityValue(argValue, abiArg.type)
|
|
1652
|
-
})
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
// Helper function to convert buffer values with auto-detection
|
|
1656
|
-
const convertBufferValue = (value: any): any => {
|
|
1657
|
-
// Direct Uint8Array
|
|
1658
|
-
if (value instanceof Uint8Array) {
|
|
1659
|
-
return Cl.buffer(value)
|
|
1660
|
-
}
|
|
1661
|
-
|
|
1662
|
-
// Object notation with explicit type
|
|
1663
|
-
if (typeof value === 'object' && value !== null && value.type && value.value) {
|
|
1664
|
-
switch (value.type) {
|
|
1665
|
-
case 'ascii':
|
|
1666
|
-
return Cl.bufferFromAscii(value.value)
|
|
1667
|
-
case 'utf8':
|
|
1668
|
-
return Cl.bufferFromUtf8(value.value)
|
|
1669
|
-
case 'hex':
|
|
1670
|
-
return Cl.bufferFromHex(value.value)
|
|
1671
|
-
default:
|
|
1672
|
-
throw new Error(\`Unsupported buffer type: \${value.type}\`)
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
// Auto-detect string type
|
|
1677
|
-
if (typeof value === 'string') {
|
|
1678
|
-
// 1. Check for hex (0x prefix or pure hex pattern)
|
|
1679
|
-
if (value.startsWith('0x') || /^[0-9a-fA-F]+$/.test(value)) {
|
|
1680
|
-
return Cl.bufferFromHex(value)
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
// 2. Check for non-ASCII characters (UTF-8)
|
|
1684
|
-
if (!/^[\\x00-\\x7F]*$/.test(value)) {
|
|
1685
|
-
return Cl.bufferFromUtf8(value)
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
// 3. Default to ASCII for simple ASCII strings
|
|
1689
|
-
return Cl.bufferFromAscii(value)
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
throw new Error(\`Invalid buffer value: \${value}\`)
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
// Helper function to convert a single JS value to ClarityValue
|
|
1696
|
-
const convertJSValueToClarityValue = (value: any, type: any): any => {
|
|
1697
|
-
if (typeof type === 'string') {
|
|
1698
|
-
switch (type) {
|
|
1699
|
-
case 'uint128':
|
|
1700
|
-
return Cl.uint(value)
|
|
1701
|
-
case 'int128':
|
|
1702
|
-
return Cl.int(value)
|
|
1703
|
-
case 'bool':
|
|
1704
|
-
return Cl.bool(value)
|
|
1705
|
-
case 'principal':
|
|
1706
|
-
if (!validateStacksAddress(value.split('.')[0])) {
|
|
1707
|
-
throw new Error('Invalid Stacks address format')
|
|
1708
|
-
}
|
|
1709
|
-
if (value.includes('.')) {
|
|
1710
|
-
const [address, contractName] = value.split('.')
|
|
1711
|
-
return Cl.contractPrincipal(address, contractName)
|
|
1712
|
-
} else {
|
|
1713
|
-
return Cl.standardPrincipal(value)
|
|
1714
|
-
}
|
|
1715
|
-
default:
|
|
1716
|
-
return value
|
|
1717
|
-
}
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
if (type['string-ascii']) {
|
|
1721
|
-
return Cl.stringAscii(value)
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
if (type['string-utf8']) {
|
|
1725
|
-
return Cl.stringUtf8(value)
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
if (type.buff) {
|
|
1729
|
-
return convertBufferValue(value)
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
if (type.optional) {
|
|
1733
|
-
return value !== null ? Cl.some(convertJSValueToClarityValue(value, type.optional)) : Cl.none()
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
if (type.list) {
|
|
1737
|
-
return Cl.list(value.map((item: any) => convertJSValueToClarityValue(item, type.list.type)))
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
if (type.tuple) {
|
|
1741
|
-
const tupleData = type.tuple.reduce((acc: any, field: any) => {
|
|
1742
|
-
acc[field.name] = convertJSValueToClarityValue(value[field.name], field.type)
|
|
1743
|
-
return acc
|
|
1744
|
-
}, {})
|
|
1745
|
-
return Cl.tuple(tupleData)
|
|
1746
|
-
}
|
|
1747
|
-
|
|
1748
|
-
if (type.response) {
|
|
1749
|
-
return 'ok' in value
|
|
1750
|
-
? Cl.ok(convertJSValueToClarityValue(value.ok, type.response.ok))
|
|
1751
|
-
: Cl.error(convertJSValueToClarityValue(value.err, type.response.error))
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
return value
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
// Helper function to find a function in an ABI by name
|
|
1758
|
-
const findFunctionInAbi = (abi: any, functionName: string): any => {
|
|
1759
|
-
if (!abi || !abi.functions) return null
|
|
1760
|
-
return abi.functions.find((func: any) => func.name === functionName)
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
// Legacy function - pre-converted Clarity values
|
|
1764
|
-
const legacyOpenContractCall = useCallback(async (params: {
|
|
1765
|
-
contractAddress: string;
|
|
1766
|
-
contractName: string;
|
|
1767
|
-
functionName: string;
|
|
1768
|
-
functionArgs: any[];
|
|
1769
|
-
network?: string;
|
|
1770
|
-
postConditions?: PostCondition[];
|
|
1771
|
-
attachment?: string;
|
|
1772
|
-
onFinish?: (data: any) => void;
|
|
1773
|
-
onCancel?: () => void;
|
|
1774
|
-
}) => {
|
|
1775
|
-
setIsRequestPending(true)
|
|
1776
|
-
|
|
1777
|
-
try {
|
|
1778
|
-
const { contractAddress, contractName, functionName, functionArgs, onFinish, onCancel, ...options } = params
|
|
1779
|
-
const network = params.network || config.network || 'mainnet'
|
|
1780
|
-
const contract = \`\${contractAddress}.\${contractName}\`
|
|
1781
|
-
|
|
1782
|
-
const result = await request('stx_callContract', {
|
|
1783
|
-
contract,
|
|
1784
|
-
functionName,
|
|
1785
|
-
functionArgs,
|
|
1786
|
-
network,
|
|
1787
|
-
...options
|
|
1788
|
-
})
|
|
1789
|
-
|
|
1790
|
-
queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
|
|
1791
|
-
onFinish?.(result)
|
|
1792
|
-
return result
|
|
1793
|
-
} catch (error) {
|
|
1794
|
-
console.error('Contract call failed:', error)
|
|
1795
|
-
throw error instanceof Error ? error : new Error('Contract call failed')
|
|
1796
|
-
} finally {
|
|
1797
|
-
setIsRequestPending(false)
|
|
1798
|
-
}
|
|
1799
|
-
}, [config.network, queryClient])
|
|
1800
|
-
|
|
1801
|
-
// Enhanced function - requires ABI, auto-converts JS values
|
|
1802
|
-
const openContractCall = useCallback(async <
|
|
1803
|
-
T extends AbiContract,
|
|
1804
|
-
FN extends ExtractFunctionNames<T>
|
|
1805
|
-
>(params: {
|
|
1806
|
-
contractAddress: string;
|
|
1807
|
-
contractName: string;
|
|
1808
|
-
functionName: FN;
|
|
1809
|
-
abi: T;
|
|
1810
|
-
functionArgs: ExtractFunctionArgs<T, FN>;
|
|
1811
|
-
network?: string;
|
|
1812
|
-
postConditions?: PostCondition[];
|
|
1813
|
-
attachment?: string;
|
|
1814
|
-
onFinish?: (data: any) => void;
|
|
1815
|
-
onCancel?: () => void;
|
|
1816
|
-
}) => {
|
|
1817
|
-
setIsRequestPending(true)
|
|
1818
|
-
|
|
1819
|
-
try {
|
|
1820
|
-
const { contractAddress, contractName, functionName, functionArgs, abi, onFinish, onCancel, ...options } = params
|
|
1821
|
-
const network = params.network || config.network || 'mainnet'
|
|
1822
|
-
const contract = \`\${contractAddress}.\${contractName}\`
|
|
1823
|
-
|
|
1824
|
-
const abiFunction = findFunctionInAbi(abi, functionName)
|
|
1825
|
-
if (!abiFunction) {
|
|
1826
|
-
throw new Error(\`Function '\${functionName}' not found in ABI\`)
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
const processedArgs = convertArgsWithAbi(functionArgs, abiFunction.args || [])
|
|
1830
|
-
|
|
1831
|
-
const result = await request('stx_callContract', {
|
|
1832
|
-
contract,
|
|
1833
|
-
functionName,
|
|
1834
|
-
functionArgs: processedArgs,
|
|
1835
|
-
network,
|
|
1836
|
-
...options
|
|
1837
|
-
})
|
|
1838
|
-
|
|
1839
|
-
queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
|
|
1840
|
-
onFinish?.(result)
|
|
1841
|
-
return result
|
|
1842
|
-
} catch (error) {
|
|
1843
|
-
console.error('Contract call failed:', error)
|
|
1844
|
-
throw error instanceof Error ? error : new Error('Contract call failed')
|
|
1845
|
-
} finally {
|
|
1846
|
-
setIsRequestPending(false)
|
|
1847
|
-
}
|
|
1848
|
-
}, [config.network, queryClient])
|
|
1849
|
-
|
|
1850
|
-
return {
|
|
1851
|
-
legacyOpenContractCall,
|
|
1852
|
-
openContractCall,
|
|
1853
|
-
isRequestPending
|
|
1854
|
-
}
|
|
1855
|
-
}`;
|
|
1856
|
-
|
|
1857
|
-
// src/plugins/react/generators/generic.ts
|
|
1858
|
-
init_format();
|
|
1859
|
-
var GENERIC_HOOKS = [
|
|
1860
|
-
"useAccount",
|
|
1861
|
-
"useConnect",
|
|
1862
|
-
"useDisconnect",
|
|
1863
|
-
"useNetwork",
|
|
1864
|
-
"useContract",
|
|
1865
|
-
"useOpenSTXTransfer",
|
|
1866
|
-
"useSignMessage",
|
|
1867
|
-
"useDeployContract",
|
|
1868
|
-
"useReadContract",
|
|
1869
|
-
"useTransaction",
|
|
1870
|
-
"useBlock",
|
|
1871
|
-
"useAccountTransactions",
|
|
1872
|
-
"useWaitForTransaction"
|
|
1873
|
-
];
|
|
1874
|
-
async function generateGenericHooks(excludeList = []) {
|
|
1875
|
-
const hooksToGenerate = GENERIC_HOOKS.filter((hookName) => !excludeList.includes(hookName));
|
|
1876
|
-
const imports = `import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
1877
|
-
import { useState, useCallback } from 'react'
|
|
1878
|
-
import { useSecondLayerConfig } from './provider'
|
|
1879
|
-
import { connect, disconnect, isConnected, request } from '@secondlayer/stacks/connect'
|
|
1880
|
-
import { Cl, validateStacksAddress } from '@secondlayer/stacks'
|
|
1881
|
-
import type { PostCondition } from '@secondlayer/stacks'
|
|
1882
|
-
import type { ExtractFunctionArgs, ExtractFunctionNames, AbiContract } from '@secondlayer/stacks/clarity'
|
|
1883
|
-
|
|
1884
|
-
const API_URLS: Record<string, string> = {
|
|
1885
|
-
mainnet: 'https://api.hiro.so',
|
|
1886
|
-
testnet: 'https://api.testnet.hiro.so',
|
|
1887
|
-
devnet: 'http://localhost:3999'
|
|
1888
|
-
}
|
|
1889
|
-
|
|
1890
|
-
async function fetchTransaction({ txId, network, apiUrl }: { txId: string; network?: string; apiUrl?: string }): Promise<any> {
|
|
1891
|
-
const baseUrl = apiUrl || API_URLS[network || 'mainnet']
|
|
1892
|
-
const response = await fetch(\`\${baseUrl}/extended/v1/tx/\${txId}\`)
|
|
1893
|
-
if (!response.ok) throw new Error(\`Failed to fetch transaction: \${response.statusText}\`)
|
|
1894
|
-
return response.json()
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
async function fetchBlock({ height, network, apiUrl }: { height: number; network?: string; apiUrl?: string }): Promise<any> {
|
|
1898
|
-
const baseUrl = apiUrl || API_URLS[network || 'mainnet']
|
|
1899
|
-
const response = await fetch(\`\${baseUrl}/extended/v1/block/by_height/\${height}\`)
|
|
1900
|
-
if (!response.ok) throw new Error(\`Failed to fetch block: \${response.statusText}\`)
|
|
1901
|
-
return response.json()
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
async function fetchAccountTransactions({ address, network, apiUrl }: { address: string; network?: string; apiUrl?: string }): Promise<any> {
|
|
1905
|
-
const baseUrl = apiUrl || API_URLS[network || 'mainnet']
|
|
1906
|
-
const response = await fetch(\`\${baseUrl}/extended/v1/address/\${address}/transactions\`)
|
|
1907
|
-
if (!response.ok) throw new Error(\`Failed to fetch transactions: \${response.statusText}\`)
|
|
1908
|
-
return response.json()
|
|
1909
|
-
}`;
|
|
1910
|
-
const header = `/**
|
|
1911
|
-
* Generated generic Stacks React hooks
|
|
1912
|
-
* DO NOT EDIT MANUALLY
|
|
1913
|
-
*/`;
|
|
1914
|
-
const hooksCode = hooksToGenerate.map((hookName) => generateGenericHook(hookName)).filter(Boolean).join(`
|
|
1915
|
-
|
|
1916
|
-
`);
|
|
1917
|
-
const code = `${imports}
|
|
1918
|
-
|
|
1919
|
-
${header}
|
|
1920
|
-
|
|
1921
|
-
${hooksCode}`;
|
|
1922
|
-
return formatCode(code);
|
|
1923
|
-
}
|
|
1924
|
-
function generateGenericHook(hookName) {
|
|
1925
|
-
switch (hookName) {
|
|
1926
|
-
case "useAccount":
|
|
1927
|
-
return `export function useAccount() {
|
|
1928
|
-
const config = useSecondLayerConfig()
|
|
1929
|
-
|
|
1930
|
-
return useQuery({
|
|
1931
|
-
queryKey: ['stacks-account', config.network],
|
|
1932
|
-
queryFn: async () => {
|
|
1933
|
-
try {
|
|
1934
|
-
// Check if already connected
|
|
1935
|
-
const connected = isConnected()
|
|
1936
|
-
|
|
1937
|
-
if (!connected) {
|
|
1938
|
-
return {
|
|
1939
|
-
address: undefined,
|
|
1940
|
-
addresses: undefined,
|
|
1941
|
-
isConnected: false,
|
|
1942
|
-
isConnecting: false,
|
|
1943
|
-
isDisconnected: true,
|
|
1944
|
-
status: 'disconnected' as const
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
|
|
1948
|
-
// Get addresses via SIP-030
|
|
1949
|
-
const result = await request('stx_getAddresses')
|
|
1950
|
-
|
|
1951
|
-
if (!result || !result.addresses || result.addresses.length === 0) {
|
|
1952
|
-
return {
|
|
1953
|
-
address: undefined,
|
|
1954
|
-
addresses: undefined,
|
|
1955
|
-
isConnected: false,
|
|
1956
|
-
isConnecting: false,
|
|
1957
|
-
isDisconnected: true,
|
|
1958
|
-
status: 'disconnected' as const
|
|
1959
|
-
}
|
|
1960
|
-
}
|
|
1961
|
-
|
|
1962
|
-
// Extract STX addresses from the response
|
|
1963
|
-
const stxAddresses = result.addresses
|
|
1964
|
-
.filter((addr: any) => addr.address.startsWith('SP') || addr.address.startsWith('ST'))
|
|
1965
|
-
.map((addr: any) => addr.address)
|
|
1966
|
-
|
|
1967
|
-
return {
|
|
1968
|
-
address: stxAddresses[0] || undefined,
|
|
1969
|
-
addresses: stxAddresses,
|
|
1970
|
-
isConnected: true,
|
|
1971
|
-
isConnecting: false,
|
|
1972
|
-
isDisconnected: false,
|
|
1973
|
-
status: 'connected' as const
|
|
1974
|
-
}
|
|
1975
|
-
} catch (error) {
|
|
1976
|
-
// Handle case where wallet is not available or user rejected
|
|
1977
|
-
return {
|
|
1978
|
-
address: undefined,
|
|
1979
|
-
addresses: undefined,
|
|
1980
|
-
isConnected: false,
|
|
1981
|
-
isConnecting: false,
|
|
1982
|
-
isDisconnected: true,
|
|
1983
|
-
status: 'disconnected' as const
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
},
|
|
1987
|
-
refetchOnWindowFocus: false,
|
|
1988
|
-
retry: false,
|
|
1989
|
-
staleTime: 1000 * 60 * 5, // 5 minutes
|
|
1990
|
-
refetchInterval: 1000 * 30, // Refetch every 30 seconds to detect wallet changes
|
|
1991
|
-
})
|
|
1992
|
-
}`;
|
|
1993
|
-
case "useConnect":
|
|
1994
|
-
return `export function useConnect() {
|
|
1995
|
-
const queryClient = useQueryClient()
|
|
1996
|
-
|
|
1997
|
-
const mutation = useMutation({
|
|
1998
|
-
mutationFn: async (options: { forceWalletSelect?: boolean } = {}) => {
|
|
1999
|
-
// SIP-030 connect
|
|
2000
|
-
return await connect(options)
|
|
2001
|
-
},
|
|
2002
|
-
onSuccess: () => {
|
|
2003
|
-
// Invalidate account queries to refetch connection state
|
|
2004
|
-
queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
|
|
2005
|
-
},
|
|
2006
|
-
onError: (error) => {
|
|
2007
|
-
console.error('Connection failed:', error)
|
|
2008
|
-
}
|
|
2009
|
-
})
|
|
2010
|
-
|
|
2011
|
-
return {
|
|
2012
|
-
// Custom connect function that works without arguments
|
|
2013
|
-
connect: (options?: { forceWalletSelect?: boolean }) => {
|
|
2014
|
-
return mutation.mutate(options || {})
|
|
2015
|
-
},
|
|
2016
|
-
connectAsync: async (options?: { forceWalletSelect?: boolean }) => {
|
|
2017
|
-
return mutation.mutateAsync(options || {})
|
|
2018
|
-
},
|
|
2019
|
-
// Expose all the mutation state
|
|
2020
|
-
isPending: mutation.isPending,
|
|
2021
|
-
isError: mutation.isError,
|
|
2022
|
-
isSuccess: mutation.isSuccess,
|
|
2023
|
-
error: mutation.error,
|
|
2024
|
-
data: mutation.data,
|
|
2025
|
-
reset: mutation.reset,
|
|
2026
|
-
// Keep the original mutate/mutateAsync for advanced users
|
|
2027
|
-
mutate: mutation.mutate,
|
|
2028
|
-
mutateAsync: mutation.mutateAsync
|
|
2029
|
-
}
|
|
2030
|
-
}`;
|
|
2031
|
-
case "useDisconnect":
|
|
2032
|
-
return `export function useDisconnect() {
|
|
2033
|
-
const queryClient = useQueryClient()
|
|
2034
|
-
|
|
2035
|
-
const mutation = useMutation({
|
|
2036
|
-
mutationFn: async () => {
|
|
2037
|
-
// SIP-030 disconnect
|
|
2038
|
-
return await disconnect()
|
|
2039
|
-
},
|
|
2040
|
-
onSuccess: () => {
|
|
2041
|
-
// Clear all cached data on disconnect
|
|
2042
|
-
queryClient.clear()
|
|
2043
|
-
},
|
|
2044
|
-
onError: (error) => {
|
|
2045
|
-
console.error('Disconnect failed:', error)
|
|
2046
|
-
}
|
|
2047
|
-
})
|
|
2048
|
-
|
|
2049
|
-
return {
|
|
2050
|
-
// Custom disconnect function
|
|
2051
|
-
disconnect: () => {
|
|
2052
|
-
return mutation.mutate()
|
|
2053
|
-
},
|
|
2054
|
-
disconnectAsync: async () => {
|
|
2055
|
-
return mutation.mutateAsync()
|
|
2056
|
-
},
|
|
2057
|
-
// Expose all the mutation state
|
|
2058
|
-
isPending: mutation.isPending,
|
|
2059
|
-
isError: mutation.isError,
|
|
2060
|
-
isSuccess: mutation.isSuccess,
|
|
2061
|
-
error: mutation.error,
|
|
2062
|
-
data: mutation.data,
|
|
2063
|
-
reset: mutation.reset,
|
|
2064
|
-
// Keep the original mutate/mutateAsync for advanced users
|
|
2065
|
-
mutate: mutation.mutate,
|
|
2066
|
-
mutateAsync: mutation.mutateAsync
|
|
2067
|
-
}
|
|
2068
|
-
}`;
|
|
2069
|
-
case "useNetwork":
|
|
2070
|
-
return `export function useNetwork() {
|
|
2071
|
-
const config = useSecondLayerConfig()
|
|
2072
|
-
|
|
2073
|
-
return useQuery({
|
|
2074
|
-
queryKey: ['stacks-network', config.network],
|
|
2075
|
-
queryFn: async () => {
|
|
2076
|
-
// Currently read-only from config
|
|
2077
|
-
// Future: Use request('stx_getNetworks') when wallet support improves
|
|
2078
|
-
const network = config.network
|
|
2079
|
-
|
|
2080
|
-
return {
|
|
2081
|
-
network,
|
|
2082
|
-
isMainnet: network === 'mainnet',
|
|
2083
|
-
isTestnet: network === 'testnet',
|
|
2084
|
-
isDevnet: network === 'devnet',
|
|
2085
|
-
// Future: Add switchNetwork when wallets support stx_networkChange
|
|
2086
|
-
// switchNetwork: async (newNetwork: string) => {
|
|
2087
|
-
// return await request('wallet_changeNetwork', { network: newNetwork })
|
|
2088
|
-
// }
|
|
2089
|
-
}
|
|
2090
|
-
},
|
|
2091
|
-
staleTime: Infinity, // Network config rarely changes
|
|
2092
|
-
refetchOnWindowFocus: false,
|
|
2093
|
-
retry: false
|
|
2094
|
-
})
|
|
2095
|
-
}`;
|
|
2096
|
-
case "useContract":
|
|
2097
|
-
return USE_CONTRACT_TEMPLATE;
|
|
2098
|
-
case "useReadContract":
|
|
2099
|
-
return `export function useReadContract<TArgs = any, TResult = any>(params: {
|
|
2100
|
-
contractAddress: string;
|
|
2101
|
-
contractName: string;
|
|
2102
|
-
functionName: string;
|
|
2103
|
-
args?: TArgs;
|
|
2104
|
-
network?: 'mainnet' | 'testnet' | 'devnet';
|
|
2105
|
-
enabled?: boolean;
|
|
2106
|
-
}) {
|
|
2107
|
-
const config = useSecondLayerConfig()
|
|
2108
|
-
|
|
2109
|
-
return useQuery<TResult>({
|
|
2110
|
-
queryKey: ['read-contract', params.contractAddress, params.contractName, params.functionName, params.args, params.network || config.network],
|
|
2111
|
-
queryFn: async () => {
|
|
2112
|
-
const { fetchCallReadOnlyFunction } = await import('@secondlayer/stacks/clarity')
|
|
2113
|
-
|
|
2114
|
-
// For now, we'll need to handle the args conversion here
|
|
2115
|
-
// In the future, we could integrate with the contract interface for automatic conversion
|
|
2116
|
-
let functionArgs: any[] = []
|
|
2117
|
-
|
|
2118
|
-
if (params.args) {
|
|
2119
|
-
// This is a simplified conversion - in practice, we'd need the ABI to do proper conversion
|
|
2120
|
-
// For now, we'll assume the args are already in the correct format or simple types
|
|
2121
|
-
if (Array.isArray(params.args)) {
|
|
2122
|
-
functionArgs = params.args
|
|
2123
|
-
} else if (typeof params.args === 'object') {
|
|
2124
|
-
// Convert object args to array (this is a basic implementation)
|
|
2125
|
-
functionArgs = Object.values(params.args)
|
|
2126
|
-
} else {
|
|
2127
|
-
functionArgs = [params.args]
|
|
2128
|
-
}
|
|
2129
|
-
}
|
|
2130
|
-
|
|
2131
|
-
return await fetchCallReadOnlyFunction({
|
|
2132
|
-
contractAddress: params.contractAddress,
|
|
2133
|
-
contractName: params.contractName,
|
|
2134
|
-
functionName: params.functionName,
|
|
2135
|
-
functionArgs,
|
|
2136
|
-
network: params.network || config.network || 'mainnet',
|
|
2137
|
-
senderAddress: config.senderAddress || 'SP000000000000000000002Q6VF78'
|
|
2138
|
-
}) as TResult
|
|
2139
|
-
},
|
|
2140
|
-
enabled: params.enabled ?? true
|
|
2141
|
-
})
|
|
2142
|
-
}`;
|
|
2143
|
-
case "useTransaction":
|
|
2144
|
-
return `export function useTransaction(txId?: string) {
|
|
2145
|
-
const config = useSecondLayerConfig()
|
|
2146
|
-
|
|
2147
|
-
return useQuery({
|
|
2148
|
-
queryKey: ['transaction', txId, config.network],
|
|
2149
|
-
queryFn: () => fetchTransaction({
|
|
2150
|
-
txId: txId!,
|
|
2151
|
-
network: config.network,
|
|
2152
|
-
apiUrl: config.apiUrl
|
|
2153
|
-
}),
|
|
2154
|
-
enabled: !!txId
|
|
2155
|
-
})
|
|
2156
|
-
}`;
|
|
2157
|
-
case "useBlock":
|
|
2158
|
-
return `export function useBlock(height?: number) {
|
|
2159
|
-
const config = useSecondLayerConfig()
|
|
2160
|
-
|
|
2161
|
-
return useQuery({
|
|
2162
|
-
queryKey: ['block', height, config.network],
|
|
2163
|
-
queryFn: () => fetchBlock({
|
|
2164
|
-
height: height!,
|
|
2165
|
-
network: config.network,
|
|
2166
|
-
apiUrl: config.apiUrl
|
|
2167
|
-
}),
|
|
2168
|
-
enabled: typeof height === 'number'
|
|
2169
|
-
})
|
|
2170
|
-
}`;
|
|
2171
|
-
case "useAccountTransactions":
|
|
2172
|
-
return `export function useAccountTransactions(address?: string) {
|
|
2173
|
-
const config = useSecondLayerConfig()
|
|
2174
|
-
|
|
2175
|
-
return useQuery({
|
|
2176
|
-
queryKey: ['account-transactions', address, config.network],
|
|
2177
|
-
queryFn: () => fetchAccountTransactions({
|
|
2178
|
-
address: address!,
|
|
2179
|
-
network: config.network,
|
|
2180
|
-
apiUrl: config.apiUrl
|
|
2181
|
-
}),
|
|
2182
|
-
enabled: !!address
|
|
2183
|
-
})
|
|
2184
|
-
}`;
|
|
2185
|
-
case "useWaitForTransaction":
|
|
2186
|
-
return `export function useWaitForTransaction(txId?: string) {
|
|
2187
|
-
const config = useSecondLayerConfig()
|
|
2188
|
-
|
|
2189
|
-
return useQuery({
|
|
2190
|
-
queryKey: ['wait-for-transaction', txId, config.network],
|
|
2191
|
-
queryFn: () => fetchTransaction({
|
|
2192
|
-
txId: txId!,
|
|
2193
|
-
network: config.network,
|
|
2194
|
-
apiUrl: config.apiUrl
|
|
2195
|
-
}),
|
|
2196
|
-
enabled: !!txId,
|
|
2197
|
-
refetchInterval: (data) => {
|
|
2198
|
-
// Stop polling when transaction is complete
|
|
2199
|
-
if (data?.tx_status === 'success' ||
|
|
2200
|
-
data?.tx_status === 'abort_by_response' ||
|
|
2201
|
-
data?.tx_status === 'abort_by_post_condition') {
|
|
2202
|
-
return false
|
|
2203
|
-
}
|
|
2204
|
-
return 2000 // Poll every 2 seconds
|
|
2205
|
-
},
|
|
2206
|
-
staleTime: 0 // Always refetch
|
|
2207
|
-
})
|
|
2208
|
-
}`;
|
|
2209
|
-
case "useOpenSTXTransfer":
|
|
2210
|
-
return `export function useOpenSTXTransfer() {
|
|
2211
|
-
const config = useSecondLayerConfig()
|
|
2212
|
-
const queryClient = useQueryClient()
|
|
2213
|
-
|
|
2214
|
-
const mutation = useMutation({
|
|
2215
|
-
mutationFn: async (params: {
|
|
2216
|
-
recipient: string;
|
|
2217
|
-
amount: string | number;
|
|
2218
|
-
memo?: string;
|
|
2219
|
-
network?: string;
|
|
2220
|
-
}) => {
|
|
2221
|
-
const { recipient, amount, memo } = params
|
|
2222
|
-
const network = params.network || config.network || 'mainnet'
|
|
2223
|
-
|
|
2224
|
-
return await request('stx_transferStx', {
|
|
2225
|
-
recipient,
|
|
2226
|
-
amount: amount.toString(),
|
|
2227
|
-
memo,
|
|
2228
|
-
network,
|
|
2229
|
-
})
|
|
2230
|
-
},
|
|
2231
|
-
onSuccess: () => {
|
|
2232
|
-
queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
|
|
2233
|
-
},
|
|
2234
|
-
onError: (error) => {
|
|
2235
|
-
console.error('STX transfer failed:', error)
|
|
2236
|
-
}
|
|
2237
|
-
})
|
|
2238
|
-
|
|
2239
|
-
const openSTXTransfer = useCallback(async (params: {
|
|
2240
|
-
recipient: string;
|
|
2241
|
-
amount: string | number;
|
|
2242
|
-
memo?: string;
|
|
2243
|
-
network?: string;
|
|
2244
|
-
}) => {
|
|
2245
|
-
return mutation.mutateAsync(params)
|
|
2246
|
-
}, [mutation])
|
|
2247
|
-
|
|
2248
|
-
return {
|
|
2249
|
-
openSTXTransfer,
|
|
2250
|
-
isPending: mutation.isPending,
|
|
2251
|
-
isError: mutation.isError,
|
|
2252
|
-
isSuccess: mutation.isSuccess,
|
|
2253
|
-
error: mutation.error,
|
|
2254
|
-
data: mutation.data,
|
|
2255
|
-
reset: mutation.reset
|
|
2256
|
-
}
|
|
2257
|
-
}`;
|
|
2258
|
-
case "useSignMessage":
|
|
2259
|
-
return `export function useSignMessage() {
|
|
2260
|
-
const config = useSecondLayerConfig()
|
|
2261
|
-
|
|
2262
|
-
const mutation = useMutation({
|
|
2263
|
-
mutationFn: async (params: {
|
|
2264
|
-
message: string;
|
|
2265
|
-
network?: string;
|
|
2266
|
-
}) => {
|
|
2267
|
-
const { message } = params
|
|
2268
|
-
const network = params.network || config.network || 'mainnet'
|
|
2269
|
-
|
|
2270
|
-
return await request('stx_signMessage', {
|
|
2271
|
-
message,
|
|
2272
|
-
network,
|
|
2273
|
-
})
|
|
2274
|
-
},
|
|
2275
|
-
onError: (error) => {
|
|
2276
|
-
console.error('Message signing failed:', error)
|
|
2277
|
-
}
|
|
2278
|
-
})
|
|
2279
|
-
|
|
2280
|
-
const signMessage = useCallback(async (params: {
|
|
2281
|
-
message: string;
|
|
2282
|
-
network?: string;
|
|
2283
|
-
}) => {
|
|
2284
|
-
return mutation.mutateAsync(params)
|
|
2285
|
-
}, [mutation])
|
|
2286
|
-
|
|
2287
|
-
return {
|
|
2288
|
-
signMessage,
|
|
2289
|
-
isPending: mutation.isPending,
|
|
2290
|
-
isError: mutation.isError,
|
|
2291
|
-
isSuccess: mutation.isSuccess,
|
|
2292
|
-
error: mutation.error,
|
|
2293
|
-
data: mutation.data,
|
|
2294
|
-
reset: mutation.reset
|
|
2295
|
-
}
|
|
2296
|
-
}`;
|
|
2297
|
-
case "useDeployContract":
|
|
2298
|
-
return `export function useDeployContract() {
|
|
2299
|
-
const config = useSecondLayerConfig()
|
|
2300
|
-
const queryClient = useQueryClient()
|
|
2301
|
-
|
|
2302
|
-
const mutation = useMutation({
|
|
2303
|
-
mutationFn: async (params: {
|
|
2304
|
-
contractName: string;
|
|
2305
|
-
codeBody: string;
|
|
2306
|
-
network?: string;
|
|
2307
|
-
}) => {
|
|
2308
|
-
const { contractName, codeBody } = params
|
|
2309
|
-
const network = params.network || config.network || 'mainnet'
|
|
2310
|
-
|
|
2311
|
-
return await request('stx_deployContract', {
|
|
2312
|
-
name: contractName,
|
|
2313
|
-
clarityCode: codeBody,
|
|
2314
|
-
network,
|
|
2315
|
-
})
|
|
2316
|
-
},
|
|
2317
|
-
onSuccess: () => {
|
|
2318
|
-
queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
|
|
2319
|
-
},
|
|
2320
|
-
onError: (error) => {
|
|
2321
|
-
console.error('Contract deployment failed:', error)
|
|
2322
|
-
}
|
|
2323
|
-
})
|
|
2324
|
-
|
|
2325
|
-
const deployContract = useCallback(async (params: {
|
|
2326
|
-
contractName: string;
|
|
2327
|
-
codeBody: string;
|
|
2328
|
-
network?: string;
|
|
2329
|
-
}) => {
|
|
2330
|
-
return mutation.mutateAsync(params)
|
|
2331
|
-
}, [mutation])
|
|
2332
|
-
|
|
2333
|
-
return {
|
|
2334
|
-
deployContract,
|
|
2335
|
-
isPending: mutation.isPending,
|
|
2336
|
-
isError: mutation.isError,
|
|
2337
|
-
isSuccess: mutation.isSuccess,
|
|
2338
|
-
error: mutation.error,
|
|
2339
|
-
data: mutation.data,
|
|
2340
|
-
reset: mutation.reset
|
|
2341
|
-
}
|
|
2342
|
-
}`;
|
|
2343
|
-
default:
|
|
2344
|
-
return "";
|
|
2345
|
-
}
|
|
2346
|
-
}
|
|
2347
|
-
|
|
2348
|
-
// src/plugins/react/provider/index.ts
|
|
2349
|
-
init_format();
|
|
2350
|
-
async function generateProvider() {
|
|
2351
|
-
const code = `/**
|
|
2352
|
-
* Generated SecondLayer React Provider
|
|
2353
|
-
* DO NOT EDIT MANUALLY
|
|
2354
|
-
*/
|
|
2355
|
-
|
|
2356
|
-
import React, { createContext, useContext } from 'react'
|
|
2357
|
-
|
|
2358
|
-
/**
|
|
2359
|
-
* SecondLayer configuration interface
|
|
2360
|
-
*/
|
|
2361
|
-
export interface SecondLayerReactConfig {
|
|
2362
|
-
/**
|
|
2363
|
-
* Network to use for API calls
|
|
2364
|
-
*/
|
|
2365
|
-
network: 'mainnet' | 'testnet' | 'devnet'
|
|
2366
|
-
|
|
2367
|
-
/**
|
|
2368
|
-
* API key for Stacks API (optional)
|
|
2369
|
-
*/
|
|
2370
|
-
apiKey?: string
|
|
2371
|
-
|
|
2372
|
-
/**
|
|
2373
|
-
* Base URL for Stacks API (optional override)
|
|
2374
|
-
*/
|
|
2375
|
-
apiUrl?: string
|
|
2376
|
-
|
|
2377
|
-
/**
|
|
2378
|
-
* Default sender address for read-only calls
|
|
2379
|
-
*/
|
|
2380
|
-
senderAddress?: string
|
|
2381
|
-
}
|
|
2382
|
-
|
|
2383
|
-
/**
|
|
2384
|
-
* Provider component props
|
|
2385
|
-
*/
|
|
2386
|
-
export interface SecondLayerProviderProps {
|
|
2387
|
-
children: React.ReactNode
|
|
2388
|
-
config: SecondLayerReactConfig
|
|
2389
|
-
}
|
|
2390
|
-
|
|
2391
|
-
/**
|
|
2392
|
-
* React context for SecondLayer configuration
|
|
2393
|
-
*/
|
|
2394
|
-
const SecondLayerContext = createContext<SecondLayerReactConfig | undefined>(undefined)
|
|
2395
|
-
SecondLayerContext.displayName = 'SecondLayerContext'
|
|
2396
|
-
|
|
2397
|
-
/**
|
|
2398
|
-
* Create a SecondLayer React configuration with defaults
|
|
2399
|
-
*/
|
|
2400
|
-
export function createSecondLayerConfig(config: SecondLayerReactConfig): SecondLayerReactConfig {
|
|
2401
|
-
return {
|
|
2402
|
-
network: config.network,
|
|
2403
|
-
apiKey: config.apiKey,
|
|
2404
|
-
apiUrl: config.apiUrl,
|
|
2405
|
-
senderAddress: config.senderAddress || 'SP000000000000000000002Q6VF78'
|
|
2406
|
-
}
|
|
2407
|
-
}
|
|
2408
|
-
|
|
2409
|
-
/**
|
|
2410
|
-
* Provider component that makes SecondLayer configuration available to hooks
|
|
2411
|
-
*/
|
|
2412
|
-
export function SecondLayerProvider({ children, config }: SecondLayerProviderProps) {
|
|
2413
|
-
const resolvedConfig = createSecondLayerConfig(config)
|
|
2414
|
-
|
|
2415
|
-
return (
|
|
2416
|
-
<SecondLayerContext.Provider value={resolvedConfig}>
|
|
2417
|
-
{children}
|
|
2418
|
-
</SecondLayerContext.Provider>
|
|
2419
|
-
)
|
|
2420
|
-
}
|
|
2421
|
-
|
|
2422
|
-
/**
|
|
2423
|
-
* Hook to access the SecondLayer configuration
|
|
2424
|
-
*/
|
|
2425
|
-
export function useSecondLayerConfig(): SecondLayerReactConfig {
|
|
2426
|
-
const context = useContext(SecondLayerContext)
|
|
2427
|
-
|
|
2428
|
-
if (context === undefined) {
|
|
2429
|
-
throw new Error(
|
|
2430
|
-
'useSecondLayerConfig must be used within a SecondLayerProvider. ' +
|
|
2431
|
-
'Make sure to wrap your app with <SecondLayerProvider config={{...}}>'
|
|
2432
|
-
)
|
|
2433
|
-
}
|
|
2434
|
-
|
|
2435
|
-
return context
|
|
2436
|
-
}`;
|
|
2437
|
-
return formatCode(code);
|
|
2438
|
-
}
|
|
2439
|
-
|
|
2440
|
-
// src/plugins/react/index.ts
|
|
2441
|
-
var react = (options = {}) => {
|
|
2442
|
-
const excludeList = options.exclude || [];
|
|
2443
|
-
return {
|
|
2444
|
-
name: "@secondlayer/cli/plugin-react",
|
|
2445
|
-
version: "1.0.0",
|
|
2446
|
-
async generate(context) {
|
|
2447
|
-
if (options.debug) {
|
|
2448
|
-
context.logger.debug(`React plugin generating hooks (excluding: ${excludeList.join(", ") || "none"})`);
|
|
2449
|
-
}
|
|
2450
|
-
const provider = await generateProvider();
|
|
2451
|
-
context.addOutput("provider", {
|
|
2452
|
-
path: "./src/generated/provider.tsx",
|
|
2453
|
-
content: provider,
|
|
2454
|
-
type: "config"
|
|
2455
|
-
});
|
|
2456
|
-
const genericHooks = await generateGenericHooks(excludeList);
|
|
2457
|
-
context.addOutput("generic-hooks", {
|
|
2458
|
-
path: "./src/generated/hooks.ts",
|
|
2459
|
-
content: genericHooks,
|
|
2460
|
-
type: "hooks"
|
|
2461
|
-
});
|
|
2462
|
-
if (context.contracts.length > 0) {
|
|
2463
|
-
const contractHooks = await generateContractHooks(context.contracts, excludeList);
|
|
2464
|
-
if (contractHooks.trim()) {
|
|
2465
|
-
context.addOutput("contract-hooks", {
|
|
2466
|
-
path: "./src/generated/contract-hooks.ts",
|
|
2467
|
-
content: contractHooks,
|
|
2468
|
-
type: "hooks"
|
|
2469
|
-
});
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
if (options.debug) {
|
|
2473
|
-
context.logger.success(`React plugin generated ${context.contracts.length} contract hook sets`);
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
2476
|
-
};
|
|
2477
|
-
};
|
|
2478
|
-
// src/plugins/testing/generators.ts
|
|
2479
|
-
import {
|
|
2480
|
-
isAbiTuple as isAbiTuple4
|
|
2481
|
-
} from "@secondlayer/stacks/clarity";
|
|
2482
|
-
function generatePublicFunction(func, contractId) {
|
|
2483
|
-
const methodName = toCamelCase8(func.name);
|
|
2484
|
-
const argsSignature = generateArgsSignature(func.args);
|
|
2485
|
-
const clarityArgs = generateClarityArgs(func.args);
|
|
2486
|
-
return `${methodName}: (${argsSignature}caller: string) => {
|
|
2487
|
-
const callerAddr = accounts.get(caller) ?? caller;
|
|
2488
|
-
return simnet.callPublicFn(
|
|
2489
|
-
'${contractId}',
|
|
2490
|
-
'${func.name}',
|
|
2491
|
-
[${clarityArgs}],
|
|
2492
|
-
callerAddr
|
|
2493
|
-
);
|
|
2494
|
-
}`;
|
|
2495
|
-
}
|
|
2496
|
-
function generateReadOnlyFunction(func, contractId) {
|
|
2497
|
-
const methodName = toCamelCase8(func.name);
|
|
2498
|
-
const argsSignature = generateArgsSignature(func.args);
|
|
2499
|
-
const clarityArgs = generateClarityArgs(func.args);
|
|
2500
|
-
const hasArgs = func.args.length > 0;
|
|
2501
|
-
const argsParam = hasArgs ? argsSignature : "";
|
|
2502
|
-
return `${methodName}: (${argsParam}) => {
|
|
2503
|
-
return simnet.callReadOnlyFn(
|
|
2504
|
-
'${contractId}',
|
|
2505
|
-
'${func.name}',
|
|
2506
|
-
[${clarityArgs}],
|
|
2507
|
-
accounts.get('deployer')!
|
|
2508
|
-
);
|
|
2509
|
-
}`;
|
|
2510
|
-
}
|
|
2511
|
-
function generatePrivateFunction(func, contractId) {
|
|
2512
|
-
const methodName = toCamelCase8(func.name);
|
|
2513
|
-
const argsSignature = generateArgsSignature(func.args);
|
|
2514
|
-
const clarityArgs = generateClarityArgs(func.args);
|
|
2515
|
-
return `${methodName}: (${argsSignature}caller: string) => {
|
|
2516
|
-
const callerAddr = accounts.get(caller) ?? caller;
|
|
2517
|
-
return simnet.callPrivateFn(
|
|
2518
|
-
'${contractId}',
|
|
2519
|
-
'${func.name}',
|
|
2520
|
-
[${clarityArgs}],
|
|
2521
|
-
callerAddr
|
|
2522
|
-
);
|
|
2523
|
-
}`;
|
|
2524
|
-
}
|
|
2525
|
-
function generateDataVarHelper(variable, contractId) {
|
|
2526
|
-
const methodName = toCamelCase8(variable.name);
|
|
2527
|
-
return `${methodName}: () => {
|
|
2528
|
-
return simnet.getDataVar('${contractId}', '${variable.name}');
|
|
2529
|
-
}`;
|
|
2530
|
-
}
|
|
2531
|
-
function getMapKeyType(keyType) {
|
|
2532
|
-
if (isAbiTuple4(keyType)) {
|
|
2533
|
-
const fields = keyType.tuple.map((field) => `${toCamelCase8(field.name)}: ${getTypeForArg({ type: field.type })}`).join("; ");
|
|
2534
|
-
return `{ ${fields} }`;
|
|
2535
|
-
}
|
|
2536
|
-
return getTypeForArg({ type: keyType });
|
|
2537
|
-
}
|
|
2538
|
-
function generateMapEntryHelper(map, contractId) {
|
|
2539
|
-
const methodName = toCamelCase8(map.name);
|
|
2540
|
-
const keyType = getMapKeyType(map.key);
|
|
2541
|
-
const keyConversion = generateMapKeyConversion(map.key);
|
|
2542
|
-
return `${methodName}: (key: ${keyType}) => {
|
|
2543
|
-
return simnet.getMapEntry(
|
|
2544
|
-
'${contractId}',
|
|
2545
|
-
'${map.name}',
|
|
2546
|
-
${keyConversion}
|
|
2547
|
-
);
|
|
2548
|
-
}`;
|
|
2549
|
-
}
|
|
2550
|
-
function generateVarsObject2(variables, contractId) {
|
|
2551
|
-
const dataVars = variables.filter((v) => v.access === "variable");
|
|
2552
|
-
if (dataVars.length === 0) {
|
|
2553
|
-
return "";
|
|
2554
|
-
}
|
|
2555
|
-
const varHelpers = dataVars.map((v) => generateDataVarHelper(v, contractId));
|
|
2556
|
-
return `vars: {
|
|
2557
|
-
${varHelpers.join(`,
|
|
2558
|
-
|
|
2559
|
-
`)}
|
|
2560
|
-
}`;
|
|
2561
|
-
}
|
|
2562
|
-
function generateMapsObject2(maps, contractId) {
|
|
2563
|
-
if (maps.length === 0) {
|
|
2564
|
-
return "";
|
|
2565
|
-
}
|
|
2566
|
-
const mapHelpers = maps.map((m) => generateMapEntryHelper(m, contractId));
|
|
2567
|
-
return `maps: {
|
|
2568
|
-
${mapHelpers.join(`,
|
|
2569
|
-
|
|
2570
|
-
`)}
|
|
2571
|
-
}`;
|
|
2572
|
-
}
|
|
2573
|
-
function generateContractHelper(contract, options) {
|
|
2574
|
-
const { abi, name, address } = contract;
|
|
2575
|
-
const functions = abi.functions || [];
|
|
2576
|
-
const variables = abi.variables || [];
|
|
2577
|
-
const maps = abi.maps || [];
|
|
2578
|
-
const pascalName = toPascalCase(name);
|
|
2579
|
-
const publicFns = functions.filter((f) => f.access === "public");
|
|
2580
|
-
const readOnlyFns = functions.filter((f) => f.access === "read-only");
|
|
2581
|
-
const privateFns = options.includePrivate ? functions.filter((f) => f.access === "private") : [];
|
|
2582
|
-
const publicHelpers = publicFns.map((f) => generatePublicFunction(f, address));
|
|
2583
|
-
const readOnlyHelpers = readOnlyFns.map((f) => generateReadOnlyFunction(f, address));
|
|
2584
|
-
const privateHelpers = privateFns.map((f) => generatePrivateFunction(f, address));
|
|
2585
|
-
const varsObject = generateVarsObject2(variables, address);
|
|
2586
|
-
const mapsObject = generateMapsObject2(maps, address);
|
|
2587
|
-
const allHelpers = [...publicHelpers, ...readOnlyHelpers, ...privateHelpers];
|
|
2588
|
-
if (varsObject) {
|
|
2589
|
-
allHelpers.push(varsObject);
|
|
2590
|
-
}
|
|
2591
|
-
if (mapsObject) {
|
|
2592
|
-
allHelpers.push(mapsObject);
|
|
2593
|
-
}
|
|
2594
|
-
if (allHelpers.length === 0) {
|
|
2595
|
-
return "";
|
|
2596
|
-
}
|
|
2597
|
-
return `export function get${pascalName}(simnet: Simnet) {
|
|
2598
|
-
const accounts = simnet.getAccounts();
|
|
2599
|
-
|
|
2600
|
-
return {
|
|
2601
|
-
${allHelpers.join(`,
|
|
2602
|
-
|
|
2603
|
-
`)}
|
|
2604
|
-
};
|
|
2605
|
-
}`;
|
|
2606
|
-
}
|
|
2607
|
-
function generateGetContracts(contracts) {
|
|
2608
|
-
const contractEntries = contracts.map((contract) => {
|
|
2609
|
-
const camelName = toCamelCase8(contract.name);
|
|
2610
|
-
const pascalName = toPascalCase(contract.name);
|
|
2611
|
-
return `${camelName}: get${pascalName}(simnet)`;
|
|
2612
|
-
}).join(`,
|
|
2613
|
-
`);
|
|
2614
|
-
return `export function getContracts(simnet: Simnet) {
|
|
2615
|
-
const accounts = simnet.getAccounts();
|
|
2616
366
|
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
};
|
|
2621
|
-
}`;
|
|
2622
|
-
}
|
|
2623
|
-
function generateTypeExports(contracts) {
|
|
2624
|
-
const typeExports = contracts.map((contract) => {
|
|
2625
|
-
const pascalName = toPascalCase(contract.name);
|
|
2626
|
-
return `export type ${pascalName}Helpers = ReturnType<typeof get${pascalName}>;`;
|
|
2627
|
-
}).join(`
|
|
2628
|
-
`);
|
|
2629
|
-
return `${typeExports}
|
|
2630
|
-
export type Contracts = ReturnType<typeof getContracts>;`;
|
|
2631
|
-
}
|
|
2632
|
-
async function generateTestingHelpers(contracts, options) {
|
|
2633
|
-
const contractHelpers = contracts.map((contract) => generateContractHelper(contract, options)).filter(Boolean);
|
|
2634
|
-
if (contractHelpers.length === 0) {
|
|
2635
|
-
return `// No contracts with functions to generate helpers for
|
|
2636
|
-
export {};`;
|
|
2637
|
-
}
|
|
2638
|
-
const getContractsCode = generateGetContracts(contracts);
|
|
2639
|
-
const typeExports = generateTypeExports(contracts);
|
|
2640
|
-
return `/**
|
|
2641
|
-
* Generated by @secondlayer/cli testing plugin
|
|
2642
|
-
* Type-safe helpers for Clarinet SDK unit tests
|
|
2643
|
-
*/
|
|
2644
|
-
|
|
2645
|
-
import { type Simnet, Cl } from '@hirosystems/clarinet-sdk';
|
|
2646
|
-
|
|
2647
|
-
// ============================================
|
|
2648
|
-
// Per-contract factory functions
|
|
2649
|
-
// ============================================
|
|
2650
|
-
|
|
2651
|
-
${contractHelpers.join(`
|
|
2652
|
-
|
|
2653
|
-
`)}
|
|
2654
|
-
|
|
2655
|
-
// ============================================
|
|
2656
|
-
// Convenience: all contracts at once
|
|
2657
|
-
// ============================================
|
|
2658
|
-
|
|
2659
|
-
${getContractsCode}
|
|
2660
|
-
|
|
2661
|
-
// ============================================
|
|
2662
|
-
// Type exports
|
|
2663
|
-
// ============================================
|
|
2664
|
-
|
|
2665
|
-
${typeExports}
|
|
2666
|
-
`;
|
|
2667
|
-
}
|
|
2668
|
-
|
|
2669
|
-
// src/plugins/testing/index.ts
|
|
2670
|
-
var testing = (options = {}) => {
|
|
2671
|
-
return {
|
|
2672
|
-
name: "@secondlayer/cli/plugin-testing",
|
|
2673
|
-
version: "1.0.0",
|
|
2674
|
-
async generate(context) {
|
|
2675
|
-
const { contracts } = context;
|
|
2676
|
-
const filteredContracts = contracts.filter((contract) => {
|
|
2677
|
-
if (options.include && !options.include.includes(contract.name)) {
|
|
2678
|
-
return false;
|
|
2679
|
-
}
|
|
2680
|
-
if (options.exclude && options.exclude.includes(contract.name)) {
|
|
2681
|
-
return false;
|
|
2682
|
-
}
|
|
2683
|
-
return true;
|
|
2684
|
-
});
|
|
2685
|
-
if (filteredContracts.length === 0) {
|
|
2686
|
-
if (options.debug) {
|
|
2687
|
-
context.logger.debug("Testing plugin: No contracts to process");
|
|
2688
|
-
}
|
|
2689
|
-
return;
|
|
2690
|
-
}
|
|
2691
|
-
if (options.debug) {
|
|
2692
|
-
context.logger.debug(`Testing plugin: Generating helpers for ${filteredContracts.length} contracts`);
|
|
2693
|
-
}
|
|
2694
|
-
const testingCode = await generateTestingHelpers(filteredContracts, options);
|
|
2695
|
-
const outputPath = options.out || "./src/generated/testing.ts";
|
|
2696
|
-
context.addOutput("testing", {
|
|
2697
|
-
path: outputPath,
|
|
2698
|
-
content: testingCode,
|
|
2699
|
-
type: "utils"
|
|
2700
|
-
});
|
|
2701
|
-
if (options.debug) {
|
|
2702
|
-
context.logger.debug(`Testing plugin: Generated helpers for ${filteredContracts.length} contracts`);
|
|
2703
|
-
}
|
|
2704
|
-
}
|
|
2705
|
-
};
|
|
2706
|
-
};
|
|
2707
|
-
|
|
2708
|
-
// src/plugins/index.ts
|
|
2709
|
-
function filterByOptions(items, options = {}) {
|
|
2710
|
-
let filtered = items;
|
|
2711
|
-
if (options.include && options.include.length > 0) {
|
|
2712
|
-
filtered = filtered.filter((item) => options.include.some((pattern) => item.name.includes(pattern) || item.name.match(new RegExp(pattern))));
|
|
2713
|
-
}
|
|
2714
|
-
if (options.exclude && options.exclude.length > 0) {
|
|
2715
|
-
filtered = filtered.filter((item) => !options.exclude.some((pattern) => item.name.includes(pattern) || item.name.match(new RegExp(pattern))));
|
|
2716
|
-
}
|
|
2717
|
-
return filtered;
|
|
2718
|
-
}
|
|
2719
|
-
function createPlugin(name, version, implementation) {
|
|
2720
|
-
return {
|
|
2721
|
-
name,
|
|
2722
|
-
version,
|
|
2723
|
-
...implementation
|
|
2724
|
-
};
|
|
367
|
+
// src/utils/config.ts
|
|
368
|
+
function defineConfig(configOrDefiner) {
|
|
369
|
+
return configOrDefiner;
|
|
2725
370
|
}
|
|
2726
371
|
export {
|
|
2727
|
-
|
|
2728
|
-
react,
|
|
2729
|
-
hasClarinetProject,
|
|
2730
|
-
filterByOptions,
|
|
2731
|
-
createPlugin,
|
|
2732
|
-
clarinet,
|
|
2733
|
-
actions,
|
|
372
|
+
defineConfig,
|
|
2734
373
|
PluginManager
|
|
2735
374
|
};
|
|
2736
375
|
|
|
2737
|
-
//# debugId=
|
|
376
|
+
//# debugId=6FDA39721A22F05264756E2164756E21
|
|
2738
377
|
//# sourceMappingURL=index.js.map
|