@caatinga/cli 0.2.3 → 2.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.
Files changed (49) hide show
  1. package/README.md +6 -7
  2. package/dist/index.js +220 -157
  3. package/package.json +3 -2
  4. package/templates/marketplace-with-token/README.md +21 -0
  5. package/templates/marketplace-with-token/caatinga.artifacts.json +10 -0
  6. package/templates/marketplace-with-token/caatinga.config.ts +30 -0
  7. package/templates/marketplace-with-token/caatinga.template.json +21 -0
  8. package/templates/marketplace-with-token/contracts/marketplace/Cargo.lock +1731 -0
  9. package/templates/marketplace-with-token/contracts/marketplace/Cargo.toml +24 -0
  10. package/templates/marketplace-with-token/contracts/marketplace/src/lib.rs +43 -0
  11. package/templates/marketplace-with-token/contracts/marketplace/test_snapshots/test/stores_token_contract_id_in_constructor.1.json +86 -0
  12. package/templates/marketplace-with-token/contracts/token/Cargo.lock +1731 -0
  13. package/templates/marketplace-with-token/contracts/token/Cargo.toml +24 -0
  14. package/templates/marketplace-with-token/contracts/token/src/lib.rs +13 -0
  15. package/templates/marketplace-with-token/index.html +12 -0
  16. package/templates/marketplace-with-token/package.json +28 -0
  17. package/templates/marketplace-with-token/src/App.tsx +57 -0
  18. package/templates/marketplace-with-token/src/main.ts +12 -0
  19. package/templates/marketplace-with-token/src/main.tsx +10 -0
  20. package/templates/marketplace-with-token/src/styles.css +157 -0
  21. package/templates/marketplace-with-token/tsconfig.json +21 -0
  22. package/templates/marketplace-with-token/vite.config.ts +6 -0
  23. package/templates/react-vite-counter/.env.example +5 -0
  24. package/templates/react-vite-counter/README.md +83 -0
  25. package/templates/react-vite-counter/caatinga.artifacts.json +10 -0
  26. package/templates/react-vite-counter/caatinga.config.ts +26 -0
  27. package/templates/react-vite-counter/caatinga.template.json +21 -0
  28. package/templates/react-vite-counter/contracts/counter/Cargo.lock +1731 -0
  29. package/templates/react-vite-counter/contracts/counter/Cargo.toml +24 -0
  30. package/templates/react-vite-counter/contracts/counter/src/lib.rs +100 -0
  31. package/templates/react-vite-counter/contracts/counter/test_snapshots/test/get_returns_zero_before_increment.1.json +76 -0
  32. package/templates/react-vite-counter/contracts/counter/test_snapshots/test/increment_returns_overflow_error.1.json +91 -0
  33. package/templates/react-vite-counter/contracts/counter/test_snapshots/test/increments_counter.1.json +91 -0
  34. package/templates/react-vite-counter/contracts/counter/test_snapshots/test/repeated_increments_preserve_state.1.json +92 -0
  35. package/templates/react-vite-counter/index.html +12 -0
  36. package/templates/react-vite-counter/package.json +30 -0
  37. package/templates/react-vite-counter/pnpm-workspace.yaml +12 -0
  38. package/templates/react-vite-counter/public/.gitkeep +1 -0
  39. package/templates/react-vite-counter/src/App.tsx +18 -0
  40. package/templates/react-vite-counter/src/caatinga.ts +20 -0
  41. package/templates/react-vite-counter/src/components/CounterCard.tsx +86 -0
  42. package/templates/react-vite-counter/src/components/WalletButton.tsx +69 -0
  43. package/templates/react-vite-counter/src/contracts/generated/counter.ts +58 -0
  44. package/templates/react-vite-counter/src/hooks/useStellarWallet.ts +70 -0
  45. package/templates/react-vite-counter/src/main.tsx +10 -0
  46. package/templates/react-vite-counter/src/styles.css +206 -0
  47. package/templates/react-vite-counter/src/wallet.ts +29 -0
  48. package/templates/react-vite-counter/tsconfig.json +22 -0
  49. package/templates/react-vite-counter/vite.config.ts +18 -0
