@caatinga/cli 0.2.4 → 2.0.1

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 (26) hide show
  1. package/README.md +6 -7
  2. package/dist/index.js +198 -150
  3. package/package.json +3 -2
  4. package/templates/marketplace-with-token/caatinga.template.json +1 -1
  5. package/templates/marketplace-with-token/contracts/marketplace/test_snapshots/test/stores_token_contract_id_in_constructor.1.json +86 -0
  6. package/templates/marketplace-with-token/package.json +3 -3
  7. package/templates/react-vite-counter/README.md +18 -2
  8. package/templates/react-vite-counter/caatinga.template.json +1 -1
  9. package/templates/react-vite-counter/contracts/counter/src/lib.rs +70 -6
  10. package/templates/react-vite-counter/contracts/counter/test_snapshots/test/get_returns_zero_before_increment.1.json +76 -0
  11. package/templates/react-vite-counter/contracts/counter/test_snapshots/test/increment_returns_overflow_error.1.json +91 -0
  12. package/templates/react-vite-counter/contracts/counter/test_snapshots/test/increments_counter.1.json +91 -0
  13. package/templates/react-vite-counter/contracts/counter/test_snapshots/test/repeated_increments_preserve_state.1.json +92 -0
  14. package/templates/react-vite-counter/package.json +4 -5
  15. package/templates/react-vite-counter/pnpm-workspace.yaml +12 -0
  16. package/templates/react-vite-counter/src/App.tsx +26 -2
  17. package/templates/react-vite-counter/src/components/CounterCard.tsx +31 -6
  18. package/templates/react-vite-counter/src/components/LoadingModal.tsx +14 -0
  19. package/templates/react-vite-counter/src/components/WalletButton.tsx +3 -39
  20. package/templates/react-vite-counter/src/context/WalletContext.tsx +64 -0
  21. package/templates/react-vite-counter/src/contracts/generated/counter.ts +1 -1
  22. package/templates/react-vite-counter/src/stubs/hot-wallet.ts +14 -0
  23. package/templates/react-vite-counter/src/styles.css +51 -0
  24. package/templates/react-vite-counter/src/wallet.ts +3 -3
  25. package/templates/react-vite-counter/vite.config.ts +5 -6
  26. package/templates/react-vite-counter/src/hooks/useStellarWallet.ts +0 -70
@@ -1,6 +1,8 @@
1
- import { useCallback, useMemo, useState } from "react";
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
2
  import { caatingaClient } from "../caatinga.js";
3
3
  import { CaatingaError } from "@caatinga/core/browser";
4
+ import { useWallet } from "../context/WalletContext.js";
5
+ import { LoadingModal } from "./LoadingModal.js";
4
6
 
