@secondlayer/cli 1.1.0 → 1.2.1

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