@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.
Files changed (115) hide show
  1. package/dist/commands/add.d.ts +8 -0
  2. package/dist/commands/add.d.ts.map +1 -0
  3. package/dist/commands/add.js +215 -0
  4. package/dist/commands/add.js.map +1 -0
  5. package/dist/commands/create.d.ts +3 -0
  6. package/dist/commands/create.d.ts.map +1 -0
  7. package/dist/commands/create.js +63 -0
  8. package/dist/commands/create.js.map +1 -0
  9. package/dist/commands/deploy.d.ts +7 -0
  10. package/dist/commands/deploy.d.ts.map +1 -0
  11. package/dist/commands/deploy.js +159 -0
  12. package/dist/commands/deploy.js.map +1 -0
  13. package/dist/index.d.ts +3 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +47 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/prompts/project.d.ts +4 -0
  18. package/dist/prompts/project.d.ts.map +1 -0
  19. package/dist/prompts/project.js +124 -0
  20. package/dist/prompts/project.js.map +1 -0
  21. package/dist/templates/installer.d.ts +4 -0
  22. package/dist/templates/installer.d.ts.map +1 -0
  23. package/dist/templates/installer.js +91 -0
  24. package/dist/templates/installer.js.map +1 -0
  25. package/dist/types/index.d.ts +40 -0
  26. package/dist/types/index.d.ts.map +1 -0
  27. package/dist/types/index.js +2 -0
  28. package/dist/types/index.js.map +1 -0
  29. package/dist/utils/clarinet.d.ts +5 -0
  30. package/dist/utils/clarinet.d.ts.map +1 -0
  31. package/dist/utils/clarinet.js +72 -0
  32. package/dist/utils/clarinet.js.map +1 -0
  33. package/dist/utils/filesystem.d.ts +4 -0
  34. package/dist/utils/filesystem.d.ts.map +1 -0
  35. package/dist/utils/filesystem.js +158 -0
  36. package/dist/utils/filesystem.js.map +1 -0
  37. package/dist/utils/git.d.ts +2 -0
  38. package/dist/utils/git.d.ts.map +1 -0
  39. package/dist/utils/git.js +21 -0
  40. package/dist/utils/git.js.map +1 -0
  41. package/dist/utils/logger.d.ts +7 -0
  42. package/dist/utils/logger.d.ts.map +1 -0
  43. package/dist/utils/logger.js +45 -0
  44. package/dist/utils/logger.js.map +1 -0
  45. package/dist/utils/package-manager.d.ts +5 -0
  46. package/dist/utils/package-manager.d.ts.map +1 -0
  47. package/dist/utils/package-manager.js +65 -0
  48. package/dist/utils/package-manager.js.map +1 -0
  49. package/dist/utils/validation.d.ts +5 -0
  50. package/dist/utils/validation.d.ts.map +1 -0
  51. package/dist/utils/validation.js +41 -0
  52. package/dist/utils/validation.js.map +1 -0
  53. package/package.json +52 -0
  54. package/templates/base/editorconfig +12 -0
  55. package/templates/base/gitignore +13 -0
  56. package/templates/base/prettierrc +7 -0
  57. package/templates/contracts/counter/counter.clar +43 -0
  58. package/templates/contracts/counter/counter.test.ts +127 -0
  59. package/templates/contracts/defi/sip010-trait.clar +11 -0
  60. package/templates/contracts/defi/staking-pool.clar +20 -0
  61. package/templates/contracts/marketplace/nft-marketplace.clar +44 -0
  62. package/templates/contracts/marketplace/nft-trait.clar +8 -0
  63. package/templates/contracts/marketplace/sip009-nft.clar +25 -0
  64. package/templates/contracts/nft/nft.clar +111 -0
  65. package/templates/contracts/nft/nft.test.ts +204 -0
  66. package/templates/contracts/token/token.clar +67 -0
  67. package/templates/contracts/token/token.test.ts +139 -0
  68. package/templates/frontends/nextjs/template/.env.example +2 -0
  69. package/templates/frontends/nextjs/template/app/globals.css +40 -0
  70. package/templates/frontends/nextjs/template/app/layout.tsx +36 -0
  71. package/templates/frontends/nextjs/template/app/page.tsx +42 -0
  72. package/templates/frontends/nextjs/template/app/providers.tsx +31 -0
  73. package/templates/frontends/nextjs/template/components/contracts/counter-interaction.tsx +80 -0
  74. package/templates/frontends/nextjs/template/components/header.tsx +24 -0
  75. package/templates/frontends/nextjs/template/components/wallet/connect-button.tsx +44 -0
  76. package/templates/frontends/nextjs/template/hooks/use-contract-call.ts +52 -0
  77. package/templates/frontends/nextjs/template/hooks/use-contract-read.ts +49 -0
  78. package/templates/frontends/nextjs/template/hooks/use-stacks.ts +26 -0
  79. package/templates/frontends/nextjs/template/lib/contracts.ts +29 -0
  80. package/templates/frontends/nextjs/template/lib/stacks.ts +18 -0
  81. package/templates/frontends/nextjs/template/next.config.js +6 -0
  82. package/templates/frontends/nextjs/template/package.json +29 -0
  83. package/templates/frontends/nextjs/template/postcss.config.js +6 -0
  84. package/templates/frontends/nextjs/template/public/logo.svg +3 -0
  85. package/templates/frontends/nextjs/template/tailwind.config.js +18 -0
  86. package/templates/frontends/nextjs/template/tsconfig.json +26 -0
  87. package/templates/frontends/react/template/.env.example +2 -0
  88. package/templates/frontends/react/template/index.html +13 -0
  89. package/templates/frontends/react/template/package.json +29 -0
  90. package/templates/frontends/react/template/postcss.config.js +6 -0
  91. package/templates/frontends/react/template/public/logo.svg +3 -0
  92. package/templates/frontends/react/template/src/App.tsx +100 -0
  93. package/templates/frontends/react/template/src/components/CounterInteraction.tsx +121 -0
  94. package/templates/frontends/react/template/src/components/Header.tsx +39 -0
  95. package/templates/frontends/react/template/src/index.css +33 -0
  96. package/templates/frontends/react/template/src/main.tsx +10 -0
  97. package/templates/frontends/react/template/tailwind.config.js +15 -0
  98. package/templates/frontends/react/template/tsconfig.json +25 -0
  99. package/templates/frontends/react/template/tsconfig.node.json +10 -0
  100. package/templates/frontends/react/template/vite.config.ts +12 -0
  101. package/templates/frontends/vue/template/.env.example +2 -0
  102. package/templates/frontends/vue/template/index.html +13 -0
  103. package/templates/frontends/vue/template/package.json +27 -0
  104. package/templates/frontends/vue/template/postcss.config.js +6 -0
  105. package/templates/frontends/vue/template/public/logo.svg +3 -0
  106. package/templates/frontends/vue/template/src/App.vue +98 -0
  107. package/templates/frontends/vue/template/src/components/AppHeader.vue +39 -0
  108. package/templates/frontends/vue/template/src/components/CounterInteraction.vue +120 -0
  109. package/templates/frontends/vue/template/src/env.d.ts +16 -0
  110. package/templates/frontends/vue/template/src/main.ts +5 -0
  111. package/templates/frontends/vue/template/src/style.css +33 -0
  112. package/templates/frontends/vue/template/tailwind.config.js +15 -0
  113. package/templates/frontends/vue/template/tsconfig.json +25 -0
  114. package/templates/frontends/vue/template/tsconfig.node.json +10 -0
  115. 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,2 @@
1
+ NEXT_PUBLIC_NETWORK=testnet
2
+ NEXT_PUBLIC_CONTRACT_ADDRESS=ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
@@ -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
+ }