@trustless-work/blocks 0.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 (74) hide show
  1. package/README.md +96 -0
  2. package/bin/index.js +1123 -0
  3. package/package.json +44 -0
  4. package/templates/deps.json +29 -0
  5. package/templates/escrows/details/Actions.tsx +149 -0
  6. package/templates/escrows/details/Entities.tsx +48 -0
  7. package/templates/escrows/details/EntityCard.tsx +98 -0
  8. package/templates/escrows/details/EscrowDetailDialog.tsx +154 -0
  9. package/templates/escrows/details/GeneralInformation.tsx +329 -0
  10. package/templates/escrows/details/MilestoneCard.tsx +254 -0
  11. package/templates/escrows/details/MilestoneDetailDialog.tsx +276 -0
  12. package/templates/escrows/details/Milestones.tsx +87 -0
  13. package/templates/escrows/details/ProgressEscrow.tsx +191 -0
  14. package/templates/escrows/details/StatisticsCard.tsx +79 -0
  15. package/templates/escrows/details/SuccessReleaseDialog.tsx +101 -0
  16. package/templates/escrows/details/useDetailsEscrow.ts +126 -0
  17. package/templates/escrows/escrow-context/EscrowAmountProvider.tsx +86 -0
  18. package/templates/escrows/escrow-context/EscrowDialogsProvider.tsx +108 -0
  19. package/templates/escrows/escrow-context/EscrowProvider.tsx +124 -0
  20. package/templates/escrows/escrows-by-role/cards/EscrowsCards.tsx +503 -0
  21. package/templates/escrows/escrows-by-role/cards/Filters.tsx +421 -0
  22. package/templates/escrows/escrows-by-role/table/EscrowsTable.tsx +427 -0
  23. package/templates/escrows/escrows-by-role/table/Filters.tsx +421 -0
  24. package/templates/escrows/escrows-by-role/useEscrowsByRole.shared.ts +336 -0
  25. package/templates/escrows/escrows-by-signer/cards/EscrowsCards.tsx +502 -0
  26. package/templates/escrows/escrows-by-signer/cards/Filters.tsx +389 -0
  27. package/templates/escrows/escrows-by-signer/table/EscrowsTable.tsx +422 -0
  28. package/templates/escrows/escrows-by-signer/table/Filters.tsx +389 -0
  29. package/templates/escrows/escrows-by-signer/useEscrowsBySigner.shared.ts +320 -0
  30. package/templates/escrows/single-release/approve-milestone/button/ApproveMilestone.tsx +78 -0
  31. package/templates/escrows/single-release/approve-milestone/dialog/ApproveMilestone.tsx +102 -0
  32. package/templates/escrows/single-release/approve-milestone/form/ApproveMilestone.tsx +80 -0
  33. package/templates/escrows/single-release/approve-milestone/shared/schema.ts +9 -0
  34. package/templates/escrows/single-release/approve-milestone/shared/useApproveMilestone.ts +67 -0
  35. package/templates/escrows/single-release/change-milestone-status/button/ChangeMilestoneStatus.tsx +78 -0
  36. package/templates/escrows/single-release/change-milestone-status/dialog/ChangeMilestoneStatus.tsx +167 -0
  37. package/templates/escrows/single-release/change-milestone-status/form/ChangeMilestoneStatus.tsx +114 -0
  38. package/templates/escrows/single-release/change-milestone-status/shared/schema.ts +15 -0
  39. package/templates/escrows/single-release/change-milestone-status/shared/useChangeMilestoneStatus.ts +77 -0
  40. package/templates/escrows/single-release/dispute-escrow/button/DisputeEscrow.tsx +68 -0
  41. package/templates/escrows/single-release/fund-escrow/button/FundEscrow.tsx +84 -0
  42. package/templates/escrows/single-release/fund-escrow/dialog/FundEscrow.tsx +77 -0
  43. package/templates/escrows/single-release/fund-escrow/form/FundEscrow.tsx +54 -0
  44. package/templates/escrows/single-release/fund-escrow/shared/schema.ts +10 -0
  45. package/templates/escrows/single-release/fund-escrow/shared/useFundEscrow.ts +66 -0
  46. package/templates/escrows/single-release/initialize-escrow/dialog/InitializeEscrow.tsx +526 -0
  47. package/templates/escrows/single-release/initialize-escrow/form/InitializeEscrow.tsx +504 -0
  48. package/templates/escrows/single-release/initialize-escrow/shared/schema.ts +232 -0
  49. package/templates/escrows/single-release/initialize-escrow/shared/useInitializeEscrow.ts +115 -0
  50. package/templates/escrows/single-release/release-escrow/button/ReleaseEscrow.tsx +80 -0
  51. package/templates/escrows/single-release/resolve-dispute/button/ResolveDispute.tsx +94 -0
  52. package/templates/escrows/single-release/resolve-dispute/dialog/ResolveDispute.tsx +123 -0
  53. package/templates/escrows/single-release/resolve-dispute/form/ResolveDispute.tsx +82 -0
  54. package/templates/escrows/single-release/resolve-dispute/shared/schema.ts +82 -0
  55. package/templates/escrows/single-release/resolve-dispute/shared/useResolveDispute.ts +58 -0
  56. package/templates/escrows/single-release/update-escrow/dialog/UpdateEscrow.tsx +485 -0
  57. package/templates/escrows/single-release/update-escrow/form/UpdateEscrow.tsx +463 -0
  58. package/templates/escrows/single-release/update-escrow/shared/schema.ts +139 -0
  59. package/templates/escrows/single-release/update-escrow/shared/useUpdateEscrow.ts +211 -0
  60. package/templates/handle-errors/errors.enum.ts +6 -0
  61. package/templates/handle-errors/handle.ts +47 -0
  62. package/templates/helpers/format.helper.ts +27 -0
  63. package/templates/helpers/useCopy.ts +13 -0
  64. package/templates/providers/ReactQueryClientProvider.tsx +28 -0
  65. package/templates/providers/TrustlessWork.tsx +30 -0
  66. package/templates/tanstak/useEscrowsByRoleQuery.ts +87 -0
  67. package/templates/tanstak/useEscrowsBySignerQuery.ts +78 -0
  68. package/templates/tanstak/useEscrowsMutations.ts +411 -0
  69. package/templates/wallet-kit/WalletButtons.tsx +116 -0
  70. package/templates/wallet-kit/WalletProvider.tsx +94 -0
  71. package/templates/wallet-kit/trustlines.ts +40 -0
  72. package/templates/wallet-kit/useWallet.ts +77 -0
  73. package/templates/wallet-kit/validators.ts +12 -0
  74. package/templates/wallet-kit/wallet-kit.ts +30 -0
