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