@secondlayer/cli 3.3.0 → 3.3.2

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.
@@ -0,0 +1,2781 @@
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
+ biome.registerProjectFolder();
103
+ }
104
+ return biome;
105
+ }
106
+ async function formatCode(code) {
107
+ const b = await getBiome();
108
+ const linted = b.lintContent(code, {
109
+ filePath: "generated.ts",
110
+ fixFileMode: "SafeFixes"
111
+ });
112
+ const formatted = b.formatContent(linted.content, {
113
+ filePath: "generated.ts"
114
+ });
115
+ return formatted.content;
116
+ }
117
+ var biome = null;
118
+ var init_format = () => {};
119
+
120
+ // src/core/plugin-manager.ts
121
+ import { promises as fs } from "fs";
122
+ import path from "path";
123
+ import { isValidAddress as _validateStacksAddress } from "@secondlayer/stacks";
124
+ import { getErrorMessage } from "@secondlayer/shared";
125
+ import { toCamelCase } from "@secondlayer/stacks/clarity";
126
+
127
+ class PluginManager {
128
+ plugins = [];
129
+ logger;
130
+ utils;
131
+ executionContext;
132
+ constructor() {
133
+ this.logger = this.createLogger();
134
+ this.utils = this.createUtils();
135
+ this.executionContext = {
136
+ phase: "config",
137
+ startTime: Date.now(),
138
+ results: new Map
139
+ };
140
+ }
141
+ register(plugin) {
142
+ if (!plugin.name || !plugin.version) {
143
+ throw new Error("Plugin must have a name and version");
144
+ }
145
+ const existing = this.plugins.find((p) => p.name === plugin.name);
146
+ if (existing) {
147
+ throw new Error(`Plugin "${plugin.name}" is already registered (version ${existing.version})`);
148
+ }
149
+ this.plugins.push(plugin);
150
+ this.logger.debug(`Registered plugin: ${plugin.name}@${plugin.version}`);
151
+ }
152
+ getPlugins() {
153
+ return [...this.plugins];
154
+ }
155
+ async transformConfig(config) {
156
+ this.executionContext.phase = "config";
157
+ let transformedConfig = { ...config };
158
+ for (const plugin of this.plugins) {
159
+ if (plugin.transformConfig) {
160
+ this.executionContext.currentPlugin = plugin;
161
+ try {
162
+ const result = await plugin.transformConfig(transformedConfig);
163
+ transformedConfig = result;
164
+ this.recordHookResult(plugin.name, "transformConfig", {
165
+ success: true
166
+ });
167
+ } catch (error) {
168
+ this.recordHookResult(plugin.name, "transformConfig", {
169
+ success: false,
170
+ error: error instanceof Error ? error : new Error(getErrorMessage(error))
171
+ });
172
+ throw new Error(`Plugin "${plugin.name}" failed during config transformation: ${getErrorMessage(error)}`);
173
+ }
174
+ }
175
+ }
176
+ const resolvedConfig = {
177
+ ...transformedConfig,
178
+ plugins: this.plugins
179
+ };
180
+ return resolvedConfig;
181
+ }
182
+ async transformContracts(contracts, _config) {
183
+ const processedContracts = [];
184
+ for (let contract of contracts) {
185
+ if (isClarinetContract(contract) && contract.abi) {
186
+ processedContracts.push(this.contractToProcessed(contract, "clarinet"));
187
+ continue;
188
+ }
189
+ if (isDirectFileContract(contract) && contract.abi) {
190
+ processedContracts.push(this.contractToProcessed(contract, "direct"));
191
+ continue;
192
+ }
193
+ for (const plugin of this.plugins) {
194
+ if (plugin.transformContract) {
195
+ this.executionContext.currentPlugin = plugin;
196
+ try {
197
+ contract = await plugin.transformContract(contract);
198
+ this.recordHookResult(plugin.name, "transformContract", {
199
+ success: true
200
+ });
201
+ } catch (error) {
202
+ this.recordHookResult(plugin.name, "transformContract", {
203
+ success: false,
204
+ error: error instanceof Error ? error : new Error(getErrorMessage(error))
205
+ });
206
+ this.logger.warn(`Plugin "${plugin.name}" failed to transform contract: ${getErrorMessage(error)}`);
207
+ }
208
+ }
209
+ }
210
+ if (contract.abi) {
211
+ processedContracts.push(this.contractToProcessed(contract, "api"));
212
+ }
213
+ }
214
+ return processedContracts;
215
+ }
216
+ async executeHook(hookName, context) {
217
+ for (const plugin of this.plugins) {
218
+ const hook = plugin[hookName];
219
+ if (typeof hook === "function") {
220
+ this.executionContext.currentPlugin = plugin;
221
+ try {
222
+ await hook.call(plugin, context);
223
+ this.recordHookResult(plugin.name, hookName, {
224
+ success: true
225
+ });
226
+ } catch (error) {
227
+ this.recordHookResult(plugin.name, hookName, {
228
+ success: false,
229
+ error: error instanceof Error ? error : new Error(getErrorMessage(error))
230
+ });
231
+ this.logger.error(`Plugin "${plugin.name}" failed during ${hookName}: ${getErrorMessage(error)}`);
232
+ }
233
+ }
234
+ }
235
+ }
236
+ async executeGeneration(contracts, config) {
237
+ this.executionContext.phase = "generate";
238
+ const outputs = new Map;
239
+ const context = {
240
+ config,
241
+ logger: this.logger,
242
+ utils: this.utils,
243
+ contracts,
244
+ outputs,
245
+ augment: (outputKey, contractName, content) => {
246
+ this.augmentOutput(outputs, outputKey, contractName, content);
247
+ },
248
+ addOutput: (key, output) => {
249
+ outputs.set(key, output);
250
+ }
251
+ };
252
+ await this.executeHook("beforeGenerate", context);
253
+ await this.executeHook("generate", context);
254
+ await this.executeHook("afterGenerate", context);
255
+ return outputs;
256
+ }
257
+ async transformOutputs(outputs) {
258
+ this.executionContext.phase = "output";
259
+ const transformedOutputs = new Map;
260
+ for (const [key, output] of outputs) {
261
+ let transformedContent = output.content;
262
+ for (const plugin of this.plugins) {
263
+ if (plugin.transformOutput) {
264
+ this.executionContext.currentPlugin = plugin;
265
+ try {
266
+ transformedContent = await plugin.transformOutput(transformedContent, output.type || "other");
267
+ this.recordHookResult(plugin.name, "transformOutput", {
268
+ success: true
269
+ });
270
+ } catch (error) {
271
+ this.recordHookResult(plugin.name, "transformOutput", {
272
+ success: false,
273
+ error: error instanceof Error ? error : new Error(getErrorMessage(error))
274
+ });
275
+ this.logger.warn(`Plugin "${plugin.name}" failed to transform output: ${getErrorMessage(error)}`);
276
+ }
277
+ }
278
+ }
279
+ transformedOutputs.set(key, {
280
+ ...output,
281
+ content: transformedContent
282
+ });
283
+ }
284
+ return transformedOutputs;
285
+ }
286
+ async writeOutputs(outputs) {
287
+ for (const [, output] of outputs) {
288
+ try {
289
+ const resolvedPath = path.resolve(process.cwd(), output.path);
290
+ await this.utils.ensureDir(path.dirname(resolvedPath));
291
+ await this.utils.writeFile(resolvedPath, output.content);
292
+ } catch (error) {
293
+ this.logger.error(`Failed to write ${output.path}: ${getErrorMessage(error)}`);
294
+ throw error;
295
+ }
296
+ }
297
+ }
298
+ getExecutionResults() {
299
+ return new Map(this.executionContext.results);
300
+ }
301
+ contractToProcessed(contract, source) {
302
+ const address = typeof contract.address === "string" ? contract.address : "";
303
+ const parsed = parseContractId(address);
304
+ return {
305
+ name: contract.name || parsed.contractName || "unknown",
306
+ address: parsed.address || "unknown",
307
+ contractName: parsed.contractName || contract.name || "unknown",
308
+ abi: contract.abi,
309
+ source: source === "api" ? "api" : "local",
310
+ metadata: contract.metadata ?? { source }
311
+ };
312
+ }
313
+ augmentOutput(outputs, outputKey, contractName, content) {
314
+ const existing = outputs.get(outputKey);
315
+ if (!existing) {
316
+ this.logger.warn(`Cannot augment non-existent output: ${outputKey}`);
317
+ return;
318
+ }
319
+ const augmentedContent = `${existing.content}
320
+
321
+ // Augmented by plugin for ${contractName}
322
+ ${JSON.stringify(content, null, 2)}`;
323
+ outputs.set(outputKey, {
324
+ ...existing,
325
+ content: augmentedContent
326
+ });
327
+ }
328
+ recordHookResult(pluginName, hookName, result) {
329
+ const key = `${pluginName}:${hookName}`;
330
+ const existing = this.executionContext.results.get(key) || [];
331
+ existing.push({ ...result, plugin: pluginName });
332
+ this.executionContext.results.set(key, existing);
333
+ }
334
+ createLogger() {
335
+ return {
336
+ info: (message) => console.log(`ℹ️ ${message}`),
337
+ warn: (message) => console.warn(`⚠️ ${message}`),
338
+ error: (message) => console.error(`❌ ${message}`),
339
+ debug: (message) => {
340
+ if (process.env.DEBUG) {
341
+ console.log(`\uD83D\uDC1B ${message}`);
342
+ }
343
+ },
344
+ success: (message) => console.log(`✅ ${message}`)
345
+ };
346
+ }
347
+ createUtils() {
348
+ return {
349
+ toCamelCase,
350
+ toKebabCase: (str) => {
351
+ return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
352
+ },
353
+ validateAddress: (address) => {
354
+ return validateStacksAddress(parseContractId(address).address);
355
+ },
356
+ parseContractId: (contractId) => {
357
+ return parseContractId(contractId);
358
+ },
359
+ formatCode: async (code) => {
360
+ const { formatCode: formatCode2 } = await Promise.resolve().then(() => (init_format(), exports_format));
361
+ return formatCode2(code);
362
+ },
363
+ resolvePath: (relativePath) => {
364
+ return path.resolve(process.cwd(), relativePath);
365
+ },
366
+ fileExists: async (filePath) => {
367
+ try {
368
+ await fs.access(filePath);
369
+ return true;
370
+ } catch {
371
+ return false;
372
+ }
373
+ },
374
+ readFile: async (filePath) => {
375
+ return fs.readFile(filePath, "utf-8");
376
+ },
377
+ writeFile: async (filePath, content) => {
378
+ await fs.writeFile(filePath, content, "utf-8");
379
+ },
380
+ ensureDir: async (dirPath) => {
381
+ await fs.mkdir(dirPath, { recursive: true });
382
+ }
383
+ };
384
+ }
385
+ }
386
+ var validateStacksAddress;
387
+ var init_plugin_manager = __esm(() => {
388
+ validateStacksAddress = _validateStacksAddress;
389
+ });
390
+
391
+ // src/utils/clarity-conversion.ts
392
+ import {
393
+ isAbiBuffer,
394
+ isAbiList,
395
+ isAbiOptional,
396
+ isAbiResponse,
397
+ isAbiStringAscii,
398
+ isAbiStringUtf8,
399
+ isAbiTuple,
400
+ toCamelCase as toCamelCase2
401
+ } from "@secondlayer/stacks/clarity";
402
+ function generateClarityConversion(argName, argType) {
403
+ const type = argType.type;
404
+ if (typeof type === "string") {
405
+ switch (type) {
406
+ case "uint128":
407
+ return `Cl.uint(${argName})`;
408
+ case "int128":
409
+ return `Cl.int(${argName})`;
410
+ case "bool":
411
+ return `Cl.bool(${argName})`;
412
+ case "principal":
413
+ case "trait_reference":
414
+ return `(() => {
415
+ const [address, contractName] = ${argName}.split(".") as [string, string | undefined];
416
+ if (!validateStacksAddress(address)) {
417
+ throw new Error("Invalid Stacks address format");
418
+ }
419
+ if (contractName !== undefined) {
420
+ if (!CONTRACT_NAME_REGEX.test(contractName)) {
421
+ throw new Error("Invalid contract name format: must start with letter and contain only letters, numbers, and hyphens");
422
+ }
423
+ return Cl.contractPrincipal(address, contractName);
424
+ }
425
+ return Cl.standardPrincipal(${argName});
426
+ })()`;
427
+ default:
428
+ return `${argName}`;
429
+ }
430
+ }
431
+ if (isAbiStringAscii(type)) {
432
+ return `Cl.stringAscii(${argName})`;
433
+ }
434
+ if (isAbiStringUtf8(type)) {
435
+ return `Cl.stringUtf8(${argName})`;
436
+ }
437
+ if (isAbiBuffer(type)) {
438
+ return `(() => {
439
+ const value = ${argName};
440
+ if (value instanceof Uint8Array) {
441
+ return Cl.buffer(value);
442
+ }
443
+ if (typeof value === 'object' && value !== null && value.type && value.value) {
444
+ switch (value.type) {
445
+ case 'ascii':
446
+ return Cl.bufferFromAscii(value.value);
447
+ case 'utf8':
448
+ return Cl.bufferFromUtf8(value.value);
449
+ case 'hex':
450
+ return Cl.bufferFromHex(value.value);
451
+ default:
452
+ throw new Error(\`Unsupported buffer type: \${value.type}\`);
453
+ }
454
+ }
455
+ if (typeof value === 'string') {
456
+ if (value.startsWith('0x') || /^[0-9a-fA-F]+$/.test(value)) {
457
+ return Cl.bufferFromHex(value);
458
+ }
459
+ const hasNonAscii = value.split('').some(char => char.charCodeAt(0) > 127);
460
+ if (hasNonAscii) {
461
+ return Cl.bufferFromUtf8(value);
462
+ }
463
+ return Cl.bufferFromAscii(value);
464
+ }
465
+ throw new Error(\`Invalid buffer value: \${value}\`);
466
+ })()`;
467
+ }
468
+ if (isAbiOptional(type)) {
469
+ const innerConversion = generateClarityConversion(argName, {
470
+ type: type.optional
471
+ });
472
+ return `${argName} !== null ? Cl.some(${innerConversion.replace(argName, `${argName}`)}) : Cl.none()`;
473
+ }
474
+ if (isAbiList(type)) {
475
+ const innerConversion = generateClarityConversion("item", {
476
+ type: type.list.type
477
+ });
478
+ const maxLength = type.list.length || 100;
479
+ return `(() => {
480
+ const listValue = ${argName};
481
+ if (listValue.length > ${maxLength}) {
482
+ throw new Error(\`List length \${listValue.length} exceeds max ${maxLength}\`);
483
+ }
484
+ return Cl.list(listValue.map(item => ${innerConversion}));
485
+ })()`;
486
+ }
487
+ if (isAbiTuple(type)) {
488
+ const requiredFields = type.tuple.map((f) => f.name);
489
+ const fieldNames = JSON.stringify(requiredFields);
490
+ const fields = type.tuple.map((field) => {
491
+ const camelFieldName = toCamelCase2(field.name);
492
+ const fieldConversion = generateClarityConversion(`tupleValue.${camelFieldName}`, { type: field.type });
493
+ return `"${field.name}": ${fieldConversion}`;
494
+ }).join(", ");
495
+ return `(() => {
496
+ const tupleValue = ${argName};
497
+ const requiredFields = ${fieldNames};
498
+ for (const fieldName of requiredFields) {
499
+ const camelName = fieldName.replace(/-([a-z])/g, (_: string, l: string) => l.toUpperCase());
500
+ if (!(fieldName in tupleValue) && !(camelName in tupleValue)) {
501
+ throw new Error(\`Missing tuple field: \${fieldName}\`);
502
+ }
503
+ }
504
+ return Cl.tuple({ ${fields} });
505
+ })()`;
506
+ }
507
+ if (isAbiResponse(type)) {
508
+ const okConversion = generateClarityConversion(`responseValue.ok`, {
509
+ type: type.response.ok
510
+ });
511
+ const errConversion = generateClarityConversion(`responseValue.err`, {
512
+ type: type.response.error
513
+ });
514
+ return `(() => {
515
+ const responseValue = ${argName};
516
+ const hasOk = 'ok' in responseValue;
517
+ const hasErr = 'err' in responseValue;
518
+ if (hasOk && !hasErr) {
519
+ return Cl.ok(${okConversion});
520
+ }
521
+ if (hasErr && !hasOk) {
522
+ return Cl.error(${errConversion});
523
+ }
524
+ throw new Error("Response must have exactly 'ok' or 'err' property");
525
+ })()`;
526
+ }
527
+ return `${argName}`;
528
+ }
529
+ var init_clarity_conversion = () => {};
530
+
531
+ // src/utils/type-mapping.ts
532
+ import {
533
+ isAbiBuffer as isAbiBuffer2,
534
+ isAbiList as isAbiList2,
535
+ isAbiOptional as isAbiOptional2,
536
+ isAbiResponse as isAbiResponse2,
537
+ isAbiStringAscii as isAbiStringAscii2,
538
+ isAbiStringUtf8 as isAbiStringUtf82,
539
+ isAbiTuple as isAbiTuple2,
540
+ toCamelCase as toCamelCase3
541
+ } from "@secondlayer/stacks/clarity";
542
+ function clarityTypeToTS(type) {
543
+ if (typeof type === "string") {
544
+ switch (type) {
545
+ case "uint128":
546
+ case "int128":
547
+ return "bigint";
548
+ case "bool":
549
+ return "boolean";
550
+ case "principal":
551
+ case "trait_reference":
552
+ return "string";
553
+ default: {
554
+ const typeStr = type;
555
+ if (typeStr === "none") {
556
+ return "null";
557
+ }
558
+ if (typeStr.includes("string") || typeStr.includes("ascii") || typeStr.includes("utf8")) {
559
+ return "string";
560
+ }
561
+ if (typeStr.includes("buff")) {
562
+ return "Uint8Array | string | { type: 'ascii' | 'utf8' | 'hex'; value: string }";
563
+ }
564
+ if (typeStr.includes("uint") || typeStr.includes("int")) {
565
+ return "bigint";
566
+ }
567
+ return "any";
568
+ }
569
+ }
570
+ }
571
+ if (isAbiBuffer2(type)) {
572
+ return "Uint8Array | string | { type: 'ascii' | 'utf8' | 'hex'; value: string }";
573
+ }
574
+ if (isAbiStringAscii2(type) || isAbiStringUtf82(type)) {
575
+ return "string";
576
+ }
577
+ if (isAbiOptional2(type)) {
578
+ const innerType = clarityTypeToTS(type.optional);
579
+ if (innerType.includes(" | ") && !innerType.startsWith("(")) {
580
+ return `(${innerType}) | null`;
581
+ }
582
+ return `${innerType} | null`;
583
+ }
584
+ if (isAbiList2(type)) {
585
+ const innerType = clarityTypeToTS(type.list.type);
586
+ if (innerType.includes(" | ") && !innerType.startsWith("(")) {
587
+ return `(${innerType})[]`;
588
+ }
589
+ return `${innerType}[]`;
590
+ }
591
+ if (isAbiTuple2(type)) {
592
+ const fields = type.tuple.map((field) => `${toCamelCase3(field.name)}: ${clarityTypeToTS(field.type)}`).join("; ");
593
+ return `{ ${fields} }`;
594
+ }
595
+ if (isAbiResponse2(type)) {
596
+ const okType = clarityTypeToTS(type.response.ok);
597
+ const errType = clarityTypeToTS(type.response.error);
598
+ return `{ ok: ${okType} } | { err: ${errType} }`;
599
+ }
600
+ return "any";
601
+ }
602
+ function getTypeForArg(arg) {
603
+ return clarityTypeToTS(arg.type);
604
+ }
605
+ var init_type_mapping = () => {};
606
+
607
+ // src/utils/generator-helpers.ts
608
+ import {
609
+ isAbiTuple as isAbiTuple3,
610
+ toCamelCase as toCamelCase4
611
+ } from "@secondlayer/stacks/clarity";
612
+ function generateArgsSignature(args) {
613
+ if (args.length === 0)
614
+ return "";
615
+ const argsTypes = args.map((arg) => {
616
+ const camelName = toCamelCase4(arg.name);
617
+ return `${camelName}: ${getTypeForArg(arg)}`;
618
+ }).join("; ");
619
+ return `args: { ${argsTypes} }, `;
620
+ }
621
+ function generateClarityArgs(args) {
622
+ if (args.length === 0)
623
+ return "";
624
+ return args.map((arg) => {
625
+ const argName = `args.${toCamelCase4(arg.name)}`;
626
+ return generateClarityConversion(argName, arg);
627
+ }).join(", ");
628
+ }
629
+ function generateMapKeyConversion(keyType) {
630
+ if (isAbiTuple3(keyType)) {
631
+ const fields = keyType.tuple.map((field) => {
632
+ const camelFieldName = toCamelCase4(field.name);
633
+ const fieldConversion = generateClarityConversion(`key.${camelFieldName}`, { type: field.type });
634
+ return `"${field.name}": ${fieldConversion}`;
635
+ }).join(", ");
636
+ return `Cl.tuple({ ${fields} })`;
637
+ }
638
+ return generateClarityConversion("key", { type: keyType });
639
+ }
640
+ var init_generator_helpers = __esm(() => {
641
+ init_clarity_conversion();
642
+ init_type_mapping();
643
+ });
644
+
645
+ // src/generators/contract.ts
646
+ import {
647
+ toCamelCase as toCamelCase5
648
+ } from "@secondlayer/stacks/clarity";
649
+ function generateNetworkUtils() {
650
+ return `/**
651
+ * API URLs for different networks
652
+ */
653
+ const API_URLS: Record<'mainnet' | 'testnet' | 'devnet', string> = {
654
+ mainnet: 'https://api.hiro.so',
655
+ testnet: 'https://api.testnet.hiro.so',
656
+ devnet: 'http://localhost:3999'
657
+ };
658
+
659
+ /**
660
+ * Infer network from Stacks address prefix
661
+ * SP/SM = mainnet, ST/SN = testnet
662
+ */
663
+ function inferNetworkFromAddress(address: string): 'mainnet' | 'testnet' | undefined {
664
+ if (address.startsWith('SP') || address.startsWith('SM')) return 'mainnet';
665
+ if (address.startsWith('ST') || address.startsWith('SN')) return 'testnet';
666
+ return undefined;
667
+ }
668
+
669
+ /**
670
+ * Get API URL, inferring network from contract address if not specified
671
+ */
672
+ function getApiUrl(
673
+ contractAddress: string,
674
+ explicitNetwork?: 'mainnet' | 'testnet' | 'devnet'
675
+ ): string {
676
+ const network = explicitNetwork ?? inferNetworkFromAddress(contractAddress) ?? 'mainnet';
677
+ return API_URLS[network];
678
+ }`;
679
+ }
680
+ function generateValidationUtils() {
681
+ return `/**
682
+ * Contract name validation regex
683
+ * Must start with letter, contain only letters, numbers, and hyphens, max 128 chars
684
+ */
685
+ const CONTRACT_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9\\-]{0,127}$/;`;
686
+ }
687
+ async function generateContractInterface(contracts) {
688
+ const imports = `import { Cl, validateStacksAddress } from '@secondlayer/stacks'`;
689
+ const header = `/**
690
+ * Generated by @secondlayer/cli
691
+ * DO NOT EDIT MANUALLY
692
+ *
693
+ * @requires @secondlayer/stacks - Install with: npm install @secondlayer/stacks
694
+ */`;
695
+ const validationUtils = generateValidationUtils();
696
+ const networkUtils = generateNetworkUtils();
697
+ const contractsCode = contracts.map((contract) => generateContract(contract)).join(`
698
+
699
+ `);
700
+ const code = `${imports}
701
+
702
+ ${header}
703
+
704
+ ${validationUtils}
705
+
706
+ ${networkUtils}
707
+
708
+ ${contractsCode}`;
709
+ return formatCode(code);
710
+ }
711
+ function generateContract(contract) {
712
+ const { name, address, contractName, abi } = contract;
713
+ const abiCode = generateAbiConstant(name, abi);
714
+ const methods = abi.functions.filter((func) => func.access !== "private").map((func) => generateMethod(func, address, contractName)).join(`,
715
+
716
+ `);
717
+ const mapsObject = generateMapsObject(abi.maps || [], address, contractName);
718
+ const varsObject = generateVarsObject(abi.variables || [], address, contractName);
719
+ const constantsObject = generateConstantsObject(abi.variables || [], address, contractName);
720
+ const allMembers = [methods, mapsObject, varsObject, constantsObject].filter(Boolean);
721
+ const contractCode = `export const ${name} = {
722
+ address: '${address}',
723
+ contractAddress: '${address}',
724
+ contractName: '${contractName}',
725
+
726
+ ${allMembers.join(`,
727
+
728
+ `)}
729
+ } as const`;
730
+ return `${abiCode}
731
+
732
+ ${contractCode}`;
733
+ }
734
+ function generateAbiConstant(name, abi) {
735
+ const abiJson = JSON.stringify(abi, null, 2).replace(/"([a-zA-Z_$][a-zA-Z0-9_$]*)":/g, "$1:").replace(/"/g, "'");
736
+ return `export const ${name}Abi = ${abiJson} as const`;
737
+ }
738
+ function generateMethod(func, address, contractName) {
739
+ const methodName = toCamelCase5(func.name);
740
+ if (func.args.length === 0) {
741
+ return `${methodName}() {
742
+ return {
743
+ contractAddress: '${address}',
744
+ contractName: '${contractName}',
745
+ functionName: '${func.name}',
746
+ functionArgs: []
747
+ }
748
+ }`;
749
+ }
750
+ if (func.args.length === 1) {
751
+ const originalArgName = func.args[0].name;
752
+ const argName = toCamelCase5(originalArgName);
753
+ const argType = getTypeForArg(func.args[0]);
754
+ const clarityConversion = generateClarityConversion(argName, func.args[0]);
755
+ return `${methodName}(...args: [{ ${argName}: ${argType} }] | [${argType}]) {
756
+ const ${argName} = args.length === 1 && typeof args[0] === 'object' && args[0] !== null && '${argName}' in args[0]
757
+ ? args[0].${argName}
758
+ : args[0] as ${argType}
759
+
760
+ return {
761
+ contractAddress: '${address}',
762
+ contractName: '${contractName}',
763
+ functionName: '${func.name}',
764
+ functionArgs: [${clarityConversion}]
765
+ }
766
+ }`;
767
+ }
768
+ const argsList = func.args.map((arg) => toCamelCase5(arg.name)).join(", ");
769
+ const argsTypes = func.args.map((arg) => {
770
+ const camelName = toCamelCase5(arg.name);
771
+ return `${camelName}: ${getTypeForArg(arg)}`;
772
+ }).join("; ");
773
+ const argsArray = func.args.map((arg) => {
774
+ const argName = toCamelCase5(arg.name);
775
+ return generateClarityConversion(argName, arg);
776
+ }).join(", ");
777
+ const objectAccess = func.args.map((arg) => {
778
+ const camelName = toCamelCase5(arg.name);
779
+ return `args[0].${camelName}`;
780
+ }).join(", ");
781
+ const positionTypes = func.args.map((arg) => getTypeForArg(arg)).join(", ");
782
+ return `${methodName}(...args: [{ ${argsTypes} }] | [${positionTypes}]) {
783
+ const [${argsList}] = args.length === 1 && typeof args[0] === 'object' && args[0] !== null
784
+ ? [${objectAccess}]
785
+ : args as [${positionTypes}]
786
+
787
+ return {
788
+ contractAddress: '${address}',
789
+ contractName: '${contractName}',
790
+ functionName: '${func.name}',
791
+ functionArgs: [${argsArray}]
792
+ }
793
+ }`;
794
+ }
795
+ function generateMapsObject(maps, address, contractName) {
796
+ if (!maps || maps.length === 0) {
797
+ return "";
798
+ }
799
+ const mapMethods = maps.map((map) => {
800
+ const methodName = toCamelCase5(map.name);
801
+ const keyType = getTypeForArg({ type: map.key });
802
+ const valueType = getTypeForArg({ type: map.value });
803
+ const keyConversion = generateMapKeyConversion(map.key);
804
+ return `${methodName}: {
805
+ async get(key: ${keyType}, options?: { network?: 'mainnet' | 'testnet' | 'devnet' }): Promise<${valueType} | null> {
806
+ try {
807
+ const { cvToJSON, serializeCV } = await import('@secondlayer/stacks/clarity');
808
+ const baseUrl = getApiUrl('${address}', options?.network);
809
+ const mapKey = ${keyConversion};
810
+ const keyHex = serializeCV(mapKey).toString('hex');
811
+
812
+ const response = await fetch(
813
+ \`\${baseUrl}/v2/map_entry/${address}/${contractName}/${map.name}\`,
814
+ {
815
+ method: 'POST',
816
+ headers: { 'Content-Type': 'application/json' },
817
+ body: JSON.stringify(keyHex)
818
+ }
819
+ );
820
+
821
+ if (!response.ok) {
822
+ throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
823
+ }
824
+
825
+ const result = await response.json();
826
+ if (!result.data || result.data === '0x09') {
827
+ return null; // none value
828
+ }
829
+
830
+ const { deserializeCV } = await import('@secondlayer/stacks/clarity');
831
+ const cv = deserializeCV(result.data);
832
+ const parsed = cvToJSON(cv);
833
+ // Unwrap the (some ...) wrapper
834
+ return parsed.value?.value ?? parsed.value ?? null;
835
+ } catch (error) {
836
+ if (error instanceof Error) {
837
+ throw new Error(\`Map access failed for '${map.name}': \${error.message}\`);
838
+ }
839
+ throw error;
840
+ }
841
+ },
842
+ keyType: ${JSON.stringify(map.key)} as const,
843
+ valueType: ${JSON.stringify(map.value)} as const
844
+ }`;
845
+ });
846
+ return `maps: {
847
+ ${mapMethods.join(`,
848
+
849
+ `)}
850
+ }`;
851
+ }
852
+ function generateVarsObject(variables, address, contractName) {
853
+ if (!variables || variables.length === 0) {
854
+ return "";
855
+ }
856
+ const dataVars = variables.filter((v) => v.access === "variable");
857
+ if (dataVars.length === 0) {
858
+ return "";
859
+ }
860
+ const varMethods = dataVars.map((variable) => {
861
+ const methodName = toCamelCase5(variable.name);
862
+ const valueType = getTypeForArg({ type: variable.type });
863
+ return `${methodName}: {
864
+ async get(options?: { network?: 'mainnet' | 'testnet' | 'devnet' }): Promise<${valueType}> {
865
+ try {
866
+ const { cvToJSON, deserializeCV } = await import('@secondlayer/stacks/clarity');
867
+ const baseUrl = getApiUrl('${address}', options?.network);
868
+
869
+ const response = await fetch(
870
+ \`\${baseUrl}/v2/data_var/${address}/${contractName}/${variable.name}?proof=0\`
871
+ );
872
+
873
+ if (!response.ok) {
874
+ throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
875
+ }
876
+
877
+ const result = await response.json();
878
+ const cv = deserializeCV(result.data);
879
+ const parsed = cvToJSON(cv);
880
+ return parsed.value ?? parsed;
881
+ } catch (error) {
882
+ if (error instanceof Error) {
883
+ throw new Error(\`Variable access failed for '${variable.name}': \${error.message}\`);
884
+ }
885
+ throw error;
886
+ }
887
+ },
888
+ type: ${JSON.stringify(variable.type)} as const
889
+ }`;
890
+ });
891
+ return `vars: {
892
+ ${varMethods.join(`,
893
+
894
+ `)}
895
+ }`;
896
+ }
897
+ function generateConstantsObject(variables, address, contractName) {
898
+ if (!variables || variables.length === 0) {
899
+ return "";
900
+ }
901
+ const constants = variables.filter((v) => v.access === "constant");
902
+ if (constants.length === 0) {
903
+ return "";
904
+ }
905
+ const constMethods = constants.map((constant) => {
906
+ const methodName = toCamelCase5(constant.name);
907
+ const valueType = getTypeForArg({ type: constant.type });
908
+ return `${methodName}: {
909
+ async get(options?: { network?: 'mainnet' | 'testnet' | 'devnet' }): Promise<${valueType}> {
910
+ try {
911
+ const { cvToJSON, deserializeCV } = await import('@secondlayer/stacks/clarity');
912
+ const baseUrl = getApiUrl('${address}', options?.network);
913
+
914
+ const response = await fetch(
915
+ \`\${baseUrl}/v2/constant_val/${address}/${contractName}/${constant.name}?proof=0\`
916
+ );
917
+
918
+ if (!response.ok) {
919
+ throw new Error(\`HTTP \${response.status}: \${response.statusText}\`);
920
+ }
921
+
922
+ const result = await response.json();
923
+ const cv = deserializeCV(result.data);
924
+ const parsed = cvToJSON(cv);
925
+ return parsed.value ?? parsed;
926
+ } catch (error) {
927
+ if (error instanceof Error) {
928
+ throw new Error(\`Constant access failed for '${constant.name}': \${error.message}\`);
929
+ }
930
+ throw error;
931
+ }
932
+ },
933
+ type: ${JSON.stringify(constant.type)} as const
934
+ }`;
935
+ });
936
+ return `constants: {
937
+ ${constMethods.join(`,
938
+
939
+ `)}
940
+ }`;
941
+ }
942
+ var init_contract = __esm(() => {
943
+ init_clarity_conversion();
944
+ init_format();
945
+ init_generator_helpers();
946
+ init_type_mapping();
947
+ });
948
+
949
+ // src/utils/abi-compat.ts
950
+ function normalizeAccess(access) {
951
+ if (access === "read_only")
952
+ return "read-only";
953
+ return access;
954
+ }
955
+ function normalizeType(type) {
956
+ if (typeof type === "string") {
957
+ switch (type) {
958
+ case "uint128":
959
+ case "int128":
960
+ case "bool":
961
+ case "principal":
962
+ case "trait_reference":
963
+ return type;
964
+ default:
965
+ return type;
966
+ }
967
+ }
968
+ if (typeof type !== "object" || type === null) {
969
+ throw new Error(`Invalid ABI type: expected object, got ${typeof type}`);
970
+ }
971
+ const typeObj = type;
972
+ if ("buffer" in typeObj) {
973
+ const buffer = typeObj.buffer;
974
+ return {
975
+ buff: {
976
+ length: buffer?.length ?? 32
977
+ }
978
+ };
979
+ }
980
+ if ("buff" in typeObj) {
981
+ const buff = typeObj.buff;
982
+ return {
983
+ buff: {
984
+ length: buff?.length ?? 32
985
+ }
986
+ };
987
+ }
988
+ if ("string-ascii" in typeObj) {
989
+ const strAscii = typeObj["string-ascii"];
990
+ return {
991
+ "string-ascii": {
992
+ length: strAscii?.length ?? 256
993
+ }
994
+ };
995
+ }
996
+ if ("string-utf8" in typeObj) {
997
+ const strUtf8 = typeObj["string-utf8"];
998
+ return {
999
+ "string-utf8": {
1000
+ length: strUtf8?.length ?? 256
1001
+ }
1002
+ };
1003
+ }
1004
+ if ("response" in typeObj) {
1005
+ const response = typeObj.response;
1006
+ return {
1007
+ response: {
1008
+ ok: normalizeType(response?.ok ?? "bool"),
1009
+ error: normalizeType(response?.error ?? "uint128")
1010
+ }
1011
+ };
1012
+ }
1013
+ if ("optional" in typeObj) {
1014
+ return {
1015
+ optional: normalizeType(typeObj.optional)
1016
+ };
1017
+ }
1018
+ if ("list" in typeObj) {
1019
+ const list = typeObj.list;
1020
+ return {
1021
+ list: {
1022
+ type: normalizeType(list?.type ?? "uint128"),
1023
+ length: list?.length ?? 100
1024
+ }
1025
+ };
1026
+ }
1027
+ if ("tuple" in typeObj) {
1028
+ const tuple = typeObj.tuple;
1029
+ return {
1030
+ tuple: tuple.map((field) => ({
1031
+ name: field.name,
1032
+ type: normalizeType(field.type)
1033
+ }))
1034
+ };
1035
+ }
1036
+ throw new Error(`Unknown ABI type structure: ${JSON.stringify(type)}`);
1037
+ }
1038
+ function normalizeFunction(func) {
1039
+ const access = normalizeAccess(func.access);
1040
+ const args = func.args ?? [];
1041
+ const outputs = func.outputs;
1042
+ return {
1043
+ name: func.name,
1044
+ access,
1045
+ args: args.map((arg) => ({
1046
+ name: arg.name,
1047
+ type: normalizeType(arg.type)
1048
+ })),
1049
+ outputs: normalizeType(typeof outputs === "object" && outputs !== null && "type" in outputs ? outputs.type : outputs)
1050
+ };
1051
+ }
1052
+ function normalizeMap(map) {
1053
+ return {
1054
+ name: map.name,
1055
+ key: normalizeType(map.key),
1056
+ value: normalizeType(map.value)
1057
+ };
1058
+ }
1059
+ function normalizeVariable(variable) {
1060
+ return {
1061
+ name: variable.name,
1062
+ type: normalizeType(variable.type),
1063
+ access: variable.access
1064
+ };
1065
+ }
1066
+ function normalizeAbi(abi) {
1067
+ if (typeof abi !== "object" || abi === null) {
1068
+ return { functions: [] };
1069
+ }
1070
+ const abiObj = abi;
1071
+ const functions = [];
1072
+ const maps = [];
1073
+ const variables = [];
1074
+ if (Array.isArray(abiObj.functions)) {
1075
+ for (const func of abiObj.functions) {
1076
+ if (typeof func === "object" && func !== null) {
1077
+ functions.push(normalizeFunction(func));
1078
+ }
1079
+ }
1080
+ }
1081
+ if (Array.isArray(abiObj.maps)) {
1082
+ for (const map of abiObj.maps) {
1083
+ if (typeof map === "object" && map !== null) {
1084
+ maps.push(normalizeMap(map));
1085
+ }
1086
+ }
1087
+ }
1088
+ if (Array.isArray(abiObj.variables)) {
1089
+ for (const variable of abiObj.variables) {
1090
+ if (typeof variable === "object" && variable !== null) {
1091
+ variables.push(normalizeVariable(variable));
1092
+ }
1093
+ }
1094
+ }
1095
+ return {
1096
+ functions,
1097
+ maps: maps.length > 0 ? maps : undefined,
1098
+ variables: variables.length > 0 ? variables : undefined
1099
+ };
1100
+ }
1101
+
1102
+ // src/plugins/index.ts
1103
+ init_plugin_manager();
1104
+
1105
+ // src/plugins/clarinet/index.ts
1106
+ init_contract();
1107
+ import { initSimnet } from "@hirosystems/clarinet-sdk";
1108
+ import { toCamelCase as toCamelCase6 } from "@secondlayer/stacks/clarity";
1109
+ function sanitizeContractName(name) {
1110
+ return toCamelCase6(name);
1111
+ }
1112
+ async function isUserDefinedContract(contractId, manifestPath) {
1113
+ const { address, contractName } = parseContractId(contractId);
1114
+ try {
1115
+ const { promises: fs2 } = await import("fs");
1116
+ const tomlContent = await fs2.readFile(manifestPath, "utf-8");
1117
+ const contractSectionRegex = /^\[contracts\.([^\]]+)\]/gm;
1118
+ const userContracts = new Set;
1119
+ let match;
1120
+ while ((match = contractSectionRegex.exec(tomlContent)) !== null) {
1121
+ userContracts.add(match[1]);
1122
+ }
1123
+ if (userContracts.has(contractName)) {
1124
+ return true;
1125
+ }
1126
+ } catch (error) {}
1127
+ const systemContractPatterns = [
1128
+ /^pox-\d+$/,
1129
+ /^bns$/,
1130
+ /^costs-\d+$/,
1131
+ /^lockup$/
1132
+ ];
1133
+ if (systemContractPatterns.some((pattern) => pattern.test(contractName))) {
1134
+ return false;
1135
+ }
1136
+ const systemAddresses = [
1137
+ "SP000000000000000000002Q6VF78",
1138
+ "ST000000000000000000002AMW42H"
1139
+ ];
1140
+ if (systemAddresses.includes(address)) {
1141
+ return false;
1142
+ }
1143
+ return true;
1144
+ }
1145
+ var clarinet = (options = {}) => {
1146
+ const manifestPath = options.path || "./Clarinet.toml";
1147
+ let simnet;
1148
+ return {
1149
+ name: "@secondlayer/cli/plugin-clarinet",
1150
+ version: "1.0.0",
1151
+ async transformConfig(config) {
1152
+ try {
1153
+ simnet = await initSimnet(manifestPath);
1154
+ const contractInterfaces = simnet.getContractsInterfaces();
1155
+ const contracts = [];
1156
+ for (const [contractId, abi] of contractInterfaces) {
1157
+ const { contractName } = parseContractId(contractId);
1158
+ if (!await isUserDefinedContract(contractId, manifestPath)) {
1159
+ if (options.debug) {
1160
+ console.log(`\uD83D\uDEAB Skipping system contract: ${contractId}`);
1161
+ }
1162
+ continue;
1163
+ }
1164
+ if (options.include && !options.include.includes(contractName)) {
1165
+ continue;
1166
+ }
1167
+ if (options.exclude && options.exclude.includes(contractName)) {
1168
+ continue;
1169
+ }
1170
+ const sanitizedName = sanitizeContractName(contractName);
1171
+ contracts.push({
1172
+ name: sanitizedName,
1173
+ address: contractId,
1174
+ abi: normalizeAbi(abi),
1175
+ _clarinetSource: true
1176
+ });
1177
+ }
1178
+ if (options.debug) {
1179
+ console.log(`\uD83D\uDD0D Clarinet plugin found ${contracts.length} user-defined contracts`);
1180
+ }
1181
+ return {
1182
+ ...config,
1183
+ contracts: [...config.contracts || [], ...contracts]
1184
+ };
1185
+ } catch (error) {
1186
+ const err = error;
1187
+ if (options.debug) {
1188
+ console.warn(`⚠️ Clarinet plugin failed to load contracts: ${err.message}`);
1189
+ }
1190
+ return config;
1191
+ }
1192
+ },
1193
+ async generate(context) {
1194
+ const clarinetContracts = context.contracts.filter((contract) => contract.metadata?.source === "clarinet");
1195
+ if (clarinetContracts.length === 0) {
1196
+ return;
1197
+ }
1198
+ if (options.debug) {
1199
+ context.logger.debug(`Generating interfaces for ${clarinetContracts.length} Clarinet contracts`);
1200
+ }
1201
+ const contractsCode = await generateContractInterface(clarinetContracts);
1202
+ context.addOutput("contracts", {
1203
+ path: context.config.out,
1204
+ content: contractsCode,
1205
+ type: "contracts"
1206
+ });
1207
+ }
1208
+ };
1209
+ };
1210
+ async function hasClarinetProject(path2 = "./Clarinet.toml") {
1211
+ try {
1212
+ const { promises: fs2 } = await import("fs");
1213
+ await fs2.access(path2);
1214
+ return true;
1215
+ } catch {
1216
+ return false;
1217
+ }
1218
+ }
1219
+ // src/plugins/actions/generators.ts
1220
+ init_generator_helpers();
1221
+ import { toCamelCase as toCamelCase7 } from "@secondlayer/stacks/clarity";
1222
+ function generateReadHelpers(contract, options) {
1223
+ const { abi } = contract;
1224
+ const functions = abi.functions || [];
1225
+ const readOnlyFunctions = functions.filter((f) => f.access === "read-only");
1226
+ if (readOnlyFunctions.length === 0) {
1227
+ return "";
1228
+ }
1229
+ const filteredFunctions = readOnlyFunctions.filter((func) => {
1230
+ if (options.includeFunctions && !options.includeFunctions.includes(func.name)) {
1231
+ return false;
1232
+ }
1233
+ if (options.excludeFunctions && options.excludeFunctions.includes(func.name)) {
1234
+ return false;
1235
+ }
1236
+ return true;
1237
+ });
1238
+ if (filteredFunctions.length === 0) {
1239
+ return "";
1240
+ }
1241
+ const helpers = filteredFunctions.map((func) => {
1242
+ const methodName = toCamelCase7(func.name);
1243
+ const argsSignature = generateArgsSignature(func.args);
1244
+ const clarityArgs = generateClarityArgs(func.args);
1245
+ return `async ${methodName}(${argsSignature}options?: {
1246
+ network?: 'mainnet' | 'testnet' | 'devnet';
1247
+ senderAddress?: string;
1248
+ }) {
1249
+ return await fetchCallReadOnlyFunction({
1250
+ contractAddress: '${contract.address}',
1251
+ contractName: '${contract.contractName}',
1252
+ functionName: '${func.name}',
1253
+ functionArgs: [${clarityArgs}],
1254
+ network: options?.network ?? inferNetworkFromAddress('${contract.address}') ?? 'mainnet',
1255
+ senderAddress: options?.senderAddress || 'SP000000000000000000002Q6VF78'
1256
+ });
1257
+ }`;
1258
+ });
1259
+ return `read: {
1260
+ ${helpers.join(`,
1261
+
1262
+ `)}
1263
+ }`;
1264
+ }
1265
+ function generateWriteHelpers(contract, options) {
1266
+ const { abi } = contract;
1267
+ const functions = abi.functions || [];
1268
+ const envVarName = options.senderKeyEnv ?? "STX_SENDER_KEY";
1269
+ const publicFunctions = functions.filter((f) => f.access === "public");
1270
+ if (publicFunctions.length === 0) {
1271
+ return "";
1272
+ }
1273
+ const filteredFunctions = publicFunctions.filter((func) => {
1274
+ if (options.includeFunctions && !options.includeFunctions.includes(func.name)) {
1275
+ return false;
1276
+ }
1277
+ if (options.excludeFunctions && options.excludeFunctions.includes(func.name)) {
1278
+ return false;
1279
+ }
1280
+ return true;
1281
+ });
1282
+ if (filteredFunctions.length === 0) {
1283
+ return "";
1284
+ }
1285
+ const helpers = filteredFunctions.map((func) => {
1286
+ const methodName = toCamelCase7(func.name);
1287
+ const argsSignature = generateArgsSignature(func.args);
1288
+ const clarityArgs = generateClarityArgs(func.args);
1289
+ return `async ${methodName}(${argsSignature}senderKey?: string, options?: {
1290
+ network?: 'mainnet' | 'testnet' | 'devnet';
1291
+ fee?: string | number | undefined;
1292
+ nonce?: bigint;
1293
+ anchorMode?: 1 | 2 | 3; // AnchorMode: OnChainOnly = 1, OffChainOnly = 2, Any = 3
1294
+ postConditions?: PostCondition[];
1295
+ validateWithAbi?: boolean;
1296
+ }) {
1297
+ const resolvedSenderKey = senderKey ?? process.env.${envVarName};
1298
+ if (!resolvedSenderKey) {
1299
+ throw new Error('senderKey required: pass as argument or set ${envVarName} env var');
1300
+ }
1301
+ const { network = 'mainnet', ...txOptions } = options ?? {};
1302
+
1303
+ return await makeContractCall({
1304
+ contractAddress: '${contract.address}',
1305
+ contractName: '${contract.contractName}',
1306
+ functionName: '${func.name}',
1307
+ functionArgs: [${clarityArgs}],
1308
+ senderKey: resolvedSenderKey,
1309
+ network,
1310
+ validateWithAbi: true,
1311
+ ...txOptions
1312
+ });
1313
+ }`;
1314
+ });
1315
+ return `write: {
1316
+ ${helpers.join(`,
1317
+
1318
+ `)}
1319
+ }`;
1320
+ }
1321
+ async function generateActionHelpers(contract, options) {
1322
+ const readHelpers = generateReadHelpers(contract, options);
1323
+ const writeHelpers = generateWriteHelpers(contract, options);
1324
+ if (!readHelpers && !writeHelpers) {
1325
+ return "";
1326
+ }
1327
+ const helpers = [readHelpers, writeHelpers].filter(Boolean);
1328
+ return helpers.join(`,
1329
+
1330
+ `);
1331
+ }
1332
+
1333
+ // src/plugins/actions/index.ts
1334
+ var actions = (options = {}) => {
1335
+ return {
1336
+ name: "@secondlayer/cli/plugin-actions",
1337
+ version: "1.0.0",
1338
+ async generate(context) {
1339
+ const { contracts } = context;
1340
+ const filteredContracts = contracts.filter((contract) => {
1341
+ if (options.include && !options.include.includes(contract.name)) {
1342
+ return false;
1343
+ }
1344
+ if (options.exclude && options.exclude.includes(contract.name)) {
1345
+ return false;
1346
+ }
1347
+ return true;
1348
+ });
1349
+ if (filteredContracts.length === 0) {
1350
+ if (options.debug) {
1351
+ context.logger.debug("Actions plugin: No contracts to process");
1352
+ }
1353
+ return;
1354
+ }
1355
+ if (options.debug) {
1356
+ context.logger.debug(`Actions plugin: Generating read/write helpers for ${filteredContracts.length} contracts`);
1357
+ }
1358
+ const contractHelpers = new Map;
1359
+ for (const contract of filteredContracts) {
1360
+ const actionsCode = await generateActionHelpers(contract, options);
1361
+ if (actionsCode) {
1362
+ contractHelpers.set(contract.name, actionsCode);
1363
+ }
1364
+ }
1365
+ if (contractHelpers.size > 0) {
1366
+ const existingOutput = context.outputs.get("contracts");
1367
+ if (existingOutput) {
1368
+ let modifiedContent = addRequiredImports(existingOutput.content);
1369
+ for (const [contractName, helpersCode] of contractHelpers) {
1370
+ modifiedContent = injectHelpersIntoContract(modifiedContent, contractName, helpersCode);
1371
+ }
1372
+ context.outputs.set("contracts", {
1373
+ ...existingOutput,
1374
+ content: modifiedContent
1375
+ });
1376
+ }
1377
+ }
1378
+ }
1379
+ };
1380
+ };
1381
+ function addRequiredImports(content) {
1382
+ const stacksImportRegex = /import\s+\{([^}]+)\}\s+from\s+['"]@secondlayer\/stacks['"];/;
1383
+ const match = content.match(stacksImportRegex);
1384
+ if (match) {
1385
+ let updatedContent = content;
1386
+ if (!updatedContent.includes("fetchCallReadOnlyFunction")) {
1387
+ updatedContent = updatedContent.replace(stacksImportRegex, `${match[0]}
1388
+ import { fetchCallReadOnlyFunction, makeContractCall } from '@secondlayer/stacks/clarity';`);
1389
+ }
1390
+ if (!updatedContent.includes("type PostCondition")) {
1391
+ updatedContent = updatedContent.replace(stacksImportRegex, `${match[0]}
1392
+ import type { PostCondition } from '@secondlayer/stacks';`);
1393
+ }
1394
+ return updatedContent;
1395
+ }
1396
+ return content;
1397
+ }
1398
+ function injectHelpersIntoContract(content, contractName, helpersCode) {
1399
+ const contractPattern = new RegExp(`(export const ${contractName} = \\{[\\s\\S]*?)\\n\\} as const;`, "g");
1400
+ return content.replace(contractPattern, (_, contractBody) => {
1401
+ const cleanBody = contractBody.replace(/,\s*$/, "");
1402
+ const indentedHelpersCode = helpersCode.split(`
1403
+ `).map((line) => {
1404
+ if (line.match(/^[a-zA-Z_$][a-zA-Z0-9_$]*\s*:/)) {
1405
+ return ` ${line}`;
1406
+ }
1407
+ return line;
1408
+ }).join(`
1409
+ `);
1410
+ return `${cleanBody},
1411
+
1412
+ ${indentedHelpersCode}
1413
+ } as const;`;
1414
+ });
1415
+ }
1416
+ // src/plugins/react/generators/contract.ts
1417
+ init_format();
1418
+
1419
+ // src/utils/case-conversion.ts
1420
+ import { toCamelCase as toCamelCase8 } from "@secondlayer/stacks/clarity";
1421
+ function capitalize(str) {
1422
+ return str.charAt(0).toUpperCase() + str.slice(1);
1423
+ }
1424
+ function toPascalCase(str) {
1425
+ return capitalize(toCamelCase8(str));
1426
+ }
1427
+
1428
+ // src/plugins/react/generators/utils.ts
1429
+ init_type_mapping();
1430
+ function generateHookArgsSignature(args) {
1431
+ if (args.length === 0)
1432
+ return "";
1433
+ const argsList = args.map((arg) => `${toCamelCase8(arg.name)}: ${clarityTypeToTS(arg.type)}`).join(", ");
1434
+ return `${argsList}`;
1435
+ }
1436
+ function generateArgsType(args) {
1437
+ if (args.length === 0)
1438
+ return "void";
1439
+ const argsList = args.map((arg) => `${toCamelCase8(arg.name)}: ${clarityTypeToTS(arg.type)}`).join("; ");
1440
+ return `{ ${argsList} }`;
1441
+ }
1442
+ function generateArgNames(args) {
1443
+ if (args.length === 0)
1444
+ return "";
1445
+ return args.map((arg) => toCamelCase8(arg.name)).join(", ");
1446
+ }
1447
+ function generateEnabledCondition(args) {
1448
+ return args.map((arg) => {
1449
+ const camelName = toCamelCase8(arg.name);
1450
+ const type = clarityTypeToTS(arg.type);
1451
+ if (type === "string")
1452
+ return `!!${camelName}`;
1453
+ if (type === "bigint")
1454
+ return `${camelName} !== undefined`;
1455
+ return `${camelName} !== undefined`;
1456
+ }).join(" && ");
1457
+ }
1458
+ function generateObjectArgs(args) {
1459
+ if (args.length === 0)
1460
+ return "";
1461
+ return args.map((arg) => `${arg.name}: ${toCamelCase8(arg.name)}`).join(", ");
1462
+ }
1463
+
1464
+ // src/plugins/react/generators/contract.ts
1465
+ async function generateContractHooks(contracts, excludeList = []) {
1466
+ const imports = `import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
1467
+ import { useCallback } from 'react'
1468
+ import { useSecondLayerConfig } from './provider'
1469
+ import { request } from '@secondlayer/stacks/connect'
1470
+ import type { PostCondition } from '@secondlayer/stacks'
1471
+ import { ${contracts.map((c) => c.name).join(", ")} } from './contracts'`;
1472
+ const header = `/**
1473
+ * Generated contract-specific React hooks
1474
+ * DO NOT EDIT MANUALLY
1475
+ */`;
1476
+ const hooksCode = contracts.map((contract) => generateContractHookMethods(contract, excludeList)).filter(Boolean).join(`
1477
+
1478
+ `);
1479
+ const code = `${imports}
1480
+
1481
+ ${header}
1482
+
1483
+ ${hooksCode}`;
1484
+ return formatCode(code);
1485
+ }
1486
+ function generateContractHookMethods(contract, excludeList) {
1487
+ const { abi, name, address, contractName } = contract;
1488
+ const functions = abi.functions || [];
1489
+ const maps = abi.maps || [];
1490
+ const variables = abi.variables || [];
1491
+ const readOnlyFunctions = functions.filter((f) => f.access === "read-only");
1492
+ const publicFunctions = functions.filter((f) => f.access === "public");
1493
+ const readHooks = readOnlyFunctions.map((func) => {
1494
+ const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(func.name))}`;
1495
+ if (excludeList.includes(hookName)) {
1496
+ return null;
1497
+ }
1498
+ return generateReadHook(func, name);
1499
+ }).filter(Boolean);
1500
+ const writeHooks = publicFunctions.map((func) => {
1501
+ const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(func.name))}`;
1502
+ if (excludeList.includes(hookName)) {
1503
+ return null;
1504
+ }
1505
+ return generateWriteHook(func, name);
1506
+ }).filter(Boolean);
1507
+ const mapHooks = maps.map((map) => {
1508
+ const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(map.name))}`;
1509
+ if (excludeList.includes(hookName)) {
1510
+ return null;
1511
+ }
1512
+ return generateMapHook(map, name, address, contractName);
1513
+ }).filter(Boolean);
1514
+ const dataVars = variables.filter((v) => v.access === "variable");
1515
+ const varHooks = dataVars.map((variable) => {
1516
+ const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(variable.name))}`;
1517
+ if (excludeList.includes(hookName)) {
1518
+ return null;
1519
+ }
1520
+ return generateVarHook(variable, name, address, contractName);
1521
+ }).filter(Boolean);
1522
+ const constants = variables.filter((v) => v.access === "constant");
1523
+ const constantHooks = constants.map((constant) => {
1524
+ const hookName = `use${capitalize(name)}${capitalize(toCamelCase8(constant.name))}`;
1525
+ if (excludeList.includes(hookName)) {
1526
+ return null;
1527
+ }
1528
+ return generateConstantHook(constant, name, address, contractName);
1529
+ }).filter(Boolean);
1530
+ const allHooks = [
1531
+ ...readHooks,
1532
+ ...writeHooks,
1533
+ ...mapHooks,
1534
+ ...varHooks,
1535
+ ...constantHooks
1536
+ ];
1537
+ if (allHooks.length === 0) {
1538
+ return "";
1539
+ }
1540
+ return allHooks.join(`
1541
+
1542
+ `);
1543
+ }
1544
+ function generateReadHook(func, contractName) {
1545
+ const hookName = `use${capitalize(contractName)}${capitalize(toCamelCase8(func.name))}`;
1546
+ const argsSignature = generateHookArgsSignature(func.args);
1547
+ const enabledParam = func.args.length > 0 ? ", options?: { enabled?: boolean }" : "options?: { enabled?: boolean }";
1548
+ const returnType = clarityTypeToTS(func.outputs);
1549
+ return `export function ${hookName}(${argsSignature}${enabledParam}) {
1550
+ const config = useSecondLayerConfig()
1551
+
1552
+ return useQuery<${returnType}>({
1553
+ queryKey: ['${func.name}', ${contractName}.address, ${generateArgNames(func.args)}],
1554
+ queryFn: () => ${contractName}.read.${toCamelCase8(func.name)}(${generateArgNames(func.args) ? `{ ${generateObjectArgs(func.args)} }, ` : ""}{
1555
+ network: config.network,
1556
+ senderAddress: config.senderAddress || 'SP000000000000000000002Q6VF78'
1557
+ }),
1558
+ ${func.args.length > 0 ? `enabled: ${generateEnabledCondition(func.args)} && (options?.enabled ?? true),` : ""}
1559
+ ...options
1560
+ })
1561
+ }`;
1562
+ }
1563
+ function generateWriteHook(func, contractName) {
1564
+ const hookName = `use${capitalize(contractName)}${capitalize(toCamelCase8(func.name))}`;
1565
+ const argsType = generateArgsType(func.args);
1566
+ return `export function ${hookName}() {
1567
+ const config = useSecondLayerConfig()
1568
+ const queryClient = useQueryClient()
1569
+
1570
+ const mutation = useMutation({
1571
+ mutationFn: async (params: {
1572
+ args: ${argsType};
1573
+ options?: {
1574
+ postConditions?: PostCondition[];
1575
+ attachment?: string;
1576
+ onFinish?: (data: any) => void;
1577
+ onCancel?: () => void;
1578
+ };
1579
+ }) => {
1580
+ const { args, options = {} } = params
1581
+ const contractCallData = ${contractName}.${toCamelCase8(func.name)}(args)
1582
+ const { contractAddress, contractName: name, functionName, functionArgs } = contractCallData
1583
+ const network = config.network || 'mainnet'
1584
+ const contract = \`\${contractAddress}.\${name}\`
1585
+
1586
+ const result = await request('stx_callContract', {
1587
+ contract,
1588
+ functionName,
1589
+ functionArgs,
1590
+ network,
1591
+ ...options
1592
+ })
1593
+
1594
+ options.onFinish?.(result)
1595
+ return result
1596
+ },
1597
+ onSuccess: () => {
1598
+ // Invalidate relevant queries on success
1599
+ queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
1600
+ },
1601
+ onError: (error) => {
1602
+ console.error('Contract call failed:', error)
1603
+ }
1604
+ })
1605
+
1606
+ const ${toCamelCase8(func.name)} = useCallback(async (
1607
+ args: ${argsType},
1608
+ options?: {
1609
+ postConditions?: PostCondition[];
1610
+ attachment?: string;
1611
+ onFinish?: (data: any) => void;
1612
+ onCancel?: () => void;
1613
+ }
1614
+ ) => {
1615
+ return mutation.mutateAsync({ args, options })
1616
+ }, [mutation])
1617
+
1618
+ return {
1619
+ ${toCamelCase8(func.name)},
1620
+ // Expose mutation state
1621
+ isPending: mutation.isPending,
1622
+ isError: mutation.isError,
1623
+ isSuccess: mutation.isSuccess,
1624
+ error: mutation.error,
1625
+ data: mutation.data,
1626
+ reset: mutation.reset
1627
+ }
1628
+ }`;
1629
+ }
1630
+ function generateMapHook(map, contractVarName, _address, _contractName) {
1631
+ const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase8(map.name))}`;
1632
+ const keyType = clarityTypeToTS(map.key);
1633
+ const valueType = clarityTypeToTS(map.value);
1634
+ return `export function ${hookName}(key: ${keyType}, options?: { enabled?: boolean }) {
1635
+ const config = useSecondLayerConfig()
1636
+
1637
+ return useQuery<${valueType} | null>({
1638
+ queryKey: ['${contractVarName}', '${map.name}', 'map', key, config.network],
1639
+ queryFn: async () => {
1640
+ return ${contractVarName}.maps.${toCamelCase8(map.name)}.get(key, { network: config.network })
1641
+ },
1642
+ enabled: options?.enabled ?? true
1643
+ })
1644
+ }`;
1645
+ }
1646
+ function generateVarHook(variable, contractVarName, _address, _contractName) {
1647
+ const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase8(variable.name))}`;
1648
+ const valueType = clarityTypeToTS(variable.type);
1649
+ return `export function ${hookName}(options?: { enabled?: boolean }) {
1650
+ const config = useSecondLayerConfig()
1651
+
1652
+ return useQuery<${valueType}>({
1653
+ queryKey: ['${contractVarName}', '${variable.name}', 'var', config.network],
1654
+ queryFn: async () => {
1655
+ return ${contractVarName}.vars.${toCamelCase8(variable.name)}.get({ network: config.network })
1656
+ },
1657
+ enabled: options?.enabled ?? true
1658
+ })
1659
+ }`;
1660
+ }
1661
+ function generateConstantHook(constant, contractVarName, _address, _contractName) {
1662
+ const hookName = `use${capitalize(contractVarName)}${capitalize(toCamelCase8(constant.name))}`;
1663
+ const valueType = clarityTypeToTS(constant.type);
1664
+ return `export function ${hookName}(options?: { enabled?: boolean }) {
1665
+ const config = useSecondLayerConfig()
1666
+
1667
+ return useQuery<${valueType}>({
1668
+ queryKey: ['${contractVarName}', '${constant.name}', 'constant', config.network],
1669
+ queryFn: async () => {
1670
+ return ${contractVarName}.constants.${toCamelCase8(constant.name)}.get({ network: config.network })
1671
+ },
1672
+ enabled: options?.enabled ?? true,
1673
+ staleTime: Infinity // Constants never change
1674
+ })
1675
+ }`;
1676
+ }
1677
+
1678
+ // src/generators/templates/use-contract.ts
1679
+ var USE_CONTRACT_TEMPLATE = `export function useContract() {
1680
+ const config = useSecondLayerConfig()
1681
+ const queryClient = useQueryClient()
1682
+ const [isRequestPending, setIsRequestPending] = useState(false)
1683
+
1684
+ // Helper function to convert JS values to Clarity values based on ABI
1685
+ const convertArgsWithAbi = (args: any, abiArgs: any[]): any[] => {
1686
+ if (!abiArgs || abiArgs.length === 0) return []
1687
+
1688
+ return abiArgs.map((abiArg, index) => {
1689
+ const argValue = Array.isArray(args)
1690
+ ? args[index]
1691
+ : args[abiArg.name] || args[abiArg.name.replace(/-/g, '').replace(/_/g, '')]
1692
+ return convertJSValueToClarityValue(argValue, abiArg.type)
1693
+ })
1694
+ }
1695
+
1696
+ // Helper function to convert buffer values with auto-detection
1697
+ const convertBufferValue = (value: any): any => {
1698
+ // Direct Uint8Array
1699
+ if (value instanceof Uint8Array) {
1700
+ return Cl.buffer(value)
1701
+ }
1702
+
1703
+ // Object notation with explicit type
1704
+ if (typeof value === 'object' && value !== null && value.type && value.value) {
1705
+ switch (value.type) {
1706
+ case 'ascii':
1707
+ return Cl.bufferFromAscii(value.value)
1708
+ case 'utf8':
1709
+ return Cl.bufferFromUtf8(value.value)
1710
+ case 'hex':
1711
+ return Cl.bufferFromHex(value.value)
1712
+ default:
1713
+ throw new Error(\`Unsupported buffer type: \${value.type}\`)
1714
+ }
1715
+ }
1716
+
1717
+ // Auto-detect string type
1718
+ if (typeof value === 'string') {
1719
+ // 1. Check for hex (0x prefix or pure hex pattern)
1720
+ if (value.startsWith('0x') || /^[0-9a-fA-F]+$/.test(value)) {
1721
+ return Cl.bufferFromHex(value)
1722
+ }
1723
+
1724
+ // 2. Check for non-ASCII characters (UTF-8)
1725
+ if (!/^[\\x00-\\x7F]*$/.test(value)) {
1726
+ return Cl.bufferFromUtf8(value)
1727
+ }
1728
+
1729
+ // 3. Default to ASCII for simple ASCII strings
1730
+ return Cl.bufferFromAscii(value)
1731
+ }
1732
+
1733
+ throw new Error(\`Invalid buffer value: \${value}\`)
1734
+ }
1735
+
1736
+ // Helper function to convert a single JS value to ClarityValue
1737
+ const convertJSValueToClarityValue = (value: any, type: any): any => {
1738
+ if (typeof type === 'string') {
1739
+ switch (type) {
1740
+ case 'uint128':
1741
+ return Cl.uint(value)
1742
+ case 'int128':
1743
+ return Cl.int(value)
1744
+ case 'bool':
1745
+ return Cl.bool(value)
1746
+ case 'principal':
1747
+ if (!validateStacksAddress(value.split('.')[0])) {
1748
+ throw new Error('Invalid Stacks address format')
1749
+ }
1750
+ if (value.includes('.')) {
1751
+ const [address, contractName] = value.split('.')
1752
+ return Cl.contractPrincipal(address, contractName)
1753
+ } else {
1754
+ return Cl.standardPrincipal(value)
1755
+ }
1756
+ default:
1757
+ return value
1758
+ }
1759
+ }
1760
+
1761
+ if (type['string-ascii']) {
1762
+ return Cl.stringAscii(value)
1763
+ }
1764
+
1765
+ if (type['string-utf8']) {
1766
+ return Cl.stringUtf8(value)
1767
+ }
1768
+
1769
+ if (type.buff) {
1770
+ return convertBufferValue(value)
1771
+ }
1772
+
1773
+ if (type.optional) {
1774
+ return value !== null ? Cl.some(convertJSValueToClarityValue(value, type.optional)) : Cl.none()
1775
+ }
1776
+
1777
+ if (type.list) {
1778
+ return Cl.list(value.map((item: any) => convertJSValueToClarityValue(item, type.list.type)))
1779
+ }
1780
+
1781
+ if (type.tuple) {
1782
+ const tupleData = type.tuple.reduce((acc: any, field: any) => {
1783
+ acc[field.name] = convertJSValueToClarityValue(value[field.name], field.type)
1784
+ return acc
1785
+ }, {})
1786
+ return Cl.tuple(tupleData)
1787
+ }
1788
+
1789
+ if (type.response) {
1790
+ return 'ok' in value
1791
+ ? Cl.ok(convertJSValueToClarityValue(value.ok, type.response.ok))
1792
+ : Cl.error(convertJSValueToClarityValue(value.err, type.response.error))
1793
+ }
1794
+
1795
+ return value
1796
+ }
1797
+
1798
+ // Helper function to find a function in an ABI by name
1799
+ const findFunctionInAbi = (abi: any, functionName: string): any => {
1800
+ if (!abi || !abi.functions) return null
1801
+ return abi.functions.find((func: any) => func.name === functionName)
1802
+ }
1803
+
1804
+ // Legacy function - pre-converted Clarity values
1805
+ const legacyOpenContractCall = useCallback(async (params: {
1806
+ contractAddress: string;
1807
+ contractName: string;
1808
+ functionName: string;
1809
+ functionArgs: any[];
1810
+ network?: string;
1811
+ postConditions?: PostCondition[];
1812
+ attachment?: string;
1813
+ onFinish?: (data: any) => void;
1814
+ onCancel?: () => void;
1815
+ }) => {
1816
+ setIsRequestPending(true)
1817
+
1818
+ try {
1819
+ const { contractAddress, contractName, functionName, functionArgs, onFinish, onCancel, ...options } = params
1820
+ const network = params.network || config.network || 'mainnet'
1821
+ const contract = \`\${contractAddress}.\${contractName}\`
1822
+
1823
+ const result = await request('stx_callContract', {
1824
+ contract,
1825
+ functionName,
1826
+ functionArgs,
1827
+ network,
1828
+ ...options
1829
+ })
1830
+
1831
+ queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
1832
+ onFinish?.(result)
1833
+ return result
1834
+ } catch (error) {
1835
+ console.error('Contract call failed:', error)
1836
+ throw error instanceof Error ? error : new Error('Contract call failed')
1837
+ } finally {
1838
+ setIsRequestPending(false)
1839
+ }
1840
+ }, [config.network, queryClient])
1841
+
1842
+ // Enhanced function - requires ABI, auto-converts JS values
1843
+ const openContractCall = useCallback(async <
1844
+ T extends AbiContract,
1845
+ FN extends ExtractFunctionNames<T>
1846
+ >(params: {
1847
+ contractAddress: string;
1848
+ contractName: string;
1849
+ functionName: FN;
1850
+ abi: T;
1851
+ functionArgs: ExtractFunctionArgs<T, FN>;
1852
+ network?: string;
1853
+ postConditions?: PostCondition[];
1854
+ attachment?: string;
1855
+ onFinish?: (data: any) => void;
1856
+ onCancel?: () => void;
1857
+ }) => {
1858
+ setIsRequestPending(true)
1859
+
1860
+ try {
1861
+ const { contractAddress, contractName, functionName, functionArgs, abi, onFinish, onCancel, ...options } = params
1862
+ const network = params.network || config.network || 'mainnet'
1863
+ const contract = \`\${contractAddress}.\${contractName}\`
1864
+
1865
+ const abiFunction = findFunctionInAbi(abi, functionName)
1866
+ if (!abiFunction) {
1867
+ throw new Error(\`Function '\${functionName}' not found in ABI\`)
1868
+ }
1869
+
1870
+ const processedArgs = convertArgsWithAbi(functionArgs, abiFunction.args || [])
1871
+
1872
+ const result = await request('stx_callContract', {
1873
+ contract,
1874
+ functionName,
1875
+ functionArgs: processedArgs,
1876
+ network,
1877
+ ...options
1878
+ })
1879
+
1880
+ queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
1881
+ onFinish?.(result)
1882
+ return result
1883
+ } catch (error) {
1884
+ console.error('Contract call failed:', error)
1885
+ throw error instanceof Error ? error : new Error('Contract call failed')
1886
+ } finally {
1887
+ setIsRequestPending(false)
1888
+ }
1889
+ }, [config.network, queryClient])
1890
+
1891
+ return {
1892
+ legacyOpenContractCall,
1893
+ openContractCall,
1894
+ isRequestPending
1895
+ }
1896
+ }`;
1897
+
1898
+ // src/plugins/react/generators/generic.ts
1899
+ init_format();
1900
+ var GENERIC_HOOKS = [
1901
+ "useAccount",
1902
+ "useConnect",
1903
+ "useDisconnect",
1904
+ "useNetwork",
1905
+ "useContract",
1906
+ "useOpenSTXTransfer",
1907
+ "useSignMessage",
1908
+ "useDeployContract",
1909
+ "useReadContract",
1910
+ "useTransaction",
1911
+ "useBlock",
1912
+ "useAccountTransactions",
1913
+ "useWaitForTransaction"
1914
+ ];
1915
+ async function generateGenericHooks(excludeList = []) {
1916
+ const hooksToGenerate = GENERIC_HOOKS.filter((hookName) => !excludeList.includes(hookName));
1917
+ const imports = `import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
1918
+ import { useState, useCallback } from 'react'
1919
+ import { useSecondLayerConfig } from './provider'
1920
+ import { connect, disconnect, isConnected, request } from '@secondlayer/stacks/connect'
1921
+ import { Cl, validateStacksAddress } from '@secondlayer/stacks'
1922
+ import type { PostCondition } from '@secondlayer/stacks'
1923
+ import type { ExtractFunctionArgs, ExtractFunctionNames, AbiContract } from '@secondlayer/stacks/clarity'
1924
+
1925
+ const API_URLS: Record<string, string> = {
1926
+ mainnet: 'https://api.hiro.so',
1927
+ testnet: 'https://api.testnet.hiro.so',
1928
+ devnet: 'http://localhost:3999'
1929
+ }
1930
+
1931
+ async function fetchTransaction({ txId, network, apiUrl }: { txId: string; network?: string; apiUrl?: string }): Promise<any> {
1932
+ const baseUrl = apiUrl || API_URLS[network || 'mainnet']
1933
+ const response = await fetch(\`\${baseUrl}/extended/v1/tx/\${txId}\`)
1934
+ if (!response.ok) throw new Error(\`Failed to fetch transaction: \${response.statusText}\`)
1935
+ return response.json()
1936
+ }
1937
+
1938
+ async function fetchBlock({ height, network, apiUrl }: { height: number; network?: string; apiUrl?: string }): Promise<any> {
1939
+ const baseUrl = apiUrl || API_URLS[network || 'mainnet']
1940
+ const response = await fetch(\`\${baseUrl}/extended/v1/block/by_height/\${height}\`)
1941
+ if (!response.ok) throw new Error(\`Failed to fetch block: \${response.statusText}\`)
1942
+ return response.json()
1943
+ }
1944
+
1945
+ async function fetchAccountTransactions({ address, network, apiUrl }: { address: string; network?: string; apiUrl?: string }): Promise<any> {
1946
+ const baseUrl = apiUrl || API_URLS[network || 'mainnet']
1947
+ const response = await fetch(\`\${baseUrl}/extended/v1/address/\${address}/transactions\`)
1948
+ if (!response.ok) throw new Error(\`Failed to fetch transactions: \${response.statusText}\`)
1949
+ return response.json()
1950
+ }`;
1951
+ const header = `/**
1952
+ * Generated generic Stacks React hooks
1953
+ * DO NOT EDIT MANUALLY
1954
+ */`;
1955
+ const hooksCode = hooksToGenerate.map((hookName) => generateGenericHook(hookName)).filter(Boolean).join(`
1956
+
1957
+ `);
1958
+ const code = `${imports}
1959
+
1960
+ ${header}
1961
+
1962
+ ${hooksCode}`;
1963
+ return formatCode(code);
1964
+ }
1965
+ function generateGenericHook(hookName) {
1966
+ switch (hookName) {
1967
+ case "useAccount":
1968
+ return `export function useAccount() {
1969
+ const config = useSecondLayerConfig()
1970
+
1971
+ return useQuery({
1972
+ queryKey: ['stacks-account', config.network],
1973
+ queryFn: async () => {
1974
+ try {
1975
+ // Check if already connected
1976
+ const connected = isConnected()
1977
+
1978
+ if (!connected) {
1979
+ return {
1980
+ address: undefined,
1981
+ addresses: undefined,
1982
+ isConnected: false,
1983
+ isConnecting: false,
1984
+ isDisconnected: true,
1985
+ status: 'disconnected' as const
1986
+ }
1987
+ }
1988
+
1989
+ // Get addresses via SIP-030
1990
+ const result = await request('stx_getAddresses')
1991
+
1992
+ if (!result || !result.addresses || result.addresses.length === 0) {
1993
+ return {
1994
+ address: undefined,
1995
+ addresses: undefined,
1996
+ isConnected: false,
1997
+ isConnecting: false,
1998
+ isDisconnected: true,
1999
+ status: 'disconnected' as const
2000
+ }
2001
+ }
2002
+
2003
+ // Extract STX addresses from the response
2004
+ const stxAddresses = result.addresses
2005
+ .filter((addr: any) => addr.address.startsWith('SP') || addr.address.startsWith('ST'))
2006
+ .map((addr: any) => addr.address)
2007
+
2008
+ return {
2009
+ address: stxAddresses[0] || undefined,
2010
+ addresses: stxAddresses,
2011
+ isConnected: true,
2012
+ isConnecting: false,
2013
+ isDisconnected: false,
2014
+ status: 'connected' as const
2015
+ }
2016
+ } catch (error) {
2017
+ // Handle case where wallet is not available or user rejected
2018
+ return {
2019
+ address: undefined,
2020
+ addresses: undefined,
2021
+ isConnected: false,
2022
+ isConnecting: false,
2023
+ isDisconnected: true,
2024
+ status: 'disconnected' as const
2025
+ }
2026
+ }
2027
+ },
2028
+ refetchOnWindowFocus: false,
2029
+ retry: false,
2030
+ staleTime: 1000 * 60 * 5, // 5 minutes
2031
+ refetchInterval: 1000 * 30, // Refetch every 30 seconds to detect wallet changes
2032
+ })
2033
+ }`;
2034
+ case "useConnect":
2035
+ return `export function useConnect() {
2036
+ const queryClient = useQueryClient()
2037
+
2038
+ const mutation = useMutation({
2039
+ mutationFn: async (options: { forceWalletSelect?: boolean } = {}) => {
2040
+ // SIP-030 connect
2041
+ return await connect(options)
2042
+ },
2043
+ onSuccess: () => {
2044
+ // Invalidate account queries to refetch connection state
2045
+ queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
2046
+ },
2047
+ onError: (error) => {
2048
+ console.error('Connection failed:', error)
2049
+ }
2050
+ })
2051
+
2052
+ return {
2053
+ // Custom connect function that works without arguments
2054
+ connect: (options?: { forceWalletSelect?: boolean }) => {
2055
+ return mutation.mutate(options || {})
2056
+ },
2057
+ connectAsync: async (options?: { forceWalletSelect?: boolean }) => {
2058
+ return mutation.mutateAsync(options || {})
2059
+ },
2060
+ // Expose all the mutation state
2061
+ isPending: mutation.isPending,
2062
+ isError: mutation.isError,
2063
+ isSuccess: mutation.isSuccess,
2064
+ error: mutation.error,
2065
+ data: mutation.data,
2066
+ reset: mutation.reset,
2067
+ // Keep the original mutate/mutateAsync for advanced users
2068
+ mutate: mutation.mutate,
2069
+ mutateAsync: mutation.mutateAsync
2070
+ }
2071
+ }`;
2072
+ case "useDisconnect":
2073
+ return `export function useDisconnect() {
2074
+ const queryClient = useQueryClient()
2075
+
2076
+ const mutation = useMutation({
2077
+ mutationFn: async () => {
2078
+ // SIP-030 disconnect
2079
+ return await disconnect()
2080
+ },
2081
+ onSuccess: () => {
2082
+ // Clear all cached data on disconnect
2083
+ queryClient.clear()
2084
+ },
2085
+ onError: (error) => {
2086
+ console.error('Disconnect failed:', error)
2087
+ }
2088
+ })
2089
+
2090
+ return {
2091
+ // Custom disconnect function
2092
+ disconnect: () => {
2093
+ return mutation.mutate()
2094
+ },
2095
+ disconnectAsync: async () => {
2096
+ return mutation.mutateAsync()
2097
+ },
2098
+ // Expose all the mutation state
2099
+ isPending: mutation.isPending,
2100
+ isError: mutation.isError,
2101
+ isSuccess: mutation.isSuccess,
2102
+ error: mutation.error,
2103
+ data: mutation.data,
2104
+ reset: mutation.reset,
2105
+ // Keep the original mutate/mutateAsync for advanced users
2106
+ mutate: mutation.mutate,
2107
+ mutateAsync: mutation.mutateAsync
2108
+ }
2109
+ }`;
2110
+ case "useNetwork":
2111
+ return `export function useNetwork() {
2112
+ const config = useSecondLayerConfig()
2113
+
2114
+ return useQuery({
2115
+ queryKey: ['stacks-network', config.network],
2116
+ queryFn: async () => {
2117
+ // Currently read-only from config
2118
+ // Future: Use request('stx_getNetworks') when wallet support improves
2119
+ const network = config.network
2120
+
2121
+ return {
2122
+ network,
2123
+ isMainnet: network === 'mainnet',
2124
+ isTestnet: network === 'testnet',
2125
+ isDevnet: network === 'devnet',
2126
+ // Future: Add switchNetwork when wallets support stx_networkChange
2127
+ // switchNetwork: async (newNetwork: string) => {
2128
+ // return await request('wallet_changeNetwork', { network: newNetwork })
2129
+ // }
2130
+ }
2131
+ },
2132
+ staleTime: Infinity, // Network config rarely changes
2133
+ refetchOnWindowFocus: false,
2134
+ retry: false
2135
+ })
2136
+ }`;
2137
+ case "useContract":
2138
+ return USE_CONTRACT_TEMPLATE;
2139
+ case "useReadContract":
2140
+ return `export function useReadContract<TArgs = any, TResult = any>(params: {
2141
+ contractAddress: string;
2142
+ contractName: string;
2143
+ functionName: string;
2144
+ args?: TArgs;
2145
+ network?: 'mainnet' | 'testnet' | 'devnet';
2146
+ enabled?: boolean;
2147
+ }) {
2148
+ const config = useSecondLayerConfig()
2149
+
2150
+ return useQuery<TResult>({
2151
+ queryKey: ['read-contract', params.contractAddress, params.contractName, params.functionName, params.args, params.network || config.network],
2152
+ queryFn: async () => {
2153
+ const { fetchCallReadOnlyFunction } = await import('@secondlayer/stacks/clarity')
2154
+
2155
+ // For now, we'll need to handle the args conversion here
2156
+ // In the future, we could integrate with the contract interface for automatic conversion
2157
+ let functionArgs: any[] = []
2158
+
2159
+ if (params.args) {
2160
+ // This is a simplified conversion - in practice, we'd need the ABI to do proper conversion
2161
+ // For now, we'll assume the args are already in the correct format or simple types
2162
+ if (Array.isArray(params.args)) {
2163
+ functionArgs = params.args
2164
+ } else if (typeof params.args === 'object') {
2165
+ // Convert object args to array (this is a basic implementation)
2166
+ functionArgs = Object.values(params.args)
2167
+ } else {
2168
+ functionArgs = [params.args]
2169
+ }
2170
+ }
2171
+
2172
+ return await fetchCallReadOnlyFunction({
2173
+ contractAddress: params.contractAddress,
2174
+ contractName: params.contractName,
2175
+ functionName: params.functionName,
2176
+ functionArgs,
2177
+ network: params.network || config.network || 'mainnet',
2178
+ senderAddress: config.senderAddress || 'SP000000000000000000002Q6VF78'
2179
+ }) as TResult
2180
+ },
2181
+ enabled: params.enabled ?? true
2182
+ })
2183
+ }`;
2184
+ case "useTransaction":
2185
+ return `export function useTransaction(txId?: string) {
2186
+ const config = useSecondLayerConfig()
2187
+
2188
+ return useQuery({
2189
+ queryKey: ['transaction', txId, config.network],
2190
+ queryFn: () => fetchTransaction({
2191
+ txId: txId!,
2192
+ network: config.network,
2193
+ apiUrl: config.apiUrl
2194
+ }),
2195
+ enabled: !!txId
2196
+ })
2197
+ }`;
2198
+ case "useBlock":
2199
+ return `export function useBlock(height?: number) {
2200
+ const config = useSecondLayerConfig()
2201
+
2202
+ return useQuery({
2203
+ queryKey: ['block', height, config.network],
2204
+ queryFn: () => fetchBlock({
2205
+ height: height!,
2206
+ network: config.network,
2207
+ apiUrl: config.apiUrl
2208
+ }),
2209
+ enabled: typeof height === 'number'
2210
+ })
2211
+ }`;
2212
+ case "useAccountTransactions":
2213
+ return `export function useAccountTransactions(address?: string) {
2214
+ const config = useSecondLayerConfig()
2215
+
2216
+ return useQuery({
2217
+ queryKey: ['account-transactions', address, config.network],
2218
+ queryFn: () => fetchAccountTransactions({
2219
+ address: address!,
2220
+ network: config.network,
2221
+ apiUrl: config.apiUrl
2222
+ }),
2223
+ enabled: !!address
2224
+ })
2225
+ }`;
2226
+ case "useWaitForTransaction":
2227
+ return `export function useWaitForTransaction(txId?: string) {
2228
+ const config = useSecondLayerConfig()
2229
+
2230
+ return useQuery({
2231
+ queryKey: ['wait-for-transaction', txId, config.network],
2232
+ queryFn: () => fetchTransaction({
2233
+ txId: txId!,
2234
+ network: config.network,
2235
+ apiUrl: config.apiUrl
2236
+ }),
2237
+ enabled: !!txId,
2238
+ refetchInterval: (data) => {
2239
+ // Stop polling when transaction is complete
2240
+ if (data?.tx_status === 'success' ||
2241
+ data?.tx_status === 'abort_by_response' ||
2242
+ data?.tx_status === 'abort_by_post_condition') {
2243
+ return false
2244
+ }
2245
+ return 2000 // Poll every 2 seconds
2246
+ },
2247
+ staleTime: 0 // Always refetch
2248
+ })
2249
+ }`;
2250
+ case "useOpenSTXTransfer":
2251
+ return `export function useOpenSTXTransfer() {
2252
+ const config = useSecondLayerConfig()
2253
+ const queryClient = useQueryClient()
2254
+
2255
+ const mutation = useMutation({
2256
+ mutationFn: async (params: {
2257
+ recipient: string;
2258
+ amount: string | number;
2259
+ memo?: string;
2260
+ network?: string;
2261
+ }) => {
2262
+ const { recipient, amount, memo } = params
2263
+ const network = params.network || config.network || 'mainnet'
2264
+
2265
+ return await request('stx_transferStx', {
2266
+ recipient,
2267
+ amount: amount.toString(),
2268
+ memo,
2269
+ network,
2270
+ })
2271
+ },
2272
+ onSuccess: () => {
2273
+ queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
2274
+ },
2275
+ onError: (error) => {
2276
+ console.error('STX transfer failed:', error)
2277
+ }
2278
+ })
2279
+
2280
+ const openSTXTransfer = useCallback(async (params: {
2281
+ recipient: string;
2282
+ amount: string | number;
2283
+ memo?: string;
2284
+ network?: string;
2285
+ }) => {
2286
+ return mutation.mutateAsync(params)
2287
+ }, [mutation])
2288
+
2289
+ return {
2290
+ openSTXTransfer,
2291
+ isPending: mutation.isPending,
2292
+ isError: mutation.isError,
2293
+ isSuccess: mutation.isSuccess,
2294
+ error: mutation.error,
2295
+ data: mutation.data,
2296
+ reset: mutation.reset
2297
+ }
2298
+ }`;
2299
+ case "useSignMessage":
2300
+ return `export function useSignMessage() {
2301
+ const config = useSecondLayerConfig()
2302
+
2303
+ const mutation = useMutation({
2304
+ mutationFn: async (params: {
2305
+ message: string;
2306
+ network?: string;
2307
+ }) => {
2308
+ const { message } = params
2309
+ const network = params.network || config.network || 'mainnet'
2310
+
2311
+ return await request('stx_signMessage', {
2312
+ message,
2313
+ network,
2314
+ })
2315
+ },
2316
+ onError: (error) => {
2317
+ console.error('Message signing failed:', error)
2318
+ }
2319
+ })
2320
+
2321
+ const signMessage = useCallback(async (params: {
2322
+ message: string;
2323
+ network?: string;
2324
+ }) => {
2325
+ return mutation.mutateAsync(params)
2326
+ }, [mutation])
2327
+
2328
+ return {
2329
+ signMessage,
2330
+ isPending: mutation.isPending,
2331
+ isError: mutation.isError,
2332
+ isSuccess: mutation.isSuccess,
2333
+ error: mutation.error,
2334
+ data: mutation.data,
2335
+ reset: mutation.reset
2336
+ }
2337
+ }`;
2338
+ case "useDeployContract":
2339
+ return `export function useDeployContract() {
2340
+ const config = useSecondLayerConfig()
2341
+ const queryClient = useQueryClient()
2342
+
2343
+ const mutation = useMutation({
2344
+ mutationFn: async (params: {
2345
+ contractName: string;
2346
+ codeBody: string;
2347
+ network?: string;
2348
+ }) => {
2349
+ const { contractName, codeBody } = params
2350
+ const network = params.network || config.network || 'mainnet'
2351
+
2352
+ return await request('stx_deployContract', {
2353
+ name: contractName,
2354
+ clarityCode: codeBody,
2355
+ network,
2356
+ })
2357
+ },
2358
+ onSuccess: () => {
2359
+ queryClient.invalidateQueries({ queryKey: ['stacks-account'] })
2360
+ },
2361
+ onError: (error) => {
2362
+ console.error('Contract deployment failed:', error)
2363
+ }
2364
+ })
2365
+
2366
+ const deployContract = useCallback(async (params: {
2367
+ contractName: string;
2368
+ codeBody: string;
2369
+ network?: string;
2370
+ }) => {
2371
+ return mutation.mutateAsync(params)
2372
+ }, [mutation])
2373
+
2374
+ return {
2375
+ deployContract,
2376
+ isPending: mutation.isPending,
2377
+ isError: mutation.isError,
2378
+ isSuccess: mutation.isSuccess,
2379
+ error: mutation.error,
2380
+ data: mutation.data,
2381
+ reset: mutation.reset
2382
+ }
2383
+ }`;
2384
+ default:
2385
+ return "";
2386
+ }
2387
+ }
2388
+
2389
+ // src/plugins/react/provider/index.ts
2390
+ init_format();
2391
+ async function generateProvider() {
2392
+ const code = `/**
2393
+ * Generated SecondLayer React Provider
2394
+ * DO NOT EDIT MANUALLY
2395
+ */
2396
+
2397
+ import React, { createContext, useContext } from 'react'
2398
+
2399
+ /**
2400
+ * SecondLayer configuration interface
2401
+ */
2402
+ export interface SecondLayerReactConfig {
2403
+ /**
2404
+ * Network to use for API calls
2405
+ */
2406
+ network: 'mainnet' | 'testnet' | 'devnet'
2407
+
2408
+ /**
2409
+ * API key for Stacks API (optional)
2410
+ */
2411
+ apiKey?: string
2412
+
2413
+ /**
2414
+ * Base URL for Stacks API (optional override)
2415
+ */
2416
+ apiUrl?: string
2417
+
2418
+ /**
2419
+ * Default sender address for read-only calls
2420
+ */
2421
+ senderAddress?: string
2422
+ }
2423
+
2424
+ /**
2425
+ * Provider component props
2426
+ */
2427
+ export interface SecondLayerProviderProps {
2428
+ children: React.ReactNode
2429
+ config: SecondLayerReactConfig
2430
+ }
2431
+
2432
+ /**
2433
+ * React context for SecondLayer configuration
2434
+ */
2435
+ const SecondLayerContext = createContext<SecondLayerReactConfig | undefined>(undefined)
2436
+ SecondLayerContext.displayName = 'SecondLayerContext'
2437
+
2438
+ /**
2439
+ * Create a SecondLayer React configuration with defaults
2440
+ */
2441
+ export function createSecondLayerConfig(config: SecondLayerReactConfig): SecondLayerReactConfig {
2442
+ return {
2443
+ network: config.network,
2444
+ apiKey: config.apiKey,
2445
+ apiUrl: config.apiUrl,
2446
+ senderAddress: config.senderAddress || 'SP000000000000000000002Q6VF78'
2447
+ }
2448
+ }
2449
+
2450
+ /**
2451
+ * Provider component that makes SecondLayer configuration available to hooks
2452
+ */
2453
+ export function SecondLayerProvider({ children, config }: SecondLayerProviderProps) {
2454
+ const resolvedConfig = createSecondLayerConfig(config)
2455
+
2456
+ return (
2457
+ <SecondLayerContext.Provider value={resolvedConfig}>
2458
+ {children}
2459
+ </SecondLayerContext.Provider>
2460
+ )
2461
+ }
2462
+
2463
+ /**
2464
+ * Hook to access the SecondLayer configuration
2465
+ */
2466
+ export function useSecondLayerConfig(): SecondLayerReactConfig {
2467
+ const context = useContext(SecondLayerContext)
2468
+
2469
+ if (context === undefined) {
2470
+ throw new Error(
2471
+ 'useSecondLayerConfig must be used within a SecondLayerProvider. ' +
2472
+ 'Make sure to wrap your app with <SecondLayerProvider config={{...}}>'
2473
+ )
2474
+ }
2475
+
2476
+ return context
2477
+ }`;
2478
+ return formatCode(code);
2479
+ }
2480
+
2481
+ // src/plugins/react/index.ts
2482
+ var react = (options = {}) => {
2483
+ const excludeList = options.exclude || [];
2484
+ return {
2485
+ name: "@secondlayer/cli/plugin-react",
2486
+ version: "1.0.0",
2487
+ async generate(context) {
2488
+ if (options.debug) {
2489
+ context.logger.debug(`React plugin generating hooks (excluding: ${excludeList.join(", ") || "none"})`);
2490
+ }
2491
+ const provider = await generateProvider();
2492
+ context.addOutput("provider", {
2493
+ path: "./src/generated/provider.tsx",
2494
+ content: provider,
2495
+ type: "config"
2496
+ });
2497
+ const genericHooks = await generateGenericHooks(excludeList);
2498
+ context.addOutput("generic-hooks", {
2499
+ path: "./src/generated/hooks.ts",
2500
+ content: genericHooks,
2501
+ type: "hooks"
2502
+ });
2503
+ if (context.contracts.length > 0) {
2504
+ const contractHooks = await generateContractHooks(context.contracts, excludeList);
2505
+ if (contractHooks.trim()) {
2506
+ context.addOutput("contract-hooks", {
2507
+ path: "./src/generated/contract-hooks.ts",
2508
+ content: contractHooks,
2509
+ type: "hooks"
2510
+ });
2511
+ }
2512
+ }
2513
+ if (options.debug) {
2514
+ context.logger.success(`React plugin generated ${context.contracts.length} contract hook sets`);
2515
+ }
2516
+ }
2517
+ };
2518
+ };
2519
+ // src/plugins/testing/generators.ts
2520
+ import {
2521
+ isAbiTuple as isAbiTuple4
2522
+ } from "@secondlayer/stacks/clarity";
2523
+ init_generator_helpers();
2524
+ init_type_mapping();
2525
+ function generatePublicFunction(func, contractId) {
2526
+ const methodName = toCamelCase8(func.name);
2527
+ const argsSignature = generateArgsSignature(func.args);
2528
+ const clarityArgs = generateClarityArgs(func.args);
2529
+ return `${methodName}: (${argsSignature}caller: string) => {
2530
+ const callerAddr = accounts.get(caller) ?? caller;
2531
+ return simnet.callPublicFn(
2532
+ '${contractId}',
2533
+ '${func.name}',
2534
+ [${clarityArgs}],
2535
+ callerAddr
2536
+ );
2537
+ }`;
2538
+ }
2539
+ function generateReadOnlyFunction(func, contractId) {
2540
+ const methodName = toCamelCase8(func.name);
2541
+ const argsSignature = generateArgsSignature(func.args);
2542
+ const clarityArgs = generateClarityArgs(func.args);
2543
+ const hasArgs = func.args.length > 0;
2544
+ const argsParam = hasArgs ? argsSignature : "";
2545
+ return `${methodName}: (${argsParam}) => {
2546
+ return simnet.callReadOnlyFn(
2547
+ '${contractId}',
2548
+ '${func.name}',
2549
+ [${clarityArgs}],
2550
+ accounts.get('deployer')!
2551
+ );
2552
+ }`;
2553
+ }
2554
+ function generatePrivateFunction(func, contractId) {
2555
+ const methodName = toCamelCase8(func.name);
2556
+ const argsSignature = generateArgsSignature(func.args);
2557
+ const clarityArgs = generateClarityArgs(func.args);
2558
+ return `${methodName}: (${argsSignature}caller: string) => {
2559
+ const callerAddr = accounts.get(caller) ?? caller;
2560
+ return simnet.callPrivateFn(
2561
+ '${contractId}',
2562
+ '${func.name}',
2563
+ [${clarityArgs}],
2564
+ callerAddr
2565
+ );
2566
+ }`;
2567
+ }
2568
+ function generateDataVarHelper(variable, contractId) {
2569
+ const methodName = toCamelCase8(variable.name);
2570
+ return `${methodName}: () => {
2571
+ return simnet.getDataVar('${contractId}', '${variable.name}');
2572
+ }`;
2573
+ }
2574
+ function getMapKeyType(keyType) {
2575
+ if (isAbiTuple4(keyType)) {
2576
+ const fields = keyType.tuple.map((field) => `${toCamelCase8(field.name)}: ${getTypeForArg({ type: field.type })}`).join("; ");
2577
+ return `{ ${fields} }`;
2578
+ }
2579
+ return getTypeForArg({ type: keyType });
2580
+ }
2581
+ function generateMapEntryHelper(map, contractId) {
2582
+ const methodName = toCamelCase8(map.name);
2583
+ const keyType = getMapKeyType(map.key);
2584
+ const keyConversion = generateMapKeyConversion(map.key);
2585
+ return `${methodName}: (key: ${keyType}) => {
2586
+ return simnet.getMapEntry(
2587
+ '${contractId}',
2588
+ '${map.name}',
2589
+ ${keyConversion}
2590
+ );
2591
+ }`;
2592
+ }
2593
+ function generateVarsObject2(variables, contractId) {
2594
+ const dataVars = variables.filter((v) => v.access === "variable");
2595
+ if (dataVars.length === 0) {
2596
+ return "";
2597
+ }
2598
+ const varHelpers = dataVars.map((v) => generateDataVarHelper(v, contractId));
2599
+ return `vars: {
2600
+ ${varHelpers.join(`,
2601
+
2602
+ `)}
2603
+ }`;
2604
+ }
2605
+ function generateMapsObject2(maps, contractId) {
2606
+ if (maps.length === 0) {
2607
+ return "";
2608
+ }
2609
+ const mapHelpers = maps.map((m) => generateMapEntryHelper(m, contractId));
2610
+ return `maps: {
2611
+ ${mapHelpers.join(`,
2612
+
2613
+ `)}
2614
+ }`;
2615
+ }
2616
+ function generateContractHelper(contract, options) {
2617
+ const { abi, name, address } = contract;
2618
+ const functions = abi.functions || [];
2619
+ const variables = abi.variables || [];
2620
+ const maps = abi.maps || [];
2621
+ const pascalName = toPascalCase(name);
2622
+ const publicFns = functions.filter((f) => f.access === "public");
2623
+ const readOnlyFns = functions.filter((f) => f.access === "read-only");
2624
+ const privateFns = options.includePrivate ? functions.filter((f) => f.access === "private") : [];
2625
+ const publicHelpers = publicFns.map((f) => generatePublicFunction(f, address));
2626
+ const readOnlyHelpers = readOnlyFns.map((f) => generateReadOnlyFunction(f, address));
2627
+ const privateHelpers = privateFns.map((f) => generatePrivateFunction(f, address));
2628
+ const varsObject = generateVarsObject2(variables, address);
2629
+ const mapsObject = generateMapsObject2(maps, address);
2630
+ const allHelpers = [...publicHelpers, ...readOnlyHelpers, ...privateHelpers];
2631
+ if (varsObject) {
2632
+ allHelpers.push(varsObject);
2633
+ }
2634
+ if (mapsObject) {
2635
+ allHelpers.push(mapsObject);
2636
+ }
2637
+ if (allHelpers.length === 0) {
2638
+ return "";
2639
+ }
2640
+ return `export function get${pascalName}(simnet: Simnet) {
2641
+ const accounts = simnet.getAccounts();
2642
+
2643
+ return {
2644
+ ${allHelpers.join(`,
2645
+
2646
+ `)}
2647
+ };
2648
+ }`;
2649
+ }
2650
+ function generateGetContracts(contracts) {
2651
+ const contractEntries = contracts.map((contract) => {
2652
+ const camelName = toCamelCase8(contract.name);
2653
+ const pascalName = toPascalCase(contract.name);
2654
+ return `${camelName}: get${pascalName}(simnet)`;
2655
+ }).join(`,
2656
+ `);
2657
+ return `export function getContracts(simnet: Simnet) {
2658
+ const accounts = simnet.getAccounts();
2659
+
2660
+ return {
2661
+ accounts,
2662
+ ${contractEntries}
2663
+ };
2664
+ }`;
2665
+ }
2666
+ function generateTypeExports(contracts) {
2667
+ const typeExports = contracts.map((contract) => {
2668
+ const pascalName = toPascalCase(contract.name);
2669
+ return `export type ${pascalName}Helpers = ReturnType<typeof get${pascalName}>;`;
2670
+ }).join(`
2671
+ `);
2672
+ return `${typeExports}
2673
+ export type Contracts = ReturnType<typeof getContracts>;`;
2674
+ }
2675
+ async function generateTestingHelpers(contracts, options) {
2676
+ const contractHelpers = contracts.map((contract) => generateContractHelper(contract, options)).filter(Boolean);
2677
+ if (contractHelpers.length === 0) {
2678
+ return `// No contracts with functions to generate helpers for
2679
+ export {};`;
2680
+ }
2681
+ const getContractsCode = generateGetContracts(contracts);
2682
+ const typeExports = generateTypeExports(contracts);
2683
+ return `/**
2684
+ * Generated by @secondlayer/cli testing plugin
2685
+ * Type-safe helpers for Clarinet SDK unit tests
2686
+ */
2687
+
2688
+ import { type Simnet, Cl } from '@hirosystems/clarinet-sdk';
2689
+
2690
+ // ============================================
2691
+ // Per-contract factory functions
2692
+ // ============================================
2693
+
2694
+ ${contractHelpers.join(`
2695
+
2696
+ `)}
2697
+
2698
+ // ============================================
2699
+ // Convenience: all contracts at once
2700
+ // ============================================
2701
+
2702
+ ${getContractsCode}
2703
+
2704
+ // ============================================
2705
+ // Type exports
2706
+ // ============================================
2707
+
2708
+ ${typeExports}
2709
+ `;
2710
+ }
2711
+
2712
+ // src/plugins/testing/index.ts
2713
+ var testing = (options = {}) => {
2714
+ return {
2715
+ name: "@secondlayer/cli/plugin-testing",
2716
+ version: "1.0.0",
2717
+ async generate(context) {
2718
+ const { contracts } = context;
2719
+ const filteredContracts = contracts.filter((contract) => {
2720
+ if (options.include && !options.include.includes(contract.name)) {
2721
+ return false;
2722
+ }
2723
+ if (options.exclude && options.exclude.includes(contract.name)) {
2724
+ return false;
2725
+ }
2726
+ return true;
2727
+ });
2728
+ if (filteredContracts.length === 0) {
2729
+ if (options.debug) {
2730
+ context.logger.debug("Testing plugin: No contracts to process");
2731
+ }
2732
+ return;
2733
+ }
2734
+ if (options.debug) {
2735
+ context.logger.debug(`Testing plugin: Generating helpers for ${filteredContracts.length} contracts`);
2736
+ }
2737
+ const testingCode = await generateTestingHelpers(filteredContracts, options);
2738
+ const outputPath = options.out || "./src/generated/testing.ts";
2739
+ context.addOutput("testing", {
2740
+ path: outputPath,
2741
+ content: testingCode,
2742
+ type: "utils"
2743
+ });
2744
+ if (options.debug) {
2745
+ context.logger.debug(`Testing plugin: Generated helpers for ${filteredContracts.length} contracts`);
2746
+ }
2747
+ }
2748
+ };
2749
+ };
2750
+
2751
+ // src/plugins/index.ts
2752
+ function filterByOptions(items, options = {}) {
2753
+ let filtered = items;
2754
+ if (options.include && options.include.length > 0) {
2755
+ filtered = filtered.filter((item) => options.include.some((pattern) => item.name.includes(pattern) || item.name.match(new RegExp(pattern))));
2756
+ }
2757
+ if (options.exclude && options.exclude.length > 0) {
2758
+ filtered = filtered.filter((item) => !options.exclude.some((pattern) => item.name.includes(pattern) || item.name.match(new RegExp(pattern))));
2759
+ }
2760
+ return filtered;
2761
+ }
2762
+ function createPlugin(name, version, implementation) {
2763
+ return {
2764
+ name,
2765
+ version,
2766
+ ...implementation
2767
+ };
2768
+ }
2769
+ export {
2770
+ testing,
2771
+ react,
2772
+ hasClarinetProject,
2773
+ filterByOptions,
2774
+ createPlugin,
2775
+ clarinet,
2776
+ actions,
2777
+ PluginManager
2778
+ };
2779
+
2780
+ //# debugId=CF062C27A0D1420964756E2164756E21
2781
+ //# sourceMappingURL=index.js.map