@secondlayer/cli 14.3.1 → 15.0.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.
@@ -1,1755 +0,0 @@
1
- import { createRequire } from "node:module";
2
- var __create = Object.create;
3
- var __getProtoOf = Object.getPrototypeOf;
4
- var __defProp = Object.defineProperty;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- function __accessProp(key) {
8
- return this[key];
9
- }
10
- var __toESMCache_node;
11
- var __toESMCache_esm;
12
- var __toESM = (mod, isNodeMode, target) => {
13
- var canCache = mod != null && typeof mod === "object";
14
- if (canCache) {
15
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
- var cached = cache.get(mod);
17
- if (cached)
18
- return cached;
19
- }
20
- target = mod != null ? __create(__getProtoOf(mod)) : {};
21
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
- for (let key of __getOwnPropNames(mod))
23
- if (!__hasOwnProp.call(to, key))
24
- __defProp(to, key, {
25
- get: __accessProp.bind(mod, key),
26
- enumerable: true
27
- });
28
- if (canCache)
29
- cache.set(mod, to);
30
- return to;
31
- };
32
- var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
- var __returnValue = (v) => v;
34
- function __exportSetter(name, newValue) {
35
- this[name] = __returnValue.bind(null, newValue);
36
- }
37
- var __export = (target, all) => {
38
- for (var name in all)
39
- __defProp(target, name, {
40
- get: all[name],
41
- enumerable: true,
42
- configurable: true,
43
- set: __exportSetter.bind(all, name)
44
- });
45
- };
46
- var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
47
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
48
-
49
- // src/types/plugin.ts
50
- function isClarinetContract(c) {
51
- return "_clarinetSource" in c && c._clarinetSource === true;
52
- }
53
- function isDirectFileContract(c) {
54
- return "_directFile" in c && c._directFile === true;
55
- }
56
-
57
- // src/utils/contract-id.ts
58
- function parseContractId(contractId) {
59
- const dotIndex = contractId.indexOf(".");
60
- if (dotIndex === -1) {
61
- throw new Error(`Invalid contract ID: "${contractId}" (expected "address.contractName")`);
62
- }
63
- return {
64
- address: contractId.slice(0, dotIndex),
65
- contractName: contractId.slice(dotIndex + 1)
66
- };
67
- }
68
-
69
- // src/utils/format.ts
70
- var exports_format = {};
71
- __export(exports_format, {
72
- formatCode: () => formatCode
73
- });
74
- import { Biome, Distribution } from "@biomejs/js-api";
75
- async function getBiome() {
76
- if (!biome) {
77
- biome = await Biome.create({
78
- distribution: Distribution.NODE
79
- });
80
- biome.applyConfiguration({
81
- formatter: {
82
- enabled: true,
83
- indentStyle: "tab",
84
- lineWidth: 80
85
- },
86
- javascript: {
87
- formatter: {
88
- semicolons: "always",
89
- quoteStyle: "single"
90
- }
91
- },
92
- organizeImports: {
93
- enabled: true
94
- },
95
- linter: {
96
- enabled: true
97
- },
98
- assists: {
99
- enabled: true
100
- }
101
- });
102
- }
103
- return biome;
104
- }
105
- async function formatCode(code) {
106
- const b = await getBiome();
107
- const linted = b.lintContent(code, {
108
- filePath: "generated.ts",
109
- fixFileMode: "SafeFixes"
110
- });
111
- const formatted = b.formatContent(linted.content, {
112
- filePath: "generated.ts"
113
- });
114
- return formatted.content;
115
- }
116
- var biome = null;
117
- var init_format = () => {};
118
-
119
- // src/core/plugin-manager.ts
120
- import { promises as fs } from "node:fs";
121
- import path from "node:path";
122
- import { isValidAddress as _validateStacksAddress } from "@secondlayer/stacks";
123
- import { getErrorMessage } from "@secondlayer/shared";
124
- import { toCamelCase } from "@secondlayer/stacks/clarity";
125
-
126
- class PluginManager {
127
- plugins = [];
128
- logger;
129
- utils;
130
- executionContext;
131
- constructor() {
132
- this.logger = this.createLogger();
133
- this.utils = this.createUtils();
134
- this.executionContext = {
135
- phase: "config",
136
- startTime: Date.now(),
137
- results: new Map
138
- };
139
- }
140
- register(plugin) {
141
- if (!plugin.name || !plugin.version) {
142
- throw new Error("Plugin must have a name and version");
143
- }
144
- const existing = this.plugins.find((p) => p.name === plugin.name);
145
- if (existing) {
146
- throw new Error(`Plugin "${plugin.name}" is already registered (version ${existing.version})`);
147
- }
148
- this.plugins.push(plugin);
149
- this.logger.debug(`Registered plugin: ${plugin.name}@${plugin.version}`);
150
- }
151
- getPlugins() {
152
- return [...this.plugins];
153
- }
154
- async transformConfig(config) {
155
- this.executionContext.phase = "config";
156
- let transformedConfig = { ...config };
157
- for (const plugin of this.plugins) {
158
- if (plugin.transformConfig) {
159
- this.executionContext.currentPlugin = plugin;
160
- try {
161
- const result = await plugin.transformConfig(transformedConfig);
162
- transformedConfig = result;
163
- this.recordHookResult(plugin.name, "transformConfig", {
164
- success: true
165
- });
166
- } catch (error) {
167
- this.recordHookResult(plugin.name, "transformConfig", {
168
- success: false,
169
- error: error instanceof Error ? error : new Error(getErrorMessage(error))
170
- });
171
- throw new Error(`Plugin "${plugin.name}" failed during config transformation: ${getErrorMessage(error)}`);
172
- }
173
- }
174
- }
175
- const resolvedConfig = {
176
- ...transformedConfig,
177
- plugins: this.plugins
178
- };
179
- return resolvedConfig;
180
- }
181
- async transformContracts(contracts, _config) {
182
- const processedContracts = [];
183
- for (let contract of contracts) {
184
- if (isClarinetContract(contract) && contract.abi) {
185
- processedContracts.push(this.contractToProcessed(contract, "clarinet"));
186
- continue;
187
- }
188
- if (isDirectFileContract(contract) && contract.abi) {
189
- processedContracts.push(this.contractToProcessed(contract, "direct"));
190
- continue;
191
- }
192
- for (const plugin of this.plugins) {
193
- if (plugin.transformContract) {
194
- this.executionContext.currentPlugin = plugin;
195
- try {
196
- contract = await plugin.transformContract(contract);
197
- this.recordHookResult(plugin.name, "transformContract", {
198
- success: true
199
- });
200
- } catch (error) {
201
- this.recordHookResult(plugin.name, "transformContract", {
202
- success: false,
203
- error: error instanceof Error ? error : new Error(getErrorMessage(error))
204
- });
205
- this.logger.warn(`Plugin "${plugin.name}" failed to transform contract: ${getErrorMessage(error)}`);
206
- }
207
- }
208
- }
209
- if (contract.abi) {
210
- processedContracts.push(this.contractToProcessed(contract, "api"));
211
- }
212
- }
213
- return processedContracts;
214
- }
215
- async executeHook(hookName, context) {
216
- for (const plugin of this.plugins) {
217
- const hook = plugin[hookName];
218
- if (typeof hook === "function") {
219
- this.executionContext.currentPlugin = plugin;
220
- try {
221
- await hook.call(plugin, context);
222
- this.recordHookResult(plugin.name, hookName, {
223
- success: true
224
- });
225
- } catch (error) {
226
- this.recordHookResult(plugin.name, hookName, {
227
- success: false,
228
- error: error instanceof Error ? error : new Error(getErrorMessage(error))
229
- });
230
- this.logger.error(`Plugin "${plugin.name}" failed during ${hookName}: ${getErrorMessage(error)}`);
231
- }
232
- }
233
- }
234
- }
235
- async executeGeneration(contracts, config) {
236
- this.executionContext.phase = "generate";
237
- const outputs = new Map;
238
- const context = {
239
- config,
240
- logger: this.logger,
241
- utils: this.utils,
242
- contracts,
243
- outputs,
244
- augment: (outputKey, contractName, content) => {
245
- this.augmentOutput(outputs, outputKey, contractName, content);
246
- },
247
- addOutput: (key, output) => {
248
- outputs.set(key, output);
249
- }
250
- };
251
- await this.executeHook("beforeGenerate", context);
252
- await this.executeHook("generate", context);
253
- await this.executeHook("afterGenerate", context);
254
- return outputs;
255
- }
256
- async transformOutputs(outputs) {
257
- this.executionContext.phase = "output";
258
- const transformedOutputs = new Map;
259
- for (const [key, output] of outputs) {
260
- let transformedContent = output.content;
261
- for (const plugin of this.plugins) {
262
- if (plugin.transformOutput) {
263
- this.executionContext.currentPlugin = plugin;
264
- try {
265
- transformedContent = await plugin.transformOutput(transformedContent, output.type || "other");
266
- this.recordHookResult(plugin.name, "transformOutput", {
267
- success: true
268
- });
269
- } catch (error) {
270
- this.recordHookResult(plugin.name, "transformOutput", {
271
- success: false,
272
- error: error instanceof Error ? error : new Error(getErrorMessage(error))
273
- });
274
- this.logger.warn(`Plugin "${plugin.name}" failed to transform output: ${getErrorMessage(error)}`);
275
- }
276
- }
277
- }
278
- transformedOutputs.set(key, {
279
- ...output,
280
- content: transformedContent
281
- });
282
- }
283
- return transformedOutputs;
284
- }
285
- async writeOutputs(outputs) {
286
- for (const [, output] of outputs) {
287
- try {
288
- const resolvedPath = path.resolve(process.cwd(), output.path);
289
- await this.utils.ensureDir(path.dirname(resolvedPath));
290
- await this.utils.writeFile(resolvedPath, output.content);
291
- } catch (error) {
292
- this.logger.error(`Failed to write ${output.path}: ${getErrorMessage(error)}`);
293
- throw error;
294
- }
295
- }
296
- }
297
- getExecutionResults() {
298
- return new Map(this.executionContext.results);
299
- }
300
- contractToProcessed(contract, source) {
301
- const address = typeof contract.address === "string" ? contract.address : "";
302
- const parsed = parseContractId(address);
303
- return {
304
- name: contract.name || parsed.contractName || "unknown",
305
- address: parsed.address || "unknown",
306
- contractName: parsed.contractName || contract.name || "unknown",
307
- abi: contract.abi,
308
- source: source === "api" ? "api" : "local",
309
- metadata: contract.metadata ?? { source }
310
- };
311
- }
312
- augmentOutput(outputs, outputKey, contractName, content) {
313
- const existing = outputs.get(outputKey);
314
- if (!existing) {
315
- this.logger.warn(`Cannot augment non-existent output: ${outputKey}`);
316
- return;
317
- }
318
- const augmentedContent = `${existing.content}
319
-
320
- // Augmented by plugin for ${contractName}
321
- ${JSON.stringify(content, null, 2)}`;
322
- outputs.set(outputKey, {
323
- ...existing,
324
- content: augmentedContent
325
- });
326
- }
327
- recordHookResult(pluginName, hookName, result) {
328
- const key = `${pluginName}:${hookName}`;
329
- const existing = this.executionContext.results.get(key) || [];
330
- existing.push({ ...result, plugin: pluginName });
331
- this.executionContext.results.set(key, existing);
332
- }
333
- createLogger() {
334
- return {
335
- info: (message) => console.log(`ℹ️ ${message}`),
336
- warn: (message) => console.warn(`⚠️ ${message}`),
337
- error: (message) => console.error(`❌ ${message}`),
338
- debug: (message) => {
339
- if (process.env.DEBUG) {
340
- console.log(`\uD83D\uDC1B ${message}`);
341
- }
342
- },
343
- success: (message) => console.log(`✅ ${message}`)
344
- };
345
- }
346
- createUtils() {
347
- return {
348
- toCamelCase,
349
- toKebabCase: (str) => {
350
- return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
351
- },
352
- validateAddress: (address) => {
353
- return validateStacksAddress(parseContractId(address).address);
354
- },
355
- parseContractId: (contractId) => {
356
- return parseContractId(contractId);
357
- },
358
- formatCode: async (code) => {
359
- const { formatCode: formatCode2 } = await Promise.resolve().then(() => (init_format(), exports_format));
360
- return formatCode2(code);
361
- },
362
- resolvePath: (relativePath) => {
363
- return path.resolve(process.cwd(), relativePath);
364
- },
365
- fileExists: async (filePath) => {
366
- try {
367
- await fs.access(filePath);
368
- return true;
369
- } catch {
370
- return false;
371
- }
372
- },
373
- readFile: async (filePath) => {
374
- return fs.readFile(filePath, "utf-8");
375
- },
376
- writeFile: async (filePath, content) => {
377
- await fs.writeFile(filePath, content, "utf-8");
378
- },
379
- ensureDir: async (dirPath) => {
380
- await fs.mkdir(dirPath, { recursive: true });
381
- }
382
- };
383
- }
384
- }
385
- var validateStacksAddress;
386
- var init_plugin_manager = __esm(() => {
387
- validateStacksAddress = _validateStacksAddress;
388
- });
389
-
390
- // src/generators/contract.ts
391
- import {
392
- generateContractInterface as buildContractInterface
393
- } from "@secondlayer/scaffold";
394
- async function generateContractInterface(contracts) {
395
- return formatCode(buildContractInterface(contracts));
396
- }
397
- var init_contract = __esm(() => {
398
- init_format();
399
- });
400
-
401
- // src/utils/case-conversion.ts
402
- import { toCamelCase as toCamelCase3 } from "@secondlayer/stacks/clarity";
403
- function capitalize(str) {
404
- return str.charAt(0).toUpperCase() + str.slice(1);
405
- }
406
- function toPascalCase(str) {
407
- return capitalize(toCamelCase3(str));
408
- }
409
- var init_case_conversion = () => {};
410
-
411
- // src/plugins/index.ts
412
- init_plugin_manager();
413
-
414
- // src/plugins/clarinet/index.ts
415
- init_contract();
416
- import { toCamelCase as toCamelCase2 } from "@secondlayer/stacks/clarity";
417
- import { normalizeAbi } from "@secondlayer/stacks/clarity";
418
- import { initSimnet } from "@stacks/clarinet-sdk";
419
-
420
- // src/utils/constants.ts
421
- var DEFAULT_SENDER_ADDRESS = "SP000000000000000000002Q6VF78";
422
- // src/plugins/shared.ts
423
- function matchesContractFilters(name, options) {
424
- if (options.include && !options.include.includes(name)) {
425
- return false;
426
- }
427
- if (options.exclude?.includes(name)) {
428
- return false;
429
- }
430
- return true;
431
- }
432
-
433
- // src/plugins/clarinet/index.ts
434
- function sanitizeContractName(name) {
435
- return toCamelCase2(name);
436
- }
437
- async function readManifestInfo(manifestPath) {
438
- try {
439
- const { promises: fs2 } = await import("node:fs");
440
- const tomlContent = await fs2.readFile(manifestPath, "utf-8");
441
- const projectContracts = new Set;
442
- const contractSectionRegex = /^\[contracts\.([^\]]+)\]/gm;
443
- let match = contractSectionRegex.exec(tomlContent);
444
- while (match !== null) {
445
- projectContracts.add(match[1]);
446
- match = contractSectionRegex.exec(tomlContent);
447
- }
448
- const requirementIds = new Set;
449
- const requirementRegex = /contract_id\s*=\s*["']([^"']+)["']/g;
450
- match = requirementRegex.exec(tomlContent);
451
- while (match !== null) {
452
- requirementIds.add(match[1]);
453
- match = requirementRegex.exec(tomlContent);
454
- }
455
- return { projectContracts, requirementIds };
456
- } catch {
457
- return null;
458
- }
459
- }
460
- function classifyContract(contractId, manifest) {
461
- const { address, contractName } = parseContractId(contractId);
462
- if (manifest) {
463
- if (manifest.projectContracts.has(contractName))
464
- return "project";
465
- if (manifest.requirementIds.has(contractId))
466
- return "requirement";
467
- return "system";
468
- }
469
- const systemContractPatterns = [
470
- /^pox-\d+$/,
471
- /^bns$/,
472
- /^costs-\d+$/,
473
- /^lockup$/,
474
- /^signer-manager$/
475
- ];
476
- if (systemContractPatterns.some((pattern) => pattern.test(contractName))) {
477
- return "system";
478
- }
479
- const systemAddresses = [
480
- DEFAULT_SENDER_ADDRESS,
481
- "ST000000000000000000002AMW42H"
482
- ];
483
- if (systemAddresses.includes(address)) {
484
- return "system";
485
- }
486
- return "project";
487
- }
488
- var clarinet = (options = {}) => {
489
- const manifestPath = options.path || "./Clarinet.toml";
490
- let simnet;
491
- return {
492
- name: "@secondlayer/cli/plugin-clarinet",
493
- version: "1.0.0",
494
- async transformConfig(config) {
495
- try {
496
- simnet = await initSimnet(manifestPath);
497
- const contractInterfaces = simnet.getContractsInterfaces();
498
- const contracts = [];
499
- const manifest = await readManifestInfo(manifestPath);
500
- const includeRequirements = options.includeRequirements ?? true;
501
- for (const [contractId, abi] of contractInterfaces) {
502
- const { contractName } = parseContractId(contractId);
503
- const kind = classifyContract(contractId, manifest);
504
- if (kind === "system" || kind === "requirement" && !includeRequirements) {
505
- if (options.debug) {
506
- console.log(`\uD83D\uDEAB Skipping ${kind} contract: ${contractId}`);
507
- }
508
- continue;
509
- }
510
- if (!matchesContractFilters(contractName, options)) {
511
- continue;
512
- }
513
- const sanitizedName = sanitizeContractName(contractName);
514
- contracts.push({
515
- name: sanitizedName,
516
- address: contractId,
517
- abi: normalizeAbi(abi),
518
- _clarinetSource: true
519
- });
520
- }
521
- if (options.debug) {
522
- console.log(`\uD83D\uDD0D Clarinet plugin found ${contracts.length} user-defined contracts`);
523
- }
524
- return {
525
- ...config,
526
- contracts: [...config.contracts || [], ...contracts]
527
- };
528
- } catch (error) {
529
- const err = error;
530
- if (await hasClarinetProject(manifestPath)) {
531
- console.warn(`⚠️ Clarinet plugin: found ${manifestPath} but failed to load contracts: ${err.message}`);
532
- } else if (options.debug) {
533
- console.warn(`⚠️ Clarinet plugin: no manifest at ${manifestPath}, skipping`);
534
- }
535
- return config;
536
- }
537
- },
538
- async generate(context) {
539
- const clarinetContracts = context.contracts.filter((contract) => contract.metadata?.source === "clarinet");
540
- if (clarinetContracts.length === 0) {
541
- return;
542
- }
543
- if (options.debug) {
544
- context.logger.debug(`Generating interfaces for ${clarinetContracts.length} Clarinet contracts`);
545
- }
546
- const contractsCode = await generateContractInterface(clarinetContracts);
547
- context.addOutput("contracts", {
548
- path: context.config.out,
549
- content: contractsCode,
550
- type: "contracts"
551
- });
552
- }
553
- };
554
- };
555
- async function hasClarinetProject(path2 = "./Clarinet.toml") {
556
- try {
557
- const { promises: fs2 } = await import("node:fs");
558
- await fs2.access(path2);
559
- return true;
560
- } catch {
561
- return false;
562
- }
563
- }
564
- // src/plugins/react/generators/contract.ts
565
- init_format();
566
-
567
- // src/plugins/react/generators/utils.ts
568
- init_case_conversion();
569
-
570
- // src/utils/type-mapping.ts
571
- import {
572
- clarityTypeToTS,
573
- generateArgsTypeSignature,
574
- getTypeForArg
575
- } from "@secondlayer/scaffold";
576
-
577
- // src/plugins/react/generators/utils.ts
578
- function generateHookArgsSignature(args) {
579
- if (args.length === 0)
580
- return "";
581
- const argsList = args.map((arg) => `${toCamelCase3(arg.name)}: ${clarityTypeToTS(arg.type)}`).join(", ");
582
- return `${argsList}`;
583
- }
584
- function generateArgsType(args) {
585
- if (args.length === 0)
586
- return "void";
587
- const argsList = args.map((arg) => `${toCamelCase3(arg.name)}: ${clarityTypeToTS(arg.type)}`).join("; ");
588
- return `{ ${argsList} }`;
589
- }
590
- function generateArgNames(args) {
591
- if (args.length === 0)
592
- return "";
593
- return args.map((arg) => toCamelCase3(arg.name)).join(", ");
594
- }
595
- function generateEnabledCondition(args) {
596
- return args.map((arg) => {
597
- const camelName = toCamelCase3(arg.name);
598
- const type = clarityTypeToTS(arg.type);
599
- if (type === "string")
600
- return `!!${camelName}`;
601
- if (type === "bigint")
602
- return `${camelName} !== undefined`;
603
- return `${camelName} !== undefined`;
604
- }).join(" && ");
605
- }
606
- function generateObjectArgs(args) {
607
- if (args.length === 0)
608
- return "";
609
- return args.map((arg) => `${arg.name}: ${toCamelCase3(arg.name)}`).join(", ");
610
- }
611
-
612
- // src/plugins/react/generators/contract.ts
613
- async function generateContractHooks(contracts, excludeList = []) {
614
- const imports = `import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
615
- import { useCallback } from 'react'
616
- import { useSecondLayerConfig } from './provider'
617
- import { request } from '@secondlayer/stacks/connect'
618
- import type { PostCondition } from '@secondlayer/stacks/postconditions'
619
- import { ${contracts.map((c) => c.name).join(", ")} } from './contracts'
620
-
621
- const API_URLS: Record<string, string> = {
622
- mainnet: 'https://api.hiro.so',
623
- testnet: 'https://api.testnet.hiro.so',
624
- devnet: 'http://localhost:3999'
625
- }
626
-
627
- async function readContractCall(params: {
628
- contractAddress: string;
629
- contractName: string;
630
- functionName: string;
631
- functionArgs: any[];
632
- outputType: any;
633
- senderAddress: string;
634
- network?: string;
635
- apiUrl?: string;
636
- }): Promise<any> {
637
- const { serializeCV, deserializeCV, clarityValueToJS } = await import('@secondlayer/stacks/clarity')
638
- // Untyped call: outputType is a runtime ABI value; relating the generic
639
- // signature (even via a direct cast) explodes into deep instantiation on
640
- // the AbiType union, so route the cast through unknown.
641
- const toJS = clarityValueToJS as unknown as (type: any, cv: any) => any
642
- const baseUrl = params.apiUrl || API_URLS[params.network || 'mainnet']
643
- const response = await fetch(
644
- \`\${baseUrl}/v2/contracts/call-read/\${params.contractAddress}/\${params.contractName}/\${params.functionName}\`,
645
- {
646
- method: 'POST',
647
- headers: { 'Content-Type': 'application/json' },
648
- body: JSON.stringify({
649
- sender: params.senderAddress,
650
- arguments: params.functionArgs.map((arg: any) => \`0x\${serializeCV(arg)}\`)
651
- })
652
- }
653
- )
654
- if (!response.ok) throw new Error(\`Read-only call failed: \${response.statusText}\`)
655
- const data = await response.json()
656
- if (!data.okay) throw new Error(data.cause ?? 'Read-only call failed')
657
- return toJS(params.outputType, deserializeCV(data.result))
658
- }`;
659
- const header = `/**
660
- * Generated contract-specific React hooks
661
- * DO NOT EDIT MANUALLY
662
- */`;
663
- const hooksCode = contracts.map((contract) => generateContractHookMethods(contract, excludeList)).filter(Boolean).join(`
664
-
665
- `);
666
- const code = `${imports}
667
-
668
- ${header}
669
-
670
- ${hooksCode}`;
671
- return formatCode(code);
672
- }
673
- function generateContractHookMethods(contract, excludeList) {
674
- const { abi, name, address, contractName } = contract;
675
- const functions = abi.functions || [];
676
- const maps = abi.maps || [];
677
- const variables = abi.variables || [];
678
- const readOnlyFunctions = functions.filter((f) => f.access === "read-only");
679
- const publicFunctions = functions.filter((f) => f.access === "public");
680
- const readHooks = readOnlyFunctions.map((func) => {
681
- const hookName = `use${capitalize(name)}${capitalize(toCamelCase3(func.name))}`;
682
- if (excludeList.includes(hookName)) {
683
- return null;
684
- }
685
- return generateReadHook(func, name);
686
- }).filter(Boolean);
687
- const writeHooks = publicFunctions.map((func) => {
688
- const hookName = `use${capitalize(name)}${capitalize(toCamelCase3(func.name))}`;
689
- if (excludeList.includes(hookName)) {
690
- return null;
691
- }
692
- return generateWriteHook(func, name);
693
- }).filter(Boolean);
694
- const mapHooks = maps.map((map) => {
695
- const hookName = `use${capitalize(name)}${capitalize(toCamelCase3(map.name))}`;
696
- if (excludeList.includes(hookName)) {
697
- return null;
698
- }
699
- return generateMapHook(map, name, address, contractName);
700
- }).filter(Boolean);
701
- const dataVars = variables.filter((v) => v.access === "variable");
702
- const varHooks = dataVars.map((variable) => {
703
- const hookName = `use${capitalize(name)}${capitalize(toCamelCase3(variable.name))}`;
704
- if (excludeList.includes(hookName)) {
705
- return null;
706
- }
707
- return generateVarHook(variable, name, address, contractName);
708
- }).filter(Boolean);
709
- const constants = variables.filter((v) => v.access === "constant");
710
- const constantHooks = constants.map((constant) => {
711
- const hookName = `use${capitalize(name)}${capitalize(toCamelCase3(constant.name))}`;
712
- if (excludeList.includes(hookName)) {
713
- return null;
714
- }
715
- return generateConstantHook(constant, name, address, contractName);
716
- }).filter(Boolean);
717
- const allHooks = [
718
- ...readHooks,
719
- ...writeHooks,
720
- ...mapHooks,
721
- ...varHooks,
722
- ...constantHooks
723
- ];
724
- if (allHooks.length === 0) {
725
- return "";
726
- }
727
- return allHooks.join(`
728
-
729
- `);
730
- }
731
- function generateReadHook(func, contractName) {
732
- const hookName = `use${capitalize(contractName)}${capitalize(toCamelCase3(func.name))}`;
733
- const argsSignature = generateHookArgsSignature(func.args);
734
- const enabledParam = func.args.length > 0 ? ", options?: { enabled?: boolean }" : "options?: { enabled?: boolean }";
735
- const returnType = clarityTypeToTS(func.outputs);
736
- const callArgs = func.args.length > 0 ? `{ ${generateObjectArgs(func.args)} }` : "";
737
- return `export function ${hookName}(${argsSignature}${enabledParam}) {
738
- const config = useSecondLayerConfig()
739
-
740
- return useQuery<${returnType}>({
741
- queryKey: ['${func.name}', ${contractName}.address, ${generateArgNames(func.args)}],
742
- queryFn: async () => {
743
- const call = ${contractName}.${toCamelCase3(func.name)}(${callArgs})
744
- return await readContractCall({
745
- ...call,
746
- outputType: ${JSON.stringify(func.outputs)},
747
- network: config.network,
748
- apiUrl: config.apiUrl,
749
- senderAddress: config.senderAddress || '${DEFAULT_SENDER_ADDRESS}'
750
- }) as ${returnType}
751
- },
752
- ${func.args.length > 0 ? `enabled: ${generateEnabledCondition(func.args)} && (options?.enabled ?? true),` : ""}
753
- ...options
754
- })
755
- }`;
756
- }
757
- function generateWriteHook(func, contractName) {
758
- const hookName = `use${capitalize(contractName)}${capitalize(toCamelCase3(func.name))}`;
759
- const argsType = generateArgsType(func.args);
760
- return `export function ${hookName}() {
761
- const config = useSecondLayerConfig()
762
- const queryClient = useQueryClient()
763
-
764
- const mutation = useMutation({
765
- mutationFn: async (params: {
766
- args: ${argsType};
767
- options?: {
768
- postConditions?: PostCondition[];
769
- attachment?: string;
770
- onFinish?: (data: any) => void;
771
- onCancel?: () => void;
772
- };
773
- }) => {
774
- const { args, options = {} } = params
775
- const contractCallData = ${contractName}.${toCamelCase3(func.name)}(args)
776
- const { contractAddress, contractName: name, functionName, functionArgs } = contractCallData
777
- const network = config.network || 'mainnet'
778
- const contract = \`\${contractAddress}.\${name}\`
779
-
780
- const result = await request('stx_callContract', {
781
- contract,
782
- functionName,
783
- functionArgs,
784
- network,
785
- ...options
786
- })
787
-
788
- options.onFinish?.(result)
789
- return result
790
- },
791
- onSuccess: () => {
792
- // Invalidate relevant queries on success
793
- queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
794
- },
795
- onError: (error) => {
796
- console.error('Contract call failed:', error)
797
- }
798
- })
799
-
800
- const ${toCamelCase3(func.name)} = useCallback(async (
801
- args: ${argsType},
802
- options?: {
803
- postConditions?: PostCondition[];
804
- attachment?: string;
805
- onFinish?: (data: any) => void;
806
- onCancel?: () => void;
807
- }
808
- ) => {
809
- return mutation.mutateAsync({ args, options })
810
- }, [mutation])
811
-
812
- return {
813
- ${toCamelCase3(func.name)},
814
- // Expose mutation state
815
- isPending: mutation.isPending,
816
- isError: mutation.isError,
817
- isSuccess: mutation.isSuccess,
818
- error: mutation.error,
819
- data: mutation.data,
820
- reset: mutation.reset
821
- }
822
- }`;
823
- }
824
- function generateMapHook(map, contractVarName, _address, _contractName) {
825
- const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase3(map.name))}`;
826
- const keyType = clarityTypeToTS(map.key);
827
- const valueType = clarityTypeToTS(map.value);
828
- return `export function ${hookName}(key: ${keyType}, options?: { enabled?: boolean }) {
829
- const config = useSecondLayerConfig()
830
-
831
- return useQuery<${valueType} | null>({
832
- queryKey: ['${contractVarName}', '${map.name}', 'map', key, config.network],
833
- queryFn: async () => {
834
- return ${contractVarName}.maps.${toCamelCase3(map.name)}.get(key, { network: config.network })
835
- },
836
- enabled: options?.enabled ?? true
837
- })
838
- }`;
839
- }
840
- function generateVarHook(variable, contractVarName, _address, _contractName) {
841
- const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase3(variable.name))}`;
842
- const valueType = clarityTypeToTS(variable.type);
843
- return `export function ${hookName}(options?: { enabled?: boolean }) {
844
- const config = useSecondLayerConfig()
845
-
846
- return useQuery<${valueType}>({
847
- queryKey: ['${contractVarName}', '${variable.name}', 'var', config.network],
848
- queryFn: async () => {
849
- return ${contractVarName}.vars.${toCamelCase3(variable.name)}.get({ network: config.network })
850
- },
851
- enabled: options?.enabled ?? true
852
- })
853
- }`;
854
- }
855
- function generateConstantHook(constant, contractVarName, _address, _contractName) {
856
- const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase3(constant.name))}`;
857
- const valueType = clarityTypeToTS(constant.type);
858
- return `export function ${hookName}(options?: { enabled?: boolean }) {
859
- const config = useSecondLayerConfig()
860
-
861
- return useQuery<${valueType}>({
862
- queryKey: ['${contractVarName}', '${constant.name}', 'constant', config.network],
863
- queryFn: async () => {
864
- return ${contractVarName}.constants.${toCamelCase3(constant.name)}.get({ network: config.network })
865
- },
866
- enabled: options?.enabled ?? true,
867
- staleTime: Infinity // Constants never change
868
- })
869
- }`;
870
- }
871
-
872
- // src/generators/templates/use-contract.ts
873
- var USE_CONTRACT_TEMPLATE = `export function useContract() {
874
- const config = useSecondLayerConfig()
875
- const queryClient = useQueryClient()
876
- const [isRequestPending, setIsRequestPending] = useState(false)
877
-
878
- // Helper function to convert JS values to Clarity values based on ABI
879
- const convertArgsWithAbi = (args: any, abiArgs: any[]): any[] => {
880
- if (!abiArgs || abiArgs.length === 0) return []
881
-
882
- return abiArgs.map((abiArg, index) => {
883
- const argValue = Array.isArray(args)
884
- ? args[index]
885
- : args[abiArg.name] || args[abiArg.name.replace(/-/g, '').replace(/_/g, '')]
886
- return convertJSValueToClarityValue(argValue, abiArg.type)
887
- })
888
- }
889
-
890
- // Helper function to convert buffer values with auto-detection
891
- const convertBufferValue = (value: any): any => {
892
- // Direct Uint8Array
893
- if (value instanceof Uint8Array) {
894
- return Cl.buffer(value)
895
- }
896
-
897
- // Object notation with explicit type
898
- if (typeof value === 'object' && value !== null && value.type && value.value) {
899
- switch (value.type) {
900
- case 'ascii':
901
- return Cl.bufferFromAscii(value.value)
902
- case 'utf8':
903
- return Cl.bufferFromUtf8(value.value)
904
- case 'hex':
905
- return Cl.bufferFromHex(value.value)
906
- default:
907
- throw new Error(\`Unsupported buffer type: \${value.type}\`)
908
- }
909
- }
910
-
911
- // Auto-detect string type
912
- if (typeof value === 'string') {
913
- // 1. Check for hex (0x prefix or pure hex pattern)
914
- if (value.startsWith('0x') || /^[0-9a-fA-F]+$/.test(value)) {
915
- return Cl.bufferFromHex(value)
916
- }
917
-
918
- // 2. Check for non-ASCII characters (UTF-8)
919
- if (!/^[\\x00-\\x7F]*$/.test(value)) {
920
- return Cl.bufferFromUtf8(value)
921
- }
922
-
923
- // 3. Default to ASCII for simple ASCII strings
924
- return Cl.bufferFromAscii(value)
925
- }
926
-
927
- throw new Error(\`Invalid buffer value: \${value}\`)
928
- }
929
-
930
- // Helper function to convert a single JS value to ClarityValue
931
- const convertJSValueToClarityValue = (value: any, type: any): any => {
932
- if (typeof type === 'string') {
933
- switch (type) {
934
- case 'uint128':
935
- return Cl.uint(value)
936
- case 'int128':
937
- return Cl.int(value)
938
- case 'bool':
939
- return Cl.bool(value)
940
- case 'principal':
941
- if (!validateStacksAddress(value.split('.')[0])) {
942
- throw new Error('Invalid Stacks address format')
943
- }
944
- if (value.includes('.')) {
945
- const [address, contractName] = value.split('.')
946
- return Cl.contractPrincipal(address, contractName)
947
- } else {
948
- return Cl.standardPrincipal(value)
949
- }
950
- default:
951
- return value
952
- }
953
- }
954
-
955
- if (type['string-ascii']) {
956
- return Cl.stringAscii(value)
957
- }
958
-
959
- if (type['string-utf8']) {
960
- return Cl.stringUtf8(value)
961
- }
962
-
963
- if (type.buff) {
964
- return convertBufferValue(value)
965
- }
966
-
967
- if (type.optional) {
968
- return value !== null ? Cl.some(convertJSValueToClarityValue(value, type.optional)) : Cl.none()
969
- }
970
-
971
- if (type.list) {
972
- return Cl.list(value.map((item: any) => convertJSValueToClarityValue(item, type.list.type)))
973
- }
974
-
975
- if (type.tuple) {
976
- const tupleData = type.tuple.reduce((acc: any, field: any) => {
977
- acc[field.name] = convertJSValueToClarityValue(value[field.name], field.type)
978
- return acc
979
- }, {})
980
- return Cl.tuple(tupleData)
981
- }
982
-
983
- if (type.response) {
984
- return 'ok' in value
985
- ? Cl.ok(convertJSValueToClarityValue(value.ok, type.response.ok))
986
- : Cl.error(convertJSValueToClarityValue(value.err, type.response.error))
987
- }
988
-
989
- return value
990
- }
991
-
992
- // Helper function to find a function in an ABI by name
993
- const findFunctionInAbi = (abi: any, functionName: string): any => {
994
- if (!abi || !abi.functions) return null
995
- return abi.functions.find((func: any) => func.name === functionName)
996
- }
997
-
998
- // Legacy function - pre-converted Clarity values
999
- const legacyOpenContractCall = useCallback(async (params: {
1000
- contractAddress: string;
1001
- contractName: string;
1002
- functionName: string;
1003
- functionArgs: any[];
1004
- network?: string;
1005
- postConditions?: PostCondition[];
1006
- attachment?: string;
1007
- onFinish?: (data: any) => void;
1008
- onCancel?: () => void;
1009
- }) => {
1010
- setIsRequestPending(true)
1011
-
1012
- try {
1013
- const { contractAddress, contractName, functionName, functionArgs, onFinish, onCancel, ...options } = params
1014
- const network = params.network || config.network || 'mainnet'
1015
- const contract = \`\${contractAddress}.\${contractName}\`
1016
-
1017
- const result = await request('stx_callContract', {
1018
- contract,
1019
- functionName,
1020
- functionArgs,
1021
- network,
1022
- ...options
1023
- })
1024
-
1025
- queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
1026
- onFinish?.(result)
1027
- return result
1028
- } catch (error) {
1029
- console.error('Contract call failed:', error)
1030
- throw error instanceof Error ? error : new Error('Contract call failed')
1031
- } finally {
1032
- setIsRequestPending(false)
1033
- }
1034
- }, [config.network, queryClient])
1035
-
1036
- // Enhanced function - requires ABI, auto-converts JS values
1037
- const openContractCall = useCallback(async <
1038
- T extends AbiContract,
1039
- FN extends ExtractFunctionNames<T>
1040
- >(params: {
1041
- contractAddress: string;
1042
- contractName: string;
1043
- functionName: FN;
1044
- abi: T;
1045
- functionArgs: ExtractFunctionArgs<T, FN>;
1046
- network?: string;
1047
- postConditions?: PostCondition[];
1048
- attachment?: string;
1049
- onFinish?: (data: any) => void;
1050
- onCancel?: () => void;
1051
- }) => {
1052
- setIsRequestPending(true)
1053
-
1054
- try {
1055
- const { contractAddress, contractName, functionName, functionArgs, abi, onFinish, onCancel, ...options } = params
1056
- const network = params.network || config.network || 'mainnet'
1057
- const contract = \`\${contractAddress}.\${contractName}\`
1058
-
1059
- const abiFunction = findFunctionInAbi(abi, functionName)
1060
- if (!abiFunction) {
1061
- throw new Error(\`Function '\${functionName}' not found in ABI\`)
1062
- }
1063
-
1064
- const processedArgs = convertArgsWithAbi(functionArgs, abiFunction.args || [])
1065
-
1066
- const result = await request('stx_callContract', {
1067
- contract,
1068
- functionName,
1069
- functionArgs: processedArgs,
1070
- network,
1071
- ...options
1072
- })
1073
-
1074
- queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
1075
- onFinish?.(result)
1076
- return result
1077
- } catch (error) {
1078
- console.error('Contract call failed:', error)
1079
- throw error instanceof Error ? error : new Error('Contract call failed')
1080
- } finally {
1081
- setIsRequestPending(false)
1082
- }
1083
- }, [config.network, queryClient])
1084
-
1085
- return {
1086
- legacyOpenContractCall,
1087
- openContractCall,
1088
- isRequestPending
1089
- }
1090
- }`;
1091
-
1092
- // src/plugins/react/generators/generic.ts
1093
- init_format();
1094
- var GENERIC_HOOKS = [
1095
- "useAccount",
1096
- "useConnect",
1097
- "useDisconnect",
1098
- "useNetwork",
1099
- "useContract",
1100
- "useOpenSTXTransfer",
1101
- "useSignMessage",
1102
- "useDeployContract",
1103
- "useReadContract",
1104
- "useTransaction",
1105
- "useBlock",
1106
- "useAccountTransactions",
1107
- "useWaitForTransaction"
1108
- ];
1109
- async function generateGenericHooks(excludeList = []) {
1110
- const hooksToGenerate = GENERIC_HOOKS.filter((hookName) => !excludeList.includes(hookName));
1111
- const imports = `import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
1112
- import { useState, useCallback } from 'react'
1113
- import { useSecondLayerConfig } from './provider'
1114
- import { connect, disconnect, isConnected, request } from '@secondlayer/stacks/connect'
1115
- import { Cl } from '@secondlayer/stacks/clarity'
1116
- import type { ExtractFunctionArgs, ExtractFunctionNames, AbiContract } from '@secondlayer/stacks/clarity'
1117
- import type { PostCondition } from '@secondlayer/stacks/postconditions'
1118
- import { validateStacksAddress } from '@secondlayer/stacks/utils'
1119
- import { createPublicClient, http } from '@secondlayer/stacks'
1120
- import { getTransaction, waitForTransactionReceipt } from '@secondlayer/stacks/actions'
1121
-
1122
- const API_URLS: Record<string, string> = {
1123
- mainnet: 'https://api.hiro.so',
1124
- testnet: 'https://api.testnet.hiro.so',
1125
- devnet: 'http://localhost:3999'
1126
- }
1127
-
1128
- function getStacksClient({ network, apiUrl }: { network?: string; apiUrl?: string }) {
1129
- return createPublicClient({ transport: http(apiUrl || API_URLS[network || 'mainnet']) })
1130
- }
1131
-
1132
- async function callReadOnly(params: {
1133
- contractAddress: string;
1134
- contractName: string;
1135
- functionName: string;
1136
- functionArgs: any[];
1137
- senderAddress: string;
1138
- network?: string;
1139
- apiUrl?: string;
1140
- }): Promise<any> {
1141
- const { serializeCV, deserializeCV } = await import('@secondlayer/stacks/clarity')
1142
- const baseUrl = params.apiUrl || API_URLS[params.network || 'mainnet']
1143
- const response = await fetch(
1144
- \`\${baseUrl}/v2/contracts/call-read/\${params.contractAddress}/\${params.contractName}/\${params.functionName}\`,
1145
- {
1146
- method: 'POST',
1147
- headers: { 'Content-Type': 'application/json' },
1148
- body: JSON.stringify({
1149
- sender: params.senderAddress,
1150
- arguments: params.functionArgs.map((arg: any) => \`0x\${serializeCV(arg)}\`)
1151
- })
1152
- }
1153
- )
1154
- if (!response.ok) throw new Error(\`Read-only call failed: \${response.statusText}\`)
1155
- const data = await response.json()
1156
- if (!data.okay) throw new Error(data.cause ?? 'Read-only call failed')
1157
- return deserializeCV(data.result)
1158
- }
1159
-
1160
- async function fetchBlock({ height, network, apiUrl }: { height: number; network?: string; apiUrl?: string }): Promise<any> {
1161
- const baseUrl = apiUrl || API_URLS[network || 'mainnet']
1162
- const response = await fetch(\`\${baseUrl}/extended/v1/block/by_height/\${height}\`)
1163
- if (!response.ok) throw new Error(\`Failed to fetch block: \${response.statusText}\`)
1164
- return response.json()
1165
- }
1166
-
1167
- async function fetchAccountTransactions({ address, network, apiUrl }: { address: string; network?: string; apiUrl?: string }): Promise<any> {
1168
- const baseUrl = apiUrl || API_URLS[network || 'mainnet']
1169
- const response = await fetch(\`\${baseUrl}/extended/v1/address/\${address}/transactions\`)
1170
- if (!response.ok) throw new Error(\`Failed to fetch transactions: \${response.statusText}\`)
1171
- return response.json()
1172
- }`;
1173
- const header = `/**
1174
- * Generated generic Stacks React hooks
1175
- * DO NOT EDIT MANUALLY
1176
- */`;
1177
- const hooksCode = hooksToGenerate.map((hookName) => generateGenericHook(hookName)).filter(Boolean).join(`
1178
-
1179
- `);
1180
- const code = `${imports}
1181
-
1182
- ${header}
1183
-
1184
- ${hooksCode}`;
1185
- return formatCode(code);
1186
- }
1187
- function generateGenericHook(hookName) {
1188
- switch (hookName) {
1189
- case "useAccount":
1190
- return `export function useAccount() {
1191
- const config = useSecondLayerConfig()
1192
-
1193
- return useQuery({
1194
- queryKey: ['stacks-account', config.network],
1195
- queryFn: async () => {
1196
- try {
1197
- // Check if already connected
1198
- const connected = isConnected()
1199
-
1200
- if (!connected) {
1201
- return {
1202
- address: undefined,
1203
- addresses: undefined,
1204
- isConnected: false,
1205
- isConnecting: false,
1206
- isDisconnected: true,
1207
- status: 'disconnected' as const
1208
- }
1209
- }
1210
-
1211
- // Get addresses via SIP-030
1212
- const result = await request('stx_getAddresses')
1213
-
1214
- if (!result || !result.addresses || result.addresses.length === 0) {
1215
- return {
1216
- address: undefined,
1217
- addresses: undefined,
1218
- isConnected: false,
1219
- isConnecting: false,
1220
- isDisconnected: true,
1221
- status: 'disconnected' as const
1222
- }
1223
- }
1224
-
1225
- // Extract STX addresses from the response
1226
- const stxAddresses = result.addresses
1227
- .filter((addr: any) => addr.address.startsWith('SP') || addr.address.startsWith('ST'))
1228
- .map((addr: any) => addr.address)
1229
-
1230
- return {
1231
- address: stxAddresses[0] || undefined,
1232
- addresses: stxAddresses,
1233
- isConnected: true,
1234
- isConnecting: false,
1235
- isDisconnected: false,
1236
- status: 'connected' as const
1237
- }
1238
- } catch (error) {
1239
- // Handle case where wallet is not available or user rejected
1240
- return {
1241
- address: undefined,
1242
- addresses: undefined,
1243
- isConnected: false,
1244
- isConnecting: false,
1245
- isDisconnected: true,
1246
- status: 'disconnected' as const
1247
- }
1248
- }
1249
- },
1250
- refetchOnWindowFocus: false,
1251
- retry: false,
1252
- staleTime: 1000 * 60 * 5, // 5 minutes
1253
- refetchInterval: 1000 * 30, // Refetch every 30 seconds to detect wallet changes
1254
- })
1255
- }`;
1256
- case "useConnect":
1257
- return `export function useConnect() {
1258
- const queryClient = useQueryClient()
1259
-
1260
- const mutation = useMutation({
1261
- mutationFn: async () => {
1262
- // SIP-030 connect
1263
- return await connect()
1264
- },
1265
- onSuccess: () => {
1266
- // Invalidate account queries to refetch connection state
1267
- queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
1268
- },
1269
- onError: (error) => {
1270
- console.error('Connection failed:', error)
1271
- }
1272
- })
1273
-
1274
- return {
1275
- // Custom connect function that works without arguments
1276
- connect: () => {
1277
- return mutation.mutate(undefined)
1278
- },
1279
- connectAsync: async () => {
1280
- return mutation.mutateAsync(undefined)
1281
- },
1282
- // Expose all the mutation state
1283
- isPending: mutation.isPending,
1284
- isError: mutation.isError,
1285
- isSuccess: mutation.isSuccess,
1286
- error: mutation.error,
1287
- data: mutation.data,
1288
- reset: mutation.reset,
1289
- // Keep the original mutate/mutateAsync for advanced users
1290
- mutate: mutation.mutate,
1291
- mutateAsync: mutation.mutateAsync
1292
- }
1293
- }`;
1294
- case "useDisconnect":
1295
- return `export function useDisconnect() {
1296
- const queryClient = useQueryClient()
1297
-
1298
- const mutation = useMutation({
1299
- mutationFn: async () => {
1300
- // SIP-030 disconnect
1301
- return await disconnect()
1302
- },
1303
- onSuccess: () => {
1304
- // Clear all cached data on disconnect
1305
- queryClient.clear()
1306
- },
1307
- onError: (error) => {
1308
- console.error('Disconnect failed:', error)
1309
- }
1310
- })
1311
-
1312
- return {
1313
- // Custom disconnect function
1314
- disconnect: () => {
1315
- return mutation.mutate()
1316
- },
1317
- disconnectAsync: async () => {
1318
- return mutation.mutateAsync()
1319
- },
1320
- // Expose all the mutation state
1321
- isPending: mutation.isPending,
1322
- isError: mutation.isError,
1323
- isSuccess: mutation.isSuccess,
1324
- error: mutation.error,
1325
- data: mutation.data,
1326
- reset: mutation.reset,
1327
- // Keep the original mutate/mutateAsync for advanced users
1328
- mutate: mutation.mutate,
1329
- mutateAsync: mutation.mutateAsync
1330
- }
1331
- }`;
1332
- case "useNetwork":
1333
- return `export function useNetwork() {
1334
- const config = useSecondLayerConfig()
1335
-
1336
- return useQuery({
1337
- queryKey: ['stacks-network', config.network],
1338
- queryFn: async () => {
1339
- // Currently read-only from config
1340
- // Future: Use request('stx_getNetworks') when wallet support improves
1341
- const network = config.network
1342
-
1343
- return {
1344
- network,
1345
- isMainnet: network === 'mainnet',
1346
- isTestnet: network === 'testnet',
1347
- isDevnet: network === 'devnet',
1348
- // Future: Add switchNetwork when wallets support stx_networkChange
1349
- // switchNetwork: async (newNetwork: string) => {
1350
- // return await request('wallet_changeNetwork', { network: newNetwork })
1351
- // }
1352
- }
1353
- },
1354
- staleTime: Infinity, // Network config rarely changes
1355
- refetchOnWindowFocus: false,
1356
- retry: false
1357
- })
1358
- }`;
1359
- case "useContract":
1360
- return USE_CONTRACT_TEMPLATE;
1361
- case "useReadContract":
1362
- return `export function useReadContract<TArgs = any, TResult = any>(params: {
1363
- contractAddress: string;
1364
- contractName: string;
1365
- functionName: string;
1366
- args?: TArgs;
1367
- network?: 'mainnet' | 'testnet' | 'devnet';
1368
- enabled?: boolean;
1369
- }) {
1370
- const config = useSecondLayerConfig()
1371
-
1372
- return useQuery<TResult>({
1373
- queryKey: ['read-contract', params.contractAddress, params.contractName, params.functionName, params.args, params.network || config.network],
1374
- queryFn: async () => {
1375
- // Args must already be ClarityValues (build them with Cl.* or a generated
1376
- // contract method's descriptor). Positional array, ABI declaration order.
1377
- let functionArgs: any[] = []
1378
-
1379
- if (params.args) {
1380
- if (Array.isArray(params.args)) {
1381
- functionArgs = params.args
1382
- } else if (typeof params.args === 'object') {
1383
- functionArgs = Object.values(params.args)
1384
- } else {
1385
- functionArgs = [params.args]
1386
- }
1387
- }
1388
-
1389
- return await callReadOnly({
1390
- contractAddress: params.contractAddress,
1391
- contractName: params.contractName,
1392
- functionName: params.functionName,
1393
- functionArgs,
1394
- network: params.network || config.network || 'mainnet',
1395
- apiUrl: config.apiUrl,
1396
- senderAddress: config.senderAddress || '${DEFAULT_SENDER_ADDRESS}'
1397
- }) as TResult
1398
- },
1399
- enabled: params.enabled ?? true
1400
- })
1401
- }`;
1402
- case "useTransaction":
1403
- return `export function useTransaction(txId?: string) {
1404
- const config = useSecondLayerConfig()
1405
-
1406
- return useQuery({
1407
- queryKey: ['transaction', txId, config.network],
1408
- queryFn: () => getTransaction(getStacksClient(config), { txid: txId! }),
1409
- enabled: !!txId
1410
- })
1411
- }`;
1412
- case "useBlock":
1413
- return `export function useBlock(height?: number) {
1414
- const config = useSecondLayerConfig()
1415
-
1416
- return useQuery({
1417
- queryKey: ['block', height, config.network],
1418
- queryFn: () => fetchBlock({
1419
- height: height!,
1420
- network: config.network,
1421
- apiUrl: config.apiUrl
1422
- }),
1423
- enabled: typeof height === 'number'
1424
- })
1425
- }`;
1426
- case "useAccountTransactions":
1427
- return `export function useAccountTransactions(address?: string) {
1428
- const config = useSecondLayerConfig()
1429
-
1430
- return useQuery({
1431
- queryKey: ['account-transactions', address, config.network],
1432
- queryFn: () => fetchAccountTransactions({
1433
- address: address!,
1434
- network: config.network,
1435
- apiUrl: config.apiUrl
1436
- }),
1437
- enabled: !!address
1438
- })
1439
- }`;
1440
- case "useWaitForTransaction":
1441
- return `export function useWaitForTransaction(txId?: string, options?: { confirmations?: number }) {
1442
- const config = useSecondLayerConfig()
1443
-
1444
- // waitForTransactionReceipt polls internally (reorg-tolerant, typed
1445
- // abort/drop/timeout rejections) — no react-query refetchInterval needed.
1446
- return useQuery({
1447
- queryKey: ['wait-for-transaction', txId, config.network, options?.confirmations],
1448
- queryFn: () => waitForTransactionReceipt(getStacksClient(config), {
1449
- txid: txId!,
1450
- confirmations: options?.confirmations
1451
- }),
1452
- enabled: !!txId,
1453
- retry: false,
1454
- staleTime: Infinity
1455
- })
1456
- }`;
1457
- case "useOpenSTXTransfer":
1458
- return `export function useOpenSTXTransfer() {
1459
- const config = useSecondLayerConfig()
1460
- const queryClient = useQueryClient()
1461
-
1462
- const mutation = useMutation({
1463
- mutationFn: async (params: {
1464
- recipient: string;
1465
- amount: string | number;
1466
- memo?: string;
1467
- network?: string;
1468
- }) => {
1469
- const { recipient, amount, memo } = params
1470
- const network = params.network || config.network || 'mainnet'
1471
-
1472
- return await request('stx_transferStx', {
1473
- recipient,
1474
- amount: amount.toString(),
1475
- memo,
1476
- network,
1477
- })
1478
- },
1479
- onSuccess: () => {
1480
- queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
1481
- },
1482
- onError: (error) => {
1483
- console.error('STX transfer failed:', error)
1484
- }
1485
- })
1486
-
1487
- const openSTXTransfer = useCallback(async (params: {
1488
- recipient: string;
1489
- amount: string | number;
1490
- memo?: string;
1491
- network?: string;
1492
- }) => {
1493
- return mutation.mutateAsync(params)
1494
- }, [mutation])
1495
-
1496
- return {
1497
- openSTXTransfer,
1498
- isPending: mutation.isPending,
1499
- isError: mutation.isError,
1500
- isSuccess: mutation.isSuccess,
1501
- error: mutation.error,
1502
- data: mutation.data,
1503
- reset: mutation.reset
1504
- }
1505
- }`;
1506
- case "useSignMessage":
1507
- return `export function useSignMessage() {
1508
- const config = useSecondLayerConfig()
1509
-
1510
- const mutation = useMutation({
1511
- mutationFn: async (params: {
1512
- message: string;
1513
- network?: string;
1514
- }) => {
1515
- const { message } = params
1516
- const network = params.network || config.network || 'mainnet'
1517
-
1518
- return await request('stx_signMessage', {
1519
- message,
1520
- network,
1521
- })
1522
- },
1523
- onError: (error) => {
1524
- console.error('Message signing failed:', error)
1525
- }
1526
- })
1527
-
1528
- const signMessage = useCallback(async (params: {
1529
- message: string;
1530
- network?: string;
1531
- }) => {
1532
- return mutation.mutateAsync(params)
1533
- }, [mutation])
1534
-
1535
- return {
1536
- signMessage,
1537
- isPending: mutation.isPending,
1538
- isError: mutation.isError,
1539
- isSuccess: mutation.isSuccess,
1540
- error: mutation.error,
1541
- data: mutation.data,
1542
- reset: mutation.reset
1543
- }
1544
- }`;
1545
- case "useDeployContract":
1546
- return `export function useDeployContract() {
1547
- const config = useSecondLayerConfig()
1548
- const queryClient = useQueryClient()
1549
-
1550
- const mutation = useMutation({
1551
- mutationFn: async (params: {
1552
- contractName: string;
1553
- codeBody: string;
1554
- network?: string;
1555
- }) => {
1556
- const { contractName, codeBody } = params
1557
- const network = params.network || config.network || 'mainnet'
1558
-
1559
- return await request('stx_deployContract', {
1560
- name: contractName,
1561
- clarityCode: codeBody,
1562
- network,
1563
- })
1564
- },
1565
- onSuccess: () => {
1566
- queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
1567
- },
1568
- onError: (error) => {
1569
- console.error('Contract deployment failed:', error)
1570
- }
1571
- })
1572
-
1573
- const deployContract = useCallback(async (params: {
1574
- contractName: string;
1575
- codeBody: string;
1576
- network?: string;
1577
- }) => {
1578
- return mutation.mutateAsync(params)
1579
- }, [mutation])
1580
-
1581
- return {
1582
- deployContract,
1583
- isPending: mutation.isPending,
1584
- isError: mutation.isError,
1585
- isSuccess: mutation.isSuccess,
1586
- error: mutation.error,
1587
- data: mutation.data,
1588
- reset: mutation.reset
1589
- }
1590
- }`;
1591
- default:
1592
- return "";
1593
- }
1594
- }
1595
-
1596
- // src/plugins/react/provider/index.ts
1597
- init_format();
1598
- async function generateProvider() {
1599
- const code = `/**
1600
- * Generated SecondLayer React Provider
1601
- * DO NOT EDIT MANUALLY
1602
- */
1603
-
1604
- import React, { createContext, useContext } from 'react'
1605
-
1606
- /**
1607
- * SecondLayer configuration interface
1608
- */
1609
- export interface SecondLayerReactConfig {
1610
- /**
1611
- * Network to use for API calls
1612
- */
1613
- network: 'mainnet' | 'testnet' | 'devnet'
1614
-
1615
- /**
1616
- * API key for Stacks API (optional)
1617
- */
1618
- apiKey?: string
1619
-
1620
- /**
1621
- * Base URL for Stacks API (optional override)
1622
- */
1623
- apiUrl?: string
1624
-
1625
- /**
1626
- * Default sender address for read-only calls
1627
- */
1628
- senderAddress?: string
1629
- }
1630
-
1631
- /**
1632
- * Provider component props
1633
- */
1634
- export interface SecondLayerProviderProps {
1635
- children: React.ReactNode
1636
- config: SecondLayerReactConfig
1637
- }
1638
-
1639
- /**
1640
- * React context for SecondLayer configuration
1641
- */
1642
- const SecondLayerContext = createContext<SecondLayerReactConfig | undefined>(undefined)
1643
- SecondLayerContext.displayName = 'SecondLayerContext'
1644
-
1645
- /**
1646
- * Create a SecondLayer React configuration with defaults
1647
- */
1648
- export function createSecondLayerConfig(config: SecondLayerReactConfig): SecondLayerReactConfig {
1649
- return {
1650
- network: config.network,
1651
- apiKey: config.apiKey,
1652
- apiUrl: config.apiUrl,
1653
- senderAddress: config.senderAddress || '${DEFAULT_SENDER_ADDRESS}'
1654
- }
1655
- }
1656
-
1657
- /**
1658
- * Provider component that makes SecondLayer configuration available to hooks
1659
- */
1660
- export function SecondLayerProvider({ children, config }: SecondLayerProviderProps) {
1661
- const resolvedConfig = createSecondLayerConfig(config)
1662
-
1663
- return (
1664
- <SecondLayerContext.Provider value={resolvedConfig}>
1665
- {children}
1666
- </SecondLayerContext.Provider>
1667
- )
1668
- }
1669
-
1670
- /**
1671
- * Hook to access the SecondLayer configuration
1672
- */
1673
- export function useSecondLayerConfig(): SecondLayerReactConfig {
1674
- const context = useContext(SecondLayerContext)
1675
-
1676
- if (context === undefined) {
1677
- throw new Error(
1678
- 'useSecondLayerConfig must be used within a SecondLayerProvider. ' +
1679
- 'Make sure to wrap your app with <SecondLayerProvider config={{...}}>'
1680
- )
1681
- }
1682
-
1683
- return context
1684
- }`;
1685
- return formatCode(code);
1686
- }
1687
-
1688
- // src/plugins/react/index.ts
1689
- var react = (options = {}) => {
1690
- const excludeList = options.exclude || [];
1691
- return {
1692
- name: "@secondlayer/cli/plugin-react",
1693
- version: "1.0.0",
1694
- async generate(context) {
1695
- if (options.debug) {
1696
- context.logger.debug(`React plugin generating hooks (excluding: ${excludeList.join(", ") || "none"})`);
1697
- }
1698
- const provider = await generateProvider();
1699
- context.addOutput("provider", {
1700
- path: "./src/generated/provider.tsx",
1701
- content: provider,
1702
- type: "config"
1703
- });
1704
- const genericHooks = await generateGenericHooks(excludeList);
1705
- context.addOutput("generic-hooks", {
1706
- path: "./src/generated/hooks.ts",
1707
- content: genericHooks,
1708
- type: "hooks"
1709
- });
1710
- if (context.contracts.length > 0) {
1711
- const contractHooks = await generateContractHooks(context.contracts, excludeList);
1712
- if (contractHooks.trim()) {
1713
- context.addOutput("contract-hooks", {
1714
- path: "./src/generated/contract-hooks.ts",
1715
- content: contractHooks,
1716
- type: "hooks"
1717
- });
1718
- }
1719
- }
1720
- if (options.debug) {
1721
- context.logger.success(`React plugin generated ${context.contracts.length} contract hook sets`);
1722
- }
1723
- }
1724
- };
1725
- };
1726
-
1727
- // src/plugins/index.ts
1728
- function filterByOptions(items, options = {}) {
1729
- let filtered = items;
1730
- if (options.include && options.include.length > 0) {
1731
- filtered = filtered.filter((item) => options.include?.some((pattern) => item.name.includes(pattern) || item.name.match(new RegExp(pattern))));
1732
- }
1733
- if (options.exclude && options.exclude.length > 0) {
1734
- filtered = filtered.filter((item) => !options.exclude?.some((pattern) => item.name.includes(pattern) || item.name.match(new RegExp(pattern))));
1735
- }
1736
- return filtered;
1737
- }
1738
- function createPlugin(name, version, implementation) {
1739
- return {
1740
- name,
1741
- version,
1742
- ...implementation
1743
- };
1744
- }
1745
- export {
1746
- react,
1747
- hasClarinetProject,
1748
- filterByOptions,
1749
- createPlugin,
1750
- clarinet,
1751
- PluginManager
1752
- };
1753
-
1754
- //# debugId=3858A2BEFB777D7764756E2164756E21
1755
- //# sourceMappingURL=index.js.map