@secondlayer/cli 1.7.0 → 1.9.0

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
@@ -74,6 +74,14 @@ import { isValidAddress as _validateStacksAddress } from "@secondlayer/stacks";
74
74
  import { getErrorMessage } from "@secondlayer/shared";
75
75
  import { toCamelCase } from "@secondlayer/stacks/clarity";
76
76
 
77
+ // src/types/plugin.ts
78
+ function isClarinetContract(c) {
79
+ return "_clarinetSource" in c && c._clarinetSource === true;
80
+ }
81
+ function isDirectFileContract(c) {
82
+ return "_directFile" in c && c._directFile === true;
83
+ }
84
+
77
85
  // src/utils/contract-id.ts
78
86
  function parseContractId(contractId) {
79
87
  const dotIndex = contractId.indexOf(".");
@@ -86,14 +94,6 @@ function parseContractId(contractId) {
86
94
  };
87
95
  }
88
96
 
89
- // src/types/plugin.ts
90
- function isClarinetContract(c) {
91
- return "_clarinetSource" in c && c._clarinetSource === true;
92
- }
93
- function isDirectFileContract(c) {
94
- return "_directFile" in c && c._directFile === true;
95
- }
96
-
97
97
  // src/core/plugin-manager.ts
98
98
  var validateStacksAddress = _validateStacksAddress;
99
99
 
@@ -361,96 +361,20 @@ import { initSimnet } from "@hirosystems/clarinet-sdk";
361
361
  import { toCamelCase as toCamelCase6 } from "@secondlayer/stacks/clarity";
362
362
 
363
363
  // src/generators/contract.ts
364
- init_format();
365
364
  import {
366
365
  toCamelCase as toCamelCase5
367
366
  } from "@secondlayer/stacks/clarity";
368
367
 
369
- // src/utils/type-mapping.ts
368
+ // src/utils/clarity-conversion.ts
370
369
  import {
371
- toCamelCase as toCamelCase2,
370
+ isAbiBuffer,
372
371
  isAbiList,
373
- isAbiTuple,
374
372
  isAbiOptional,
375
373
  isAbiResponse,
376
- isAbiBuffer,
377
374
  isAbiStringAscii,
378
- isAbiStringUtf8
379
- } from "@secondlayer/stacks/clarity";
380
- function clarityTypeToTS(type) {
381
- if (typeof type === "string") {
382
- switch (type) {
383
- case "uint128":
384
- case "int128":
385
- return "bigint";
386
- case "bool":
387
- return "boolean";
388
- case "principal":
389
- case "trait_reference":
390
- return "string";
391
- default: {
392
- const typeStr = type;
393
- if (typeStr === "none") {
394
- return "null";
395
- }
396
- if (typeStr.includes("string") || typeStr.includes("ascii") || typeStr.includes("utf8")) {
397
- return "string";
398
- }
399
- if (typeStr.includes("buff")) {
400
- return "Uint8Array | string | { type: 'ascii' | 'utf8' | 'hex'; value: string }";
401
- }
402
- if (typeStr.includes("uint") || typeStr.includes("int")) {
403
- return "bigint";
404
- }
405
- return "any";
406
- }
407
- }
408
- }
409
- if (isAbiBuffer(type)) {
410
- return "Uint8Array | string | { type: 'ascii' | 'utf8' | 'hex'; value: string }";
411
- }
412
- if (isAbiStringAscii(type) || isAbiStringUtf8(type)) {
413
- return "string";
414
- }
415
- if (isAbiOptional(type)) {
416
- const innerType = clarityTypeToTS(type.optional);
417
- if (innerType.includes(" | ") && !innerType.startsWith("(")) {
418
- return `(${innerType}) | null`;
419
- }
420
- return `${innerType} | null`;
421
- }
422
- if (isAbiList(type)) {
423
- const innerType = clarityTypeToTS(type.list.type);
424
- if (innerType.includes(" | ") && !innerType.startsWith("(")) {
425
- return `(${innerType})[]`;
426
- }
427
- return `${innerType}[]`;
428
- }
429
- if (isAbiTuple(type)) {
430
- const fields = type.tuple.map((field) => `${toCamelCase2(field.name)}: ${clarityTypeToTS(field.type)}`).join("; ");
431
- return `{ ${fields} }`;
432
- }
433
- if (isAbiResponse(type)) {
434
- const okType = clarityTypeToTS(type.response.ok);
435
- const errType = clarityTypeToTS(type.response.error);
436
- return `{ ok: ${okType} } | { err: ${errType} }`;
437
- }
438
- return "any";
439
- }
440
- function getTypeForArg(arg) {
441
- return clarityTypeToTS(arg.type);
442
- }
443
-
444
- // src/utils/clarity-conversion.ts
445
- import {
446
- toCamelCase as toCamelCase3,
447
- isAbiStringAscii as isAbiStringAscii2,
448
- isAbiStringUtf8 as isAbiStringUtf82,
449
- isAbiBuffer as isAbiBuffer2,
450
- isAbiOptional as isAbiOptional2,
451
- isAbiList as isAbiList2,
452
- isAbiTuple as isAbiTuple2,
453
- isAbiResponse as isAbiResponse2
375
+ isAbiStringUtf8,
376
+ isAbiTuple,
377
+ toCamelCase as toCamelCase2
454
378
  } from "@secondlayer/stacks/clarity";