@@ -0,0 +1,411 @@
1
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
2
+ import {
3
+ EscrowType,
4
+ FundEscrowPayload,
5
+ InitializeMultiReleaseEscrowPayload,
6
+ InitializeSingleReleaseEscrowPayload,
7
+ UpdateMultiReleaseEscrowPayload,
8
+ UpdateSingleReleaseEscrowPayload,
9
+ useFundEscrow,
10
+ useInitializeEscrow,
11
+ useUpdateEscrow,
12
+ ChangeMilestoneStatusPayload,
13
+ useChangeMilestoneStatus,
14
+ ApproveMilestonePayload,
15
+ useApproveMilestone,
16
+ useSendTransaction,
17
+ useStartDispute,
18
+ useReleaseFunds,
19
+ useResolveDispute,
20
+ MultiReleaseStartDisputePayload,
21
+ SingleReleaseStartDisputePayload,
22
+ MultiReleaseReleaseFundsPayload,
23
+ SingleReleaseReleaseFundsPayload,
24
+ MultiReleaseResolveDisputePayload,
25
+ SingleReleaseResolveDisputePayload,
26
+ } from "@trustless-work/escrow";
27
+ import { signTransaction } from "../wallet-kit/wallet-kit";
28
+
29
+ export const useEscrowsMutations = () => {
30
+ const queryClient = useQueryClient();
31
+ const { deployEscrow } = useInitializeEscrow();
32
+ const { updateEscrow } = useUpdateEscrow();
33
+ const { fundEscrow } = useFundEscrow();
34
+ const { changeMilestoneStatus } = useChangeMilestoneStatus();
35
+ const { approveMilestone } = useApproveMilestone();
36
+ const { sendTransaction } = useSendTransaction();
37
+ const { startDispute } = useStartDispute();
38
+ const { releaseFunds } = useReleaseFunds();
39
+ const { resolveDispute } = useResolveDispute();
40
+
41
+ const deployEscrowMutation = useMutation({
42
+ mutationFn: async ({
43
+ payload,
44
+ type,
45
+ address,
46
+ }: {
47
+ payload:
48
+ | InitializeSingleReleaseEscrowPayload
49
+ | InitializeMultiReleaseEscrowPayload;
50
+ type: EscrowType;
51
+ address: string;
52
+ }) => {
53
+ const { unsignedTransaction } = await deployEscrow(payload, type);
54
+
55
+ if (!unsignedTransaction) {
56
+ throw new Error(
57
+ "Unsigned transaction is missing from deployEscrow response."
58
+ );
59
+ }
60
+
61
+ const signedTxXdr = await signTransaction({
62
+ unsignedTransaction,
63
+ address,
64
+ });
65
+
66
+ if (!signedTxXdr) {
67
+ throw new Error("Signed transaction is missing.");
68
+ }
69
+
70
+ const response = await sendTransaction(signedTxXdr);
71
+
72
+ if (response.status !== "SUCCESS") {
73
+ throw new Error("Transaction failed to send");
74
+ }
75
+
76
+ return response;
77
+ },
78
+ onSuccess: () => {
79
+ queryClient.invalidateQueries({ queryKey: ["escrows"] });
80
+ },
81
+ onError: (error) => {
82
+ console.error(error);
83
+ },
84
+ });
85
+
86
+ const updateEscrowMutation = useMutation({
87
+ mutationFn: async ({
88
+ payload,
89
+ type,
90
+ address,
91
+ }: {
92
+ payload:
93
+ | UpdateSingleReleaseEscrowPayload
94
+ | UpdateMultiReleaseEscrowPayload;
95
+ type: EscrowType;
96
+ address: string;
97
+ }) => {
98
+ const { unsignedTransaction } = await updateEscrow(payload, type);
99
+
100
+ if (!unsignedTransaction) {
101
+ throw new Error(
102
+ "Unsigned transaction is missing from updateEscrow response."
103
+ );
104
+ }
105
+
106
+ const signedTxXdr = await signTransaction({
107
+ unsignedTransaction,
108
+ address,
109
+ });
110
+
111
+ if (!signedTxXdr) {
112
+ throw new Error("Signed transaction is missing.");
113
+ }
114
+
115
+ const response = await sendTransaction(signedTxXdr);
116
+
117
+ if (response.status !== "SUCCESS") {
118
+ throw new Error("Transaction failed to send");
119
+ }
120
+
121
+ return response;
122
+ },
123
+ onSuccess: () => {
124
+ queryClient.invalidateQueries({ queryKey: ["escrows"] });
125
+ },
126
+ onError: (error) => {
127
+ console.error(error);
128
+ },
129
+ });
130
+
131
+ const fundEscrowMutation = useMutation({
132
+ mutationFn: async ({
133
+ payload,
134
+ type,
135
+ address,
136
+ }: {
137
+ payload: FundEscrowPayload;
138
+ type: EscrowType;
139
+ address: string;
140
+ }) => {
141
+ // Step 1: Get unsigned transaction
142
+ const { unsignedTransaction } = await fundEscrow(payload, type);
143
+
144
+ if (!unsignedTransaction) {
145
+ throw new Error(
146
+ "Unsigned transaction is missing from fundEscrow response."
147
+ );
148
+ }
149
+
150
+ // Step 2: Sign transaction
151
+ const signedTxXdr = await signTransaction({
152
+ unsignedTransaction,
153
+ address,
154
+ });
155
+
156
+ if (!signedTxXdr) {
157
+ throw new Error("Signed transaction is missing.");
158
+ }
159
+
160
+ // Step 3: Send transaction
161
+ const response = await sendTransaction(signedTxXdr);
162
+
163
+ if (response.status !== "SUCCESS") {
164
+ throw new Error("Transaction failed to send");
165
+ }
166
+
167
+ return response;
168
+ },
169
+ onSuccess: () => {
170
+ queryClient.invalidateQueries({ queryKey: ["escrows"] });
171
+ },
172
+ onError: (error) => {
173
+ console.error(error);
174
+ },
175
+ });
176
+
177
+ const approveMilestoneMutation = useMutation({
178
+ mutationFn: async ({
179
+ payload,
180
+ type,
181
+ address,
182
+ }: {
183
+ payload: ApproveMilestonePayload;
184
+ type: EscrowType;
185
+ address: string;
186
+ }) => {
187
+ const { unsignedTransaction } = await approveMilestone(payload, type);
188
+
189
+ if (!unsignedTransaction) {
190
+ throw new Error(
191
+ "Unsigned transaction is missing from approveMilestone response."
192
+ );
193
+ }
194
+
195
+ const signedTxXdr = await signTransaction({
196
+ unsignedTransaction,
197
+ address,
198
+ });
199
+
200
+ if (!signedTxXdr) {
201
+ throw new Error("Signed transaction is missing.");
202
+ }
203
+
204
+ const response = await sendTransaction(signedTxXdr);
205
+
206
+ if (response.status !== "SUCCESS") {
207
+ throw new Error("Transaction failed to send");
208
+ }
209
+
210
+ return response;
211
+ },
212
+ onSuccess: () => {
213
+ queryClient.invalidateQueries({ queryKey: ["escrows"] });
214
+ },
215
+ onError: (error) => {
216
+ console.error(error);
217
+ },
218
+ });
219
+
220
+ const changeMilestoneStatusMutation = useMutation({
221
+ mutationFn: async ({
222
+ payload,
223
+ type,
224
+ address,
225
+ }: {
226
+ payload: ChangeMilestoneStatusPayload;
227
+ type: EscrowType;
228
+ address: string;
229
+ }) => {
230
+ const { unsignedTransaction } = await changeMilestoneStatus(
231
+ payload,
232
+ type
233
+ );
234
+
235
+ if (!unsignedTransaction) {
236
+ throw new Error(
237
+ "Unsigned transaction is missing from changeMilestoneStatus response."
238
+ );
239
+ }
240
+
241
+ const signedTxXdr = await signTransaction({
242
+ unsignedTransaction,
243
+ address,
244
+ });
245
+
246
+ if (!signedTxXdr) {
247
+ throw new Error("Signed transaction is missing.");
248
+ }
249
+
250
+ const response = await sendTransaction(signedTxXdr);
251
+
252
+ if (response.status !== "SUCCESS") {
253
+ throw new Error("Transaction failed to send");
254
+ }
255
+
256
+ return response;
257
+ },
258
+ onSuccess: () => {
259
+ queryClient.invalidateQueries({ queryKey: ["escrows"] });
260
+ },
261
+ onError: (error) => {
262
+ console.error(error);
263
+ },
264
+ });
265
+
266
+ const startDisputeMutation = useMutation({
267
+ mutationFn: async ({
268
+ payload,
269
+ type,
270
+ address,
271
+ }: {
272
+ payload:
273
+ | MultiReleaseStartDisputePayload
274
+ | SingleReleaseStartDisputePayload;
275
+ type: EscrowType;
276
+ address: string;
277
+ }) => {
278
+ const { unsignedTransaction } = await startDispute(payload, type);
279
+
280
+ if (!unsignedTransaction) {
281
+ throw new Error(
282
+ "Unsigned transaction is missing from startDispute response."
283
+ );
284
+ }
285
+
286
+ const signedTxXdr = await signTransaction({
287
+ unsignedTransaction,
288
+ address,
289
+ });
290
+
291
+ if (!signedTxXdr) {
292
+ throw new Error("Signed transaction is missing.");
293
+ }
294
+
295
+ const response = await sendTransaction(signedTxXdr);
296
+
297
+ if (response.status !== "SUCCESS") {
298
+ throw new Error("Transaction failed to send");
299
+ }
300
+
301
+ return response;
302
+ },
303
+ onSuccess: () => {
304
+ queryClient.invalidateQueries({ queryKey: ["escrows"] });
305
+ },
306
+ onError: (error) => {
307
+ console.error(error);
308
+ },
309
+ });
310
+
311
+ const releaseFundsMutation = useMutation({
312
+ mutationFn: async ({
313
+ payload,
314
+ type,
315
+ address,
316
+ }: {
317
+ payload:
318
+ | MultiReleaseReleaseFundsPayload
319
+ | SingleReleaseReleaseFundsPayload;
320
+ type: EscrowType;
321
+ address: string;
322
+ }) => {
323
+ const { unsignedTransaction } = await releaseFunds(payload, type);
324
+
325
+ if (!unsignedTransaction) {
326
+ throw new Error(
327
+ "Unsigned transaction is missing from releaseFunds response."
328
+ );
329
+ }
330
+
331
+ const signedTxXdr = await signTransaction({
332
+ unsignedTransaction,
333
+ address,
334
+ });
335
+
336
+ if (!signedTxXdr) {
337
+ throw new Error("Signed transaction is missing.");
338
+ }
339
+
340
+ const response = await sendTransaction(signedTxXdr);
341
+
342
+ if (response.status !== "SUCCESS") {
343
+ throw new Error("Transaction failed to send");
344
+ }
345
+
346
+ return response;
347
+ },
348
+ onSuccess: () => {
349
+ queryClient.invalidateQueries({ queryKey: ["escrows"] });
350
+ },
351
+ onError: (error) => {
352
+ console.error(error);
353
+ },
354
+ });
355
+
356
+ const resolveDisputeMutation = useMutation({
357
+ mutationFn: async ({
358
+ payload,
359
+ type,
360
+ address,
361
+ }: {
362
+ payload:
363
+ | MultiReleaseResolveDisputePayload
364
+ | SingleReleaseResolveDisputePayload;
365
+ type: EscrowType;
366
+ address: string;
367
+ }) => {
368
+ const { unsignedTransaction } = await resolveDispute(payload, type);
369
+
370
+ if (!unsignedTransaction) {
371
+ throw new Error(
372
+ "Unsigned transaction is missing from resolveDispute response."
373
+ );
374
+ }
375
+
376
+ const signedTxXdr = await signTransaction({
377
+ unsignedTransaction,
378
+ address,
379
+ });
380
+
381
+ if (!signedTxXdr) {
382
+ throw new Error("Signed transaction is missing.");
383
+ }
384
+
385
+ const response = await sendTransaction(signedTxXdr);
386
+
387
+ if (response.status !== "SUCCESS") {
388
+ throw new Error("Transaction failed to send");
389
+ }
390
+
391
+ return response;
392
+ },
393
+ onSuccess: () => {
394
+ queryClient.invalidateQueries({ queryKey: ["escrows"] });
395
+ },
396
+ onError: (error) => {
397
+ console.error(error);
398
+ },
399
+ });
400
+
401
+ return {
402
+ deployEscrow: deployEscrowMutation,
403
+ updateEscrow: updateEscrowMutation,
404
+ fundEscrow: fundEscrowMutation,
405
+ changeMilestoneStatus: changeMilestoneStatusMutation,
406
+ approveMilestone: approveMilestoneMutation,
407
+ startDispute: startDisputeMutation,
408
+ releaseFunds: releaseFundsMutation,
409
+ resolveDispute: resolveDisputeMutation,
410
+ };
411
+ };
@@ -0,0 +1,116 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { useWallet } from "./useWallet";
5
+ import { useWalletContext } from "./WalletProvider";
6
+ import { Button } from "__UI_BASE__/button";
7
+ import { Popover, PopoverContent, PopoverTrigger } from "__UI_BASE__/popover";
8
+ import { Check, Copy, LogOut, ChevronDown, Wallet } from "lucide-react";
9
+
10
+ /**
11
+ * Wallet connection/disconnection button component
12
+ * Shows different states based on wallet connection status
13
+ */
14
+ export const WalletButton = () => {
15
+ const { handleConnect, handleDisconnect } = useWallet();
16
+ const { walletAddress, walletName } = useWalletContext();
17
+ const [copied, setCopied] = React.useState(false);
18
+
19
+ const shortAddress = React.useMemo(() => {
20
+ if (!walletAddress) return "";
21
+ if (walletAddress.length <= 10) return walletAddress;
22
+ return `${walletAddress.slice(0, 6)}…${walletAddress.slice(-4)}`;
23
+ }, [walletAddress]);
24
+
25
+ const copyAddress = async () => {
26
+ if (!walletAddress) return;
27
+ try {
28
+ await navigator.clipboard.writeText(walletAddress);
29
+ setCopied(true);
30
+ setTimeout(() => setCopied(false), 1500);
31
+ } catch (_) {
32
+ // noop
33
+ }
34
+ };
35
+
36
+ if (walletAddress) {
37
+ return (
38
+ <Popover>
39
+ <PopoverTrigger asChild>
40
+ <Button
41
+ variant="outline"
42
+ className="h-10 px-4 gap-2 font-medium bg-transparent"
43
+ >
44
+ <Wallet className="h-4 w-4" />
45
+ <span className="hidden sm:inline">{walletName}</span>
46
+ <span className="font-mono text-sm text-muted-foreground">
47
+ {shortAddress}
48
+ </span>
49
+ <ChevronDown className="h-4 w-4 ml-1" />
50
+ </Button>
51
+ </PopoverTrigger>
52
+ <PopoverContent className="w-80 p-0" align="end">
53
+ <div className="p-4 space-y-3">
54
+ <div className="flex items-center justify-between">
55
+ <div className="flex items-center gap-2">
56
+ <Wallet className="h-4 w-4 text-muted-foreground" />
57
+ <span className="font-medium">{walletName}</span>
58
+ </div>
59
+ <span className="text-xs px-2 py-1 rounded-md bg-muted text-muted-foreground">
60
+ Testnet
61
+ </span>
62
+ </div>
63
+
64
+ <div className="p-3 rounded-lg bg-muted/50 border">
65
+ <p className="text-xs text-muted-foreground mb-1">Address</p>
66
+ <p className="font-mono text-sm break-all">{walletAddress}</p>
67
+ </div>
68
+ </div>
69
+
70
+ <div className="border-t p-4">
71
+ <div className="flex gap-2">
72
+ <Button
73
+ onClick={copyAddress}
74
+ variant="ghost"
75
+ size="sm"
76
+ className="flex-1 cursor-pointer"
77
+ disabled={copied}
78
+ >
79
+ {copied ? (
80
+ <>
81
+ <Check className="h-4 w-4 mr-2" />
82
+ Copied
83
+ </>
84
+ ) : (
85
+ <>
86
+ <Copy className="h-4 w-4 mr-2" />
87
+ Copy
88
+ </>
89
+ )}
90
+ </Button>
91
+ <Button
92
+ onClick={handleDisconnect}
93
+ variant="outline"
94
+ size="sm"
95
+ className="flex-1 text-destructive hover:text-destructive bg-transparent cursor-pointer"
96
+ >
97
+ <LogOut className="h-4 w-4 mr-2" />
98
+ Disconnect
99
+ </Button>
100
+ </div>
101
+ </div>
102
+ </PopoverContent>
103
+ </Popover>
104
+ );
105
+ }
106
+
107
+ return (
108
+ <Button
109
+ className="h-10 px-6 gap-2 font-medium cursor-pointer"
110
+ onClick={handleConnect}
111
+ >
112
+ <Wallet className="h-4 w-4" />
113
+ Connect Wallet
114
+ </Button>
115
+ );
116
+ };
@@ -0,0 +1,94 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import {
5
+ createContext,
6
+ useContext,
7
+ useState,
8
+ useEffect,
9
+ ReactNode,
10
+ } from "react";
11
+
12
+ /**
13
+ * Type definition for the wallet context
14
+ * Contains wallet address, name, and functions to manage wallet state
15
+ */
16
+ type WalletContextType = {
17
+ walletAddress: string | null;
18
+ walletName: string | null;
19
+ setWalletInfo: (address: string, name: string) => void;
20
+ clearWalletInfo: () => void;
21
+ };
22
+
23
+ /**
24
+ * Create the React context for wallet state management
25
+ */
26
+ const WalletContext = createContext<WalletContextType | undefined>(undefined);
27
+
28
+ /**
29
+ * Wallet Provider component that wraps the application
30
+ * Manages wallet state and provides wallet information to child components
31
+ * Automatically loads saved wallet information from localStorage on initialization
32
+ */
33
+ export const WalletProvider = ({ children }: { children: ReactNode }) => {
34
+ const [walletAddress, setWalletAddress] = useState<string | null>(null);
35
+ const [walletName, setWalletName] = useState<string | null>(null);
36
+
37
+ /**
38
+ * Load saved wallet information from localStorage when the component mounts
39
+ * This ensures the wallet state persists across browser sessions
40
+ */
41
+ useEffect(() => {
42
+ const storedAddress = localStorage.getItem("walletAddress");
43
+ const storedName = localStorage.getItem("walletName");
44
+
45
+ if (storedAddress) setWalletAddress(storedAddress);
46
+ if (storedName) setWalletName(storedName);
47
+ }, []);
48
+
49
+ /**
50
+ * Set wallet information and save it to localStorage
51
+ * This function is called when a wallet is successfully connected
52
+ *
53
+ * @param address - The wallet's public address
54
+ * @param name - The name/identifier of the wallet (e.g., "Freighter", "Albedo")
55
+ */
56
+ const setWalletInfo = (address: string, name: string) => {
57
+ setWalletAddress(address);
58
+ setWalletName(name);
59
+ localStorage.setItem("walletAddress", address);
60
+ localStorage.setItem("walletName", name);
61
+ };
62
+
63
+ /**
64
+ * Clear wallet information and remove it from localStorage
65
+ * This function is called when disconnecting a wallet
66
+ */
67
+ const clearWalletInfo = () => {
68
+ setWalletAddress(null);
69
+ setWalletName(null);
70
+ localStorage.removeItem("walletAddress");
71
+ localStorage.removeItem("walletName");
72
+ };
73
+
74
+ return (
75
+ <WalletContext.Provider
76
+ value={{ walletAddress, walletName, setWalletInfo, clearWalletInfo }}
77
+ >
78
+ {children}
79
+ </WalletContext.Provider>
80
+ );
81
+ };
82
+
83
+ /**
84
+ * Custom hook to access the wallet context
85
+ * Provides wallet state and functions to components
86
+ * Throws an error if used outside of WalletProvider
87
+ */
88
+ export const useWalletContext = () => {
89
+ const context = useContext(WalletContext);
90
+ if (!context) {
91
+ throw new Error("useWalletContext must be used within WalletProvider");
92
+ }
93
+ return context;
94
+ };
@@ -0,0 +1,40 @@
1
+ export const trustlines = [
2
+ // TESTNET
3
+ {
4
+ name: "USDC",
5
+ address: "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA",
6
+ decimals: 10000000,
7
+ network: "testnet",
8
+ },
9
+ {
10
+ name: "EURC",
11
+ address: "GB3Q6QDZYTHWT7E5PVS3W7FUT5GVAFC5KSZFFLPU25GO7VTC3NM2ZTVO",
12
+ decimals: 10000000,
13
+ network: "testnet",
14
+ },
15
+ // MAINNET
16
+ {
17
+ name: "USDC",
18
+ address: "CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75",
19
+ decimals: 10000000,
20
+ network: "mainnet",
21
+ },
22
+ {
23
+ name: "EURC",
24
+ address: "GB3Q6QDZYTHWT7E5PVS3W7FUT5GVAFC5KSZFFLPU25GO7VTC3NM2ZTVO",
25
+ decimals: 10000000,
26
+ network: "mainnet",
27
+ },
28
+ ];
29
+
30
+ // TODO: add network dynamic filter
31
+ export const trustlineOptions = Array.from(
32
+ new Map(
33
+ trustlines
34
+ .filter((trustline) => trustline.network === "testnet")
35
+ .map((trustline) => [
36
+ trustline.address,
37
+ { value: trustline.address, label: trustline.name },
38
+ ])
39
+ ).values()
40
+ );