@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.
@@ -576,22 +576,38 @@ function matchesContractFilters(name, options) {
576
576
  function sanitizeContractName(name) {
577
577
  return toCamelCase2(name);
578
578
  }
579
- async function isUserDefinedContract(contractId, manifestPath) {
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
- userContracts.add(match[1]);
587
+ projectContracts.add(match[1]);
589
588
  match = contractSectionRegex.exec(tomlContent);
590
589
  }
591
- if (userContracts.has(contractName)) {
592
- return true;
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
- } catch {}
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 false;
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 false;
625
+ return "system";
610
626
  }
611
- return true;
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
- if (!await isUserDefinedContract(contractId, manifestPath)) {
644
+ const kind = classifyContract(contractId, manifest);
645
+ if (kind === "system" || kind === "requirement" && !includeRequirements) {
627
646
  if (options.debug) {
628
- console.log(`\uD83D\uDEAB Skipping system contract: ${contractId}`);
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 toCamelCase4 } from "@secondlayer/stacks/clarity";
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(toCamelCase4(str));
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) => `${toCamelCase4(arg.name)}: ${clarityTypeToTS(arg.type)}`).join(", ");
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) => `${toCamelCase4(arg.name)}: ${clarityTypeToTS(arg.type)}`).join("; ");
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) => toCamelCase4(arg.name)).join(", ");
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 = toCamelCase4(arg.name);
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}: ${toCamelCase4(arg.name)}`).join(", ");
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(toCamelCase4(func.name))}`;
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(toCamelCase4(func.name))}`;
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(toCamelCase4(map.name))}`;
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(toCamelCase4(variable.name))}`;
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(toCamelCase4(constant.name))}`;
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(toCamelCase4(func.name))}`;
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: () => ${contractName}.read.${toCamelCase4(func.name)}(${generateArgNames(func.args) ? `{ ${generateObjectArgs(func.args)} }, ` : ""}{
1023
- network: config.network,
1024
- senderAddress: config.senderAddress || '${DEFAULT_SENDER_ADDRESS}'
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(toCamelCase4(func.name))}`;
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}.${toCamelCase4(func.name)}(args)
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 ${toCamelCase4(func.name)} = useCallback(async (
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
- ${toCamelCase4(func.name)},
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(toCamelCase4(map.name))}`;
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.${toCamelCase4(map.name)}.get(key, { network: config.network })
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(toCamelCase4(variable.name))}`;
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.${toCamelCase4(variable.name)}.get({ network: config.network })
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(toCamelCase4(constant.name))}`;
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.${toCamelCase4(constant.name)}.get({ network: config.network })
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, validateStacksAddress } from '@secondlayer/stacks'
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 (options: { forceWalletSelect?: boolean } = {}) => {
1409
+ mutationFn: async () => {
1508
1410
  // SIP-030 connect
1509
- return await connect(options)
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: (options?: { forceWalletSelect?: boolean }) => {
1523
- return mutation.mutate(options || {})
1424
+ connect: () => {
1425
+ return mutation.mutate(undefined)
1524
1426
  },
1525
- connectAsync: async (options?: { forceWalletSelect?: boolean }) => {
1526
- return mutation.mutateAsync(options || {})
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
- const { fetchCallReadOnlyFunction } = await import('@secondlayer/stacks/clarity')
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 fetchCallReadOnlyFunction({
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: (data) => {
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 (data?.tx_status === 'success' ||
1709
- data?.tx_status === 'abort_by_response' ||
1710
- data?.tx_status === 'abort_by_post_condition') {
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 = toCamelCase4(func.name);
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 = toCamelCase4(func.name);
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 = toCamelCase4(func.name);
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 = toCamelCase4(variable.name);
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) => `${toCamelCase4(field.name)}: ${getTypeForArg({ type: field.type })}`).join("; ");
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 = toCamelCase4(map.name);
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 = toCamelCase4(contract.name);
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=B22EAFB44071D00264756E2164756E21
2146
+ //# debugId=15CE4A0BEE59E1A664756E2164756E21
2239
2147
  //# sourceMappingURL=index.js.map