455
379
  function generateClarityConversion(argName, argType) {
456
380
  const type = argType.type;
@@ -481,13 +405,13 @@ function generateClarityConversion(argName, argType) {
481
405
  return `${argName}`;
482
406
  }
483
407
  }
484
- if (isAbiStringAscii2(type)) {
408
+ if (isAbiStringAscii(type)) {
485
409
  return `Cl.stringAscii(${argName})`;
486
410
  }
487
- if (isAbiStringUtf82(type)) {
411
+ if (isAbiStringUtf8(type)) {
488
412
  return `Cl.stringUtf8(${argName})`;
489
413
  }
490
- if (isAbiBuffer2(type)) {
414
+ if (isAbiBuffer(type)) {
491
415
  return `(() => {
492
416
  const value = ${argName};
493
417
  if (value instanceof Uint8Array) {
@@ -518,13 +442,13 @@ function generateClarityConversion(argName, argType) {
518
442
  throw new Error(\`Invalid buffer value: \${value}\`);
519
443
  })()`;
520
444
  }
521
- if (isAbiOptional2(type)) {
445
+ if (isAbiOptional(type)) {
522
446
  const innerConversion = generateClarityConversion(argName, {
523
447
  type: type.optional
524
448
  });
525
449
  return `${argName} !== null ? Cl.some(${innerConversion.replace(argName, `${argName}`)}) : Cl.none()`;
526
450
  }
527
- if (isAbiList2(type)) {
451
+ if (isAbiList(type)) {
528
452
  const innerConversion = generateClarityConversion("item", {
529
453
  type: type.list.type
530
454
  });
@@ -537,11 +461,11 @@ function generateClarityConversion(argName, argType) {
537
461
  return Cl.list(listValue.map(item => ${innerConversion}));
538
462
  })()`;
539
463
  }
540
- if (isAbiTuple2(type)) {
464
+ if (isAbiTuple(type)) {
541
465
  const requiredFields = type.tuple.map((f) => f.name);
542
466
  const fieldNames = JSON.stringify(requiredFields);
543
467
  const fields = type.tuple.map((field) => {
544
- const camelFieldName = toCamelCase3(field.name);
468
+ const camelFieldName = toCamelCase2(field.name);
545
469
  const fieldConversion = generateClarityConversion(`tupleValue.${camelFieldName}`, { type: field.type });
546
470
  return `"${field.name}": ${fieldConversion}`;
547
471
  }).join(", ");
@@ -557,7 +481,7 @@ function generateClarityConversion(argName, argType) {
557
481
  return Cl.tuple({ ${fields} });
558
482
  })()`;
559
483
  }
560
- if (isAbiResponse2(type)) {
484
+ if (isAbiResponse(type)) {
561
485
  const okConversion = generateClarityConversion(`responseValue.ok`, {
562
486
  type: type.response.ok
563
487
  });
@@ -580,8 +504,91 @@ function generateClarityConversion(argName, argType) {
580
504
  return `${argName}`;
581
505
  }
582
506
 
507
+ // src/generators/contract.ts
508
+ init_format();
509
+
510
+ // src/utils/generator-helpers.ts
511
+ import {
512
+ isAbiTuple as isAbiTuple3,
513
+ toCamelCase as toCamelCase4
514
+ } from "@secondlayer/stacks/clarity";
515
+
516
+ // src/utils/type-mapping.ts
517
+ import {
518
+ isAbiBuffer as isAbiBuffer2,
519
+ isAbiList as isAbiList2,
520
+ isAbiOptional as isAbiOptional2,
521
+ isAbiResponse as isAbiResponse2,
522
+ isAbiStringAscii as isAbiStringAscii2,
523
+ isAbiStringUtf8 as isAbiStringUtf82,
524
+ isAbiTuple as isAbiTuple2,
525
+ toCamelCase as toCamelCase3
526
+ } from "@secondlayer/stacks/clarity";
527
+ function clarityTypeToTS(type) {
528
+ if (typeof type === "string") {
529
+ switch (type) {
530
+ case "uint128":
531
+ case "int128":
532
+ return "bigint";
533
+ case "bool":
534
+ return "boolean";
535
+ case "principal":
536
+ case "trait_reference":
537
+ return "string";
538
+ default: {
539
+ const typeStr = type;
540
+ if (typeStr === "none") {
541
+ return "null";
542
+ }
543
+ if (typeStr.includes("string") || typeStr.includes("ascii") || typeStr.includes("utf8")) {
544
+ return "string";
545
+ }
546
+ if (typeStr.includes("buff")) {
547
+ return "Uint8Array | string | { type: 'ascii' | 'utf8' | 'hex'; value: string }";
548
+ }
549
+ if (typeStr.includes("uint") || typeStr.includes("int")) {
550
+ return "bigint";
551
+ }
552
+ return "any";
553
+ }
554
+ }
555
+ }
556
+ if (isAbiBuffer2(type)) {
557
+ return "Uint8Array | string | { type: 'ascii' | 'utf8' | 'hex'; value: string }";
558
+ }
559
+ if (isAbiStringAscii2(type) || isAbiStringUtf82(type)) {
560
+ return "string";
561
+ }
562
+ if (isAbiOptional2(type)) {
563
+ const innerType = clarityTypeToTS(type.optional);
564
+ if (innerType.includes(" | ") && !innerType.startsWith("(")) {
565
+ return `(${innerType}) | null`;
566
+ }
567
+ return `${innerType} | null`;
568
+ }
569
+ if (isAbiList2(type)) {
570
+ const innerType = clarityTypeToTS(type.list.type);
571
+ if (innerType.includes(" | ") && !innerType.startsWith("(")) {
572
+ return `(${innerType})[]`;
573
+ }
574
+ return `${innerType}[]`;
575
+ }
576
+ if (isAbiTuple2(type)) {
577
+ const fields = type.tuple.map((field) => `${toCamelCase3(field.name)}: ${clarityTypeToTS(field.type)}`).join("; ");
578
+ return `{ ${fields} }`;
579
+ }
580
+ if (isAbiResponse2(type)) {
581
+ const okType = clarityTypeToTS(type.response.ok);
582
+ const errType = clarityTypeToTS(type.response.error);
583
+ return `{ ok: ${okType} } | { err: ${errType} }`;
584
+ }
585
+ return "any";
586
+ }
587
+ function getTypeForArg(arg) {
588
+ return clarityTypeToTS(arg.type);
589
+ }
590
+
583
591
  // src/utils/generator-helpers.ts
584
- import { toCamelCase as toCamelCase4, isAbiTuple as isAbiTuple3 } from "@secondlayer/stacks/clarity";
585
592
  function generateArgsSignature(args) {
586
593
  if (args.length === 0)
587
594
  return "";
@@ -1366,100 +1373,266 @@ ${indentedHelpersCode}
1366
1373
  } as const;`;
1367
1374
  });
1368
1375
  }
1369
- // src/plugins/react/provider/index.ts
1376
+ // src/plugins/react/generators/contract.ts
1370
1377
  init_format();
1371
- async function generateProvider() {
1372
- const code = `/**
1373
- * Generated SecondLayer React Provider
1374
- * DO NOT EDIT MANUALLY
1375
- */
1376
-
1377
- import React, { createContext, useContext } from 'react'
1378
-
1379
- /**
1380
- * SecondLayer configuration interface
1381
- */
1382
- export interface SecondLayerReactConfig {
1383
- /**
1384
- * Network to use for API calls
1385
- */
1386
- network: 'mainnet' | 'testnet' | 'devnet'
1387
-
1388
- /**
1389
- * API key for Stacks API (optional)
1390
- */
1391
- apiKey?: string
1392
-
1393
- /**
1394
- * Base URL for Stacks API (optional override)
1395
- */
1396
- apiUrl?: string
1397
1378
 
1398
- /**
1399
- * Default sender address for read-only calls
1400
- */
1401
- senderAddress?: string
1379
+ // src/utils/case-conversion.ts
1380
+ import { toCamelCase as toCamelCase8 } from "@secondlayer/stacks/clarity";
1381
+ function capitalize(str) {
1382
+ return str.charAt(0).toUpperCase() + str.slice(1);
1402
1383
  }
1403
-
1404
- /**
1405
- * Provider component props
1406
- */
1407
- export interface SecondLayerProviderProps {
1408
- children: React.ReactNode
1409
- config: SecondLayerReactConfig
1384
+ function toPascalCase(str) {
1385
+ return capitalize(toCamelCase8(str));
1410
1386
  }
1411
1387
 
1412
- /**
1413
- * React context for SecondLayer configuration
1414
- */
1415
- const SecondLayerContext = createContext<SecondLayerReactConfig | undefined>(undefined)
1416
- SecondLayerContext.displayName = 'SecondLayerContext'
1417
-
1418
- /**
1419
- * Create a SecondLayer React configuration with defaults
1420
- */
1421
- export function createSecondLayerConfig(config: SecondLayerReactConfig): SecondLayerReactConfig {
1422
- return {
1423
- network: config.network,
1424
- apiKey: config.apiKey,
1425
- apiUrl: config.apiUrl,
1426
- senderAddress: config.senderAddress || 'SP000000000000000000002Q6VF78'
1427
- }
1388
+ // src/plugins/react/generators/utils.ts
1389
+ function generateHookArgsSignature(args) {
1390
+ if (args.length === 0)
1391
+ return "";
1392
+ const argsList = args.map((arg) => `${toCamelCase8(arg.name)}: ${clarityTypeToTS(arg.type)}`).join(", ");
1393
+ return `${argsList}`;
1428
1394
  }
1429
-
1430
- /**
1431
- * Provider component that makes SecondLayer configuration available to hooks
1432
- */
1433
- export function SecondLayerProvider({ children, config }: SecondLayerProviderProps) {
1434
- const resolvedConfig = createSecondLayerConfig(config)
1435
-
1436
- return (
1437
- <SecondLayerContext.Provider value={resolvedConfig}>
1438
- {children}
1439
- </SecondLayerContext.Provider>
1440
- )
1395
+ function generateArgsType(args) {
1396
+ if (args.length === 0)
1397
+ return "void";
1398
+ const argsList = args.map((arg) => `${toCamelCase8(arg.name)}: ${clarityTypeToTS(arg.type)}`).join("; ");
1399
+ return `{ ${argsList} }`;
1400
+ }
1401
+ function generateArgNames(args) {
1402
+ if (args.length === 0)
1403
+ return "";
1404
+ return args.map((arg) => toCamelCase8(arg.name)).join(", ");
1405
+ }
1406
+ function generateEnabledCondition(args) {
1407
+ return args.map((arg) => {
1408
+ const camelName = toCamelCase8(arg.name);
1409
+ const type = clarityTypeToTS(arg.type);
1410
+ if (type === "string")
1411
+ return `!!${camelName}`;
1412
+ if (type === "bigint")
1413
+ return `${camelName} !== undefined`;
1414
+ return `${camelName} !== undefined`;
1415
+ }).join(" && ");
1416
+ }
1417
+ function generateObjectArgs(args) {
1418
+ if (args.length === 0)
1419
+ return "";
1420
+ return args.map((arg) => `${arg.name}: ${toCamelCase8(arg.name)}`).join(", ");
1441
1421
  }
1442
1422
 
1443
- /**
1444
- * Hook to access the SecondLayer configuration
1445
- */
1446
- export function useSecondLayerConfig(): SecondLayerReactConfig {
1447
- const context = useContext(SecondLayerContext)
1423
+ // src/plugins/react/generators/contract.ts
1424
+ async function generateContractHooks(contracts, excludeList = []) {
1425
+ const imports = `import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
1426
+ import { useCallback } from 'react'
1427
+ import { useSecondLayerConfig } from './provider'
1428
+ import { request } from '@secondlayer/stacks/connect'
1429
+ import type { PostCondition } from '@secondlayer/stacks'
1430
+ import { ${contracts.map((c) => c.name).join(", ")} } from './contracts'`;
1431
+ const header = `/**
1432
+ * Generated contract-specific React hooks
1433
+ * DO NOT EDIT MANUALLY
1434
+ */`;
1435
+ const hooksCode = contracts.map((contract) => generateContractHookMethods(contract, excludeList)).filter(Boolean).join(`
1448
1436
 
1449
- if (context === undefined) {
1450
- throw new Error(
1451
- 'useSecondLayerConfig must be used within a SecondLayerProvider. ' +
1452
- 'Make sure to wrap your app with <SecondLayerProvider config={{...}}>'
1453
- )
1437
+ `);
1438
+ const code = `${imports}
1439
+
1440
+ ${header}
1441
+
1442
+ ${hooksCode}`;
1443
+ return formatCode(code);
1444
+ }
1445
+ function generateContractHookMethods(contract, excludeList) {
1446
+ const { abi, name, address, contractName } = contract;
1447
+ const functions = abi.functions || [];
1448
+ const maps = abi.maps || [];
1449
+ const variables = abi.variables || [];
1450
+ const readOnlyFunctions = functions.filter((f) => f.access === "read-only");
1451
+ const publicFunctions = functions.filter((f) => f.access === "public");
1452
+ const readHooks = readOnlyFunctions.map((func) => {
1453
+ const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(func.name))}`;
1454
+ if (excludeList.includes(hookName)) {
1455
+ return null;
1456
+ }
1457
+ return generateReadHook(func, name);
1458
+ }).filter(Boolean);
1459
+ const writeHooks = publicFunctions.map((func) => {
1460
+ const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(func.name))}`;
1461
+ if (excludeList.includes(hookName)) {
1462
+ return null;
1463
+ }
1464
+ return generateWriteHook(func, name);
1465
+ }).filter(Boolean);
1466
+ const mapHooks = maps.map((map) => {
1467
+ const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(map.name))}`;
1468
+ if (excludeList.includes(hookName)) {
1469
+ return null;
1470
+ }
1471
+ return generateMapHook(map, name, address, contractName);
1472
+ }).filter(Boolean);
1473
+ const dataVars = variables.filter((v) => v.access === "variable");
1474
+ const varHooks = dataVars.map((variable) => {
1475
+ const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(variable.name))}`;
1476
+ if (excludeList.includes(hookName)) {
1477
+ return null;
1478
+ }
1479
+ return generateVarHook(variable, name, address, contractName);
1480
+ }).filter(Boolean);
1481
+ const constants = variables.filter((v) => v.access === "constant");
1482
+ const constantHooks = constants.map((constant) => {
1483
+ const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(constant.name))}`;
1484
+ if (excludeList.includes(hookName)) {
1485
+ return null;
1486
+ }
1487
+ return generateConstantHook(constant, name, address, contractName);
1488
+ }).filter(Boolean);
1489
+ const allHooks = [
1490
+ ...readHooks,
1491
+ ...writeHooks,
1492
+ ...mapHooks,
1493
+ ...varHooks,
1494
+ ...constantHooks
1495
+ ];
1496
+ if (allHooks.length === 0) {
1497
+ return "";
1454
1498
  }
1499
+ return allHooks.join(`
1455
1500
 
1456
- return context
1501
+ `);
1502
+ }
1503
+ function generateReadHook(func, contractName) {
1504
+ const hookName = `use${capitalize(contractName)}${capitalize(toCamelCase8(func.name))}`;
1505
+ const argsSignature = generateHookArgsSignature(func.args);
1506
+ const enabledParam = func.args.length > 0 ? ", options?: { enabled?: boolean }" : "options?: { enabled?: boolean }";
1507
+ const returnType = clarityTypeToTS(func.outputs);
1508
+ return `export function ${hookName}(${argsSignature}${enabledParam}) {
1509
+ const config = useSecondLayerConfig()
1510
+
1511
+ return useQuery<${returnType}>({
1512
+ queryKey: ['${func.name}', ${contractName}.address, ${generateArgNames(func.args)}],
1513
+ queryFn: () => ${contractName}.read.${toCamelCase8(func.name)}(${generateArgNames(func.args) ? `{ ${generateObjectArgs(func.args)} }, ` : ""}{
1514
+ network: config.network,
1515
+ senderAddress: config.senderAddress || 'SP000000000000000000002Q6VF78'
1516
+ }),
1517
+ ${func.args.length > 0 ? `enabled: ${generateEnabledCondition(func.args)} && (options?.enabled ?? true),` : ""}
1518
+ ...options
1519
+ })
1457
1520
  }`;
1458
- return formatCode(code);
1459
1521
  }
1522
+ function generateWriteHook(func, contractName) {
1523
+ const hookName = `use${capitalize(contractName)}${capitalize(toCamelCase8(func.name))}`;
1524
+ const argsType = generateArgsType(func.args);
1525
+ return `export function ${hookName}() {
1526
+ const config = useSecondLayerConfig()
1527
+ const queryClient = useQueryClient()
1528
+
1529
+ const mutation = useMutation({
1530
+ mutationFn: async (params: {
1531
+ args: ${argsType};
1532
+ options?: {
1533
+ postConditions?: PostCondition[];
1534
+ attachment?: string;
1535
+ onFinish?: (data: any) => void;
1536
+ onCancel?: () => void;
1537
+ };
1538
+ }) => {
1539
+ const { args, options = {} } = params
1540
+ const contractCallData = ${contractName}.${toCamelCase8(func.name)}(args)
1541
+ const { contractAddress, contractName: name, functionName, functionArgs } = contractCallData
1542
+ const network = config.network || 'mainnet'
1543
+ const contract = \`\${contractAddress}.\${name}\`
1460
1544
 
1461
- // src/plugins/react/generators/generic.ts
1462
- init_format();
1545
+ const result = await request('stx_callContract', {
1546
+ contract,
1547
+ functionName,
1548
+ functionArgs,
1549
+ network,
1550
+ ...options
1551
+ })
1552
+
1553
+ options.onFinish?.(result)
1554
+ return result
1555
+ },
1556
+ onSuccess: () => {
1557
+ // Invalidate relevant queries on success
1558
+ queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
1559
+ },
1560
+ onError: (error) => {
1561
+ console.error('Contract call failed:', error)
1562
+ }
1563
+ })
1564
+
1565
+ const ${toCamelCase8(func.name)} = useCallback(async (
1566
+ args: ${argsType},
1567
+ options?: {
1568
+ postConditions?: PostCondition[];
1569
+ attachment?: string;
1570
+ onFinish?: (data: any) => void;
1571
+ onCancel?: () => void;
1572
+ }
1573
+ ) => {
1574
+ return mutation.mutateAsync({ args, options })
1575
+ }, [mutation])
1576
+
1577
+ return {
1578
+ ${toCamelCase8(func.name)},
1579
+ // Expose mutation state
1580
+ isPending: mutation.isPending,
1581
+ isError: mutation.isError,
1582
+ isSuccess: mutation.isSuccess,
1583
+ error: mutation.error,
1584
+ data: mutation.data,
1585
+ reset: mutation.reset
1586
+ }
1587
+ }`;
1588
+ }
1589
+ function generateMapHook(map, contractVarName, _address, _contractName) {
1590
+ const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase8(map.name))}`;
1591
+ const keyType = clarityTypeToTS(map.key);
1592
+ const valueType = clarityTypeToTS(map.value);
1593
+ return `export function ${hookName}(key: ${keyType}, options?: { enabled?: boolean }) {
1594
+ const config = useSecondLayerConfig()
1595
+
1596
+ return useQuery<${valueType} | null>({
1597
+ queryKey: ['${contractVarName}', '${map.name}', 'map', key, config.network],
1598
+ queryFn: async () => {
1599
+ return ${contractVarName}.maps.${toCamelCase8(map.name)}.get(key, { network: config.network })
1600
+ },
1601
+ enabled: options?.enabled ?? true
1602
+ })
1603
+ }`;
1604
+ }
1605
+ function generateVarHook(variable, contractVarName, _address, _contractName) {
1606
+ const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase8(variable.name))}`;
1607
+ const valueType = clarityTypeToTS(variable.type);
1608
+ return `export function ${hookName}(options?: { enabled?: boolean }) {
1609
+ const config = useSecondLayerConfig()
1610
+
1611
+ return useQuery<${valueType}>({
1612
+ queryKey: ['${contractVarName}', '${variable.name}', 'var', config.network],
1613
+ queryFn: async () => {
1614
+ return ${contractVarName}.vars.${toCamelCase8(variable.name)}.get({ network: config.network })
1615
+ },
1616
+ enabled: options?.enabled ?? true
1617
+ })
1618
+ }`;
1619
+ }
1620
+ function generateConstantHook(constant, contractVarName, _address, _contractName) {
1621
+ const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase8(constant.name))}`;
1622
+ const valueType = clarityTypeToTS(constant.type);
1623
+ return `export function ${hookName}(options?: { enabled?: boolean }) {
1624
+ const config = useSecondLayerConfig()
1625
+
1626
+ return useQuery<${valueType}>({
1627
+ queryKey: ['${contractVarName}', '${constant.name}', 'constant', config.network],
1628
+ queryFn: async () => {
1629
+ return ${contractVarName}.constants.${toCamelCase8(constant.name)}.get({ network: config.network })
1630
+ },
1631
+ enabled: options?.enabled ?? true,
1632
+ staleTime: Infinity // Constants never change
1633
+ })
1634
+ }`;
1635
+ }
1463
1636
 
1464
1637
  // src/generators/templates/use-contract.ts
1465
1638
  var USE_CONTRACT_TEMPLATE = `export function useContract() {
@@ -1682,6 +1855,7 @@ var USE_CONTRACT_TEMPLATE = `export function useContract() {
1682
1855
  }`;
1683
1856
 
1684
1857
  // src/plugins/react/generators/generic.ts
1858
+ init_format();
1685
1859
  var GENERIC_HOOKS = [
1686
1860
  "useAccount",
1687
1861
  "useConnect",
@@ -2171,259 +2345,96 @@ function generateGenericHook(hookName) {
2171
2345
  }
2172
2346
  }
2173
2347
 
2174
- // src/plugins/react/generators/contract.ts
2348
+ // src/plugins/react/provider/index.ts
2175
2349
  init_format();
2350
+ async function generateProvider() {
2351
+ const code = `/**
2352
+ * Generated SecondLayer React Provider
2353
+ * DO NOT EDIT MANUALLY
2354
+ */
2176
2355
 
2177
- // src/utils/case-conversion.ts
2178
- import { toCamelCase as toCamelCase8 } from "@secondlayer/stacks/clarity";
2179
- function capitalize(str) {
2180
- return str.charAt(0).toUpperCase() + str.slice(1);
2181
- }
2182
- function toPascalCase(str) {
2183
- return capitalize(toCamelCase8(str));
2184
- }
2185
-
2186
- // src/plugins/react/generators/utils.ts
2187
- function generateHookArgsSignature(args) {
2188
- if (args.length === 0)
2189
- return "";
2190
- const argsList = args.map((arg) => `${toCamelCase8(arg.name)}: ${clarityTypeToTS(arg.type)}`).join(", ");
2191
- return `${argsList}`;
2192
- }
2193
- function generateArgsType(args) {
2194
- if (args.length === 0)
2195
- return "void";
2196
- const argsList = args.map((arg) => `${toCamelCase8(arg.name)}: ${clarityTypeToTS(arg.type)}`).join("; ");
2197
- return `{ ${argsList} }`;
2198
- }
2199
- function generateArgNames(args) {
2200
- if (args.length === 0)
2201
- return "";
2202
- return args.map((arg) => toCamelCase8(arg.name)).join(", ");
2203
- }
2204
- function generateEnabledCondition(args) {
2205
- return args.map((arg) => {
2206
- const camelName = toCamelCase8(arg.name);
2207
- const type = clarityTypeToTS(arg.type);
2208
- if (type === "string")
2209
- return `!!${camelName}`;
2210
- if (type === "bigint")
2211
- return `${camelName} !== undefined`;
2212
- return `${camelName} !== undefined`;
2213
- }).join(" && ");
2214
- }
2215
- function generateObjectArgs(args) {
2216
- if (args.length === 0)
2217
- return "";
2218
- return args.map((arg) => `${arg.name}: ${toCamelCase8(arg.name)}`).join(", ");
2219
- }
2220
-
2221
- // src/plugins/react/generators/contract.ts
2222
- async function generateContractHooks(contracts, excludeList = []) {
2223
- const imports = `import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
2224
- import { useCallback } from 'react'
2225
- import { useSecondLayerConfig } from './provider'
2226
- import { request } from '@secondlayer/stacks/connect'
2227
- import type { PostCondition } from '@secondlayer/stacks'
2228
- import { ${contracts.map((c) => c.name).join(", ")} } from './contracts'`;
2229
- const header = `/**
2230
- * Generated contract-specific React hooks
2231
- * DO NOT EDIT MANUALLY
2232
- */`;
2233
- const hooksCode = contracts.map((contract) => generateContractHookMethods(contract, excludeList)).filter(Boolean).join(`
2356
+ import React, { createContext, useContext } from 'react'
2234
2357
 
2235
- `);
2236
- const code = `${imports}
2358
+ /**
2359
+ * SecondLayer configuration interface
2360
+ */
2361
+ export interface SecondLayerReactConfig {
2362
+ /**
2363
+ * Network to use for API calls
2364
+ */
2365
+ network: 'mainnet' | 'testnet' | 'devnet'
2237
2366
 
2238
- ${header}
2367
+ /**
2368
+ * API key for Stacks API (optional)
2369
+ */
2370
+ apiKey?: string
2239
2371
 
2240
- ${hooksCode}`;
2241
- return formatCode(code);
2242
- }
2243
- function generateContractHookMethods(contract, excludeList) {
2244
- const { abi, name, address, contractName } = contract;
2245
- const functions = abi.functions || [];
2246
- const maps = abi.maps || [];
2247
- const variables = abi.variables || [];
2248
- const readOnlyFunctions = functions.filter((f) => f.access === "read-only");
2249
- const publicFunctions = functions.filter((f) => f.access === "public");
2250
- const readHooks = readOnlyFunctions.map((func) => {
2251
- const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(func.name))}`;
2252
- if (excludeList.includes(hookName)) {
2253
- return null;
2254
- }
2255
- return generateReadHook(func, name);
2256
- }).filter(Boolean);
2257
- const writeHooks = publicFunctions.map((func) => {
2258
- const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(func.name))}`;
2259
- if (excludeList.includes(hookName)) {
2260
- return null;
2261
- }
2262
- return generateWriteHook(func, name);
2263
- }).filter(Boolean);
2264
- const mapHooks = maps.map((map) => {
2265
- const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(map.name))}`;
2266
- if (excludeList.includes(hookName)) {
2267
- return null;
2268
- }
2269
- return generateMapHook(map, name, address, contractName);
2270
- }).filter(Boolean);
2271
- const dataVars = variables.filter((v) => v.access === "variable");
2272
- const varHooks = dataVars.map((variable) => {
2273
- const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(variable.name))}`;
2274
- if (excludeList.includes(hookName)) {
2275
- return null;
2276
- }
2277
- return generateVarHook(variable, name, address, contractName);
2278
- }).filter(Boolean);
2279
- const constants = variables.filter((v) => v.access === "constant");
2280
- const constantHooks = constants.map((constant) => {
2281
- const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(constant.name))}`;
2282
- if (excludeList.includes(hookName)) {
2283
- return null;
2284
- }
2285
- return generateConstantHook(constant, name, address, contractName);
2286
- }).filter(Boolean);
2287
- const allHooks = [...readHooks, ...writeHooks, ...mapHooks, ...varHooks, ...constantHooks];
2288
- if (allHooks.length === 0) {
2289
- return "";
2290
- }
2291
- return allHooks.join(`
2372
+ /**
2373
+ * Base URL for Stacks API (optional override)
2374
+ */
2375
+ apiUrl?: string
2292
2376
 
2293
- `);
2377
+ /**
2378
+ * Default sender address for read-only calls
2379
+ */
2380
+ senderAddress?: string
2294
2381
  }
2295
- function generateReadHook(func, contractName) {
2296
- const hookName = `use${capitalize(contractName)}${capitalize(toCamelCase8(func.name))}`;
2297
- const argsSignature = generateHookArgsSignature(func.args);
2298
- const enabledParam = func.args.length > 0 ? ", options?: { enabled?: boolean }" : "options?: { enabled?: boolean }";
2299
- const returnType = clarityTypeToTS(func.outputs);
2300
- return `export function ${hookName}(${argsSignature}${enabledParam}) {
2301
- const config = useSecondLayerConfig()
2302
2382
 
2303
- return useQuery<${returnType}>({
2304
- queryKey: ['${func.name}', ${contractName}.address, ${generateArgNames(func.args)}],
2305
- queryFn: () => ${contractName}.read.${toCamelCase8(func.name)}(${generateArgNames(func.args) ? `{ ${generateObjectArgs(func.args)} }, ` : ""}{
2306
- network: config.network,
2307
- senderAddress: config.senderAddress || 'SP000000000000000000002Q6VF78'
2308
- }),
2309
- ${func.args.length > 0 ? `enabled: ${generateEnabledCondition(func.args)} && (options?.enabled ?? true),` : ""}
2310
- ...options
2311
- })
2312
- }`;
2383
+ /**
2384
+ * Provider component props
2385
+ */
2386
+ export interface SecondLayerProviderProps {
2387
+ children: React.ReactNode
2388
+ config: SecondLayerReactConfig
2313
2389
  }
2314
- function generateWriteHook(func, contractName) {
2315
- const hookName = `use${capitalize(contractName)}${capitalize(toCamelCase8(func.name))}`;
2316
- const argsType = generateArgsType(func.args);
2317
- return `export function ${hookName}() {
2318
- const config = useSecondLayerConfig()
2319
- const queryClient = useQueryClient()
2320
-
2321
- const mutation = useMutation({
2322
- mutationFn: async (params: {
2323
- args: ${argsType};
2324
- options?: {
2325
- postConditions?: PostCondition[];
2326
- attachment?: string;
2327
- onFinish?: (data: any) => void;
2328
- onCancel?: () => void;
2329
- };
2330
- }) => {
2331
- const { args, options = {} } = params
2332
- const contractCallData = ${contractName}.${toCamelCase8(func.name)}(args)
2333
- const { contractAddress, contractName: name, functionName, functionArgs } = contractCallData
2334
- const network = config.network || 'mainnet'
2335
- const contract = \`\${contractAddress}.\${name}\`
2336
-
2337
- const result = await request('stx_callContract', {
2338
- contract,
2339
- functionName,
2340
- functionArgs,
2341
- network,
2342
- ...options
2343
- })
2344
-
2345
- options.onFinish?.(result)
2346
- return result
2347
- },
2348
- onSuccess: () => {
2349
- // Invalidate relevant queries on success
2350
- queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
2351
- },
2352
- onError: (error) => {
2353
- console.error('Contract call failed:', error)
2354
- }
2355
- })
2356
2390
 
2357
- const ${toCamelCase8(func.name)} = useCallback(async (
2358
- args: ${argsType},
2359
- options?: {
2360
- postConditions?: PostCondition[];
2361
- attachment?: string;
2362
- onFinish?: (data: any) => void;
2363
- onCancel?: () => void;
2364
- }
2365
- ) => {
2366
- return mutation.mutateAsync({ args, options })
2367
- }, [mutation])
2391
+ /**
2392
+ * React context for SecondLayer configuration
2393
+ */
2394
+ const SecondLayerContext = createContext<SecondLayerReactConfig | undefined>(undefined)
2395
+ SecondLayerContext.displayName = 'SecondLayerContext'
2368
2396
 
2397
+ /**
2398
+ * Create a SecondLayer React configuration with defaults
2399
+ */
2400
+ export function createSecondLayerConfig(config: SecondLayerReactConfig): SecondLayerReactConfig {
2369
2401
  return {
2370
- ${toCamelCase8(func.name)},
2371
- // Expose mutation state
2372
- isPending: mutation.isPending,
2373
- isError: mutation.isError,
2374
- isSuccess: mutation.isSuccess,
2375
- error: mutation.error,
2376
- data: mutation.data,
2377
- reset: mutation.reset
2402
+ network: config.network,
2403
+ apiKey: config.apiKey,
2404
+ apiUrl: config.apiUrl,
2405
+ senderAddress: config.senderAddress || 'SP000000000000000000002Q6VF78'
2378
2406
  }
2379
- }`;
2380
2407
  }
2381
- function generateMapHook(map, contractVarName, _address, _contractName) {
2382
- const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase8(map.name))}`;
2383
- const keyType = clarityTypeToTS(map.key);
2384
- const valueType = clarityTypeToTS(map.value);
2385
- return `export function ${hookName}(key: ${keyType}, options?: { enabled?: boolean }) {
2386
- const config = useSecondLayerConfig()
2387
2408
 
2388
- return useQuery<${valueType} | null>({
2389
- queryKey: ['${contractVarName}', '${map.name}', 'map', key, config.network],
2390
- queryFn: async () => {
2391
- return ${contractVarName}.maps.${toCamelCase8(map.name)}.get(key, { network: config.network })
2392
- },
2393
- enabled: options?.enabled ?? true
2394
- })
2395
- }`;
2396
- }
2397
- function generateVarHook(variable, contractVarName, _address, _contractName) {
2398
- const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase8(variable.name))}`;
2399
- const valueType = clarityTypeToTS(variable.type);
2400
- return `export function ${hookName}(options?: { enabled?: boolean }) {
2401
- const config = useSecondLayerConfig()
2409
+ /**
2410
+ * Provider component that makes SecondLayer configuration available to hooks
2411
+ */
2412
+ export function SecondLayerProvider({ children, config }: SecondLayerProviderProps) {
2413
+ const resolvedConfig = createSecondLayerConfig(config)
2402
2414
 
2403
- return useQuery<${valueType}>({
2404
- queryKey: ['${contractVarName}', '${variable.name}', 'var', config.network],
2405
- queryFn: async () => {
2406
- return ${contractVarName}.vars.${toCamelCase8(variable.name)}.get({ network: config.network })
2407
- },
2408
- enabled: options?.enabled ?? true
2409
- })
2410
- }`;
2415
+ return (
2416
+ <SecondLayerContext.Provider value={resolvedConfig}>
2417
+ {children}
2418
+ </SecondLayerContext.Provider>
2419
+ )
2411
2420
  }
2412
- function generateConstantHook(constant, contractVarName, _address, _contractName) {
2413
- const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase8(constant.name))}`;
2414
- const valueType = clarityTypeToTS(constant.type);
2415
- return `export function ${hookName}(options?: { enabled?: boolean }) {
2416
- const config = useSecondLayerConfig()
2417
2421
 
2418
- return useQuery<${valueType}>({
2419
- queryKey: ['${contractVarName}', '${constant.name}', 'constant', config.network],
2420
- queryFn: async () => {
2421
- return ${contractVarName}.constants.${toCamelCase8(constant.name)}.get({ network: config.network })
2422
- },
2423
- enabled: options?.enabled ?? true,
2424
- staleTime: Infinity // Constants never change
2425
- })
2422
+ /**
2423
+ * Hook to access the SecondLayer configuration
2424
+ */
2425
+ export function useSecondLayerConfig(): SecondLayerReactConfig {
2426
+ const context = useContext(SecondLayerContext)
2427
+
2428
+ if (context === undefined) {
2429
+ throw new Error(
2430
+ 'useSecondLayerConfig must be used within a SecondLayerProvider. ' +
2431
+ 'Make sure to wrap your app with <SecondLayerProvider config={{...}}>'
2432
+ )
2433
+ }
2434
+
2435
+ return context
2426
2436
  }`;
2437
+ return formatCode(code);
2427
2438
  }
2428
2439
 
2429
2440
  // src/plugins/react/index.ts
@@ -2723,5 +2734,5 @@ export {
2723
2734
  PluginManager
2724
2735
  };
2725
2736
 
2726
- //# debugId=184A6BEF0A21821664756E2164756E21
2737
+ //# debugId=A40A68476DA46C9B64756E2164756E21
2727
2738
  //# sourceMappingURL=index.js.map