stellar-agent 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 (59) hide show
  1. package/README.md +162 -0
  2. package/package.json +37 -0
  3. package/src/core-skills/module-help.csv +5 -0
  4. package/src/core-skills/module.yaml +33 -0
  5. package/src/core-skills/stellar-brainstorming/SKILL.md +6 -0
  6. package/src/core-skills/stellar-brainstorming/steps/step-01-session-setup.md +67 -0
  7. package/src/core-skills/stellar-brainstorming/steps/step-02a-user-selected.md +20 -0
  8. package/src/core-skills/stellar-brainstorming/steps/step-02b-ai-recommended.md +29 -0
  9. package/src/core-skills/stellar-brainstorming/steps/step-03-technique-execution.md +69 -0
  10. package/src/core-skills/stellar-brainstorming/steps/step-04-idea-organization.md +64 -0
  11. package/src/core-skills/stellar-brainstorming/workflow.md +50 -0
  12. package/src/core-skills/stellar-help/SKILL.md +71 -0
  13. package/src/core-skills/stellar-party-mode/SKILL.md +109 -0
  14. package/src/scripts/resolve_config.py +170 -0
  15. package/src/scripts/resolve_customization.py +209 -0
  16. package/src/stellar-skills/1-analysis/stellar-agent-analyst/SKILL.md +71 -0
  17. package/src/stellar-skills/1-analysis/stellar-agent-analyst/customize.toml +41 -0
  18. package/src/stellar-skills/1-analysis/stellar-analytics/SKILL.md +239 -0
  19. package/src/stellar-skills/1-analysis/stellar-domain-research/SKILL.md +82 -0
  20. package/src/stellar-skills/1-analysis/stellar-market-research/SKILL.md +90 -0
  21. package/src/stellar-skills/2-planning/stellar-agent-pm/SKILL.md +57 -0
  22. package/src/stellar-skills/2-planning/stellar-agent-pm/customize.toml +36 -0
  23. package/src/stellar-skills/2-planning/stellar-epics-stories/SKILL.md +106 -0
  24. package/src/stellar-skills/2-planning/stellar-prd/SKILL.md +115 -0
  25. package/src/stellar-skills/2-planning/stellar-project-brief/SKILL.md +83 -0
  26. package/src/stellar-skills/3-architecture/stellar-agent-architect/SKILL.md +53 -0
  27. package/src/stellar-skills/3-architecture/stellar-agent-architect/customize.toml +31 -0
  28. package/src/stellar-skills/3-architecture/stellar-architecture-doc/SKILL.md +162 -0
  29. package/src/stellar-skills/4-implementation/stellar-agent-developer/SKILL.md +54 -0
  30. package/src/stellar-skills/4-implementation/stellar-agent-developer/customize.toml +56 -0
  31. package/src/stellar-skills/4-implementation/stellar-agent-devops/SKILL.md +54 -0
  32. package/src/stellar-skills/4-implementation/stellar-agent-devops/customize.toml +36 -0
  33. package/src/stellar-skills/4-implementation/stellar-agent-frontend/SKILL.md +54 -0
  34. package/src/stellar-skills/4-implementation/stellar-agent-frontend/customize.toml +52 -0
  35. package/src/stellar-skills/4-implementation/stellar-agent-qa/SKILL.md +54 -0
  36. package/src/stellar-skills/4-implementation/stellar-agent-qa/customize.toml +31 -0
  37. package/src/stellar-skills/4-implementation/stellar-create-asset/SKILL.md +145 -0
  38. package/src/stellar-skills/4-implementation/stellar-create-transaction/SKILL.md +134 -0
  39. package/src/stellar-skills/4-implementation/stellar-deploy-contract/SKILL.md +124 -0
  40. package/src/stellar-skills/4-implementation/stellar-freighter-integration/SKILL.md +193 -0
  41. package/src/stellar-skills/4-implementation/stellar-horizon-integration/SKILL.md +198 -0
  42. package/src/stellar-skills/4-implementation/stellar-init-contract/SKILL.md +102 -0
  43. package/src/stellar-skills/4-implementation/stellar-liquidity-pool/SKILL.md +156 -0
  44. package/src/stellar-skills/4-implementation/stellar-nextjs-setup/SKILL.md +198 -0
  45. package/src/stellar-skills/4-implementation/stellar-nextjs-soroban/SKILL.md +228 -0
  46. package/src/stellar-skills/4-implementation/stellar-nextjs-wallet/SKILL.md +276 -0
  47. package/src/stellar-skills/4-implementation/stellar-sep10-auth/SKILL.md +252 -0
  48. package/src/stellar-skills/4-implementation/stellar-setup-environment/SKILL.md +163 -0
  49. package/src/stellar-skills/4-implementation/stellar-setup-trustline/SKILL.md +107 -0
  50. package/src/stellar-skills/4-implementation/stellar-test-contract/SKILL.md +146 -0
  51. package/src/stellar-skills/4-implementation/stellar-write-contract/SKILL.md +140 -0
  52. package/src/stellar-skills/module-help.csv +24 -0
  53. package/src/stellar-skills/module.yaml +103 -0
  54. package/tools/installer/cli-utils.js +39 -0
  55. package/tools/installer/commands/init.js +335 -0
  56. package/tools/installer/fs-native.js +116 -0
  57. package/tools/installer/prompts.js +852 -0
  58. package/tools/installer/stellar-cli.js +80 -0
  59. package/tools/installer/yaml-format.js +245 -0