@@ -0,0 +1,24 @@
1
+ [package]
2
+ name = "counter"
3
+ version = "0.1.0"
4
+ rust-version = "1.84.0"
5
+ edition = "2021"
6
+
7
+ [lib]
8
+ crate-type = ["cdylib"]
9
+
10
+ [dependencies]
11
+ soroban-sdk = "22.0.1"
12
+
13
+ [dev-dependencies]
14
+ soroban-sdk = { version = "22.0.1", features = ["testutils"] }
15
+
16
+ [profile.release]
17
+ opt-level = "z"
18
+ overflow-checks = true
19
+ debug = 0
20
+ strip = "symbols"
21
+ debug-assertions = false
22
+ panic = "abort"
23
+ codegen-units = 1
24
+ lto = true
@@ -0,0 +1,100 @@
1
+ #![no_std]
2
+
3
+ use soroban_sdk::{contract, contracterror, contractimpl, contracttype, Env};
4
+
5
+ const INSTANCE_TTL_THRESHOLD: u32 = 100;
6
+ const INSTANCE_TTL_EXTEND_TO: u32 = 518_400;
7
+
8
+ #[contracttype]
9
+ #[derive(Clone)]
10
+ pub enum DataKey {
11
+ Count,
12
+ }
13
+
14
+ #[contracterror]
15
+ #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
16
+ #[repr(u32)]
17
+ pub enum CounterError {
18
+ Overflow = 1,
19
+ }
20
+
21
+ #[contract]
22
+ pub struct CounterContract;
23
+
24
+ fn refresh_instance_ttl(env: &Env) {
25
+ env.storage()
26
+ .instance()
27
+ .extend_ttl(INSTANCE_TTL_THRESHOLD, INSTANCE_TTL_EXTEND_TO);
28
+ }
29
+
30
+ #[contractimpl]
31
+ impl CounterContract {
32
+ pub fn get(env: Env) -> u32 {
33
+ let count = env.storage().instance().get(&DataKey::Count).unwrap_or(0);
34
+ refresh_instance_ttl(&env);
35
+ count
36
+ }
37
+
38
+ pub fn increment(env: Env) -> Result<u32, CounterError> {
39
+ let count = Self::get(env.clone())
40
+ .checked_add(1)
41
+ .ok_or(CounterError::Overflow)?;
42
+
43
+ env.storage().instance().set(&DataKey::Count, &count);
44
+ refresh_instance_ttl(&env);
45
+
46
+ Ok(count)
47
+ }
48
+ }
49
+
50
+ #[cfg(test)]
51
+ mod test {
52
+ use super::*;
53
+ use soroban_sdk::Env;
54
+
55
+ #[test]
56
+ fn increments_counter() {
57
+ let env = Env::default();
58
+ let contract_id = env.register(CounterContract, ());
59
+ let client = CounterContractClient::new(&env, &contract_id);
60
+
61
+ assert_eq!(client.get(), 0);
62
+ assert_eq!(client.increment(), 1);
63
+ assert_eq!(client.get(), 1);
64
+ }
65
+
66
+ #[test]
67
+ fn get_returns_zero_before_increment() {
68
+ let env = Env::default();
69
+ let contract_id = env.register(CounterContract, ());
70
+ let client = CounterContractClient::new(&env, &contract_id);
71
+
72
+ assert_eq!(client.get(), 0);
73
+ }
74
+
75
+ #[test]
76
+ fn repeated_increments_preserve_state() {
77
+ let env = Env::default();
78
+ let contract_id = env.register(CounterContract, ());
79
+ let client = CounterContractClient::new(&env, &contract_id);
80
+
81
+ assert_eq!(client.increment(), 1);
82
+ assert_eq!(client.increment(), 2);
83
+ assert_eq!(client.increment(), 3);
84
+ assert_eq!(client.get(), 3);
85
+ }
86
+
87
+ #[test]
88
+ fn increment_returns_overflow_error() {
89
+ let env = Env::default();
90
+ let contract_id = env.register(CounterContract, ());
91
+ let client = CounterContractClient::new(&env, &contract_id);
92
+
93
+ env.as_contract(&contract_id, || {
94
+ env.storage().instance().set(&DataKey::Count, &u32::MAX);
95
+ });
96
+
97
+ assert_eq!(client.try_increment(), Err(Ok(CounterError::Overflow)));
98
+ assert_eq!(client.get(), u32::MAX);
99
+ }
100
+ }
@@ -0,0 +1,76 @@
1
+ {
2
+ "generators": {
3
+ "address": 1,
4
+ "nonce": 0
5
+ },
6
+ "auth": [
7
+ [],
8
+ []
9
+ ],
10
+ "ledger": {
11
+ "protocol_version": 22,
12
+ "sequence_number": 0,
13
+ "timestamp": 0,
14
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
15
+ "base_reserve": 0,
16
+ "min_persistent_entry_ttl": 4096,
17
+ "min_temp_entry_ttl": 16,
18
+ "max_entry_ttl": 6312000,
19
+ "ledger_entries": [
20
+ [
21
+ {
22
+ "contract_data": {
23
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
24
+ "key": "ledger_key_contract_instance",
25
+ "durability": "persistent"
26
+ }
27
+ },
28
+ [
29
+ {
30
+ "last_modified_ledger_seq": 0,
31
+ "data": {
32
+ "contract_data": {
33
+ "ext": "v0",
34
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
35
+ "key": "ledger_key_contract_instance",
36
+ "durability": "persistent",
37
+ "val": {
38
+ "contract_instance": {
39
+ "executable": {
40
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
41
+ },
42
+ "storage": null
43
+ }
44
+ }
45
+ }
46
+ },
47
+ "ext": "v0"
48
+ },
49
+ 4095
50
+ ]
51
+ ],
52
+ [
53
+ {
54
+ "contract_code": {
55
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
56
+ }
57
+ },
58
+ [
59
+ {
60
+ "last_modified_ledger_seq": 0,
61
+ "data": {
62
+ "contract_code": {
63
+ "ext": "v0",
64
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
65
+ "code": ""
66
+ }
67
+ },
68
+ "ext": "v0"
69
+ },
70
+ 4095
71
+ ]
72
+ ]
73
+ ]
74
+ },
75
+ "events": []
76
+ }
@@ -0,0 +1,91 @@
1
+ {
2
+ "generators": {
3
+ "address": 1,
4
+ "nonce": 0
5
+ },
6
+ "auth": [
7
+ [],
8
+ [],
9
+ [],
10
+ []
11
+ ],
12
+ "ledger": {
13
+ "protocol_version": 22,
14
+ "sequence_number": 0,
15
+ "timestamp": 0,
16
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
17
+ "base_reserve": 0,
18
+ "min_persistent_entry_ttl": 4096,
19
+ "min_temp_entry_ttl": 16,
20
+ "max_entry_ttl": 6312000,
21
+ "ledger_entries": [
22
+ [
23
+ {
24
+ "contract_data": {
25
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
26
+ "key": "ledger_key_contract_instance",
27
+ "durability": "persistent"
28
+ }
29
+ },
30
+ [
31
+ {
32
+ "last_modified_ledger_seq": 0,
33
+ "data": {
34
+ "contract_data": {
35
+ "ext": "v0",
36
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
37
+ "key": "ledger_key_contract_instance",
38
+ "durability": "persistent",
39
+ "val": {
40
+ "contract_instance": {
41
+ "executable": {
42
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
43
+ },
44
+ "storage": [
45
+ {
46
+ "key": {
47
+ "vec": [
48
+ {
49
+ "symbol": "Count"
50
+ }
51
+ ]
52
+ },
53
+ "val": {
54
+ "u32": 4294967295
55
+ }
56
+ }
57
+ ]
58
+ }
59
+ }
60
+ }
61
+ },
62
+ "ext": "v0"
63
+ },
64
+ 4095
65
+ ]
66
+ ],
67
+ [
68
+ {
69
+ "contract_code": {
70
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
71
+ }
72
+ },
73
+ [
74
+ {
75
+ "last_modified_ledger_seq": 0,
76
+ "data": {
77
+ "contract_code": {
78
+ "ext": "v0",
79
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
80
+ "code": ""
81
+ }
82
+ },
83
+ "ext": "v0"
84
+ },
85
+ 4095
86
+ ]
87
+ ]
88
+ ]
89
+ },
90
+ "events": []
91
+ }
@@ -0,0 +1,91 @@
1
+ {
2
+ "generators": {
3
+ "address": 1,
4
+ "nonce": 0
5
+ },
6
+ "auth": [
7
+ [],
8
+ [],
9
+ [],
10
+ []
11
+ ],
12
+ "ledger": {
13
+ "protocol_version": 22,
14
+ "sequence_number": 0,
15
+ "timestamp": 0,
16
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
17
+ "base_reserve": 0,
18
+ "min_persistent_entry_ttl": 4096,
19
+ "min_temp_entry_ttl": 16,
20
+ "max_entry_ttl": 6312000,
21
+ "ledger_entries": [
22
+ [
23
+ {
24
+ "contract_data": {
25
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
26
+ "key": "ledger_key_contract_instance",
27
+ "durability": "persistent"
28
+ }
29
+ },
30
+ [
31
+ {
32
+ "last_modified_ledger_seq": 0,
33
+ "data": {
34
+ "contract_data": {
35
+ "ext": "v0",
36
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
37
+ "key": "ledger_key_contract_instance",
38
+ "durability": "persistent",
39
+ "val": {
40
+ "contract_instance": {
41
+ "executable": {
42
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
43
+ },
44
+ "storage": [
45
+ {
46
+ "key": {
47
+ "vec": [
48
+ {
49
+ "symbol": "Count"
50
+ }
51
+ ]
52
+ },
53
+ "val": {
54
+ "u32": 1
55
+ }
56
+ }
57
+ ]
58
+ }
59
+ }
60
+ }
61
+ },
62
+ "ext": "v0"
63
+ },
64
+ 4095
65
+ ]
66
+ ],
67
+ [
68
+ {
69
+ "contract_code": {
70
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
71
+ }
72
+ },
73
+ [
74
+ {
75
+ "last_modified_ledger_seq": 0,
76
+ "data": {
77
+ "contract_code": {
78
+ "ext": "v0",
79
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
80
+ "code": ""
81
+ }
82
+ },
83
+ "ext": "v0"
84
+ },
85
+ 4095
86
+ ]
87
+ ]
88
+ ]
89
+ },
90
+ "events": []
91
+ }
@@ -0,0 +1,92 @@
1
+ {
2
+ "generators": {
3
+ "address": 1,
4
+ "nonce": 0
5
+ },
6
+ "auth": [
7
+ [],
8
+ [],
9
+ [],
10
+ [],
11
+ []
12
+ ],
13
+ "ledger": {
14
+ "protocol_version": 22,
15
+ "sequence_number": 0,
16
+ "timestamp": 0,
17
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
18
+ "base_reserve": 0,
19
+ "min_persistent_entry_ttl": 4096,
20
+ "min_temp_entry_ttl": 16,
21
+ "max_entry_ttl": 6312000,
22
+ "ledger_entries": [
23
+ [
24
+ {
25
+ "contract_data": {
26
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
27
+ "key": "ledger_key_contract_instance",
28
+ "durability": "persistent"
29
+ }
30
+ },
31
+ [
32
+ {
33
+ "last_modified_ledger_seq": 0,
34
+ "data": {
35
+ "contract_data": {
36
+ "ext": "v0",
37
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
38
+ "key": "ledger_key_contract_instance",
39
+ "durability": "persistent",
40
+ "val": {
41
+ "contract_instance": {
42
+ "executable": {
43
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
44
+ },
45
+ "storage": [
46
+ {
47
+ "key": {
48
+ "vec": [
49
+ {
50
+ "symbol": "Count"
51
+ }
52
+ ]
53
+ },
54
+ "val": {
55
+ "u32": 3
56
+ }
57
+ }
58
+ ]
59
+ }
60
+ }
61
+ }
62
+ },
63
+ "ext": "v0"
64
+ },
65
+ 4095
66
+ ]
67
+ ],
68
+ [
69
+ {
70
+ "contract_code": {
71
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
72
+ }
73
+ },
74
+ [
75
+ {
76
+ "last_modified_ledger_seq": 0,
77
+ "data": {
78
+ "contract_code": {
79
+ "ext": "v0",
80
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
81
+ "code": ""
82
+ }
83
+ },
84
+ "ext": "v0"
85
+ },
86
+ 4095
87
+ ]
88
+ ]
89
+ ]
90
+ },
91
+ "events": []
92
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>__PROJECT_NAME__</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "__PROJECT_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc && vite build",
9
+ "preview": "vite preview",
10
+ "caatinga:build": "caatinga build counter",
11
+ "caatinga:deploy": "caatinga deploy counter",
12
+ "caatinga:generate": "caatinga generate counter"
13
+ },
14
+ "dependencies": {
15
+ "@caatinga/client": "^2.0.0",
16
+ "@caatinga/core": "^2.0.0",
17
+ "@creit.tech/xbull-wallet-connect": "^0.4.0",
18
+ "@vitejs/plugin-react": "^4.3.4",
19
+ "react": "^18.3.1",
20
+ "react-dom": "^18.3.1",
21
+ "stellar-wallets-kit": "github:Creit-Tech/Stellar-Wallets-Kit#v0.0.7",
22
+ "vite": "^6.0.6"
23
+ },
24
+ "devDependencies": {
25
+ "@caatinga/cli": "^2.0.0",
26
+ "@types/react": "^18.3.18",
27
+ "@types/react-dom": "^18.3.5",
28
+ "typescript": "^5.7.2"
29
+ }
30
+ }
@@ -0,0 +1,12 @@
1
+ # pnpm 10.26+ / 11.x block lifecycle scripts by default; vite depends on esbuild.
2
+ allowBuilds:
3
+ esbuild: true
4
+
5
+ # stellar-wallets-kit@0.0.7 is unpublished from npm and is therefore pinned
6
+ # to a github: URL in package.json. Its own dependencies also reference
7
+ # @creit.tech/xbull-wallet-connect via a github: URL, and pnpm 10.26+ (and
8
+ # 11.x) defaults blockExoticSubdeps to true, which would refuse to install
9
+ # that exotic subdep. Allow it explicitly. This is a targeted opt-out for
10
+ # this one transitive dep; direct dependencies in package.json still must
11
+ # come from a trusted source.
12
+ blockExoticSubdeps: false
@@ -0,0 +1,18 @@
1
+ import { CounterCard } from "./components/CounterCard";
2
+ import { WalletButton } from "./components/WalletButton";
3
+
4
+ export default function App() {
5
+ return (
6
+ <main className="app-shell">
7
+ <header className="topbar">
8
+ <div>
9
+ <p className="eyebrow">Caatinga</p>
10
+ <h1>__PROJECT_NAME__</h1>
11
+ </div>
12
+ <WalletButton />
13
+ </header>
14
+
15
+ <CounterCard />
16
+ </main>
17
+ );
18
+ }
@@ -0,0 +1,20 @@
1
+ import { createCaatingaClient } from "@caatinga/client";
2
+ import type { CaatingaArtifacts } from "@caatinga/core/browser";
3
+ import artifactsJson from "../caatinga.artifacts.json";
4
+ import * as Counter from "./contracts/generated/counter.js";
5
+ import { stellarWalletAdapter } from "./wallet.js";
6
+
7
+ const artifacts = artifactsJson as CaatingaArtifacts;
8
+
9
+ export const caatingaClient = createCaatingaClient({
10
+ network: {
11
+ name: "testnet",
12
+ rpcUrl: "https://soroban-testnet.stellar.org",
13
+ networkPassphrase: "Test SDF Network ; September 2015"
14
+ },
15
+ artifacts,
16
+ wallet: stellarWalletAdapter,
17
+ contracts: {
18
+ counter: { binding: Counter }
19
+ }
20
+ });
@@ -0,0 +1,86 @@
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
+ import { caatingaClient } from "../caatinga.js";
3
+ import { CaatingaError } from "@caatinga/core/browser";
4
+
5
+ function formatCaatingaError(error: unknown): string {
6
+ if (error instanceof CaatingaError) {
7
+ return `[${error.code}] ${error.message}\n\n${error.hint}`;
8
+ }
9
+
10
+ return error instanceof Error ? error.message : String(error);
11
+ }
12
+
13
+ export function CounterCard() {
14
+ const [count, setCount] = useState<number | null>(null);
15
+ const [loading, setLoading] = useState(false);
16
+ const [error, setError] = useState<string | null>(null);
17
+ const formattedCount = useMemo(
18
+ () => (count === null ? "Not loaded" : new Intl.NumberFormat().format(count)),
19
+ [count]
20
+ );
21
+
22
+ const refresh = useCallback(async () => {
23
+ setLoading(true);
24
+ setError(null);
25
+
26
+ try {
27
+ const nextCount = await caatingaClient.contract("counter").read<number>("get");
28
+ setCount(nextCount);
29
+ } catch (caught) {
30
+ setError(formatCaatingaError(caught));
31
+ } finally {
32
+ setLoading(false);
33
+ }
34
+ }, []);
35
+
36
+ useEffect(() => {
37
+ void refresh();
38
+ }, [refresh]);
39
+
40
+ async function increment() {
41
+ setLoading(true);
42
+ setError(null);
43
+
44
+ try {
45
+ const result = await caatingaClient.contract("counter").invoke<number>("increment");
46
+ if (typeof result.result === "number") {
47
+ setCount(result.result);
48
+ } else {
49
+ await refresh();
50
+ }
51
+ } catch (caught) {
52
+ setError(formatCaatingaError(caught));
53
+ } finally {
54
+ setLoading(false);
55
+ }
56
+ }
57
+
58
+ return (
59
+ <section className="counter-panel" aria-labelledby="counter-title">
60
+ <div className="counter-panel__header">
61
+ <div>
62
+ <p className="eyebrow">Counter Contract</p>
63
+ <h2 id="counter-title">Counter</h2>
64
+ </div>
65
+ <span className="network-pill">testnet</span>
66
+ </div>
67
+
68
+ <div className="counter-value">{formattedCount}</div>
69
+
70
+ <div className="counter-actions">
71
+ <button type="button" onClick={increment} disabled={loading}>
72
+ {loading ? "Incrementing…" : "Increment"}
73
+ </button>
74
+ <button className="secondary-button" type="button" onClick={refresh} disabled={loading}>
75
+ Refresh
76
+ </button>
77
+ </div>
78
+
79
+ {error ? (
80
+ <pre className="counter-error" role="alert">
81
+ {error}
82
+ </pre>
83
+ ) : null}
84
+ </section>
85
+ );
86
+ }