@secondlayer/cli 8.14.3 → 9.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -8
- package/dist/cli.js +103 -7
- package/dist/cli.js.map +3 -3
- package/dist/plugins/index.d.ts +6 -19
- package/dist/plugins/index.js +172 -264
- package/dist/plugins/index.js.map +8 -10
- package/package.json +5 -5
package/dist/plugins/index.js
CHANGED
|
@@ -576,22 +576,38 @@ function matchesContractFilters(name, options) {
|
|
|
576
576
|
function sanitizeContractName(name) {
|
|
577
577
|
return toCamelCase2(name);
|
|
578
578
|
}
|
|
579
|
-
async function
|
|
580
|
-
const { address, contractName } = parseContractId(contractId);
|
|
579
|
+
async function readManifestInfo(manifestPath) {
|
|
581
580
|
try {
|
|
582
581
|
const { promises: fs2 } = await import("node:fs");
|
|
583
582
|
const tomlContent = await fs2.readFile(manifestPath, "utf-8");
|
|
583
|
+
const projectContracts = new Set;
|
|
584
584
|
const contractSectionRegex = /^\[contracts\.([^\]]+)\]/gm;
|
|
585
|
-
const userContracts = new Set;
|
|
586
585
|
let match = contractSectionRegex.exec(tomlContent);
|
|
587
586
|
while (match !== null) {
|
|
588
|
-
|
|
587
|
+
projectContracts.add(match[1]);
|
|
589
588
|
match = contractSectionRegex.exec(tomlContent);
|
|
590
589
|
}
|
|
591
|
-
|
|
592
|
-
|
|
590
|
+
const requirementIds = new Set;
|
|
591
|
+
const requirementRegex = /contract_id\s*=\s*["']([^"']+)["']/g;
|
|
592
|
+
match = requirementRegex.exec(tomlContent);
|
|
593
|
+
while (match !== null) {
|
|
594
|
+
requirementIds.add(match[1]);
|
|
595
|
+
match = requirementRegex.exec(tomlContent);
|
|
593
596
|
}
|
|
594
|
-
|
|
597
|
+
return { projectContracts, requirementIds };
|
|
598
|
+
} catch {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
function classifyContract(contractId, manifest) {
|
|
603
|
+
const { address, contractName } = parseContractId(contractId);
|
|
604
|
+
if (manifest) {
|
|
605
|
+
if (manifest.projectContracts.has(contractName))
|
|
606
|
+
return "project";
|
|
607
|
+
if (manifest.requirementIds.has(contractId))
|
|
608
|
+
return "requirement";
|
|
609
|
+
return "system";
|
|
610
|
+
}
|
|
595
611
|
const systemContractPatterns = [
|
|
596
612
|
/^pox-\d+$/,
|
|
597
613
|
/^bns$/,
|
|
@@ -599,16 +615,16 @@ async function isUserDefinedContract(contractId, manifestPath) {
|
|
|
599
615
|
/^lockup$/
|
|
600
616
|
];
|
|
601
617
|
if (systemContractPatterns.some((pattern) => pattern.test(contractName))) {
|
|
602
|
-
return
|
|
618
|
+
return "system";
|
|
603
619
|
}
|
|
604
620
|
const systemAddresses = [
|
|
605
621
|
DEFAULT_SENDER_ADDRESS,
|
|
606
622
|
"ST000000000000000000002AMW42H"
|
|
607
623
|
];
|
|
608
624
|
if (systemAddresses.includes(address)) {
|
|
609
|
-
return
|
|
625
|
+
return "system";
|
|
610
626
|
}
|
|
611
|
-
return
|
|
627
|
+
return "project";
|
|
612
628
|
}
|
|
613
629
|
var clarinet = (options = {}) => {
|
|
614
630
|
const manifestPath = options.path || "./Clarinet.toml";
|
|
@@ -621,11 +637,14 @@ var clarinet = (options = {}) => {
|
|
|
621
637
|
simnet = await initSimnet(manifestPath);
|
|
622
638
|
const contractInterfaces = simnet.getContractsInterfaces();
|
|
623
639
|
const contracts = [];
|
|
640
|
+
const manifest = await readManifestInfo(manifestPath);
|
|
641
|
+
const includeRequirements = options.includeRequirements ?? true;
|
|
624
642
|
for (const [contractId, abi] of contractInterfaces) {
|
|
625
643
|
const { contractName } = parseContractId(contractId);
|
|
626
|
-
|
|
644
|
+
const kind = classifyContract(contractId, manifest);
|
|
645
|
+
if (kind === "system" || kind === "requirement" && !includeRequirements) {
|
|
627
646
|
if (options.debug) {
|
|
628
|
-
console.log(`\uD83D\uDEAB Skipping
|
|
647
|
+
console.log(`\uD83D\uDEAB Skipping ${kind} contract: ${contractId}`);
|
|
629
648
|
}
|
|
630
649
|
continue;
|
|
631
650
|
}
|
|
@@ -683,208 +702,16 @@ async function hasClarinetProject(path2 = "./Clarinet.toml") {
|
|
|
683
702
|
return false;
|
|
684
703
|
}
|
|
685
704
|
}
|
|
686
|
-
// src/plugins/actions/generators.ts
|
|
687
|
-
import { toCamelCase as toCamelCase3 } from "@secondlayer/stacks/clarity";
|
|
688
|
-
|
|
689
|
-
// src/utils/generator-helpers.ts
|
|
690
|
-
import {
|
|
691
|
-
generateArgsSignature,
|
|
692
|
-
generateClarityArgs,
|
|
693
|
-
generateMapKeyConversion
|
|
694
|
-
} from "@secondlayer/scaffold";
|
|
695
|
-
|
|
696
|
-
// src/plugins/actions/generators.ts
|
|
697
|
-
function applyFunctionFilters(functions, options) {
|
|
698
|
-
return functions.filter((func) => {
|
|
699
|
-
if (options.includeFunctions && !options.includeFunctions.includes(func.name)) {
|
|
700
|
-
return false;
|
|
701
|
-
}
|
|
702
|
-
if (options.excludeFunctions?.includes(func.name)) {
|
|
703
|
-
return false;
|
|
704
|
-
}
|
|
705
|
-
return true;
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
|
-
function generateReadHelpers(contract, options) {
|
|
709
|
-
const { abi } = contract;
|
|
710
|
-
const functions = abi.functions || [];
|
|
711
|
-
const readOnlyFunctions = functions.filter((f) => f.access === "read-only");
|
|
712
|
-
if (readOnlyFunctions.length === 0) {
|
|
713
|
-
return "";
|
|
714
|
-
}
|
|
715
|
-
const filteredFunctions = applyFunctionFilters(readOnlyFunctions, options);
|
|
716
|
-
if (filteredFunctions.length === 0) {
|
|
717
|
-
return "";
|
|
718
|
-
}
|
|
719
|
-
const helpers = filteredFunctions.map((func) => {
|
|
720
|
-
const methodName = toCamelCase3(func.name);
|
|
721
|
-
const argsSignature = generateArgsSignature(func.args);
|
|
722
|
-
const clarityArgs = generateClarityArgs(func.args);
|
|
723
|
-
return `async ${methodName}(${argsSignature}options?: {
|
|
724
|
-
network?: 'mainnet' | 'testnet' | 'devnet';
|
|
725
|
-
senderAddress?: string;
|
|
726
|
-
}) {
|
|
727
|
-
return await fetchCallReadOnlyFunction({
|
|
728
|
-
contractAddress: '${contract.address}',
|
|
729
|
-
contractName: '${contract.contractName}',
|
|
730
|
-
functionName: '${func.name}',
|
|
731
|
-
functionArgs: [${clarityArgs}],
|
|
732
|
-
network: options?.network ?? inferNetworkFromAddress('${contract.address}') ?? 'mainnet',
|
|
733
|
-
senderAddress: options?.senderAddress || '${DEFAULT_SENDER_ADDRESS}'
|
|
734
|
-
});
|
|
735
|
-
}`;
|
|
736
|
-
});
|
|
737
|
-
return `read: {
|
|
738
|
-
${helpers.join(`,
|
|
739
|
-
|
|
740
|
-
`)}
|
|
741
|
-
}`;
|
|
742
|
-
}
|
|
743
|
-
function generateWriteHelpers(contract, options) {
|
|
744
|
-
const { abi } = contract;
|
|
745
|
-
const functions = abi.functions || [];
|
|
746
|
-
const envVarName = options.senderKeyEnv ?? "STX_SENDER_KEY";
|
|
747
|
-
const publicFunctions = functions.filter((f) => f.access === "public");
|
|
748
|
-
if (publicFunctions.length === 0) {
|
|
749
|
-
return "";
|
|
750
|
-
}
|
|
751
|
-
const filteredFunctions = applyFunctionFilters(publicFunctions, options);
|
|
752
|
-
if (filteredFunctions.length === 0) {
|
|
753
|
-
return "";
|
|
754
|
-
}
|
|
755
|
-
const helpers = filteredFunctions.map((func) => {
|
|
756
|
-
const methodName = toCamelCase3(func.name);
|
|
757
|
-
const argsSignature = generateArgsSignature(func.args);
|
|
758
|
-
const clarityArgs = generateClarityArgs(func.args);
|
|
759
|
-
return `async ${methodName}(${argsSignature}senderKey?: string, options?: {
|
|
760
|
-
network?: 'mainnet' | 'testnet' | 'devnet';
|
|
761
|
-
fee?: string | number | undefined;
|
|
762
|
-
nonce?: bigint;
|
|
763
|
-
anchorMode?: 1 | 2 | 3; // AnchorMode: OnChainOnly = 1, OffChainOnly = 2, Any = 3
|
|
764
|
-
postConditions?: PostCondition[];
|
|
765
|
-
validateWithAbi?: boolean;
|
|
766
|
-
}) {
|
|
767
|
-
const resolvedSenderKey = senderKey ?? process.env.${envVarName};
|
|
768
|
-
if (!resolvedSenderKey) {
|
|
769
|
-
throw new Error('senderKey required: pass as argument or set ${envVarName} env var');
|
|
770
|
-
}
|
|
771
|
-
const { network = 'mainnet', ...txOptions } = options ?? {};
|
|
772
|
-
|
|
773
|
-
return await makeContractCall({
|
|
774
|
-
contractAddress: '${contract.address}',
|
|
775
|
-
contractName: '${contract.contractName}',
|
|
776
|
-
functionName: '${func.name}',
|
|
777
|
-
functionArgs: [${clarityArgs}],
|
|
778
|
-
senderKey: resolvedSenderKey,
|
|
779
|
-
network,
|
|
780
|
-
validateWithAbi: true,
|
|
781
|
-
...txOptions
|
|
782
|
-
});
|
|
783
|
-
}`;
|
|
784
|
-
});
|
|
785
|
-
return `write: {
|
|
786
|
-
${helpers.join(`,
|
|
787
|
-
|
|
788
|
-
`)}
|
|
789
|
-
}`;
|
|
790
|
-
}
|
|
791
|
-
async function generateActionHelpers(contract, options) {
|
|
792
|
-
const readHelpers = generateReadHelpers(contract, options);
|
|
793
|
-
const writeHelpers = generateWriteHelpers(contract, options);
|
|
794
|
-
if (!readHelpers && !writeHelpers) {
|
|
795
|
-
return "";
|
|
796
|
-
}
|
|
797
|
-
const helpers = [readHelpers, writeHelpers].filter(Boolean);
|
|
798
|
-
return helpers.join(`,
|
|
799
|
-
|
|
800
|
-
`);
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
// src/plugins/actions/index.ts
|
|
804
|
-
var actions = (options = {}) => {
|
|
805
|
-
return {
|
|
806
|
-
name: "@secondlayer/cli/plugin-actions",
|
|
807
|
-
version: "1.0.0",
|
|
808
|
-
async generate(context) {
|
|
809
|
-
const { contracts } = context;
|
|
810
|
-
const filteredContracts = contracts.filter((contract) => matchesContractFilters(contract.name, options));
|
|
811
|
-
if (filteredContracts.length === 0) {
|
|
812
|
-
if (options.debug) {
|
|
813
|
-
context.logger.debug("Actions plugin: No contracts to process");
|
|
814
|
-
}
|
|
815
|
-
return;
|
|
816
|
-
}
|
|
817
|
-
if (options.debug) {
|
|
818
|
-
context.logger.debug(`Actions plugin: Generating read/write helpers for ${filteredContracts.length} contracts`);
|
|
819
|
-
}
|
|
820
|
-
const contractHelpers = new Map;
|
|
821
|
-
for (const contract of filteredContracts) {
|
|
822
|
-
const actionsCode = await generateActionHelpers(contract, options);
|
|
823
|
-
if (actionsCode) {
|
|
824
|
-
contractHelpers.set(contract.name, actionsCode);
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
if (contractHelpers.size > 0) {
|
|
828
|
-
const existingOutput = context.outputs.get("contracts");
|
|
829
|
-
if (existingOutput) {
|
|
830
|
-
let modifiedContent = addRequiredImports(existingOutput.content);
|
|
831
|
-
for (const [contractName, helpersCode] of contractHelpers) {
|
|
832
|
-
modifiedContent = injectHelpersIntoContract(modifiedContent, contractName, helpersCode);
|
|
833
|
-
}
|
|
834
|
-
context.outputs.set("contracts", {
|
|
835
|
-
...existingOutput,
|
|
836
|
-
content: modifiedContent
|
|
837
|
-
});
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
};
|
|
842
|
-
};
|
|
843
|
-
function addRequiredImports(content) {
|
|
844
|
-
const stacksImportRegex = /import\s+\{([^}]+)\}\s+from\s+['"]@secondlayer\/stacks['"];/;
|
|
845
|
-
const match = content.match(stacksImportRegex);
|
|
846
|
-
if (match) {
|
|
847
|
-
let updatedContent = content;
|
|
848
|
-
if (!updatedContent.includes("fetchCallReadOnlyFunction")) {
|
|
849
|
-
updatedContent = updatedContent.replace(stacksImportRegex, `${match[0]}
|
|
850
|
-
import { fetchCallReadOnlyFunction, makeContractCall } from '@secondlayer/stacks/clarity';`);
|
|
851
|
-
}
|
|
852
|
-
if (!updatedContent.includes("type PostCondition")) {
|
|
853
|
-
updatedContent = updatedContent.replace(stacksImportRegex, `${match[0]}
|
|
854
|
-
import type { PostCondition } from '@secondlayer/stacks';`);
|
|
855
|
-
}
|
|
856
|
-
return updatedContent;
|
|
857
|
-
}
|
|
858
|
-
return content;
|
|
859
|
-
}
|
|
860
|
-
function injectHelpersIntoContract(content, contractName, helpersCode) {
|
|
861
|
-
const contractPattern = new RegExp(`(export const ${contractName} = \\{[\\s\\S]*?)\\n\\} as const;`, "g");
|
|
862
|
-
return content.replace(contractPattern, (_, contractBody) => {
|
|
863
|
-
const cleanBody = contractBody.replace(/,\s*$/, "");
|
|
864
|
-
const indentedHelpersCode = helpersCode.split(`
|
|
865
|
-
`).map((line) => {
|
|
866
|
-
if (line.match(/^[a-zA-Z_$][a-zA-Z0-9_$]*\s*:/)) {
|
|
867
|
-
return ` ${line}`;
|
|
868
|
-
}
|
|
869
|
-
return line;
|
|
870
|
-
}).join(`
|
|
871
|
-
`);
|
|
872
|
-
return `${cleanBody},
|
|
873
|
-
|
|
874
|
-
${indentedHelpersCode}
|
|
875
|
-
} as const;`;
|
|
876
|
-
});
|
|
877
|
-
}
|
|
878
705
|
// src/plugins/react/generators/contract.ts
|
|
879
706
|
init_format();
|
|
880
707
|
|
|
881
708
|
// src/utils/case-conversion.ts
|
|
882
|
-
import { toCamelCase as
|
|
709
|
+
import { toCamelCase as toCamelCase3 } from "@secondlayer/stacks/clarity";
|
|
883
710
|
function capitalize(str) {
|
|
884
711
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
885
712
|
}
|
|
886
713
|
function toPascalCase(str) {
|
|
887
|
-
return capitalize(
|
|
714
|
+
return capitalize(toCamelCase3(str));
|
|
888
715
|
}
|
|
889
716
|
|
|
890
717
|
// src/utils/type-mapping.ts
|
|
@@ -898,23 +725,23 @@ import {
|
|
|
898
725
|
function generateHookArgsSignature(args) {
|
|
899
726
|
if (args.length === 0)
|
|
900
727
|
return "";
|
|
901
|
-
const argsList = args.map((arg) => `${
|
|
728
|
+
const argsList = args.map((arg) => `${toCamelCase3(arg.name)}: ${clarityTypeToTS(arg.type)}`).join(", ");
|
|
902
729
|
return `${argsList}`;
|
|
903
730
|
}
|
|
904
731
|
function generateArgsType(args) {
|
|
905
732
|
if (args.length === 0)
|
|
906
733
|
return "void";
|
|
907
|
-
const argsList = args.map((arg) => `${
|
|
734
|
+
const argsList = args.map((arg) => `${toCamelCase3(arg.name)}: ${clarityTypeToTS(arg.type)}`).join("; ");
|
|
908
735
|
return `{ ${argsList} }`;
|
|
909
736
|
}
|
|
910
737
|
function generateArgNames(args) {
|
|
911
738
|
if (args.length === 0)
|
|
912
739
|
return "";
|
|
913
|
-
return args.map((arg) =>
|
|
740
|
+
return args.map((arg) => toCamelCase3(arg.name)).join(", ");
|
|
914
741
|
}
|
|
915
742
|
function generateEnabledCondition(args) {
|
|
916
743
|
return args.map((arg) => {
|
|
917
|
-
const camelName =
|
|
744
|
+
const camelName = toCamelCase3(arg.name);
|
|
918
745
|
const type = clarityTypeToTS(arg.type);
|
|
919
746
|
if (type === "string")
|
|
920
747
|
return `!!${camelName}`;
|
|
@@ -926,7 +753,7 @@ function generateEnabledCondition(args) {
|
|
|
926
753
|
function generateObjectArgs(args) {
|
|
927
754
|
if (args.length === 0)
|
|
928
755
|
return "";
|
|
929
|
-
return args.map((arg) => `${arg.name}: ${
|
|
756
|
+
return args.map((arg) => `${arg.name}: ${toCamelCase3(arg.name)}`).join(", ");
|
|
930
757
|
}
|
|
931
758
|
|
|
932
759
|
// src/plugins/react/generators/contract.ts
|
|
@@ -935,8 +762,47 @@ async function generateContractHooks(contracts, excludeList = []) {
|
|
|
935
762
|
import { useCallback } from 'react'
|
|
936
763
|
import { useSecondLayerConfig } from './provider'
|
|
937
764
|
import { request } from '@secondlayer/stacks/connect'
|
|
938
|
-
import type { PostCondition } from '@secondlayer/stacks'
|
|
939
|
-
import { ${contracts.map((c) => c.name).join(", ")} } from './contracts'
|
|
765
|
+
import type { PostCondition } from '@secondlayer/stacks/postconditions'
|
|
766
|
+
import { ${contracts.map((c) => c.name).join(", ")} } from './contracts'
|
|
767
|
+
|
|
768
|
+
const API_URLS: Record<string, string> = {
|
|
769
|
+
mainnet: 'https://api.hiro.so',
|
|
770
|
+
testnet: 'https://api.testnet.hiro.so',
|
|
771
|
+
devnet: 'http://localhost:3999'
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
async function readContractCall(params: {
|
|
775
|
+
contractAddress: string;
|
|
776
|
+
contractName: string;
|
|
777
|
+
functionName: string;
|
|
778
|
+
functionArgs: any[];
|
|
779
|
+
outputType: any;
|
|
780
|
+
senderAddress: string;
|
|
781
|
+
network?: string;
|
|
782
|
+
apiUrl?: string;
|
|
783
|
+
}): Promise<any> {
|
|
784
|
+
const { serializeCV, deserializeCV, clarityValueToJS } = await import('@secondlayer/stacks/clarity')
|
|
785
|
+
// Untyped call: outputType is a runtime ABI value; relating the generic
|
|
786
|
+
// signature (even via a direct cast) explodes into deep instantiation on
|
|
787
|
+
// the AbiType union, so route the cast through unknown.
|
|
788
|
+
const toJS = clarityValueToJS as unknown as (type: any, cv: any) => any
|
|
789
|
+
const baseUrl = params.apiUrl || API_URLS[params.network || 'mainnet']
|
|
790
|
+
const response = await fetch(
|
|
791
|
+
\`\${baseUrl}/v2/contracts/call-read/\${params.contractAddress}/\${params.contractName}/\${params.functionName}\`,
|
|
792
|
+
{
|
|
793
|
+
method: 'POST',
|
|
794
|
+
headers: { 'Content-Type': 'application/json' },
|
|
795
|
+
body: JSON.stringify({
|
|
796
|
+
sender: params.senderAddress,
|
|
797
|
+
arguments: params.functionArgs.map((arg: any) => \`0x\${serializeCV(arg)}\`)
|
|
798
|
+
})
|
|
799
|
+
}
|
|
800
|
+
)
|
|
801
|
+
if (!response.ok) throw new Error(\`Read-only call failed: \${response.statusText}\`)
|
|
802
|
+
const data = await response.json()
|
|
803
|
+
if (!data.okay) throw new Error(data.cause ?? 'Read-only call failed')
|
|
804
|
+
return toJS(params.outputType, deserializeCV(data.result))
|
|
805
|
+
}`;
|
|
940
806
|
const header = `/**
|
|
941
807
|
* Generated contract-specific React hooks
|
|
942
808
|
* DO NOT EDIT MANUALLY
|
|
@@ -959,21 +825,21 @@ function generateContractHookMethods(contract, excludeList) {
|
|
|
959
825
|
const readOnlyFunctions = functions.filter((f) => f.access === "read-only");
|
|
960
826
|
const publicFunctions = functions.filter((f) => f.access === "public");
|
|
961
827
|
const readHooks = readOnlyFunctions.map((func) => {
|
|
962
|
-
const hookName = `use${capitalize(name)}${capitalize(
|
|
828
|
+
const hookName = `use${capitalize(name)}${capitalize(toCamelCase3(func.name))}`;
|
|
963
829
|
if (excludeList.includes(hookName)) {
|
|
964
830
|
return null;
|
|
965
831
|
}
|
|
966
832
|
return generateReadHook(func, name);
|
|
967
833
|
}).filter(Boolean);
|
|
968
834
|
const writeHooks = publicFunctions.map((func) => {
|
|
969
|
-
const hookName = `use${capitalize(name)}${capitalize(
|
|
835
|
+
const hookName = `use${capitalize(name)}${capitalize(toCamelCase3(func.name))}`;
|
|
970
836
|
if (excludeList.includes(hookName)) {
|
|
971
837
|
return null;
|
|
972
838
|
}
|
|
973
839
|
return generateWriteHook(func, name);
|
|
974
840
|
}).filter(Boolean);
|
|
975
841
|
const mapHooks = maps.map((map) => {
|
|
976
|
-
const hookName = `use${capitalize(name)}${capitalize(
|
|
842
|
+
const hookName = `use${capitalize(name)}${capitalize(toCamelCase3(map.name))}`;
|
|
977
843
|
if (excludeList.includes(hookName)) {
|
|
978
844
|
return null;
|
|
979
845
|
}
|
|
@@ -981,7 +847,7 @@ function generateContractHookMethods(contract, excludeList) {
|
|
|
981
847
|
}).filter(Boolean);
|
|
982
848
|
const dataVars = variables.filter((v) => v.access === "variable");
|
|
983
849
|
const varHooks = dataVars.map((variable) => {
|
|
984
|
-
const hookName = `use${capitalize(name)}${capitalize(
|
|
850
|
+
const hookName = `use${capitalize(name)}${capitalize(toCamelCase3(variable.name))}`;
|
|
985
851
|
if (excludeList.includes(hookName)) {
|
|
986
852
|
return null;
|
|
987
853
|
}
|
|
@@ -989,7 +855,7 @@ function generateContractHookMethods(contract, excludeList) {
|
|
|
989
855
|
}).filter(Boolean);
|
|
990
856
|
const constants = variables.filter((v) => v.access === "constant");
|
|
991
857
|
const constantHooks = constants.map((constant) => {
|
|
992
|
-
const hookName = `use${capitalize(name)}${capitalize(
|
|
858
|
+
const hookName = `use${capitalize(name)}${capitalize(toCamelCase3(constant.name))}`;
|
|
993
859
|
if (excludeList.includes(hookName)) {
|
|
994
860
|
return null;
|
|
995
861
|
}
|
|
@@ -1010,26 +876,33 @@ function generateContractHookMethods(contract, excludeList) {
|
|
|
1010
876
|
`);
|
|
1011
877
|
}
|
|
1012
878
|
function generateReadHook(func, contractName) {
|
|
1013
|
-
const hookName = `use${capitalize(contractName)}${capitalize(
|
|
879
|
+
const hookName = `use${capitalize(contractName)}${capitalize(toCamelCase3(func.name))}`;
|
|
1014
880
|
const argsSignature = generateHookArgsSignature(func.args);
|
|
1015
881
|
const enabledParam = func.args.length > 0 ? ", options?: { enabled?: boolean }" : "options?: { enabled?: boolean }";
|
|
1016
882
|
const returnType = clarityTypeToTS(func.outputs);
|
|
883
|
+
const callArgs = func.args.length > 0 ? `{ ${generateObjectArgs(func.args)} }` : "";
|
|
1017
884
|
return `export function ${hookName}(${argsSignature}${enabledParam}) {
|
|
1018
885
|
const config = useSecondLayerConfig()
|
|
1019
886
|
|
|
1020
887
|
return useQuery<${returnType}>({
|
|
1021
888
|
queryKey: ['${func.name}', ${contractName}.address, ${generateArgNames(func.args)}],
|
|
1022
|
-
queryFn: () =>
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
889
|
+
queryFn: async () => {
|
|
890
|
+
const call = ${contractName}.${toCamelCase3(func.name)}(${callArgs})
|
|
891
|
+
return await readContractCall({
|
|
892
|
+
...call,
|
|
893
|
+
outputType: ${JSON.stringify(func.outputs)},
|
|
894
|
+
network: config.network,
|
|
895
|
+
apiUrl: config.apiUrl,
|
|
896
|
+
senderAddress: config.senderAddress || '${DEFAULT_SENDER_ADDRESS}'
|
|
897
|
+
}) as ${returnType}
|
|
898
|
+
},
|
|
1026
899
|
${func.args.length > 0 ? `enabled: ${generateEnabledCondition(func.args)} && (options?.enabled ?? true),` : ""}
|
|
1027
900
|
...options
|
|
1028
901
|
})
|
|
1029
902
|
}`;
|
|
1030
903
|
}
|
|
1031
904
|
function generateWriteHook(func, contractName) {
|
|
1032
|
-
const hookName = `use${capitalize(contractName)}${capitalize(
|
|
905
|
+
const hookName = `use${capitalize(contractName)}${capitalize(toCamelCase3(func.name))}`;
|
|
1033
906
|
const argsType = generateArgsType(func.args);
|
|
1034
907
|
return `export function ${hookName}() {
|
|
1035
908
|
const config = useSecondLayerConfig()
|
|
@@ -1046,7 +919,7 @@ function generateWriteHook(func, contractName) {
|
|
|
1046
919
|
};
|
|
1047
920
|
}) => {
|
|
1048
921
|
const { args, options = {} } = params
|
|
1049
|
-
const contractCallData = ${contractName}.${
|
|
922
|
+
const contractCallData = ${contractName}.${toCamelCase3(func.name)}(args)
|
|
1050
923
|
const { contractAddress, contractName: name, functionName, functionArgs } = contractCallData
|
|
1051
924
|
const network = config.network || 'mainnet'
|
|
1052
925
|
const contract = \`\${contractAddress}.\${name}\`
|
|
@@ -1071,7 +944,7 @@ function generateWriteHook(func, contractName) {
|
|
|
1071
944
|
}
|
|
1072
945
|
})
|
|
1073
946
|
|
|
1074
|
-
const ${
|
|
947
|
+
const ${toCamelCase3(func.name)} = useCallback(async (
|
|
1075
948
|
args: ${argsType},
|
|
1076
949
|
options?: {
|
|
1077
950
|
postConditions?: PostCondition[];
|
|
@@ -1084,7 +957,7 @@ function generateWriteHook(func, contractName) {
|
|
|
1084
957
|
}, [mutation])
|
|
1085
958
|
|
|
1086
959
|
return {
|
|
1087
|
-
${
|
|
960
|
+
${toCamelCase3(func.name)},
|
|
1088
961
|
// Expose mutation state
|
|
1089
962
|
isPending: mutation.isPending,
|
|
1090
963
|
isError: mutation.isError,
|
|
@@ -1096,7 +969,7 @@ function generateWriteHook(func, contractName) {
|
|
|
1096
969
|
}`;
|
|
1097
970
|
}
|
|
1098
971
|
function generateMapHook(map, contractVarName, _address, _contractName) {
|
|
1099
|
-
const hookName = `use${capitalize(contractVarName)}${capitalize(
|
|
972
|
+
const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase3(map.name))}`;
|
|
1100
973
|
const keyType = clarityTypeToTS(map.key);
|
|
1101
974
|
const valueType = clarityTypeToTS(map.value);
|
|
1102
975
|
return `export function ${hookName}(key: ${keyType}, options?: { enabled?: boolean }) {
|
|
@@ -1105,14 +978,14 @@ function generateMapHook(map, contractVarName, _address, _contractName) {
|
|
|
1105
978
|
return useQuery<${valueType} | null>({
|
|
1106
979
|
queryKey: ['${contractVarName}', '${map.name}', 'map', key, config.network],
|
|
1107
980
|
queryFn: async () => {
|
|
1108
|
-
return ${contractVarName}.maps.${
|
|
981
|
+
return ${contractVarName}.maps.${toCamelCase3(map.name)}.get(key, { network: config.network })
|
|
1109
982
|
},
|
|
1110
983
|
enabled: options?.enabled ?? true
|
|
1111
984
|
})
|
|
1112
985
|
}`;
|
|
1113
986
|
}
|
|
1114
987
|
function generateVarHook(variable, contractVarName, _address, _contractName) {
|
|
1115
|
-
const hookName = `use${capitalize(contractVarName)}${capitalize(
|
|
988
|
+
const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase3(variable.name))}`;
|
|
1116
989
|
const valueType = clarityTypeToTS(variable.type);
|
|
1117
990
|
return `export function ${hookName}(options?: { enabled?: boolean }) {
|
|
1118
991
|
const config = useSecondLayerConfig()
|
|
@@ -1120,14 +993,14 @@ function generateVarHook(variable, contractVarName, _address, _contractName) {
|
|
|
1120
993
|
return useQuery<${valueType}>({
|
|
1121
994
|
queryKey: ['${contractVarName}', '${variable.name}', 'var', config.network],
|
|
1122
995
|
queryFn: async () => {
|
|
1123
|
-
return ${contractVarName}.vars.${
|
|
996
|
+
return ${contractVarName}.vars.${toCamelCase3(variable.name)}.get({ network: config.network })
|
|
1124
997
|
},
|
|
1125
998
|
enabled: options?.enabled ?? true
|
|
1126
999
|
})
|
|
1127
1000
|
}`;
|
|
1128
1001
|
}
|
|
1129
1002
|
function generateConstantHook(constant, contractVarName, _address, _contractName) {
|
|
1130
|
-
const hookName = `use${capitalize(contractVarName)}${capitalize(
|
|
1003
|
+
const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase3(constant.name))}`;
|
|
1131
1004
|
const valueType = clarityTypeToTS(constant.type);
|
|
1132
1005
|
return `export function ${hookName}(options?: { enabled?: boolean }) {
|
|
1133
1006
|
const config = useSecondLayerConfig()
|
|
@@ -1135,7 +1008,7 @@ function generateConstantHook(constant, contractVarName, _address, _contractName
|
|
|
1135
1008
|
return useQuery<${valueType}>({
|
|
1136
1009
|
queryKey: ['${contractVarName}', '${constant.name}', 'constant', config.network],
|
|
1137
1010
|
queryFn: async () => {
|
|
1138
|
-
return ${contractVarName}.constants.${
|
|
1011
|
+
return ${contractVarName}.constants.${toCamelCase3(constant.name)}.get({ network: config.network })
|
|
1139
1012
|
},
|
|
1140
1013
|
enabled: options?.enabled ?? true,
|
|
1141
1014
|
staleTime: Infinity // Constants never change
|
|
@@ -1386,9 +1259,10 @@ async function generateGenericHooks(excludeList = []) {
|
|
|
1386
1259
|
import { useState, useCallback } from 'react'
|
|
1387
1260
|
import { useSecondLayerConfig } from './provider'
|
|
1388
1261
|
import { connect, disconnect, isConnected, request } from '@secondlayer/stacks/connect'
|
|
1389
|
-
import { Cl
|
|
1390
|
-
import type { PostCondition } from '@secondlayer/stacks'
|
|
1262
|
+
import { Cl } from '@secondlayer/stacks/clarity'
|
|
1391
1263
|
import type { ExtractFunctionArgs, ExtractFunctionNames, AbiContract } from '@secondlayer/stacks/clarity'
|
|
1264
|
+
import type { PostCondition } from '@secondlayer/stacks/postconditions'
|
|
1265
|
+
import { validateStacksAddress } from '@secondlayer/stacks/utils'
|
|
1392
1266
|
|
|
1393
1267
|
const API_URLS: Record<string, string> = {
|
|
1394
1268
|
mainnet: 'https://api.hiro.so',
|
|
@@ -1396,6 +1270,34 @@ const API_URLS: Record<string, string> = {
|
|
|
1396
1270
|
devnet: 'http://localhost:3999'
|
|
1397
1271
|
}
|
|
1398
1272
|
|
|
1273
|
+
async function callReadOnly(params: {
|
|
1274
|
+
contractAddress: string;
|
|
1275
|
+
contractName: string;
|
|
1276
|
+
functionName: string;
|
|
1277
|
+
functionArgs: any[];
|
|
1278
|
+
senderAddress: string;
|
|
1279
|
+
network?: string;
|
|
1280
|
+
apiUrl?: string;
|
|
1281
|
+
}): Promise<any> {
|
|
1282
|
+
const { serializeCV, deserializeCV } = await import('@secondlayer/stacks/clarity')
|
|
1283
|
+
const baseUrl = params.apiUrl || API_URLS[params.network || 'mainnet']
|
|
1284
|
+
const response = await fetch(
|
|
1285
|
+
\`\${baseUrl}/v2/contracts/call-read/\${params.contractAddress}/\${params.contractName}/\${params.functionName}\`,
|
|
1286
|
+
{
|
|
1287
|
+
method: 'POST',
|
|
1288
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1289
|
+
body: JSON.stringify({
|
|
1290
|
+
sender: params.senderAddress,
|
|
1291
|
+
arguments: params.functionArgs.map((arg: any) => \`0x\${serializeCV(arg)}\`)
|
|
1292
|
+
})
|
|
1293
|
+
}
|
|
1294
|
+
)
|
|
1295
|
+
if (!response.ok) throw new Error(\`Read-only call failed: \${response.statusText}\`)
|
|
1296
|
+
const data = await response.json()
|
|
1297
|
+
if (!data.okay) throw new Error(data.cause ?? 'Read-only call failed')
|
|
1298
|
+
return deserializeCV(data.result)
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1399
1301
|
async function fetchTransaction({ txId, network, apiUrl }: { txId: string; network?: string; apiUrl?: string }): Promise<any> {
|
|
1400
1302
|
const baseUrl = apiUrl || API_URLS[network || 'mainnet']
|
|
1401
1303
|
const response = await fetch(\`\${baseUrl}/extended/v1/tx/\${txId}\`)
|
|
@@ -1504,9 +1406,9 @@ function generateGenericHook(hookName) {
|
|
|
1504
1406
|
const queryClient = useQueryClient()
|
|
1505
1407
|
|
|
1506
1408
|
const mutation = useMutation({
|
|
1507
|
-
mutationFn: async (
|
|
1409
|
+
mutationFn: async () => {
|
|
1508
1410
|
// SIP-030 connect
|
|
1509
|
-
return await connect(
|
|
1411
|
+
return await connect()
|
|
1510
1412
|
},
|
|
1511
1413
|
onSuccess: () => {
|
|
1512
1414
|
// Invalidate account queries to refetch connection state
|
|
@@ -1519,11 +1421,11 @@ function generateGenericHook(hookName) {
|
|
|
1519
1421
|
|
|
1520
1422
|
return {
|
|
1521
1423
|
// Custom connect function that works without arguments
|
|
1522
|
-
connect: (
|
|
1523
|
-
return mutation.mutate(
|
|
1424
|
+
connect: () => {
|
|
1425
|
+
return mutation.mutate(undefined)
|
|
1524
1426
|
},
|
|
1525
|
-
connectAsync: async (
|
|
1526
|
-
return mutation.mutateAsync(
|
|
1427
|
+
connectAsync: async () => {
|
|
1428
|
+
return mutation.mutateAsync(undefined)
|
|
1527
1429
|
},
|
|
1528
1430
|
// Expose all the mutation state
|
|
1529
1431
|
isPending: mutation.isPending,
|
|
@@ -1618,31 +1520,27 @@ function generateGenericHook(hookName) {
|
|
|
1618
1520
|
return useQuery<TResult>({
|
|
1619
1521
|
queryKey: ['read-contract', params.contractAddress, params.contractName, params.functionName, params.args, params.network || config.network],
|
|
1620
1522
|
queryFn: async () => {
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
// For now, we'll need to handle the args conversion here
|
|
1624
|
-
// In the future, we could integrate with the contract interface for automatic conversion
|
|
1523
|
+
// Args must already be ClarityValues (build them with Cl.* or a generated
|
|
1524
|
+
// contract method's descriptor). Positional array, ABI declaration order.
|
|
1625
1525
|
let functionArgs: any[] = []
|
|
1626
|
-
|
|
1526
|
+
|
|
1627
1527
|
if (params.args) {
|
|
1628
|
-
// This is a simplified conversion - in practice, we'd need the ABI to do proper conversion
|
|
1629
|
-
// For now, we'll assume the args are already in the correct format or simple types
|
|
1630
1528
|
if (Array.isArray(params.args)) {
|
|
1631
1529
|
functionArgs = params.args
|
|
1632
1530
|
} else if (typeof params.args === 'object') {
|
|
1633
|
-
// Convert object args to array (this is a basic implementation)
|
|
1634
1531
|
functionArgs = Object.values(params.args)
|
|
1635
1532
|
} else {
|
|
1636
1533
|
functionArgs = [params.args]
|
|
1637
1534
|
}
|
|
1638
1535
|
}
|
|
1639
|
-
|
|
1640
|
-
return await
|
|
1536
|
+
|
|
1537
|
+
return await callReadOnly({
|
|
1641
1538
|
contractAddress: params.contractAddress,
|
|
1642
1539
|
contractName: params.contractName,
|
|
1643
1540
|
functionName: params.functionName,
|
|
1644
1541
|
functionArgs,
|
|
1645
1542
|
network: params.network || config.network || 'mainnet',
|
|
1543
|
+
apiUrl: config.apiUrl,
|
|
1646
1544
|
senderAddress: config.senderAddress || '${DEFAULT_SENDER_ADDRESS}'
|
|
1647
1545
|
}) as TResult
|
|
1648
1546
|
},
|
|
@@ -1703,11 +1601,13 @@ function generateGenericHook(hookName) {
|
|
|
1703
1601
|
apiUrl: config.apiUrl
|
|
1704
1602
|
}),
|
|
1705
1603
|
enabled: !!txId,
|
|
1706
|
-
refetchInterval: (
|
|
1604
|
+
refetchInterval: (queryOrData: any) => {
|
|
1605
|
+
// react-query v5 passes the query object, v4 passed the data — support both
|
|
1606
|
+
const tx = queryOrData?.state?.data ?? queryOrData
|
|
1707
1607
|
// Stop polling when transaction is complete
|
|
1708
|
-
if (
|
|
1709
|
-
|
|
1710
|
-
|
|
1608
|
+
if (tx?.tx_status === 'success' ||
|
|
1609
|
+
tx?.tx_status === 'abort_by_response' ||
|
|
1610
|
+
tx?.tx_status === 'abort_by_post_condition') {
|
|
1711
1611
|
return false
|
|
1712
1612
|
}
|
|
1713
1613
|
return 2000 // Poll every 2 seconds
|
|
@@ -1988,8 +1888,17 @@ var react = (options = {}) => {
|
|
|
1988
1888
|
import {
|
|
1989
1889
|
isAbiTuple
|
|
1990
1890
|
} from "@secondlayer/stacks/clarity";
|
|
1891
|
+
|
|
1892
|
+
// src/utils/generator-helpers.ts
|
|
1893
|
+
import {
|
|
1894
|
+
generateArgsSignature,
|
|
1895
|
+
generateClarityArgs,
|
|
1896
|
+
generateMapKeyConversion
|
|
1897
|
+
} from "@secondlayer/scaffold";
|
|
1898
|
+
|
|
1899
|
+
// src/plugins/testing/generators.ts
|
|
1991
1900
|
function generatePublicFunction(func, contractId) {
|
|
1992
|
-
const methodName =
|
|
1901
|
+
const methodName = toCamelCase3(func.name);
|
|
1993
1902
|
const argsSignature = generateArgsSignature(func.args);
|
|
1994
1903
|
const clarityArgs = generateClarityArgs(func.args);
|
|
1995
1904
|
return `${methodName}: (${argsSignature}caller: string) => {
|
|
@@ -2003,7 +1912,7 @@ function generatePublicFunction(func, contractId) {
|
|
|
2003
1912
|
}`;
|
|
2004
1913
|
}
|
|
2005
1914
|
function generateReadOnlyFunction(func, contractId) {
|
|
2006
|
-
const methodName =
|
|
1915
|
+
const methodName = toCamelCase3(func.name);
|
|
2007
1916
|
const argsSignature = generateArgsSignature(func.args);
|
|
2008
1917
|
const clarityArgs = generateClarityArgs(func.args);
|
|
2009
1918
|
const hasArgs = func.args.length > 0;
|
|
@@ -2018,7 +1927,7 @@ function generateReadOnlyFunction(func, contractId) {
|
|
|
2018
1927
|
}`;
|
|
2019
1928
|
}
|
|
2020
1929
|
function generatePrivateFunction(func, contractId) {
|
|
2021
|
-
const methodName =
|
|
1930
|
+
const methodName = toCamelCase3(func.name);
|
|
2022
1931
|
const argsSignature = generateArgsSignature(func.args);
|
|
2023
1932
|
const clarityArgs = generateClarityArgs(func.args);
|
|
2024
1933
|
return `${methodName}: (${argsSignature}caller: string) => {
|
|
@@ -2032,20 +1941,20 @@ function generatePrivateFunction(func, contractId) {
|
|
|
2032
1941
|
}`;
|
|
2033
1942
|
}
|
|
2034
1943
|
function generateDataVarHelper(variable, contractId) {
|
|
2035
|
-
const methodName =
|
|
1944
|
+
const methodName = toCamelCase3(variable.name);
|
|
2036
1945
|
return `${methodName}: () => {
|
|
2037
1946
|
return simnet.getDataVar('${contractId}', '${variable.name}');
|
|
2038
1947
|
}`;
|
|
2039
1948
|
}
|
|
2040
1949
|
function getMapKeyType(keyType) {
|
|
2041
1950
|
if (isAbiTuple(keyType)) {
|
|
2042
|
-
const fields = keyType.tuple.map((field) => `${
|
|
1951
|
+
const fields = keyType.tuple.map((field) => `${toCamelCase3(field.name)}: ${getTypeForArg({ type: field.type })}`).join("; ");
|
|
2043
1952
|
return `{ ${fields} }`;
|
|
2044
1953
|
}
|
|
2045
1954
|
return getTypeForArg({ type: keyType });
|
|
2046
1955
|
}
|
|
2047
1956
|
function generateMapEntryHelper(map, contractId) {
|
|
2048
|
-
const methodName =
|
|
1957
|
+
const methodName = toCamelCase3(map.name);
|
|
2049
1958
|
const keyType = getMapKeyType(map.key);
|
|
2050
1959
|
const keyConversion = generateMapKeyConversion(map.key);
|
|
2051
1960
|
return `${methodName}: (key: ${keyType}) => {
|
|
@@ -2115,7 +2024,7 @@ function generateContractHelper(contract, options) {
|
|
|
2115
2024
|
}
|
|
2116
2025
|
function generateGetContracts(contracts) {
|
|
2117
2026
|
const contractEntries = contracts.map((contract) => {
|
|
2118
|
-
const camelName =
|
|
2027
|
+
const camelName = toCamelCase3(contract.name);
|
|
2119
2028
|
const pascalName = toPascalCase(contract.name);
|
|
2120
2029
|
return `${camelName}: get${pascalName}(simnet)`;
|
|
2121
2030
|
}).join(`,
|
|
@@ -2231,9 +2140,8 @@ export {
|
|
|
2231
2140
|
filterByOptions,
|
|
2232
2141
|
createPlugin,
|
|
2233
2142
|
clarinet,
|
|
2234
|
-
actions,
|
|
2235
2143
|
PluginManager
|
|
2236
2144
|
};
|
|
2237
2145
|
|
|
2238
|
-
//# debugId=
|
|
2146
|
+
//# debugId=15CE4A0BEE59E1A664756E2164756E21
|
|
2239
2147
|
//# sourceMappingURL=index.js.map
|