@@ -0,0 +1,198 @@
1
+ ---
2
+ name: stellar-nextjs-setup
3
+ description: 'Scaffold a Next.js App Router project with Stellar SDK, environment configuration, and network setup. Use when the user wants to build a Stellar dApp frontend with Next.js.'
4
+ ---
5
+
6
+ # Next.js + Stellar dApp Setup
7
+
8
+ ## Purpose
9
+
10
+ Bootstrap a production-ready Next.js App Router project wired up to the Stellar network.
11
+
12
+ ## On Activation
13
+
14
+ Load `{network_preference}` and `{project_name}` from `{project-root}/_stellar/stellar/config.yaml`.
15
+
16
+ ## Workflow
17
+
18
+ ### Step 1: Create Next.js Project
19
+
20
+ ```bash
21
+ npx create-next-app@latest {project_name} \
22
+ --typescript \
23
+ --tailwind \
24
+ --eslint \
25
+ --app \
26
+ --src-dir \
27
+ --import-alias "@/*"
28
+ cd {project_name}
29
+ ```
30
+
31
+ ### Step 2: Install Stellar Dependencies
32
+
33
+ ```bash
34
+ npm install stellar-sdk @stellar/freighter-api bignumber.js
35
+ ```
36
+
37
+ For the TypeScript Wallet SDK (higher-level wallet operations):
38
+ ```bash
39
+ npm install @stellar/typescript-wallet-sdk
40
+ ```
41
+
42
+ ### Step 3: Configure Environment Variables
43
+
44
+ Create `.env.local`:
45
+
46
+ ```env
47
+ # Network
48
+ NEXT_PUBLIC_STELLAR_NETWORK=testnet
49
+ NEXT_PUBLIC_HORIZON_URL=https://horizon-testnet.stellar.org
50
+ NEXT_PUBLIC_STELLAR_RPC_URL=https://soroban-testnet.stellar.org
51
+ NEXT_PUBLIC_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
52
+
53
+ # Contract IDs (fill in after deployment)
54
+ NEXT_PUBLIC_CONTRACT_ID=
55
+
56
+ # For mainnet, override:
57
+ # NEXT_PUBLIC_STELLAR_NETWORK=mainnet
58
+ # NEXT_PUBLIC_HORIZON_URL=https://horizon.stellar.org
59
+ # NEXT_PUBLIC_STELLAR_RPC_URL=https://soroban-mainnet.stellar.org
60
+ # NEXT_PUBLIC_NETWORK_PASSPHRASE="Public Global Stellar Network ; September 2015"
61
+ ```
62
+
63
+ Create `.env.local.example` (commit this, not `.env.local`):
64
+ ```env
65
+ NEXT_PUBLIC_STELLAR_NETWORK=testnet
66
+ NEXT_PUBLIC_HORIZON_URL=https://horizon-testnet.stellar.org
67
+ NEXT_PUBLIC_STELLAR_RPC_URL=https://soroban-testnet.stellar.org
68
+ NEXT_PUBLIC_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
69
+ NEXT_PUBLIC_CONTRACT_ID=
70
+ ```
71
+
72
+ Add to `.gitignore`:
73
+ ```
74
+ .env.local
75
+ .env.*.local
76
+ ```
77
+
78
+ ### Step 4: Create Network Config Module
79
+
80
+ `src/lib/stellar/config.ts`:
81
+
82
+ ```typescript
83
+ export const NETWORK = process.env.NEXT_PUBLIC_STELLAR_NETWORK || 'testnet';
84
+ export const HORIZON_URL = process.env.NEXT_PUBLIC_HORIZON_URL!;
85
+ export const STELLAR_RPC_URL = process.env.NEXT_PUBLIC_STELLAR_RPC_URL!;
86
+ export const NETWORK_PASSPHRASE = process.env.NEXT_PUBLIC_NETWORK_PASSPHRASE!;
87
+ export const CONTRACT_ID = process.env.NEXT_PUBLIC_CONTRACT_ID!;
88
+
89
+ export function isMainnet() {
90
+ return NETWORK === 'mainnet';
91
+ }
92
+ ```
93
+
94
+ ### Step 5: Create Horizon Server Instance
95
+
96
+ `src/lib/stellar/horizon.ts`:
97
+
98
+ ```typescript
99
+ import { Horizon } from 'stellar-sdk';
100
+ import { HORIZON_URL } from './config';
101
+
102
+ export const horizonServer = new Horizon.Server(HORIZON_URL, {
103
+ allowHttp: HORIZON_URL.startsWith('http://'), // allow for local dev
104
+ });
105
+ ```
106
+
107
+ ### Step 6: Create Soroban RPC Client
108
+
109
+ `src/lib/stellar/soroban.ts`:
110
+
111
+ ```typescript
112
+ import { SorobanRpc } from 'stellar-sdk';
113
+ import { STELLAR_RPC_URL } from './config';
114
+
115
+ export const rpcServer = new SorobanRpc.Server(STELLAR_RPC_URL, {
116
+ allowHttp: STELLAR_RPC_URL.startsWith('http://'),
117
+ });
118
+ ```
119
+
120
+ ### Step 7: Project Structure
121
+
122
+ Recommended folder layout:
123
+
124
+ ```
125
+ src/
126
+ app/
127
+ page.tsx # home page
128
+ layout.tsx # root layout with providers
129
+ globals.css
130
+ components/
131
+ stellar/
132
+ WalletButton.tsx # connect/disconnect Freighter
133
+ AccountBalance.tsx # display XLM and token balances
134
+ TransactionStatus.tsx
135
+ lib/
136
+ stellar/
137
+ config.ts # network config
138
+ horizon.ts # Horizon server
139
+ soroban.ts # Soroban RPC client
140
+ utils.ts # shared helpers (stroops ↔ XLM, etc.)
141
+ hooks/
142
+ useFreighter.ts # wallet state hook
143
+ useAccount.ts # account data from Horizon
144
+ types/
145
+ stellar.ts # shared TypeScript types
146
+ ```
147
+
148
+ ### Step 8: Utility Helpers
149
+
150
+ `src/lib/stellar/utils.ts`:
151
+
152
+ ```typescript
153
+ import BigNumber from 'bignumber.js';
154
+
155
+ // Convert stroops (integer) to XLM (decimal string)
156
+ export function stroopsToXlm(stroops: string | number): string {
157
+ return new BigNumber(stroops).dividedBy(1e7).toFixed(7);
158
+ }
159
+
160
+ // Convert XLM to stroops
161
+ export function xlmToStroops(xlm: string | number): string {
162
+ return new BigNumber(xlm).multipliedBy(1e7).integerValue().toFixed();
163
+ }
164
+
165
+ // Shorten a Stellar public key for display
166
+ export function shortenKey(key: string, chars = 4): string {
167
+ return `${key.slice(0, chars + 1)}...${key.slice(-chars)}`;
168
+ }
169
+ ```
170
+
171
+ ### Step 9: TypeScript Types
172
+
173
+ `src/types/stellar.ts`:
174
+
175
+ ```typescript
176
+ export type StellarNetwork = 'testnet' | 'mainnet' | 'local';
177
+
178
+ export interface AccountInfo {
179
+ publicKey: string;
180
+ balances: {
181
+ asset: string;
182
+ balance: string;
183
+ }[];
184
+ }
185
+
186
+ export interface ContractInvokeResult {
187
+ success: boolean;
188
+ value?: unknown;
189
+ error?: string;
190
+ txHash?: string;
191
+ }
192
+ ```
193
+
194
+ ### Next Steps
195
+
196
+ - `stellar-nextjs-wallet` — add Freighter wallet connect/disconnect
197
+ - `stellar-nextjs-soroban` — call Soroban contracts from Next.js
198
+ - `stellar-nextjs-horizon` via `stellar-horizon-integration` — fetch account and transaction data
@@ -0,0 +1,228 @@
1
+ ---
2
+ name: stellar-nextjs-soroban
3
+ description: 'Call Soroban smart contracts from a Next.js App Router project using Server Actions, API routes, and client-side invocation with Freighter signing. Use when the user wants to invoke Soroban contracts from their Next.js Stellar dApp.'
4
+ ---
5
+
6
+ # Next.js + Soroban Contract Integration
7
+
8
+ ## Purpose
9
+
10
+ Connect a Next.js App Router frontend to deployed Soroban contracts — covering read-only queries (Server Components / API routes) and signed mutations (client-side with Freighter).
11
+
12
+ ## On Activation
13
+
14
+ Load `{network_preference}` and `{primary_language}` from `{project-root}/_stellar/stellar/config.yaml`. Confirm `stellar-nextjs-setup` and `stellar-nextjs-wallet` are complete.
15
+
16
+ ## Key Distinction
17
+
18
+ | Operation | Where to run | Why |
19
+ |---|---|---|
20
+ | Read-only query (no auth) | Server Component or API route | No wallet needed; can cache |
21
+ | Write / mutation | Client Component (needs Freighter) | Must sign with user's key |
22
+
23
+ ## Workflow
24
+
25
+ ### Step 1: Generate TypeScript Bindings (Recommended)
26
+
27
+ If the contract has an interface, generate typed bindings:
28
+
29
+ ```bash
30
+ stellar contract bindings typescript \
31
+ --network {network_preference} \
32
+ --id {contract_id} \
33
+ --output-dir src/lib/stellar/contracts/{contract_name}
34
+ ```
35
+
36
+ This generates a fully-typed client — use it instead of raw `SorobanRpc` calls.
37
+
38
+ ### Step 2: Read-Only Query in a Server Component
39
+
40
+ ```tsx
41
+ // src/app/dashboard/page.tsx (Server Component — no "use client")
42
+ import { SorobanRpc, Contract, scValToNative, xdr } from 'stellar-sdk';
43
+ import { rpcServer } from '@/lib/stellar/soroban';
44
+ import { CONTRACT_ID, NETWORK_PASSPHRASE } from '@/lib/stellar/config';
45
+
46
+ async function getContractState(): Promise<number> {
47
+ const contract = new Contract(CONTRACT_ID);
48
+ const tx = contract.call('get_count'); // no-auth view function
49
+
50
+ const simResult = await rpcServer.simulateTransaction(tx as any);
51
+ if (SorobanRpc.Api.isSimulationError(simResult)) {
52
+ throw new Error(simResult.error);
53
+ }
54
+
55
+ const returnVal = (simResult as SorobanRpc.Api.SimulateTransactionSuccessResponse)
56
+ .result?.retval;
57
+ return returnVal ? (scValToNative(returnVal) as number) : 0;
58
+ }
59
+
60
+ export default async function DashboardPage() {
61
+ const count = await getContractState();
62
+ return <div>Count: {count}</div>;
63
+ }
64
+ ```
65
+
66
+ ### Step 3: Read-Only Query via API Route (for client-side use)
67
+
68
+ `src/app/api/contract/count/route.ts`:
69
+
70
+ ```typescript
71
+ import { NextResponse } from 'next/server';
72
+ import { SorobanRpc, Contract, scValToNative } from 'stellar-sdk';
73
+ import { rpcServer } from '@/lib/stellar/soroban';
74
+ import { CONTRACT_ID } from '@/lib/stellar/config';
75
+
76
+ export async function GET() {
77
+ try {
78
+ const contract = new Contract(CONTRACT_ID);
79
+ const tx = contract.call('get_count');
80
+ const simResult = await rpcServer.simulateTransaction(tx as any);
81
+
82
+ if (SorobanRpc.Api.isSimulationError(simResult)) {
83
+ return NextResponse.json({ error: simResult.error }, { status: 500 });
84
+ }
85
+
86
+ const retval = (simResult as SorobanRpc.Api.SimulateTransactionSuccessResponse)
87
+ .result?.retval;
88
+ const count = retval ? scValToNative(retval) : 0;
89
+ return NextResponse.json({ count });
90
+ } catch (err) {
91
+ return NextResponse.json({ error: String(err) }, { status: 500 });
92
+ }
93
+ }
94
+ ```
95
+
96
+ ### Step 4: Signed Contract Call (Client Component)
97
+
98
+ For functions that require auth (user must sign with Freighter):
99
+
100
+ `src/components/stellar/IncrementButton.tsx`:
101
+
102
+ ```tsx
103
+ "use client";
104
+
105
+ import { useState } from 'react';
106
+ import {
107
+ SorobanRpc,
108
+ TransactionBuilder,
109
+ Contract,
110
+ Networks,
111
+ nativeToScVal,
112
+ } from 'stellar-sdk';
113
+ import { rpcServer } from '@/lib/stellar/soroban';
114
+ import { horizonServer } from '@/lib/stellar/horizon';
115
+ import { useWallet } from '@/contexts/WalletContext';
116
+ import { CONTRACT_ID, NETWORK_PASSPHRASE } from '@/lib/stellar/config';
117
+
118
+ export function IncrementButton() {
119
+ const { publicKey, sign, connected } = useWallet();
120
+ const [status, setStatus] = useState<string>('');
121
+
122
+ async function handleIncrement() {
123
+ if (!publicKey) return;
124
+ setStatus('Building transaction...');
125
+
126
+ try {
127
+ // 1. Load account from Horizon for sequence number
128
+ const account = await horizonServer.loadAccount(publicKey);
129
+
130
+ // 2. Build the contract call transaction
131
+ const contract = new Contract(CONTRACT_ID);
132
+ const tx = new TransactionBuilder(account, {
133
+ fee: '100',
134
+ networkPassphrase: NETWORK_PASSPHRASE,
135
+ })
136
+ .addOperation(contract.call('increment'))
137
+ .setTimeout(30)
138
+ .build();
139
+
140
+ // 3. Simulate to get auth + resource estimates
141
+ setStatus('Simulating...');
142
+ const simResult = await rpcServer.simulateTransaction(tx);
143
+ if (SorobanRpc.Api.isSimulationError(simResult)) {
144
+ throw new Error(simResult.error);
145
+ }
146
+
147
+ // 4. Assemble (inject auth + footprint from simulation)
148
+ const preparedTx = SorobanRpc.assembleTransaction(tx, simResult).build();
149
+
150
+ // 5. Sign with Freighter
151
+ setStatus('Waiting for signature...');
152
+ const signedXdr = await sign(preparedTx.toXDR());
153
+ const signedTx = TransactionBuilder.fromXDR(signedXdr, NETWORK_PASSPHRASE);
154
+
155
+ // 6. Submit
156
+ setStatus('Submitting...');
157
+ const sendResult = await rpcServer.sendTransaction(signedTx);
158
+ if (sendResult.status === 'ERROR') {
159
+ throw new Error(sendResult.errorResult?.toString());
160
+ }
161
+
162
+ // 7. Poll for confirmation
163
+ let getResult = await rpcServer.getTransaction(sendResult.hash);
164
+ while (getResult.status === SorobanRpc.Api.GetTransactionStatus.NOT_FOUND) {
165
+ await new Promise(r => setTimeout(r, 1000));
166
+ getResult = await rpcServer.getTransaction(sendResult.hash);
167
+ }
168
+
169
+ if (getResult.status === SorobanRpc.Api.GetTransactionStatus.SUCCESS) {
170
+ setStatus(`Done! Tx: ${sendResult.hash.slice(0, 8)}...`);
171
+ } else {
172
+ throw new Error('Transaction failed');
173
+ }
174
+ } catch (err) {
175
+ setStatus(`Error: ${err instanceof Error ? err.message : String(err)}`);
176
+ }
177
+ }
178
+
179
+ if (!connected) return <p>Connect wallet to interact with contract</p>;
180
+
181
+ return (
182
+ <div>
183
+ <button onClick={handleIncrement} className="btn btn-primary">
184
+ Increment
185
+ </button>
186
+ {status && <p className="text-sm mt-2">{status}</p>}
187
+ </div>
188
+ );
189
+ }
190
+ ```
191
+
192
+ ### Step 5: Working with ScVal Types
193
+
194
+ Common conversions between JavaScript values and Soroban's `xdr.ScVal`:
195
+
196
+ ```typescript
197
+ import { nativeToScVal, scValToNative, xdr } from 'stellar-sdk';
198
+
199
+ // JS → ScVal
200
+ nativeToScVal(42, { type: 'u32' }) // u32
201
+ nativeToScVal('hello') // string/symbol
202
+ nativeToScVal(publicKey, { type: 'address' }) // Address
203
+ nativeToScVal(true) // bool
204
+
205
+ // ScVal → JS
206
+ const value = scValToNative(scVal); // auto-detects type
207
+ ```
208
+
209
+ ### Step 6: Fee Strategy
210
+
211
+ ```typescript
212
+ const feeStats = await horizonServer.feeStats();
213
+ const fee = feeStats.fee_charged.p75; // p75 for reliable inclusion
214
+ ```
215
+
216
+ ### Step 7: Error Handling
217
+
218
+ | Scenario | Check |
219
+ |---|---|
220
+ | Simulation failed | `SorobanRpc.Api.isSimulationError(result)` |
221
+ | Contract panic / `Error::` | `simResult.error` string contains contract error name |
222
+ | Transaction not found after submit | Poll `getTransaction` with timeout |
223
+ | Auth required but not provided | Contract returns auth error — check contract ABI |
224
+
225
+ ### Next Steps
226
+
227
+ - `stellar-test-contract` — write Rust tests before wiring up the frontend
228
+ - `stellar-sep10-auth` — add JWT auth for API routes that need user identity
@@ -0,0 +1,276 @@
1
+ ---
2
+ name: stellar-nextjs-wallet
3
+ description: 'Integrate Freighter wallet into a Next.js App Router project with connect/disconnect, signing, and account state management. Use when the user wants wallet functionality in their Next.js Stellar dApp.'
4
+ ---
5
+
6
+ # Next.js Wallet Integration (Freighter)
7
+
8
+ ## Purpose
9
+
10
+ Add Freighter wallet connect/disconnect, account state, and transaction signing to a Next.js App Router project.
11
+
12
+ ## On Activation
13
+
14
+ Load `{network_preference}` from `{project-root}/_stellar/stellar/config.yaml`. Verify `@stellar/freighter-api` is installed.
15
+
16
+ ## Core Concept
17
+
18
+ Freighter is a browser extension. All Freighter API calls must run **client-side only** (inside `"use client"` components or hooks). Never call Freighter APIs in Server Components or server-side code.
19
+
20
+ ## Workflow
21
+
22
+ ### Step 1: Install Freighter API
23
+
24
+ ```bash
25
+ npm install @stellar/freighter-api
26
+ ```
27
+
28
+ ### Step 2: Wallet State Hook
29
+
30
+ `src/hooks/useFreighter.ts`:
31
+
32
+ ```typescript
33
+ "use client";
34
+
35
+ import { useState, useEffect, useCallback } from 'react';
36
+ import {
37
+ isConnected,
38
+ getPublicKey,
39
+ signTransaction,
40
+ setAllowed,
41
+ isAllowed,
42
+ getNetworkDetails,
43
+ } from '@stellar/freighter-api';
44
+ import { NETWORK_PASSPHRASE, NETWORK } from '@/lib/stellar/config';
45
+
46
+ interface FreighterState {
47
+ connected: boolean;
48
+ publicKey: string | null;
49
+ loading: boolean;
50
+ error: string | null;
51
+ }
52
+
53
+ export function useFreighter() {
54
+ const [state, setState] = useState<FreighterState>({
55
+ connected: false,
56
+ publicKey: null,
57
+ loading: true,
58
+ error: null,
59
+ });
60
+
61
+ // Check connection on mount
62
+ useEffect(() => {
63
+ async function checkConnection() {
64
+ try {
65
+ const connected = await isConnected();
66
+ if (connected) {
67
+ const allowed = await isAllowed();
68
+ if (allowed) {
69
+ const publicKey = await getPublicKey();
70
+ setState({ connected: true, publicKey, loading: false, error: null });
71
+ return;
72
+ }
73
+ }
74
+ setState(s => ({ ...s, connected: false, publicKey: null, loading: false }));
75
+ } catch {
76
+ setState(s => ({ ...s, loading: false, error: 'Freighter not installed' }));
77
+ }
78
+ }
79
+ checkConnection();
80
+ }, []);
81
+
82
+ const connect = useCallback(async () => {
83
+ setState(s => ({ ...s, loading: true, error: null }));
84
+ try {
85
+ await setAllowed();
86
+ const publicKey = await getPublicKey();
87
+
88
+ // Verify network matches
89
+ const networkDetails = await getNetworkDetails();
90
+ if (networkDetails.networkPassphrase !== NETWORK_PASSPHRASE) {
91
+ throw new Error(
92
+ `Switch Freighter to ${NETWORK}. Current: ${networkDetails.networkName}`
93
+ );
94
+ }
95
+
96
+ setState({ connected: true, publicKey, loading: false, error: null });
97
+ } catch (err) {
98
+ setState(s => ({
99
+ ...s,
100
+ loading: false,
101
+ error: err instanceof Error ? err.message : 'Connection failed',
102
+ }));
103
+ }
104
+ }, []);
105
+
106
+ const disconnect = useCallback(() => {
107
+ setState({ connected: false, publicKey: null, loading: false, error: null });
108
+ }, []);
109
+
110
+ const sign = useCallback(
111
+ async (xdr: string): Promise<string> => {
112
+ if (!state.publicKey) throw new Error('Wallet not connected');
113
+ const result = await signTransaction(xdr, {
114
+ accountToSign: state.publicKey,
115
+ networkPassphrase: NETWORK_PASSPHRASE,
116
+ });
117
+ return result;
118
+ },
119
+ [state.publicKey]
120
+ );
121
+
122
+ return { ...state, connect, disconnect, sign };
123
+ }
124
+ ```
125
+
126
+ ### Step 3: Wallet Button Component
127
+
128
+ `src/components/stellar/WalletButton.tsx`:
129
+
130
+ ```tsx
131
+ "use client";
132
+
133
+ import { useFreighter } from '@/hooks/useFreighter';
134
+ import { shortenKey } from '@/lib/stellar/utils';
135
+
136
+ export function WalletButton() {
137
+ const { connected, publicKey, loading, error, connect, disconnect } = useFreighter();
138
+
139
+ if (loading) {
140
+ return <button disabled className="btn btn-ghost">Loading...</button>;
141
+ }
142
+
143
+ if (error) {
144
+ return (
145
+ <button
146
+ onClick={() => window.open('https://www.freighter.app/', '_blank')}
147
+ className="btn btn-error btn-sm"
148
+ >
149
+ Install Freighter
150
+ </button>
151
+ );
152
+ }
153
+
154
+ if (connected && publicKey) {
155
+ return (
156
+ <div className="flex items-center gap-2">
157
+ <span className="font-mono text-sm">{shortenKey(publicKey)}</span>
158
+ <button onClick={disconnect} className="btn btn-outline btn-sm">
159
+ Disconnect
160
+ </button>
161
+ </div>
162
+ );
163
+ }
164
+
165
+ return (
166
+ <button onClick={connect} className="btn btn-primary">
167
+ Connect Wallet
168
+ </button>
169
+ );
170
+ }
171
+ ```
172
+
173
+ ### Step 4: Root Layout with Wallet Context (optional)
174
+
175
+ For apps that need wallet state across many components, add a React Context:
176
+
177
+ `src/contexts/WalletContext.tsx`:
178
+
179
+ ```tsx
180
+ "use client";
181
+
182
+ import { createContext, useContext, ReactNode } from 'react';
183
+ import { useFreighter } from '@/hooks/useFreighter';
184
+
185
+ type WalletContextType = ReturnType<typeof useFreighter>;
186
+ const WalletContext = createContext<WalletContextType | null>(null);
187
+
188
+ export function WalletProvider({ children }: { children: ReactNode }) {
189
+ const wallet = useFreighter();
190
+ return (
191
+ <WalletContext.Provider value={wallet}>{children}</WalletContext.Provider>
192
+ );
193
+ }
194
+
195
+ export function useWallet() {
196
+ const ctx = useContext(WalletContext);
197
+ if (!ctx) throw new Error('useWallet must be used inside WalletProvider');
198
+ return ctx;
199
+ }
200
+ ```
201
+
202
+ Wire into `src/app/layout.tsx`:
203
+ ```tsx
204
+ import { WalletProvider } from '@/contexts/WalletContext';
205
+
206
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
207
+ return (
208
+ <html lang="en">
209
+ <body>
210
+ <WalletProvider>
211
+ {children}
212
+ </WalletProvider>
213
+ </body>
214
+ </html>
215
+ );
216
+ }
217
+ ```
218
+
219
+ ### Step 5: Signing and Submitting a Transaction
220
+
221
+ Pattern for client components that need to send a transaction:
222
+
223
+ ```tsx
224
+ "use client";
225
+
226
+ import { TransactionBuilder, Networks, Operation, Asset } from 'stellar-sdk';
227
+ import { horizonServer } from '@/lib/stellar/horizon';
228
+ import { useWallet } from '@/contexts/WalletContext';
229
+ import { NETWORK_PASSPHRASE } from '@/lib/stellar/config';
230
+
231
+ export function SendPaymentForm() {
232
+ const { publicKey, sign, connected } = useWallet();
233
+
234
+ async function handleSend(destination: string, amount: string) {
235
+ if (!publicKey) return;
236
+
237
+ const account = await horizonServer.loadAccount(publicKey);
238
+ const tx = new TransactionBuilder(account, {
239
+ fee: '100',
240
+ networkPassphrase: NETWORK_PASSPHRASE,
241
+ })
242
+ .addOperation(
243
+ Operation.payment({
244
+ destination,
245
+ asset: Asset.native(),
246
+ amount,
247
+ })
248
+ )
249
+ .setTimeout(30)
250
+ .build();
251
+
252
+ const signedXdr = await sign(tx.toXDR());
253
+ const signedTx = TransactionBuilder.fromXDR(signedXdr, NETWORK_PASSPHRASE);
254
+ const result = await horizonServer.submitTransaction(signedTx);
255
+ console.log('Submitted:', result.hash);
256
+ }
257
+
258
+ if (!connected) return <p>Connect wallet first</p>;
259
+ // ... render form
260
+ }
261
+ ```
262
+
263
+ ### Step 6: Edge Cases to Handle
264
+
265
+ | Case | Handling |
266
+ |---|---|
267
+ | Freighter not installed | Show "Install Freighter" link to `freighter.app` |
268
+ | Wrong network selected | Check `networkPassphrase` on connect; show error |
269
+ | User rejects transaction | Catch error from `signTransaction`, show toast |
270
+ | Account not funded | Catch `400` from Horizon submit, check `result_codes` |
271
+ | Freighter locked | `getPublicKey()` throws — prompt user to unlock |
272
+
273
+ ### Next Steps
274
+
275
+ - `stellar-nextjs-soroban` — call Soroban contracts using the connected wallet
276
+ - `stellar-sep10-auth` — add SEP-10 JWT auth for backend calls