@devvmichael/create-stacks-app 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/add.d.ts +8 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +215 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/create.d.ts +3 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +63 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/deploy.d.ts +7 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +159 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/project.d.ts +4 -0
- package/dist/prompts/project.d.ts.map +1 -0
- package/dist/prompts/project.js +124 -0
- package/dist/prompts/project.js.map +1 -0
- package/dist/templates/installer.d.ts +4 -0
- package/dist/templates/installer.d.ts.map +1 -0
- package/dist/templates/installer.js +91 -0
- package/dist/templates/installer.js.map +1 -0
- package/dist/types/index.d.ts +40 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/clarinet.d.ts +5 -0
- package/dist/utils/clarinet.d.ts.map +1 -0
- package/dist/utils/clarinet.js +72 -0
- package/dist/utils/clarinet.js.map +1 -0
- package/dist/utils/filesystem.d.ts +4 -0
- package/dist/utils/filesystem.d.ts.map +1 -0
- package/dist/utils/filesystem.js +158 -0
- package/dist/utils/filesystem.js.map +1 -0
- package/dist/utils/git.d.ts +2 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +21 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +45 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/package-manager.d.ts +5 -0
- package/dist/utils/package-manager.d.ts.map +1 -0
- package/dist/utils/package-manager.js +65 -0
- package/dist/utils/package-manager.js.map +1 -0
- package/dist/utils/validation.d.ts +5 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +41 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +52 -0
- package/templates/base/editorconfig +12 -0
- package/templates/base/gitignore +13 -0
- package/templates/base/prettierrc +7 -0
- package/templates/contracts/counter/counter.clar +43 -0
- package/templates/contracts/counter/counter.test.ts +127 -0
- package/templates/contracts/defi/sip010-trait.clar +11 -0
- package/templates/contracts/defi/staking-pool.clar +20 -0
- package/templates/contracts/marketplace/nft-marketplace.clar +44 -0
- package/templates/contracts/marketplace/nft-trait.clar +8 -0
- package/templates/contracts/marketplace/sip009-nft.clar +25 -0
- package/templates/contracts/nft/nft.clar +111 -0
- package/templates/contracts/nft/nft.test.ts +204 -0
- package/templates/contracts/token/token.clar +67 -0
- package/templates/contracts/token/token.test.ts +139 -0
- package/templates/frontends/nextjs/template/.env.example +2 -0
- package/templates/frontends/nextjs/template/app/globals.css +40 -0
- package/templates/frontends/nextjs/template/app/layout.tsx +36 -0
- package/templates/frontends/nextjs/template/app/page.tsx +42 -0
- package/templates/frontends/nextjs/template/app/providers.tsx +31 -0
- package/templates/frontends/nextjs/template/components/contracts/counter-interaction.tsx +80 -0
- package/templates/frontends/nextjs/template/components/header.tsx +24 -0
- package/templates/frontends/nextjs/template/components/wallet/connect-button.tsx +44 -0
- package/templates/frontends/nextjs/template/hooks/use-contract-call.ts +52 -0
- package/templates/frontends/nextjs/template/hooks/use-contract-read.ts +49 -0
- package/templates/frontends/nextjs/template/hooks/use-stacks.ts +26 -0
- package/templates/frontends/nextjs/template/lib/contracts.ts +29 -0
- package/templates/frontends/nextjs/template/lib/stacks.ts +18 -0
- package/templates/frontends/nextjs/template/next.config.js +6 -0
- package/templates/frontends/nextjs/template/package.json +29 -0
- package/templates/frontends/nextjs/template/postcss.config.js +6 -0
- package/templates/frontends/nextjs/template/public/logo.svg +3 -0
- package/templates/frontends/nextjs/template/tailwind.config.js +18 -0
- package/templates/frontends/nextjs/template/tsconfig.json +26 -0
- package/templates/frontends/react/template/.env.example +2 -0
- package/templates/frontends/react/template/index.html +13 -0
- package/templates/frontends/react/template/package.json +29 -0
- package/templates/frontends/react/template/postcss.config.js +6 -0
- package/templates/frontends/react/template/public/logo.svg +3 -0
- package/templates/frontends/react/template/src/App.tsx +100 -0
- package/templates/frontends/react/template/src/components/CounterInteraction.tsx +121 -0
- package/templates/frontends/react/template/src/components/Header.tsx +39 -0
- package/templates/frontends/react/template/src/index.css +33 -0
- package/templates/frontends/react/template/src/main.tsx +10 -0
- package/templates/frontends/react/template/tailwind.config.js +15 -0
- package/templates/frontends/react/template/tsconfig.json +25 -0
- package/templates/frontends/react/template/tsconfig.node.json +10 -0
- package/templates/frontends/react/template/vite.config.ts +12 -0
- package/templates/frontends/vue/template/.env.example +2 -0
- package/templates/frontends/vue/template/index.html +13 -0
- package/templates/frontends/vue/template/package.json +27 -0
- package/templates/frontends/vue/template/postcss.config.js +6 -0
- package/templates/frontends/vue/template/public/logo.svg +3 -0
- package/templates/frontends/vue/template/src/App.vue +98 -0
- package/templates/frontends/vue/template/src/components/AppHeader.vue +39 -0
- package/templates/frontends/vue/template/src/components/CounterInteraction.vue +120 -0
- package/templates/frontends/vue/template/src/env.d.ts +16 -0
- package/templates/frontends/vue/template/src/main.ts +5 -0
- package/templates/frontends/vue/template/src/style.css +33 -0
- package/templates/frontends/vue/template/tailwind.config.js +15 -0
- package/templates/frontends/vue/template/tsconfig.json +25 -0
- package/templates/frontends/vue/template/tsconfig.node.json +10 -0
- package/templates/frontends/vue/template/vite.config.ts +12 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Cl } from "@stacks/transactions";
|
|
3
|
+
|
|
4
|
+
const accounts = simnet.getAccounts();
|
|
5
|
+
const deployer = accounts.get("deployer")!;
|
|
6
|
+
const wallet1 = accounts.get("wallet_1")!;
|
|
7
|
+
const wallet2 = accounts.get("wallet_2")!;
|
|
8
|
+
|
|
9
|
+
describe("NFT Contract (SIP-009)", () => {
|
|
10
|
+
describe("Minting", () => {
|
|
11
|
+
it("should mint an NFT successfully", () => {
|
|
12
|
+
const block = simnet.callPublicFn(
|
|
13
|
+
"nft",
|
|
14
|
+
"mint",
|
|
15
|
+
[Cl.principal(wallet1), Cl.stringAscii("ipfs://test-uri-1")],
|
|
16
|
+
deployer
|
|
17
|
+
);
|
|
18
|
+
expect(block.result).toBeOk(Cl.uint(1));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should increment last token id", () => {
|
|
22
|
+
simnet.callPublicFn(
|
|
23
|
+
"nft",
|
|
24
|
+
"mint",
|
|
25
|
+
[Cl.principal(wallet1), Cl.stringAscii("ipfs://test-uri-1")],
|
|
26
|
+
deployer
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const result = simnet.callReadOnlyFn(
|
|
30
|
+
"nft",
|
|
31
|
+
"get-last-token-id",
|
|
32
|
+
[],
|
|
33
|
+
deployer
|
|
34
|
+
);
|
|
35
|
+
expect(result.result).toBeOk(Cl.uint(1));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should store token URI", () => {
|
|
39
|
+
simnet.callPublicFn(
|
|
40
|
+
"nft",
|
|
41
|
+
"mint",
|
|
42
|
+
[Cl.principal(wallet1), Cl.stringAscii("ipfs://test-uri-1")],
|
|
43
|
+
deployer
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const result = simnet.callReadOnlyFn(
|
|
47
|
+
"nft",
|
|
48
|
+
"get-token-uri",
|
|
49
|
+
[Cl.uint(1)],
|
|
50
|
+
deployer
|
|
51
|
+
);
|
|
52
|
+
expect(result.result).toBeOk(Cl.some(Cl.stringAscii("ipfs://test-uri-1")));
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("Ownership", () => {
|
|
57
|
+
it("should return correct owner", () => {
|
|
58
|
+
simnet.callPublicFn(
|
|
59
|
+
"nft",
|
|
60
|
+
"mint",
|
|
61
|
+
[Cl.principal(wallet1), Cl.stringAscii("ipfs://test-uri-1")],
|
|
62
|
+
deployer
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const result = simnet.callReadOnlyFn(
|
|
66
|
+
"nft",
|
|
67
|
+
"get-owner",
|
|
68
|
+
[Cl.uint(1)],
|
|
69
|
+
deployer
|
|
70
|
+
);
|
|
71
|
+
expect(result.result).toBeOk(Cl.some(Cl.principal(wallet1)));
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("Transfer", () => {
|
|
76
|
+
it("should transfer NFT successfully", () => {
|
|
77
|
+
simnet.callPublicFn(
|
|
78
|
+
"nft",
|
|
79
|
+
"mint",
|
|
80
|
+
[Cl.principal(wallet1), Cl.stringAscii("ipfs://test-uri-1")],
|
|
81
|
+
deployer
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const block = simnet.callPublicFn(
|
|
85
|
+
"nft",
|
|
86
|
+
"transfer",
|
|
87
|
+
[Cl.uint(1), Cl.principal(wallet1), Cl.principal(wallet2)],
|
|
88
|
+
wallet1
|
|
89
|
+
);
|
|
90
|
+
expect(block.result).toBeOk(Cl.bool(true));
|
|
91
|
+
|
|
92
|
+
const result = simnet.callReadOnlyFn(
|
|
93
|
+
"nft",
|
|
94
|
+
"get-owner",
|
|
95
|
+
[Cl.uint(1)],
|
|
96
|
+
deployer
|
|
97
|
+
);
|
|
98
|
+
expect(result.result).toBeOk(Cl.some(Cl.principal(wallet2)));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should fail if sender is not owner", () => {
|
|
102
|
+
simnet.callPublicFn(
|
|
103
|
+
"nft",
|
|
104
|
+
"mint",
|
|
105
|
+
[Cl.principal(wallet1), Cl.stringAscii("ipfs://test-uri-1")],
|
|
106
|
+
deployer
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const block = simnet.callPublicFn(
|
|
110
|
+
"nft",
|
|
111
|
+
"transfer",
|
|
112
|
+
[Cl.uint(1), Cl.principal(wallet1), Cl.principal(wallet2)],
|
|
113
|
+
wallet2
|
|
114
|
+
);
|
|
115
|
+
expect(block.result).toBeErr(Cl.uint(101));
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("Marketplace", () => {
|
|
120
|
+
it("should list NFT for sale", () => {
|
|
121
|
+
simnet.callPublicFn(
|
|
122
|
+
"nft",
|
|
123
|
+
"mint",
|
|
124
|
+
[Cl.principal(wallet1), Cl.stringAscii("ipfs://test-uri-1")],
|
|
125
|
+
deployer
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const block = simnet.callPublicFn(
|
|
129
|
+
"nft",
|
|
130
|
+
"list-for-sale",
|
|
131
|
+
[Cl.uint(1), Cl.uint(1000000)],
|
|
132
|
+
wallet1
|
|
133
|
+
);
|
|
134
|
+
expect(block.result).toBeOk(Cl.bool(true));
|
|
135
|
+
|
|
136
|
+
const listing = simnet.callReadOnlyFn(
|
|
137
|
+
"nft",
|
|
138
|
+
"get-listing",
|
|
139
|
+
[Cl.uint(1)],
|
|
140
|
+
deployer
|
|
141
|
+
);
|
|
142
|
+
expect(listing.result).toBeSome(
|
|
143
|
+
Cl.tuple({
|
|
144
|
+
price: Cl.uint(1000000),
|
|
145
|
+
seller: Cl.principal(wallet1),
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("should allow purchase of listed NFT", () => {
|
|
151
|
+
simnet.callPublicFn(
|
|
152
|
+
"nft",
|
|
153
|
+
"mint",
|
|
154
|
+
[Cl.principal(wallet1), Cl.stringAscii("ipfs://test-uri-1")],
|
|
155
|
+
deployer
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
simnet.callPublicFn(
|
|
159
|
+
"nft",
|
|
160
|
+
"list-for-sale",
|
|
161
|
+
[Cl.uint(1), Cl.uint(1000000)],
|
|
162
|
+
wallet1
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const block = simnet.callPublicFn("nft", "buy", [Cl.uint(1)], wallet2);
|
|
166
|
+
expect(block.result).toBeOk(Cl.uint(1));
|
|
167
|
+
|
|
168
|
+
const result = simnet.callReadOnlyFn(
|
|
169
|
+
"nft",
|
|
170
|
+
"get-owner",
|
|
171
|
+
[Cl.uint(1)],
|
|
172
|
+
deployer
|
|
173
|
+
);
|
|
174
|
+
expect(result.result).toBeOk(Cl.some(Cl.principal(wallet2)));
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should unlist NFT", () => {
|
|
178
|
+
simnet.callPublicFn(
|
|
179
|
+
"nft",
|
|
180
|
+
"mint",
|
|
181
|
+
[Cl.principal(wallet1), Cl.stringAscii("ipfs://test-uri-1")],
|
|
182
|
+
deployer
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
simnet.callPublicFn(
|
|
186
|
+
"nft",
|
|
187
|
+
"list-for-sale",
|
|
188
|
+
[Cl.uint(1), Cl.uint(1000000)],
|
|
189
|
+
wallet1
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const block = simnet.callPublicFn("nft", "unlist", [Cl.uint(1)], wallet1);
|
|
193
|
+
expect(block.result).toBeOk(Cl.bool(true));
|
|
194
|
+
|
|
195
|
+
const listing = simnet.callReadOnlyFn(
|
|
196
|
+
"nft",
|
|
197
|
+
"get-listing",
|
|
198
|
+
[Cl.uint(1)],
|
|
199
|
+
deployer
|
|
200
|
+
);
|
|
201
|
+
expect(listing.result).toBeNone();
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
;; SIP-010 Fungible Token
|
|
2
|
+
;; A standard fungible token implementation
|
|
3
|
+
|
|
4
|
+
(impl-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)
|
|
5
|
+
|
|
6
|
+
;; Token configuration
|
|
7
|
+
(define-constant CONTRACT-OWNER tx-sender)
|
|
8
|
+
(define-constant TOKEN-NAME "MyToken")
|
|
9
|
+
(define-constant TOKEN-SYMBOL "MTK")
|
|
10
|
+
(define-constant TOKEN-DECIMALS u6)
|
|
11
|
+
(define-constant TOKEN-SUPPLY u1000000000000) ;; 1 million tokens with 6 decimals
|
|
12
|
+
|
|
13
|
+
;; Error codes
|
|
14
|
+
(define-constant ERR-OWNER-ONLY (err u100))
|
|
15
|
+
(define-constant ERR-NOT-TOKEN-OWNER (err u101))
|
|
16
|
+
(define-constant ERR-INSUFFICIENT-BALANCE (err u102))
|
|
17
|
+
|
|
18
|
+
;; Storage
|
|
19
|
+
(define-fungible-token my-token TOKEN-SUPPLY)
|
|
20
|
+
(define-data-var token-uri (optional (string-utf8 256)) none)
|
|
21
|
+
|
|
22
|
+
;; Initialize token supply to contract owner
|
|
23
|
+
(ft-mint? my-token TOKEN-SUPPLY CONTRACT-OWNER)
|
|
24
|
+
|
|
25
|
+
;; SIP-010 Functions
|
|
26
|
+
|
|
27
|
+
(define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34))))
|
|
28
|
+
(begin
|
|
29
|
+
(asserts! (is-eq tx-sender sender) ERR-NOT-TOKEN-OWNER)
|
|
30
|
+
(try! (ft-transfer? my-token amount sender recipient))
|
|
31
|
+
(match memo to-print (print to-print) 0x)
|
|
32
|
+
(ok true)))
|
|
33
|
+
|
|
34
|
+
(define-read-only (get-name)
|
|
35
|
+
(ok TOKEN-NAME))
|
|
36
|
+
|
|
37
|
+
(define-read-only (get-symbol)
|
|
38
|
+
(ok TOKEN-SYMBOL))
|
|
39
|
+
|
|
40
|
+
(define-read-only (get-decimals)
|
|
41
|
+
(ok TOKEN-DECIMALS))
|
|
42
|
+
|
|
43
|
+
(define-read-only (get-balance (who principal))
|
|
44
|
+
(ok (ft-get-balance my-token who)))
|
|
45
|
+
|
|
46
|
+
(define-read-only (get-total-supply)
|
|
47
|
+
(ok (ft-get-supply my-token)))
|
|
48
|
+
|
|
49
|
+
(define-read-only (get-token-uri)
|
|
50
|
+
(ok (var-get token-uri)))
|
|
51
|
+
|
|
52
|
+
;; Admin functions
|
|
53
|
+
|
|
54
|
+
(define-public (set-token-uri (value (string-utf8 256)))
|
|
55
|
+
(begin
|
|
56
|
+
(asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
|
|
57
|
+
(ok (var-set token-uri (some value)))))
|
|
58
|
+
|
|
59
|
+
;; Mint additional tokens (owner only)
|
|
60
|
+
(define-public (mint (amount uint) (recipient principal))
|
|
61
|
+
(begin
|
|
62
|
+
(asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
|
|
63
|
+
(ft-mint? my-token amount recipient)))
|
|
64
|
+
|
|
65
|
+
;; Burn tokens
|
|
66
|
+
(define-public (burn (amount uint))
|
|
67
|
+
(ft-burn? my-token amount tx-sender))
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { Cl } from "@stacks/transactions";
|
|
3
|
+
|
|
4
|
+
const accounts = simnet.getAccounts();
|
|
5
|
+
const deployer = accounts.get("deployer")!;
|
|
6
|
+
const wallet1 = accounts.get("wallet_1")!;
|
|
7
|
+
const wallet2 = accounts.get("wallet_2")!;
|
|
8
|
+
|
|
9
|
+
describe("Token Contract (SIP-010)", () => {
|
|
10
|
+
describe("Token Info", () => {
|
|
11
|
+
it("should return correct name", () => {
|
|
12
|
+
const result = simnet.callReadOnlyFn("token", "get-name", [], deployer);
|
|
13
|
+
expect(result.result).toBeOk(Cl.stringAscii("MyToken"));
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should return correct symbol", () => {
|
|
17
|
+
const result = simnet.callReadOnlyFn("token", "get-symbol", [], deployer);
|
|
18
|
+
expect(result.result).toBeOk(Cl.stringAscii("MTK"));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should return correct decimals", () => {
|
|
22
|
+
const result = simnet.callReadOnlyFn(
|
|
23
|
+
"token",
|
|
24
|
+
"get-decimals",
|
|
25
|
+
[],
|
|
26
|
+
deployer
|
|
27
|
+
);
|
|
28
|
+
expect(result.result).toBeOk(Cl.uint(6));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should return correct total supply", () => {
|
|
32
|
+
const result = simnet.callReadOnlyFn(
|
|
33
|
+
"token",
|
|
34
|
+
"get-total-supply",
|
|
35
|
+
[],
|
|
36
|
+
deployer
|
|
37
|
+
);
|
|
38
|
+
expect(result.result).toBeOk(Cl.uint(1000000000000));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("Balance", () => {
|
|
43
|
+
it("should have initial supply at deployer", () => {
|
|
44
|
+
const result = simnet.callReadOnlyFn(
|
|
45
|
+
"token",
|
|
46
|
+
"get-balance",
|
|
47
|
+
[Cl.principal(deployer)],
|
|
48
|
+
deployer
|
|
49
|
+
);
|
|
50
|
+
expect(result.result).toBeOk(Cl.uint(1000000000000));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should have zero balance for new accounts", () => {
|
|
54
|
+
const result = simnet.callReadOnlyFn(
|
|
55
|
+
"token",
|
|
56
|
+
"get-balance",
|
|
57
|
+
[Cl.principal(wallet1)],
|
|
58
|
+
deployer
|
|
59
|
+
);
|
|
60
|
+
expect(result.result).toBeOk(Cl.uint(0));
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("Transfer", () => {
|
|
65
|
+
it("should transfer tokens successfully", () => {
|
|
66
|
+
const amount = 1000000; // 1 token
|
|
67
|
+
|
|
68
|
+
const block = simnet.callPublicFn(
|
|
69
|
+
"token",
|
|
70
|
+
"transfer",
|
|
71
|
+
[
|
|
72
|
+
Cl.uint(amount),
|
|
73
|
+
Cl.principal(deployer),
|
|
74
|
+
Cl.principal(wallet1),
|
|
75
|
+
Cl.none(),
|
|
76
|
+
],
|
|
77
|
+
deployer
|
|
78
|
+
);
|
|
79
|
+
expect(block.result).toBeOk(Cl.bool(true));
|
|
80
|
+
|
|
81
|
+
const balance = simnet.callReadOnlyFn(
|
|
82
|
+
"token",
|
|
83
|
+
"get-balance",
|
|
84
|
+
[Cl.principal(wallet1)],
|
|
85
|
+
deployer
|
|
86
|
+
);
|
|
87
|
+
expect(balance.result).toBeOk(Cl.uint(amount));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should fail if sender is not tx-sender", () => {
|
|
91
|
+
const block = simnet.callPublicFn(
|
|
92
|
+
"token",
|
|
93
|
+
"transfer",
|
|
94
|
+
[
|
|
95
|
+
Cl.uint(1000000),
|
|
96
|
+
Cl.principal(deployer),
|
|
97
|
+
Cl.principal(wallet2),
|
|
98
|
+
Cl.none(),
|
|
99
|
+
],
|
|
100
|
+
wallet1
|
|
101
|
+
);
|
|
102
|
+
expect(block.result).toBeErr(Cl.uint(101));
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("Mint", () => {
|
|
107
|
+
it("should allow owner to mint", () => {
|
|
108
|
+
const block = simnet.callPublicFn(
|
|
109
|
+
"token",
|
|
110
|
+
"mint",
|
|
111
|
+
[Cl.uint(1000000), Cl.principal(wallet1)],
|
|
112
|
+
deployer
|
|
113
|
+
);
|
|
114
|
+
expect(block.result).toBeOk(Cl.bool(true));
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should prevent non-owner from minting", () => {
|
|
118
|
+
const block = simnet.callPublicFn(
|
|
119
|
+
"token",
|
|
120
|
+
"mint",
|
|
121
|
+
[Cl.uint(1000000), Cl.principal(wallet1)],
|
|
122
|
+
wallet1
|
|
123
|
+
);
|
|
124
|
+
expect(block.result).toBeErr(Cl.uint(100));
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("Burn", () => {
|
|
129
|
+
it("should allow token holder to burn", () => {
|
|
130
|
+
const block = simnet.callPublicFn(
|
|
131
|
+
"token",
|
|
132
|
+
"burn",
|
|
133
|
+
[Cl.uint(1000000)],
|
|
134
|
+
deployer
|
|
135
|
+
);
|
|
136
|
+
expect(block.result).toBeOk(Cl.bool(true));
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
--foreground-rgb: 0, 0, 0;
|
|
7
|
+
--background-start-rgb: 214, 219, 220;
|
|
8
|
+
--background-end-rgb: 255, 255, 255;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@media (prefers-color-scheme: dark) {
|
|
12
|
+
:root {
|
|
13
|
+
--foreground-rgb: 255, 255, 255;
|
|
14
|
+
--background-start-rgb: 0, 0, 0;
|
|
15
|
+
--background-end-rgb: 0, 0, 0;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
body {
|
|
20
|
+
color: rgb(var(--foreground-rgb));
|
|
21
|
+
background: linear-gradient(
|
|
22
|
+
to bottom,
|
|
23
|
+
transparent,
|
|
24
|
+
rgb(var(--background-end-rgb))
|
|
25
|
+
)
|
|
26
|
+
rgb(var(--background-start-rgb));
|
|
27
|
+
min-height: 100vh;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.btn-primary {
|
|
31
|
+
@apply bg-stacks-purple hover:bg-stacks-purple-dark text-white font-semibold py-2 px-4 rounded-lg transition-colors;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.btn-secondary {
|
|
35
|
+
@apply border border-gray-300 hover:bg-gray-100 dark:border-gray-600 dark:hover:bg-gray-800 font-semibold py-2 px-4 rounded-lg transition-colors;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.card {
|
|
39
|
+
@apply bg-white dark:bg-gray-900 rounded-xl shadow-lg p-6 border border-gray-200 dark:border-gray-700;
|
|
40
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import { Inter } from 'next/font/google';
|
|
3
|
+
import './globals.css';
|
|
4
|
+
import { Providers } from './providers';
|
|
5
|
+
import { Header } from '@/components/header';
|
|
6
|
+
|
|
7
|
+
const inter = Inter({ subsets: ['latin'] });
|
|
8
|
+
|
|
9
|
+
export const metadata: Metadata = {
|
|
10
|
+
title: 'Stacks App',
|
|
11
|
+
description: 'A full-stack Stacks blockchain application',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default function RootLayout({
|
|
15
|
+
children,
|
|
16
|
+
}: {
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
}) {
|
|
19
|
+
return (
|
|
20
|
+
<html lang="en">
|
|
21
|
+
<body className={inter.className}>
|
|
22
|
+
<Providers>
|
|
23
|
+
<div className="flex min-h-screen flex-col">
|
|
24
|
+
<Header />
|
|
25
|
+
<main className="flex-1 container mx-auto px-4 py-8">
|
|
26
|
+
{children}
|
|
27
|
+
</main>
|
|
28
|
+
<footer className="border-t border-gray-200 dark:border-gray-800 py-6 text-center text-sm text-gray-500">
|
|
29
|
+
Built with Create Stacks App
|
|
30
|
+
</footer>
|
|
31
|
+
</div>
|
|
32
|
+
</Providers>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { CounterInteraction } from '@/components/contracts/counter-interaction';
|
|
2
|
+
|
|
3
|
+
export default function Home() {
|
|
4
|
+
return (
|
|
5
|
+
<div>
|
|
6
|
+
<div className="mb-8 text-center">
|
|
7
|
+
<h1 className="mb-4 text-4xl font-bold">Welcome to Your Stacks App</h1>
|
|
8
|
+
<p className="text-lg text-gray-600 dark:text-gray-400">
|
|
9
|
+
A full-stack Stacks blockchain application
|
|
10
|
+
</p>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
|
|
14
|
+
<CounterInteraction />
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div className="mt-12 text-center">
|
|
18
|
+
<h2 className="mb-4 text-2xl font-bold">Get Started</h2>
|
|
19
|
+
<div className="grid gap-4 md:grid-cols-3 max-w-3xl mx-auto">
|
|
20
|
+
<div className="card">
|
|
21
|
+
<h3 className="font-semibold mb-2">📝 Edit Contracts</h3>
|
|
22
|
+
<p className="text-sm text-gray-600 dark:text-gray-400">
|
|
23
|
+
Modify contracts in <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded">contracts/</code>
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
26
|
+
<div className="card">
|
|
27
|
+
<h3 className="font-semibold mb-2">🧪 Run Tests</h3>
|
|
28
|
+
<p className="text-sm text-gray-600 dark:text-gray-400">
|
|
29
|
+
Run <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded">npm run test</code>
|
|
30
|
+
</p>
|
|
31
|
+
</div>
|
|
32
|
+
<div className="card">
|
|
33
|
+
<h3 className="font-semibold mb-2">🚀 Deploy</h3>
|
|
34
|
+
<p className="text-sm text-gray-600 dark:text-gray-400">
|
|
35
|
+
Run <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded">npm run deploy:testnet</code>
|
|
36
|
+
</p>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
import { Connect } from '@stacks/connect-react';
|
|
5
|
+
import { userSession } from '@/lib/stacks';
|
|
6
|
+
|
|
7
|
+
interface ProvidersProps {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Providers({ children }: ProvidersProps) {
|
|
12
|
+
const appDetails = {
|
|
13
|
+
name: 'Stacks App',
|
|
14
|
+
icon: typeof window !== 'undefined' ? window.location.origin + '/logo.svg' : '/logo.svg',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Connect
|
|
19
|
+
authOptions={{
|
|
20
|
+
appDetails,
|
|
21
|
+
redirectTo: '/',
|
|
22
|
+
onFinish: () => {
|
|
23
|
+
window.location.reload();
|
|
24
|
+
},
|
|
25
|
+
userSession,
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</Connect>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { useContractCall } from '@/hooks/use-contract-call';
|
|
5
|
+
import { useContractRead } from '@/hooks/use-contract-read';
|
|
6
|
+
import { counterContract } from '@/lib/contracts';
|
|
7
|
+
import { useStacks } from '@/hooks/use-stacks';
|
|
8
|
+
|
|
9
|
+
export function CounterInteraction() {
|
|
10
|
+
const { isConnected } = useStacks();
|
|
11
|
+
const { data: counter, isLoading: isLoadingCounter, refetch } = useContractRead(
|
|
12
|
+
counterContract,
|
|
13
|
+
'get-counter'
|
|
14
|
+
);
|
|
15
|
+
const { call: increment, isLoading: isIncrementing } = useContractCall(
|
|
16
|
+
counterContract,
|
|
17
|
+
'increment'
|
|
18
|
+
);
|
|
19
|
+
const { call: decrement, isLoading: isDecrementing } = useContractCall(
|
|
20
|
+
counterContract,
|
|
21
|
+
'decrement'
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const handleIncrement = async () => {
|
|
25
|
+
try {
|
|
26
|
+
await increment([]);
|
|
27
|
+
// Refetch after a delay to allow transaction to process
|
|
28
|
+
setTimeout(refetch, 2000);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('Increment failed:', error);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const handleDecrement = async () => {
|
|
35
|
+
try {
|
|
36
|
+
await decrement([]);
|
|
37
|
+
setTimeout(refetch, 2000);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Decrement failed:', error);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const counterValue = counter?.value ?? 0;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="card">
|
|
47
|
+
<h2 className="text-2xl font-bold mb-4">Counter Contract</h2>
|
|
48
|
+
|
|
49
|
+
<div className="mb-6 text-center">
|
|
50
|
+
<div className="text-6xl font-bold text-stacks-purple">
|
|
51
|
+
{isLoadingCounter ? '...' : counterValue.toString()}
|
|
52
|
+
</div>
|
|
53
|
+
<p className="text-sm text-gray-500 mt-2">Current count</p>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{isConnected ? (
|
|
57
|
+
<div className="flex gap-2">
|
|
58
|
+
<button
|
|
59
|
+
onClick={handleDecrement}
|
|
60
|
+
disabled={isDecrementing || counterValue === 0}
|
|
61
|
+
className="btn-secondary flex-1 disabled:opacity-50"
|
|
62
|
+
>
|
|
63
|
+
{isDecrementing ? 'Processing...' : '− Decrement'}
|
|
64
|
+
</button>
|
|
65
|
+
<button
|
|
66
|
+
onClick={handleIncrement}
|
|
67
|
+
disabled={isIncrementing}
|
|
68
|
+
className="btn-primary flex-1 disabled:opacity-50"
|
|
69
|
+
>
|
|
70
|
+
{isIncrementing ? 'Processing...' : '+ Increment'}
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
73
|
+
) : (
|
|
74
|
+
<p className="text-center text-gray-500">
|
|
75
|
+
Connect your wallet to interact with the contract
|
|
76
|
+
</p>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ConnectButton } from '@/components/wallet/connect-button';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
|
|
6
|
+
export function Header() {
|
|
7
|
+
return (
|
|
8
|
+
<header className="border-b border-gray-200 dark:border-gray-800">
|
|
9
|
+
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
|
|
10
|
+
<Link href="/" className="flex items-center gap-2">
|
|
11
|
+
<svg
|
|
12
|
+
className="w-8 h-8 text-stacks-purple"
|
|
13
|
+
viewBox="0 0 32 32"
|
|
14
|
+
fill="currentColor"
|
|
15
|
+
>
|
|
16
|
+
<path d="M16 0L3 8v16l13 8 13-8V8L16 0zm0 4l9 5.5v11L16 26l-9-5.5v-11L16 4z" />
|
|
17
|
+
</svg>
|
|
18
|
+
<span className="font-bold text-xl">Stacks App</span>
|
|
19
|
+
</Link>
|
|
20
|
+
<ConnectButton />
|
|
21
|
+
</div>
|
|
22
|
+
</header>
|
|
23
|
+
);
|
|
24
|
+
}
|