5
7
  function formatCaatingaError(error: unknown): string {
6
8
  if (error instanceof CaatingaError) {
@@ -11,13 +13,22 @@ function formatCaatingaError(error: unknown): string {
11
13
  }
12
14
 
13
15
  export function CounterCard() {
16
+ const { publicKey } = useWallet();
14
17
  const [count, setCount] = useState<number | null>(null);
15
- const [loading, setLoading] = useState(false);
18
+ // Start in the loading state: the component only mounts once a wallet is
19
+ // connected, and it fetches immediately — so the first paint should read as
20
+ // "Loading…" instead of flashing "Not loaded" while the read resolves.
21
+ const [loading, setLoading] = useState(true);
16
22
  const [error, setError] = useState<string | null>(null);
17
- const formattedCount = useMemo(
18
- () => (count === null ? "Unknown" : new Intl.NumberFormat().format(count)),
19
- [count]
20
- );
23
+ const formattedCount = useMemo(() => {
24
+ if (count !== null) {
25
+ return new Intl.NumberFormat().format(count);
26
+ }
27
+
28
+ // While loading, the LoadingModal overlay covers this; show a neutral
29
+ // placeholder (not "Not loaded") so nothing flashes behind it.
30
+ return loading ? "—" : "Not loaded";
31
+ }, [count, loading]);
21
32
 
22
33
  const refresh = useCallback(async () => {
23
34
  setLoading(true);
@@ -33,6 +44,18 @@ export function CounterCard() {
33
44
  }
34
45
  }, []);
35
46
 
47
+ useEffect(() => {
48
+ // Reads route through the wallet for the source account, so only fetch once
49
+ // a wallet is connected. Avoids the "Not loaded" state firing before connect.
50
+ if (!publicKey) {
51
+ setCount(null);
52
+ setLoading(false);
53
+ return;
54
+ }
55
+
56
+ void refresh();
57
+ }, [publicKey, refresh]);
58
+
36
59
  async function increment() {
37
60
  setLoading(true);
38
61
  setError(null);
@@ -53,6 +76,8 @@ export function CounterCard() {
53
76
 
54
77
  return (
55
78
  <section className="counter-panel" aria-labelledby="counter-title">
79
+ {loading ? <LoadingModal label={count === null ? "Loading…" : "Updating…"} /> : null}
80
+
56
81
  <div className="counter-panel__header">
57
82
  <div>
58
83
  <p className="eyebrow">Counter Contract</p>
@@ -0,0 +1,14 @@
1
+ interface LoadingModalProps {
2
+ label?: string;
3
+ }
4
+
5
+ export function LoadingModal({ label = "Loading…" }: LoadingModalProps) {
6
+ return (
7
+ <div className="loading-modal" role="status" aria-live="polite" aria-label={label}>
8
+ <div className="loading-modal__card">
9
+ <span className="loading-modal__spinner" aria-hidden="true" />
10
+ <span className="loading-modal__label">{label}</span>
11
+ </div>
12
+ </div>
13
+ );
14
+ }
@@ -1,5 +1,4 @@
1
- import { WalletNetwork, WalletType } from "../wallet.js";
2
- import { useStellarWallet } from "../hooks/useStellarWallet.js";
1
+ import { useWallet } from "../context/WalletContext.js";
3
2
 
4
3
  function shortenAddress(address: string): string {
5
4
  if (address.length <= 12) {
@@ -10,49 +9,14 @@ function shortenAddress(address: string): string {
10
9
  }
11
10
 
12
11
  export function WalletButton() {
13
- const {
14
- publicKey,
15
- selectedWallet,
16
- network,
17
- loading,
18
- error,
19
- selectWallet,
20
- selectNetwork,
21
- connect,
22
- disconnect
23
- } = useStellarWallet();
12
+ const { publicKey, loading, error, connect, disconnect } = useWallet();
24
13
 
25
14
  return (
26
15
  <div className="wallet-shell">
27
- <div className="wallet-controls">
28
- <label>
29
- <span>Wallet</span>
30
- <select
31
- value={selectedWallet}
32
- onChange={(event) => void selectWallet(event.target.value as WalletType)}
33
- >
34
- <option value={WalletType.XBULL}>xBull</option>
35
- <option value={WalletType.FREIGHTER}>Freighter</option>
36
- <option value={WalletType.ALBEDO}>Albedo</option>
37
- <option value={WalletType.RABET}>Rabet</option>
38
- <option value={WalletType.WALLET_CONNECT}>WalletConnect</option>
39
- </select>
40
- </label>
41
- <label>
42
- <span>Network</span>
43
- <select
44
- value={network}
45
- onChange={(event) => void selectNetwork(event.target.value as WalletNetwork)}
46
- >
47
- <option value={WalletNetwork.TESTNET}>Testnet</option>
48
- <option value={WalletNetwork.PUBLIC}>Public</option>
49
- </select>
50
- </label>
51
- </div>
52
16
  <button
53
17
  className="wallet-button"
54
18
  type="button"
55
- onClick={publicKey ? disconnect : () => void connect()}
19
+ onClick={publicKey ? () => void disconnect() : () => void connect()}
56
20
  disabled={loading}
57
21
  aria-live="polite"
58
22
  >
@@ -0,0 +1,64 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useMemo,
6
+ useState,
7
+ type ReactNode
8
+ } from "react";
9
+ import { stellarWalletAdapter } from "../wallet.js";
10
+
11
+ interface WalletContextValue {
12
+ publicKey: string | null;
13
+ loading: boolean;
14
+ error: string | null;
15
+ connect: () => Promise<void>;
16
+ disconnect: () => Promise<void>;
17
+ }
18
+
19
+ const WalletContext = createContext<WalletContextValue | null>(null);
20
+
21
+ export function WalletProvider({ children }: { children: ReactNode }) {
22
+ const [publicKey, setPublicKey] = useState<string | null>(null);
23
+ const [loading, setLoading] = useState(false);
24
+ const [error, setError] = useState<string | null>(null);
25
+
26
+ const connect = useCallback(async () => {
27
+ setLoading(true);
28
+ setError(null);
29
+
30
+ try {
31
+ // openModal lists only installed/available wallets and resolves with the
32
+ // chosen account address (rejects if the user dismisses the modal).
33
+ const address = await stellarWalletAdapter.openModal();
34
+ setPublicKey(address);
35
+ } catch (caught) {
36
+ const message = caught instanceof Error ? caught.message : String(caught);
37
+ setError(message);
38
+ } finally {
39
+ setLoading(false);
40
+ }
41
+ }, []);
42
+
43
+ const disconnect = useCallback(async () => {
44
+ await stellarWalletAdapter.disconnect();
45
+ setPublicKey(null);
46
+ setError(null);
47
+ }, []);
48
+
49
+ const value = useMemo<WalletContextValue>(
50
+ () => ({ publicKey, loading, error, connect, disconnect }),
51
+ [publicKey, loading, error, connect, disconnect]
52
+ );
53
+
54
+ return <WalletContext.Provider value={value}>{children}</WalletContext.Provider>;
55
+ }
56
+
57
+ export function useWallet(): WalletContextValue {
58
+ const context = useContext(WalletContext);
59
+ if (!context) {
60
+ throw new Error("useWallet must be used within a WalletProvider");
61
+ }
62
+
63
+ return context;
64
+ }
@@ -45,7 +45,7 @@ export class Client {
45
45
  ) {}
46
46
 
47
47
  increment(): ExampleTransaction {
48
- return new ExampleTransaction("increment");
48
+ return new ExampleTransaction("increment", 1);
49
49
  }
50
50
 
51
51
  get(): ExampleTransaction {
@@ -0,0 +1,14 @@
1
+ // Stub for @hot-wallet/sdk. Stellar Wallets Kit pulls this NEAR-ecosystem SDK
2
+ // (via its HOT Wallet module) into every import, dragging @near-js/crypto and
3
+ // randombytes — Node-only code that references `global` and crashes in the
4
+ // browser. The Caatinga adapter filters HOT Wallet out of the wallet list, so
5
+ // this SDK is never actually used; aliasing it here (see vite.config.ts) keeps
6
+ // the NEAR chain out of the bundle entirely.
7
+ //
8
+ // `HOT` is only referenced inside HotWalletModule methods, which are never
9
+ // reached, so a throwing stub is safe.
10
+ export const HOT = {
11
+ request(): Promise<never> {
12
+ return Promise.reject(new Error("HOT Wallet is not supported in this build."));
13
+ }
14
+ };
@@ -140,6 +140,7 @@ button:hover {
140
140
  }
141
141
 
142
142
  .counter-panel {
143
+ position: relative;
143
144
  border: 1px solid #d9d5ca;
144
145
  border-radius: 8px;
145
146
  padding: clamp(20px, 5vw, 40px);
@@ -147,6 +148,56 @@ button:hover {
147
148
  box-shadow: 0 18px 60px rgba(32, 35, 42, 0.08);
148
149
  }
149
150
 
151
+ .loading-modal {
152
+ position: absolute;
153
+ inset: 0;
154
+ z-index: 5;
155
+ display: flex;
156
+ align-items: center;
157
+ justify-content: center;
158
+ border-radius: 8px;
159
+ background: rgba(255, 253, 247, 0.72);
160
+ backdrop-filter: blur(2px);
161
+ }
162
+
163
+ .loading-modal__card {
164
+ display: flex;
165
+ align-items: center;
166
+ gap: 12px;
167
+ padding: 14px 20px;
168
+ border: 1px solid #d9d5ca;
169
+ border-radius: 10px;
170
+ background: #fffdf7;
171
+ box-shadow: 0 12px 40px rgba(32, 35, 42, 0.14);
172
+ color: #20232a;
173
+ font-weight: 700;
174
+ }
175
+
176
+ .loading-modal__spinner {
177
+ width: 18px;
178
+ height: 18px;
179
+ border: 3px solid #d9d5ca;
180
+ border-top-color: #1d6154;
181
+ border-radius: 50%;
182
+ animation: loading-spin 0.7s linear infinite;
183
+ }
184
+
185
+ .loading-modal__label {
186
+ font-size: 0.95rem;
187
+ }
188
+
189
+ @keyframes loading-spin {
190
+ to {
191
+ transform: rotate(360deg);
192
+ }
193
+ }
194
+
195
+ @media (prefers-reduced-motion: reduce) {
196
+ .loading-modal__spinner {
197
+ animation-duration: 1.6s;
198
+ }
199
+ }
200
+
150
201
  .counter-panel__header {
151
202
  display: flex;
152
203
  align-items: flex-start;
@@ -1,16 +1,16 @@
1
+ // Keep in sync with examples/counter-web/src/wallet.ts (WalletConnect defaults differ per project).
1
2
  import {
2
3
  createStellarWalletsKitAdapter,
4
+ WalletNetwork,
3
5
  type StellarWalletsKitMetadata
4
6
  } from "@caatinga/client/stellar-wallets-kit";
5
- import { WalletNetwork, WalletType } from "stellar-wallets-kit";
6
7
 
7
8
  export const stellarWalletAdapter = createStellarWalletsKitAdapter({
8
9
  network: WalletNetwork.TESTNET,
9
- selectedWallet: WalletType.XBULL,
10
10
  walletConnectMetadata: getWalletConnectMetadata()
11
11
  });
12
12
 
13
- export { WalletNetwork, WalletType };
13
+ export { WalletNetwork };
14
14
 
15
15
  function getWalletConnectMetadata(): StellarWalletsKitMetadata | undefined {
16
16
  const projectId = import.meta.env.VITE_WALLETCONNECT_PROJECT_ID as string | undefined;
@@ -1,16 +1,15 @@
1
1
  import { defineConfig } from "vite";
2
2
  import react from "@vitejs/plugin-react";
3
- import { createRequire } from "node:module";
4
- import { dirname, join } from "node:path";
5
-
6
- const require = createRequire(import.meta.url);
7
- const xbullWalletConnectRoot = dirname(require.resolve("@creit.tech/xbull-wallet-connect/package.json"));
3
+ import { fileURLToPath } from "node:url";
8
4
 
9
5
  export default defineConfig({
10
6
  plugins: [react()],
11
7
  resolve: {
12
8
  alias: {
13
- "@creit-tech/xbull-wallet-connect": join(xbullWalletConnectRoot, "src/index.ts")
9
+ // Stellar Wallets Kit drags NEAR's @hot-wallet/sdk (Node-only crypto) into
10
+ // the browser bundle. The adapter filters HOT Wallet out, so stub the SDK
11
+ // to keep the NEAR chain out of the build. See src/stubs/hot-wallet.ts.
12
+ "@hot-wallet/sdk": fileURLToPath(new URL("./src/stubs/hot-wallet.ts", import.meta.url))
14
13
  }
15
14
  }
16
15
  });
@@ -1,70 +0,0 @@
1
- import { useCallback, useEffect, useState } from "react";
2
- import { stellarWalletAdapter, WalletNetwork, WalletType } from "../wallet.js";
3
-
4
- export function useStellarWallet() {
5
- const [publicKey, setPublicKey] = useState<string | null>(null);
6
- const [selectedWallet, setSelectedWallet] = useState<WalletType>(WalletType.XBULL);
7
- const [network, setNetwork] = useState<WalletNetwork>(WalletNetwork.TESTNET);
8
- const [loading, setLoading] = useState(false);
9
- const [error, setError] = useState<string | null>(null);
10
-
11
- const selectWallet = useCallback(async (wallet: WalletType) => {
12
- setError(null);
13
- await stellarWalletAdapter.setWallet(wallet);
14
- setSelectedWallet(wallet);
15
- setPublicKey(null);
16
- }, []);
17
-
18
- const selectNetwork = useCallback(async (nextNetwork: WalletNetwork) => {
19
- setError(null);
20
- await stellarWalletAdapter.setNetwork(nextNetwork);
21
- setNetwork(nextNetwork);
22
- setPublicKey(null);
23
- }, []);
24
-
25
- const connect = useCallback(async () => {
26
- setLoading(true);
27
- setError(null);
28
-
29
- try {
30
- if (selectedWallet === WalletType.WALLET_CONNECT) {
31
- await stellarWalletAdapter.startWalletConnect();
32
- await stellarWalletAdapter.connectWalletConnect();
33
- }
34
-
35
- const key = await stellarWalletAdapter.getPublicKey();
36
- setPublicKey(key);
37
- return key;
38
- } catch (caught) {
39
- const message = caught instanceof Error ? caught.message : String(caught);
40
- setPublicKey(null);
41
- setError(message);
42
- throw caught;
43
- } finally {
44
- setLoading(false);
45
- }
46
- }, [selectedWallet]);
47
-
48
- const disconnect = useCallback(() => {
49
- setPublicKey(null);
50
- setError(null);
51
- }, []);
52
-
53
- useEffect(() => {
54
- stellarWalletAdapter.onWalletConnectSessionDeleted(() => {
55
- setPublicKey(null);
56
- });
57
- }, []);
58
-
59
- return {
60
- publicKey,
61
- selectedWallet,
62
- network,
63
- loading,
64
- error,
65
- selectWallet,
66
- selectNetwork,
67
- connect,
68
- disconnect
69
- };
70
- }