@devvmichael/create-stacks-app 0.2.3 → 0.2.4
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 +2 -2
- package/dist/utils/clarinet.d.ts +1 -1
- package/dist/utils/clarinet.js +34 -42
- package/dist/utils/clarinet.js.map +1 -1
- package/package.json +1 -1
- package/templates/frontends/react/template/src/App.tsx +38 -29
- package/templates/frontends/react/template/src/components/CounterInteraction.tsx +34 -32
- package/templates/frontends/vue/template/src/App.vue +18 -20
- package/templates/frontends/vue/template/src/components/CounterInteraction.vue +15 -17
package/README.md
CHANGED
|
@@ -50,13 +50,13 @@ npx @devvmichael/create-stacks-app
|
|
|
50
50
|
npx @devvmichael/create-stacks-app my-dapp [options]
|
|
51
51
|
|
|
52
52
|
Options:
|
|
53
|
-
-t, --template <name> Frontend template: nextjs, react, vue
|
|
53
|
+
-t, --template <name> Frontend template: nextjs, react, vue
|
|
54
54
|
-c, --contracts <list> Contracts to include: counter,token,nft
|
|
55
55
|
--typescript Use TypeScript (default: true)
|
|
56
56
|
--no-typescript Use JavaScript
|
|
57
57
|
--tailwind Include Tailwind CSS (default: true)
|
|
58
58
|
--no-git Skip Git initialization
|
|
59
|
-
--package-manager <pm> Package manager: npm, pnpm, yarn
|
|
59
|
+
--package-manager <pm> Package manager: npm, pnpm, yarn
|
|
60
60
|
--skip-install Skip dependency installation
|
|
61
61
|
-y, --yes Skip prompts, use defaults
|
|
62
62
|
```
|
package/dist/utils/clarinet.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProjectConfig } from
|
|
1
|
+
import type { ProjectConfig } from "../types/index.js";
|
|
2
2
|
export declare function checkClarinetInstallation(): Promise<boolean>;
|
|
3
3
|
export declare function initializeClarinet(config: ProjectConfig): Promise<void>;
|
|
4
4
|
export declare function updateClarinetConfig(projectPath: string, contracts: string[]): Promise<void>;
|
package/dist/utils/clarinet.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { exec } from
|
|
2
|
-
import { promisify } from
|
|
3
|
-
import ora from
|
|
4
|
-
import path from
|
|
5
|
-
import fs from
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import fs from "fs-extra";
|
|
6
6
|
const execAsync = promisify(exec);
|
|
7
7
|
export async function checkClarinetInstallation() {
|
|
8
8
|
try {
|
|
9
|
-
await execAsync(
|
|
9
|
+
await execAsync("clarinet --version");
|
|
10
10
|
return true;
|
|
11
11
|
}
|
|
12
12
|
catch {
|
|
@@ -14,50 +14,42 @@ export async function checkClarinetInstallation() {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
export async function initializeClarinet(config) {
|
|
17
|
-
const spinner = ora(
|
|
17
|
+
const spinner = ora("Initializing Clarinet...").start();
|
|
18
18
|
try {
|
|
19
19
|
const { projectPath, projectName } = config;
|
|
20
|
-
// Create
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
balance = 10_000_000_000_000_000
|
|
45
|
-
|
|
46
|
-
[accounts.wallet_2]
|
|
47
|
-
mnemonic = "hold excess usual excess ring elephant install account glad dry display sauce"
|
|
48
|
-
balance = 10_000_000_000_000_000
|
|
49
|
-
`;
|
|
50
|
-
await fs.writeFile(path.join(projectPath, 'settings', 'Devnet.toml'), devnetToml);
|
|
51
|
-
spinner.succeed('Clarinet initialized');
|
|
20
|
+
// Create a temporary directory for Clarinet initialization
|
|
21
|
+
const tempDir = path.join(projectPath, ".temp_clarinet");
|
|
22
|
+
await fs.ensureDir(tempDir);
|
|
23
|
+
// Run clarinet new in temp directory
|
|
24
|
+
try {
|
|
25
|
+
await execAsync(`cd "${tempDir}" && clarinet new "${projectName}"`);
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
// If clarinet new fails, it might be because of directory structure
|
|
29
|
+
// specific error handling could be added here
|
|
30
|
+
throw new Error(`Clarinet initialization failed: ${e.message}`);
|
|
31
|
+
}
|
|
32
|
+
const sourceDir = path.join(tempDir, projectName);
|
|
33
|
+
// Copy generated Clarinet.toml
|
|
34
|
+
if (await fs.pathExists(path.join(sourceDir, "Clarinet.toml"))) {
|
|
35
|
+
await fs.copy(path.join(sourceDir, "Clarinet.toml"), path.join(projectPath, "Clarinet.toml"));
|
|
36
|
+
}
|
|
37
|
+
// Copy settings directory (Devnet.toml)
|
|
38
|
+
if (await fs.pathExists(path.join(sourceDir, "settings"))) {
|
|
39
|
+
await fs.copy(path.join(sourceDir, "settings"), path.join(projectPath, "settings"));
|
|
40
|
+
}
|
|
41
|
+
// Cleanup temp directory
|
|
42
|
+
await fs.remove(tempDir);
|
|
43
|
+
spinner.succeed("Clarinet initialized");
|
|
52
44
|
}
|
|
53
45
|
catch (error) {
|
|
54
|
-
spinner.fail(
|
|
46
|
+
spinner.fail("Failed to initialize Clarinet");
|
|
55
47
|
throw error;
|
|
56
48
|
}
|
|
57
49
|
}
|
|
58
50
|
export async function updateClarinetConfig(projectPath, contracts) {
|
|
59
|
-
const clarinetTomlPath = path.join(projectPath,
|
|
60
|
-
let tomlContent = await fs.readFile(clarinetTomlPath,
|
|
51
|
+
const clarinetTomlPath = path.join(projectPath, "Clarinet.toml");
|
|
52
|
+
let tomlContent = await fs.readFile(clarinetTomlPath, "utf-8");
|
|
61
53
|
// Add contracts based on selection
|
|
62
54
|
for (const contract of contracts) {
|
|
63
55
|
tomlContent += `
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clarinet.js","sourceRoot":"","sources":["../../src/utils/clarinet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,UAAU,CAAC;AAG1B,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;AAElC,MAAM,CAAC,KAAK,UAAU,yBAAyB;IAC7C,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,oBAAoB,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAqB;IAC5D,MAAM,OAAO,GAAG,GAAG,CAAC,0BAA0B,CAAC,CAAC,KAAK,EAAE,CAAC;IAExD,IAAI,CAAC;QACH,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;QAE5C,
|
|
1
|
+
{"version":3,"file":"clarinet.js","sourceRoot":"","sources":["../../src/utils/clarinet.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback } from
|
|
2
|
-
import {
|
|
3
|
-
import { StacksTestnet, StacksMainnet } from
|
|
4
|
-
import { Header } from
|
|
5
|
-
import { CounterInteraction } from
|
|
6
|
-
|
|
7
|
-
const appConfig = new AppConfig(['store_write', 'publish_data']);
|
|
8
|
-
const userSession = new UserSession({ appConfig });
|
|
1
|
+
import { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { connect, disconnect, getLocalStorage } from "@stacks/connect";
|
|
3
|
+
import { StacksTestnet, StacksMainnet } from "@stacks/network";
|
|
4
|
+
import { Header } from "./components/Header";
|
|
5
|
+
import { CounterInteraction } from "./components/CounterInteraction";
|
|
9
6
|
|
|
10
7
|
const network =
|
|
11
|
-
import.meta.env.VITE_NETWORK ===
|
|
8
|
+
import.meta.env.VITE_NETWORK === "mainnet"
|
|
12
9
|
? new StacksMainnet()
|
|
13
10
|
: new StacksTestnet();
|
|
14
11
|
|
|
@@ -16,29 +13,34 @@ function App() {
|
|
|
16
13
|
const [address, setAddress] = useState<string | null>(null);
|
|
17
14
|
|
|
18
15
|
useEffect(() => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
// Check local storage for existing session
|
|
17
|
+
const storage = getLocalStorage();
|
|
18
|
+
const networkKey =
|
|
19
|
+
import.meta.env.VITE_NETWORK === "mainnet" ? "mainnet" : "testnet";
|
|
20
|
+
if (storage?.addresses?.[networkKey]) {
|
|
21
|
+
setAddress(storage.addresses[networkKey]);
|
|
23
22
|
}
|
|
24
23
|
}, []);
|
|
25
24
|
|
|
26
|
-
const handleConnect = useCallback(() => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
const handleConnect = useCallback(async () => {
|
|
26
|
+
try {
|
|
27
|
+
const response = await connect();
|
|
28
|
+
// Access the first address from the response
|
|
29
|
+
const userAddress = response.addresses?.[0]?.address;
|
|
30
|
+
if (userAddress) {
|
|
31
|
+
setAddress(userAddress);
|
|
32
|
+
// Optional: reload if needed to reset state, or handle reactively
|
|
34
33
|
window.location.reload();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error("Failed to connect:", error);
|
|
37
|
+
}
|
|
38
38
|
}, []);
|
|
39
39
|
|
|
40
40
|
const handleDisconnect = useCallback(() => {
|
|
41
|
-
|
|
41
|
+
disconnect();
|
|
42
|
+
setAddress(null);
|
|
43
|
+
window.location.reload();
|
|
42
44
|
}, []);
|
|
43
45
|
|
|
44
46
|
return (
|
|
@@ -51,7 +53,9 @@ function App() {
|
|
|
51
53
|
|
|
52
54
|
<main className="flex-1 container mx-auto px-4 py-8">
|
|
53
55
|
<div className="mb-8 text-center">
|
|
54
|
-
<h1 className="mb-4 text-4xl font-bold">
|
|
56
|
+
<h1 className="mb-4 text-4xl font-bold">
|
|
57
|
+
Welcome to Your Stacks App
|
|
58
|
+
</h1>
|
|
55
59
|
<p className="text-lg text-gray-400">
|
|
56
60
|
A full-stack Stacks blockchain application
|
|
57
61
|
</p>
|
|
@@ -71,19 +75,24 @@ function App() {
|
|
|
71
75
|
<div className="card">
|
|
72
76
|
<h3 className="font-semibold mb-2">📝 Edit Contracts</h3>
|
|
73
77
|
<p className="text-sm text-gray-400">
|
|
74
|
-
Modify contracts in
|
|
78
|
+
Modify contracts in{" "}
|
|
79
|
+
<code className="bg-gray-800 px-1 rounded">contracts/</code>
|
|
75
80
|
</p>
|
|
76
81
|
</div>
|
|
77
82
|
<div className="card">
|
|
78
83
|
<h3 className="font-semibold mb-2">🧪 Run Tests</h3>
|
|
79
84
|
<p className="text-sm text-gray-400">
|
|
80
|
-
Run
|
|
85
|
+
Run{" "}
|
|
86
|
+
<code className="bg-gray-800 px-1 rounded">npm run test</code>
|
|
81
87
|
</p>
|
|
82
88
|
</div>
|
|
83
89
|
<div className="card">
|
|
84
90
|
<h3 className="font-semibold mb-2">🚀 Deploy</h3>
|
|
85
91
|
<p className="text-sm text-gray-400">
|
|
86
|
-
Run
|
|
92
|
+
Run{" "}
|
|
93
|
+
<code className="bg-gray-800 px-1 rounded">
|
|
94
|
+
npm run deploy:testnet
|
|
95
|
+
</code>
|
|
87
96
|
</p>
|
|
88
97
|
</div>
|
|
89
98
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback } from
|
|
2
|
-
import {
|
|
3
|
-
import { callReadOnlyFunction, cvToValue } from
|
|
4
|
-
import type { StacksNetwork } from
|
|
1
|
+
import { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { request } from "@stacks/connect";
|
|
3
|
+
import { callReadOnlyFunction, cvToValue } from "@stacks/transactions";
|
|
4
|
+
import type { StacksNetwork } from "@stacks/network";
|
|
5
5
|
|
|
6
6
|
interface CounterInteractionProps {
|
|
7
7
|
network: StacksNetwork;
|
|
@@ -9,10 +9,16 @@ interface CounterInteractionProps {
|
|
|
9
9
|
senderAddress: string | null;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const contractAddress =
|
|
13
|
-
|
|
12
|
+
const contractAddress =
|
|
13
|
+
import.meta.env.VITE_CONTRACT_ADDRESS ||
|
|
14
|
+
"ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM";
|
|
15
|
+
const contractName = "counter";
|
|
14
16
|
|
|
15
|
-
export function CounterInteraction({
|
|
17
|
+
export function CounterInteraction({
|
|
18
|
+
network,
|
|
19
|
+
isConnected,
|
|
20
|
+
senderAddress,
|
|
21
|
+
}: CounterInteractionProps) {
|
|
16
22
|
const [counter, setCounter] = useState<number | null>(null);
|
|
17
23
|
const [isLoading, setIsLoading] = useState(true);
|
|
18
24
|
const [isIncrementing, setIsIncrementing] = useState(false);
|
|
@@ -23,15 +29,15 @@ export function CounterInteraction({ network, isConnected, senderAddress }: Coun
|
|
|
23
29
|
const result = await callReadOnlyFunction({
|
|
24
30
|
contractAddress,
|
|
25
31
|
contractName,
|
|
26
|
-
functionName:
|
|
32
|
+
functionName: "get-counter",
|
|
27
33
|
functionArgs: [],
|
|
28
34
|
network,
|
|
29
35
|
senderAddress: contractAddress,
|
|
30
36
|
});
|
|
31
37
|
const value = cvToValue(result);
|
|
32
|
-
setCounter(value?.value
|
|
38
|
+
setCounter(value?.value ? Number(value.value) : 0);
|
|
33
39
|
} catch (error) {
|
|
34
|
-
console.error(
|
|
40
|
+
console.error("Failed to fetch counter:", error);
|
|
35
41
|
} finally {
|
|
36
42
|
setIsLoading(false);
|
|
37
43
|
}
|
|
@@ -45,18 +51,16 @@ export function CounterInteraction({ network, isConnected, senderAddress }: Coun
|
|
|
45
51
|
if (!senderAddress) return;
|
|
46
52
|
setIsIncrementing(true);
|
|
47
53
|
try {
|
|
48
|
-
await
|
|
49
|
-
contractAddress
|
|
50
|
-
|
|
51
|
-
functionName: 'increment',
|
|
54
|
+
await request("stx_callContract", {
|
|
55
|
+
contract: `${contractAddress}.${contractName}`,
|
|
56
|
+
functionName: "increment",
|
|
52
57
|
functionArgs: [],
|
|
53
|
-
|
|
54
|
-
onFinish: () => {
|
|
55
|
-
setTimeout(fetchCounter, 2000);
|
|
56
|
-
},
|
|
58
|
+
postConditions: [],
|
|
57
59
|
});
|
|
60
|
+
|
|
61
|
+
setTimeout(fetchCounter, 2000);
|
|
58
62
|
} catch (error) {
|
|
59
|
-
console.error(
|
|
63
|
+
console.error("Increment failed:", error);
|
|
60
64
|
} finally {
|
|
61
65
|
setIsIncrementing(false);
|
|
62
66
|
}
|
|
@@ -66,18 +70,16 @@ export function CounterInteraction({ network, isConnected, senderAddress }: Coun
|
|
|
66
70
|
if (!senderAddress) return;
|
|
67
71
|
setIsDecrementing(true);
|
|
68
72
|
try {
|
|
69
|
-
await
|
|
70
|
-
contractAddress
|
|
71
|
-
|
|
72
|
-
functionName: 'decrement',
|
|
73
|
+
await request("stx_callContract", {
|
|
74
|
+
contract: `${contractAddress}.${contractName}`,
|
|
75
|
+
functionName: "decrement",
|
|
73
76
|
functionArgs: [],
|
|
74
|
-
|
|
75
|
-
onFinish: () => {
|
|
76
|
-
setTimeout(fetchCounter, 2000);
|
|
77
|
-
},
|
|
77
|
+
postConditions: [],
|
|
78
78
|
});
|
|
79
|
+
|
|
80
|
+
setTimeout(fetchCounter, 2000);
|
|
79
81
|
} catch (error) {
|
|
80
|
-
console.error(
|
|
82
|
+
console.error("Decrement failed:", error);
|
|
81
83
|
} finally {
|
|
82
84
|
setIsDecrementing(false);
|
|
83
85
|
}
|
|
@@ -88,8 +90,8 @@ export function CounterInteraction({ network, isConnected, senderAddress }: Coun
|
|
|
88
90
|
<h2 className="text-2xl font-bold mb-4">Counter Contract</h2>
|
|
89
91
|
|
|
90
92
|
<div className="mb-6 text-center">
|
|
91
|
-
<div className="text-6xl font-bold text-
|
|
92
|
-
{isLoading ?
|
|
93
|
+
<div className="text-6xl font-bold text-gray-100">
|
|
94
|
+
{isLoading ? "..." : counter}
|
|
93
95
|
</div>
|
|
94
96
|
<p className="text-sm text-gray-500 mt-2">Current count</p>
|
|
95
97
|
</div>
|
|
@@ -101,14 +103,14 @@ export function CounterInteraction({ network, isConnected, senderAddress }: Coun
|
|
|
101
103
|
disabled={isDecrementing || counter === 0}
|
|
102
104
|
className="btn-secondary flex-1 disabled:opacity-50"
|
|
103
105
|
>
|
|
104
|
-
{isDecrementing ?
|
|
106
|
+
{isDecrementing ? "Processing..." : "− Decrement"}
|
|
105
107
|
</button>
|
|
106
108
|
<button
|
|
107
109
|
onClick={handleIncrement}
|
|
108
110
|
disabled={isIncrementing}
|
|
109
111
|
className="btn-primary flex-1 disabled:opacity-50"
|
|
110
112
|
>
|
|
111
|
-
{isIncrementing ?
|
|
113
|
+
{isIncrementing ? "Processing..." : "+ Increment"}
|
|
112
114
|
</button>
|
|
113
115
|
</div>
|
|
114
116
|
) : (
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, onMounted } from 'vue';
|
|
3
|
-
import {
|
|
3
|
+
import { connect, disconnect, getLocalStorage } from '@stacks/connect';
|
|
4
4
|
import { StacksTestnet, StacksMainnet } from '@stacks/network';
|
|
5
5
|
import AppHeader from './components/AppHeader.vue';
|
|
6
6
|
import CounterInteraction from './components/CounterInteraction.vue';
|
|
7
7
|
|
|
8
|
-
const appConfig = new AppConfig(['store_write', 'publish_data']);
|
|
9
|
-
const userSession = new UserSession({ appConfig });
|
|
10
|
-
|
|
11
8
|
const network =
|
|
12
9
|
import.meta.env.VITE_NETWORK === 'mainnet'
|
|
13
10
|
? new StacksMainnet()
|
|
@@ -16,29 +13,30 @@ const network =
|
|
|
16
13
|
const address = ref<string | null>(null);
|
|
17
14
|
|
|
18
15
|
onMounted(() => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
address.value =
|
|
16
|
+
const storage = getLocalStorage();
|
|
17
|
+
const networkKey = import.meta.env.VITE_NETWORK === 'mainnet' ? 'mainnet' : 'testnet';
|
|
18
|
+
if (storage?.addresses?.[networkKey]) {
|
|
19
|
+
address.value = storage.addresses[networkKey];
|
|
23
20
|
}
|
|
24
21
|
});
|
|
25
22
|
|
|
26
|
-
function handleConnect() {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
redirectTo: '/',
|
|
33
|
-
onFinish: () => {
|
|
23
|
+
async function handleConnect() {
|
|
24
|
+
try {
|
|
25
|
+
const response = await connect();
|
|
26
|
+
const userAddress = response.addresses?.[0]?.address;
|
|
27
|
+
if (userAddress) {
|
|
28
|
+
address.value = userAddress;
|
|
34
29
|
window.location.reload();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
}
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Failed to connect:', error);
|
|
33
|
+
}
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
function handleDisconnect() {
|
|
41
|
-
|
|
37
|
+
disconnect();
|
|
38
|
+
address.value = null;
|
|
39
|
+
window.location.reload();
|
|
42
40
|
}
|
|
43
41
|
</script>
|
|
44
42
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, onMounted } from 'vue';
|
|
3
|
-
import {
|
|
3
|
+
import { request } from '@stacks/connect';
|
|
4
4
|
import { callReadOnlyFunction, cvToValue } from '@stacks/transactions';
|
|
5
5
|
import type { StacksNetwork } from '@stacks/network';
|
|
6
6
|
|
|
@@ -29,7 +29,8 @@ async function fetchCounter() {
|
|
|
29
29
|
senderAddress: contractAddress,
|
|
30
30
|
});
|
|
31
31
|
const value = cvToValue(result);
|
|
32
|
-
|
|
32
|
+
// Handle both bigint (Stacks returns bigint now often) and number types safely
|
|
33
|
+
counter.value = value?.value ? Number(value.value) : 0;
|
|
33
34
|
} catch (error) {
|
|
34
35
|
console.error('Failed to fetch counter:', error);
|
|
35
36
|
} finally {
|
|
@@ -45,16 +46,15 @@ async function handleIncrement() {
|
|
|
45
46
|
if (!props.senderAddress) return;
|
|
46
47
|
isIncrementing.value = true;
|
|
47
48
|
try {
|
|
48
|
-
await
|
|
49
|
-
contractAddress
|
|
50
|
-
contractName,
|
|
49
|
+
await request('stx_callContract', {
|
|
50
|
+
contract: `${contractAddress}.${contractName}`,
|
|
51
51
|
functionName: 'increment',
|
|
52
52
|
functionArgs: [],
|
|
53
|
-
|
|
54
|
-
onFinish: () => {
|
|
55
|
-
setTimeout(fetchCounter, 2000);
|
|
56
|
-
},
|
|
53
|
+
postConditions: [],
|
|
57
54
|
});
|
|
55
|
+
|
|
56
|
+
// Speculatively refetch
|
|
57
|
+
setTimeout(fetchCounter, 2000);
|
|
58
58
|
} catch (error) {
|
|
59
59
|
console.error('Increment failed:', error);
|
|
60
60
|
} finally {
|
|
@@ -66,16 +66,14 @@ async function handleDecrement() {
|
|
|
66
66
|
if (!props.senderAddress) return;
|
|
67
67
|
isDecrementing.value = true;
|
|
68
68
|
try {
|
|
69
|
-
await
|
|
70
|
-
contractAddress
|
|
71
|
-
contractName,
|
|
69
|
+
await request('stx_callContract', {
|
|
70
|
+
contract: `${contractAddress}.${contractName}`,
|
|
72
71
|
functionName: 'decrement',
|
|
73
72
|
functionArgs: [],
|
|
74
|
-
|
|
75
|
-
onFinish: () => {
|
|
76
|
-
setTimeout(fetchCounter, 2000);
|
|
77
|
-
},
|
|
73
|
+
postConditions: [],
|
|
78
74
|
});
|
|
75
|
+
|
|
76
|
+
setTimeout(fetchCounter, 2000);
|
|
79
77
|
} catch (error) {
|
|
80
78
|
console.error('Decrement failed:', error);
|
|
81
79
|
} finally {
|
|
@@ -89,7 +87,7 @@ async function handleDecrement() {
|
|
|
89
87
|
<h2 class="text-2xl font-bold mb-4">Counter Contract</h2>
|
|
90
88
|
|
|
91
89
|
<div class="mb-6 text-center">
|
|
92
|
-
<div class="text-6xl font-bold text-
|
|
90
|
+
<div class="text-6xl font-bold text-gray-100">
|
|
93
91
|
{{ isLoading ? '...' : counter }}
|
|
94
92
|
</div>
|
|
95
93
|
<p class="text-sm text-gray-500 mt-2">Current count</p>
|