@karn_lat/protocol-sdk 0.1.0-alpha.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 (157) hide show
  1. package/README.md +68 -0
  2. package/dist/__tests__/setup.d.ts +14 -0
  3. package/dist/__tests__/setup.d.ts.map +1 -0
  4. package/dist/__tests__/setup.js +44 -0
  5. package/dist/__tests__/setup.js.map +1 -0
  6. package/dist/clients/GovernorClient.d.ts +9 -0
  7. package/dist/clients/GovernorClient.d.ts.map +1 -0
  8. package/dist/clients/GovernorClient.js +18 -0
  9. package/dist/clients/GovernorClient.js.map +1 -0
  10. package/dist/clients/TreasuryClient.d.ts +9 -0
  11. package/dist/clients/TreasuryClient.d.ts.map +1 -0
  12. package/dist/clients/TreasuryClient.js +18 -0
  13. package/dist/clients/TreasuryClient.js.map +1 -0
  14. package/dist/clients/ValocracyClient.d.ts +13 -0
  15. package/dist/clients/ValocracyClient.d.ts.map +1 -0
  16. package/dist/clients/ValocracyClient.js +32 -0
  17. package/dist/clients/ValocracyClient.js.map +1 -0
  18. package/dist/clients/index.d.ts +4 -0
  19. package/dist/clients/index.d.ts.map +1 -0
  20. package/dist/clients/index.js +4 -0
  21. package/dist/clients/index.js.map +1 -0
  22. package/dist/generated/governor/src/index.d.ts +400 -0
  23. package/dist/generated/governor/src/index.d.ts.map +1 -0
  24. package/dist/generated/governor/src/index.js +63 -0
  25. package/dist/generated/governor/src/index.js.map +1 -0
  26. package/dist/generated/treasury/src/index.d.ts +474 -0
  27. package/dist/generated/treasury/src/index.d.ts.map +1 -0
  28. package/dist/generated/treasury/src/index.js +54 -0
  29. package/dist/generated/treasury/src/index.js.map +1 -0
  30. package/dist/generated/valocracy/src/index.d.ts +807 -0
  31. package/dist/generated/valocracy/src/index.d.ts.map +1 -0
  32. package/dist/generated/valocracy/src/index.js +114 -0
  33. package/dist/generated/valocracy/src/index.js.map +1 -0
  34. package/dist/index.d.ts +5 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +5 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/react/hooks/useGovernor.d.ts +24 -0
  39. package/dist/react/hooks/useGovernor.d.ts.map +1 -0
  40. package/dist/react/hooks/useGovernor.js +45 -0
  41. package/dist/react/hooks/useGovernor.js.map +1 -0
  42. package/dist/react/hooks/useMultiWallet.d.ts +35 -0
  43. package/dist/react/hooks/useMultiWallet.d.ts.map +1 -0
  44. package/dist/react/hooks/useMultiWallet.js +87 -0
  45. package/dist/react/hooks/useMultiWallet.js.map +1 -0
  46. package/dist/react/hooks/useTreasury.d.ts +14 -0
  47. package/dist/react/hooks/useTreasury.d.ts.map +1 -0
  48. package/dist/react/hooks/useTreasury.js +45 -0
  49. package/dist/react/hooks/useTreasury.js.map +1 -0
  50. package/dist/react/hooks/useValocracy.d.ts +16 -0
  51. package/dist/react/hooks/useValocracy.d.ts.map +1 -0
  52. package/dist/react/hooks/useValocracy.js +54 -0
  53. package/dist/react/hooks/useValocracy.js.map +1 -0
  54. package/dist/react/hooks/useWallet.d.ts +13 -0
  55. package/dist/react/hooks/useWallet.d.ts.map +1 -0
  56. package/dist/react/hooks/useWallet.js +51 -0
  57. package/dist/react/hooks/useWallet.js.map +1 -0
  58. package/dist/react/index.d.ts +7 -0
  59. package/dist/react/index.d.ts.map +1 -0
  60. package/dist/react/index.js +7 -0
  61. package/dist/react/index.js.map +1 -0
  62. package/dist/react/providers/KarnProvider.d.ts +25 -0
  63. package/dist/react/providers/KarnProvider.d.ts.map +1 -0
  64. package/dist/react/providers/KarnProvider.js +25 -0
  65. package/dist/react/providers/KarnProvider.js.map +1 -0
  66. package/dist/utils/decay.d.ts +19 -0
  67. package/dist/utils/decay.d.ts.map +1 -0
  68. package/dist/utils/decay.js +33 -0
  69. package/dist/utils/decay.js.map +1 -0
  70. package/dist/utils/index.d.ts +4 -0
  71. package/dist/utils/index.d.ts.map +1 -0
  72. package/dist/utils/index.js +4 -0
  73. package/dist/utils/index.js.map +1 -0
  74. package/dist/utils/polling.d.ts +75 -0
  75. package/dist/utils/polling.d.ts.map +1 -0
  76. package/dist/utils/polling.js +104 -0
  77. package/dist/utils/polling.js.map +1 -0
  78. package/dist/utils/simulation.d.ts +67 -0
  79. package/dist/utils/simulation.d.ts.map +1 -0
  80. package/dist/utils/simulation.js +88 -0
  81. package/dist/utils/simulation.js.map +1 -0
  82. package/dist/wallet/WalletManager.d.ts +77 -0
  83. package/dist/wallet/WalletManager.d.ts.map +1 -0
  84. package/dist/wallet/WalletManager.js +268 -0
  85. package/dist/wallet/WalletManager.js.map +1 -0
  86. package/dist/wallet/adapters/AlbedoAdapter.d.ts +47 -0
  87. package/dist/wallet/adapters/AlbedoAdapter.d.ts.map +1 -0
  88. package/dist/wallet/adapters/AlbedoAdapter.js +84 -0
  89. package/dist/wallet/adapters/AlbedoAdapter.js.map +1 -0
  90. package/dist/wallet/adapters/FreighterAdapter.d.ts +42 -0
  91. package/dist/wallet/adapters/FreighterAdapter.d.ts.map +1 -0
  92. package/dist/wallet/adapters/FreighterAdapter.js +107 -0
  93. package/dist/wallet/adapters/FreighterAdapter.js.map +1 -0
  94. package/dist/wallet/adapters/LobstrAdapter.d.ts +34 -0
  95. package/dist/wallet/adapters/LobstrAdapter.d.ts.map +1 -0
  96. package/dist/wallet/adapters/LobstrAdapter.js +89 -0
  97. package/dist/wallet/adapters/LobstrAdapter.js.map +1 -0
  98. package/dist/wallet/adapters/RabetAdapter.d.ts +39 -0
  99. package/dist/wallet/adapters/RabetAdapter.d.ts.map +1 -0
  100. package/dist/wallet/adapters/RabetAdapter.js +104 -0
  101. package/dist/wallet/adapters/RabetAdapter.js.map +1 -0
  102. package/dist/wallet/adapters/xBullAdapter.d.ts +41 -0
  103. package/dist/wallet/adapters/xBullAdapter.d.ts.map +1 -0
  104. package/dist/wallet/adapters/xBullAdapter.js +72 -0
  105. package/dist/wallet/adapters/xBullAdapter.js.map +1 -0
  106. package/dist/wallet/index.d.ts +20 -0
  107. package/dist/wallet/index.d.ts.map +1 -0
  108. package/dist/wallet/index.js +23 -0
  109. package/dist/wallet/index.js.map +1 -0
  110. package/dist/wallet/types.d.ts +165 -0
  111. package/dist/wallet/types.d.ts.map +1 -0
  112. package/dist/wallet/types.js +50 -0
  113. package/dist/wallet/types.js.map +1 -0
  114. package/examples/basic-usage.ts +28 -0
  115. package/jest.config.js +37 -0
  116. package/package.json +58 -0
  117. package/src/__tests__/README.md +364 -0
  118. package/src/__tests__/setup.ts +57 -0
  119. package/src/__tests__/utils/decay.test.ts +331 -0
  120. package/src/__tests__/wallet/WalletManager.test.ts +410 -0
  121. package/src/clients/GovernorClient.ts +23 -0
  122. package/src/clients/TreasuryClient.ts +23 -0
  123. package/src/clients/ValocracyClient.ts +48 -0
  124. package/src/clients/index.ts +3 -0
  125. package/src/generated/governor/README.md +54 -0
  126. package/src/generated/governor/package.json +17 -0
  127. package/src/generated/governor/src/index.ts +428 -0
  128. package/src/generated/governor/tsconfig.json +98 -0
  129. package/src/generated/treasury/README.md +54 -0
  130. package/src/generated/treasury/package.json +17 -0
  131. package/src/generated/treasury/src/index.ts +495 -0
  132. package/src/generated/treasury/tsconfig.json +98 -0
  133. package/src/generated/valocracy/README.md +54 -0
  134. package/src/generated/valocracy/package.json +17 -0
  135. package/src/generated/valocracy/src/index.ts +831 -0
  136. package/src/generated/valocracy/tsconfig.json +98 -0
  137. package/src/index.ts +4 -0
  138. package/src/react/hooks/useGovernor.ts +69 -0
  139. package/src/react/hooks/useMultiWallet.ts +169 -0
  140. package/src/react/hooks/useTreasury.ts +57 -0
  141. package/src/react/hooks/useValocracy.ts +66 -0
  142. package/src/react/hooks/useWallet.ts +60 -0
  143. package/src/react/index.ts +6 -0
  144. package/src/react/providers/KarnProvider.tsx +63 -0
  145. package/src/utils/decay.ts +44 -0
  146. package/src/utils/index.ts +3 -0
  147. package/src/utils/polling.ts +193 -0
  148. package/src/utils/simulation.ts +136 -0
  149. package/src/wallet/WalletManager.ts +360 -0
  150. package/src/wallet/adapters/AlbedoAdapter.ts +140 -0
  151. package/src/wallet/adapters/FreighterAdapter.ts +179 -0
  152. package/src/wallet/adapters/LobstrAdapter.ts +142 -0
  153. package/src/wallet/adapters/RabetAdapter.ts +162 -0
  154. package/src/wallet/adapters/xBullAdapter.ts +123 -0
  155. package/src/wallet/index.ts +37 -0
  156. package/src/wallet/types.ts +204 -0
  157. package/tsconfig.json +40 -0
@@ -0,0 +1,193 @@
1
+ import { rpc } from '@stellar/stellar-sdk';
2
+
3
+ export interface PollingOptions {
4
+ /**
5
+ * Maximum number of polling attempts
6
+ * @default 30
7
+ */
8
+ maxAttempts?: number;
9
+
10
+ /**
11
+ * Interval between polling attempts in milliseconds
12
+ * @default 1000 (1 second)
13
+ */
14
+ intervalMs?: number;
15
+
16
+ /**
17
+ * Whether to use exponential backoff
18
+ * @default true
19
+ */
20
+ exponentialBackoff?: boolean;
21
+
22
+ /**
23
+ * Maximum interval between attempts when using exponential backoff (ms)
24
+ * @default 10000 (10 seconds)
25
+ */
26
+ maxIntervalMs?: number;
27
+
28
+ /**
29
+ * Callback function called on each polling attempt
30
+ */
31
+ onAttempt?: (attempt: number, status: string) => void;
32
+ }
33
+
34
+ export interface PollingResult<T> {
35
+ /**
36
+ * Whether the operation succeeded
37
+ */
38
+ success: boolean;
39
+
40
+ /**
41
+ * The final transaction response (if successful)
42
+ */
43
+ response?: T;
44
+
45
+ /**
46
+ * Error message (if failed)
47
+ */
48
+ error?: string;
49
+
50
+ /**
51
+ * Number of attempts made
52
+ */
53
+ attempts: number;
54
+
55
+ /**
56
+ * Total time elapsed in milliseconds
57
+ */
58
+ elapsedMs: number;
59
+ }
60
+
61
+ /**
62
+ * Poll for a transaction result with exponential backoff
63
+ *
64
+ * @param server - Stellar RPC server instance
65
+ * @param transactionHash - The transaction hash to poll for
66
+ * @param options - Polling configuration options
67
+ * @returns Promise resolving to polling result
68
+ */
69
+
70
+ export async function pollTransactionResult(
71
+ server: rpc.Server,
72
+ transactionHash: string,
73
+ options: PollingOptions = {}
74
+ ): Promise<PollingResult<rpc.Api.GetTransactionResponse>> {
75
+ const {
76
+ maxAttempts = 30,
77
+ intervalMs = 1000,
78
+ exponentialBackoff = true,
79
+ maxIntervalMs = 10000,
80
+ onAttempt,
81
+ } = options;
82
+
83
+ const startTime = Date.now();
84
+ let attempts = 0;
85
+ let currentInterval = intervalMs;
86
+
87
+ while (attempts < maxAttempts) {
88
+ attempts++;
89
+
90
+ try {
91
+ const response = await server.getTransaction(transactionHash);
92
+
93
+ const status = response.status as string;
94
+ onAttempt?.(attempts, status);
95
+
96
+ // SUCCESS - Transaction confirmed
97
+ if (response.status === rpc.Api.GetTransactionStatus.SUCCESS) {
98
+ return {
99
+ success: true,
100
+ response,
101
+ attempts,
102
+ elapsedMs: Date.now() - startTime,
103
+ };
104
+ }
105
+
106
+ // FAILED - Transaction failed permanently
107
+ if (response.status === rpc.Api.GetTransactionStatus.FAILED) {
108
+ return {
109
+ success: false,
110
+ error: `Transaction failed: ${JSON.stringify(response)}`,
111
+ attempts,
112
+ elapsedMs: Date.now() - startTime,
113
+ };
114
+ }
115
+
116
+ // NOT_FOUND - Still pending, continue polling
117
+ if (response.status === rpc.Api.GetTransactionStatus.NOT_FOUND) {
118
+ // Wait before next attempt
119
+ if (attempts < maxAttempts) {
120
+ await sleep(currentInterval);
121
+
122
+ // Exponential backoff: double interval each time, up to max
123
+ if (exponentialBackoff) {
124
+ currentInterval = Math.min(currentInterval * 2, maxIntervalMs);
125
+ }
126
+ }
127
+ continue;
128
+ }
129
+
130
+ // Unknown status (shouldn't reach here, but handle gracefully)
131
+ return {
132
+ success: false,
133
+ error: `Unknown transaction status: ${status}`,
134
+ attempts,
135
+ elapsedMs: Date.now() - startTime,
136
+ };
137
+ } catch (err) {
138
+ // Network error or RPC error
139
+ if (attempts >= maxAttempts) {
140
+ return {
141
+ success: false,
142
+ error: err instanceof Error ? err.message : 'Unknown error during polling',
143
+ attempts,
144
+ elapsedMs: Date.now() - startTime,
145
+ };
146
+ }
147
+
148
+ // Retry on error
149
+ await sleep(currentInterval);
150
+ if (exponentialBackoff) {
151
+ currentInterval = Math.min(currentInterval * 2, maxIntervalMs);
152
+ }
153
+ }
154
+ }
155
+
156
+ // Max attempts reached
157
+ return {
158
+ success: false,
159
+ error: `Transaction polling timed out after ${attempts} attempts (${Date.now() - startTime}ms)`,
160
+ attempts,
161
+ elapsedMs: Date.now() - startTime,
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Wait for a specified duration
167
+ *
168
+ * @param ms - Duration to wait in milliseconds
169
+ * @returns Promise that resolves after the duration
170
+ */
171
+
172
+ export function sleep(ms: number): Promise<void> {
173
+ return new Promise((resolve) => setTimeout(resolve, ms));
174
+ }
175
+
176
+ /**
177
+ * Poll for multiple transactions in parallel
178
+ *
179
+ * @param server - Stellar RPC server instance
180
+ * @param transactionHashes - Array of transaction hashes to poll
181
+ * @param options - Polling configuration options
182
+ * @returns Promise resolving to array of polling results
183
+ */
184
+
185
+ export async function pollMultipleTransactions(
186
+ server: rpc.Server,
187
+ transactionHashes: string[],
188
+ options: PollingOptions = {}
189
+ ): Promise<PollingResult<rpc.Api.GetTransactionResponse>[]> {
190
+ return Promise.all(
191
+ transactionHashes.map((hash) => pollTransactionResult(server, hash, options))
192
+ );
193
+ }
@@ -0,0 +1,136 @@
1
+ import { rpc } from '@stellar/stellar-sdk';
2
+ import type { AssembledTransaction } from '@stellar/stellar-sdk/contract';
3
+
4
+ export interface SimulationResult<T> {
5
+ /**
6
+ * Whether the simulation succeeded
7
+ */
8
+ success: boolean;
9
+
10
+ /**
11
+ * The decoded result value (if successful)
12
+ */
13
+ result?: T;
14
+
15
+ /**
16
+ * Error message (if failed)
17
+ */
18
+ error?: string;
19
+
20
+ /**
21
+ * Estimated resource fees in stroops
22
+ */
23
+ fee?: number;
24
+
25
+ /**
26
+ * Raw simulation response from RPC
27
+ */
28
+ raw?: rpc.Api.SimulateTransactionResponse;
29
+ }
30
+
31
+ /**
32
+ * Simulate a transaction and extract the result
33
+ *
34
+ * @param tx - AssembledTransaction to simulate
35
+ * @returns Promise resolving to simulation result
36
+ */
37
+ export async function simulateTransaction<T>(
38
+ tx: AssembledTransaction<T>
39
+ ): Promise<SimulationResult<T>> {
40
+ try {
41
+ // AssembledTransaction has `result` property if already simulated
42
+ // The simulation happens automatically by default in generated clients
43
+
44
+ // Try to get the result directly (already simulated)
45
+ if (tx.result !== undefined) {
46
+ return {
47
+ success: true,
48
+ result: tx.result,
49
+ fee: 0, // Fee info not available without raw simulation
50
+ };
51
+ }
52
+
53
+ // If result not available, transaction likely failed or wasn't simulated
54
+ return {
55
+ success: false,
56
+ error: 'Transaction simulation not available',
57
+ };
58
+ } catch (err) {
59
+ return {
60
+ success: false,
61
+ error: err instanceof Error ? err.message : 'Unknown simulation error',
62
+ };
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Check if a transaction needs restoration (contract data needs to be restored)
68
+ *
69
+ * @param tx - AssembledTransaction to check
70
+ * @returns Promise resolving to boolean indicating if restoration is needed
71
+ */
72
+ export async function needsRestoration<T>(
73
+ tx: AssembledTransaction<T>
74
+ ): Promise<boolean> {
75
+ // For generated clients, restoration info is not directly accessible
76
+ // Return false as default - users should check simulation errors
77
+ return false;
78
+ }
79
+
80
+ /**
81
+ * Estimate the total fee for a transaction (base fee + resource fee)
82
+ *
83
+ * @param tx - AssembledTransaction to estimate
84
+ * @returns Promise resolving to estimated fee in stroops
85
+ */
86
+ export async function estimateFee<T>(
87
+ tx: AssembledTransaction<T>
88
+ ): Promise<number> {
89
+ // Return a default estimate since simulation details not directly accessible
90
+ // Users should check actual fee after building transaction
91
+ return 100000; // Default estimate: 100,000 stroops (~0.01 XLM)
92
+ }
93
+
94
+ /**
95
+ * Simulate multiple transactions in parallel
96
+ *
97
+ * @param transactions - Array of AssembledTransactions to simulate
98
+ * @returns Promise resolving to array of simulation results
99
+ */
100
+ export async function simulateMultiple<T>(
101
+ transactions: AssembledTransaction<T>[]
102
+ ): Promise<SimulationResult<T>[]> {
103
+ return Promise.all(transactions.map((tx) => simulateTransaction(tx)));
104
+ }
105
+
106
+ /**
107
+ * Extract error details from a failed simulation
108
+ *
109
+ * @param simulation - The simulation response
110
+ * @returns Human-readable error message
111
+ */
112
+ export function getSimulationError(
113
+ simulation: rpc.Api.SimulateTransactionResponse
114
+ ): string {
115
+ if (rpc.Api.isSimulationError(simulation)) {
116
+ return simulation.error || 'Unknown simulation error';
117
+ }
118
+
119
+ if (rpc.Api.isSimulationRestore(simulation)) {
120
+ return 'Contract requires restoration';
121
+ }
122
+
123
+ return 'Simulation did not succeed';
124
+ }
125
+
126
+ /**
127
+ * Check if a simulation result indicates success
128
+ *
129
+ * @param simulation - The simulation response
130
+ * @returns True if simulation succeeded
131
+ */
132
+ export function isSimulationSuccess(
133
+ simulation: rpc.Api.SimulateTransactionResponse
134
+ ): boolean {
135
+ return rpc.Api.isSimulationSuccess(simulation);
136
+ }
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Manages wallet selection, connection state, and provides unified API
3
+ * for interacting with any supported Stellar wallet.
4
+ */
5
+
6
+ import {
7
+ WalletAdapter,
8
+ WalletType,
9
+ WalletState,
10
+ WalletMetadata,
11
+ WalletConnection,
12
+ WalletEvent,
13
+ WalletEventPayload,
14
+ WalletEventListener,
15
+ SignTransactionOptions,
16
+ WalletError,
17
+ WalletErrorCode,
18
+ } from './types.js';
19
+
20
+ import { FreighterAdapter } from './adapters/FreighterAdapter.js';
21
+ import { AlbedoAdapter } from './adapters/AlbedoAdapter.js';
22
+ import { LobstrAdapter } from './adapters/LobstrAdapter.js';
23
+ import { XBullAdapter } from './adapters/xBullAdapter.js';
24
+ import { RabetAdapter } from './adapters/RabetAdapter.js';
25
+
26
+ export class WalletManager {
27
+ private adapters: Map<WalletType, WalletAdapter>;
28
+ private currentAdapter: WalletAdapter | null = null;
29
+ private state: WalletState;
30
+ private eventListeners: Map<WalletEvent, Set<WalletEventListener>>;
31
+
32
+ constructor() {
33
+ // Initialize all wallet adapters
34
+ this.adapters = new Map([
35
+ [WalletType.FREIGHTER, new FreighterAdapter() as WalletAdapter],
36
+ [WalletType.ALBEDO, new AlbedoAdapter() as WalletAdapter],
37
+ [WalletType.LOBSTR, new LobstrAdapter() as WalletAdapter],
38
+ [WalletType.XBULL, new XBullAdapter() as WalletAdapter],
39
+ [WalletType.RABET, new RabetAdapter() as WalletAdapter],
40
+ ]);
41
+
42
+ // Initialize state
43
+ this.state = {
44
+ isConnected: false,
45
+ address: null,
46
+ walletType: null,
47
+ isConnecting: false,
48
+ error: null,
49
+ walletName: null,
50
+ };
51
+
52
+ // Initialize event listeners
53
+ this.eventListeners = new Map([
54
+ [WalletEvent.CONNECT, new Set()],
55
+ [WalletEvent.DISCONNECT, new Set()],
56
+ [WalletEvent.ACCOUNT_CHANGED, new Set()],
57
+ [WalletEvent.NETWORK_CHANGED, new Set()],
58
+ ]);
59
+
60
+ // Try to restore previous connection from localStorage
61
+ this.restoreConnection();
62
+ }
63
+
64
+ /**
65
+ * Get all available (installed) wallets
66
+ */
67
+ async getAvailableWallets(): Promise<WalletMetadata[]> {
68
+ const available: WalletMetadata[] = [];
69
+
70
+ for (const adapter of this.adapters.values()) {
71
+ if (await adapter.isAvailable()) {
72
+ available.push(adapter.metadata);
73
+ }
74
+ }
75
+
76
+ return available;
77
+ }
78
+
79
+ /**
80
+ * Get all wallet metadata (including unavailable wallets)
81
+ */
82
+ getAllWallets(): WalletMetadata[] {
83
+ return Array.from(this.adapters.values()).map((adapter) => adapter.metadata);
84
+ }
85
+
86
+ /**
87
+ * Get current wallet state
88
+ */
89
+ getState(): WalletState {
90
+ return { ...this.state };
91
+ }
92
+
93
+ /**
94
+ * Connect to a specific wallet
95
+ */
96
+ async connect(walletType: WalletType): Promise<WalletConnection> {
97
+ const adapter = this.adapters.get(walletType);
98
+
99
+ if (!adapter) {
100
+ throw new WalletError(
101
+ `Wallet type ${walletType} is not supported`,
102
+ WalletErrorCode.UNSUPPORTED_METHOD
103
+ );
104
+ }
105
+
106
+ // Check if wallet is available
107
+ const isAvailable = await adapter.isAvailable();
108
+ if (!isAvailable) {
109
+ throw new WalletError(
110
+ `${adapter.metadata.name} is not installed`,
111
+ WalletErrorCode.NOT_INSTALLED,
112
+ walletType
113
+ );
114
+ }
115
+
116
+ // Disconnect any existing connection
117
+ if (this.currentAdapter) {
118
+ await this.disconnect();
119
+ }
120
+
121
+ // Update state
122
+ this.state.isConnecting = true;
123
+ this.state.error = null;
124
+
125
+ try {
126
+ // Connect to wallet
127
+ const address = await adapter.connect();
128
+
129
+ // Update state
130
+ this.currentAdapter = adapter;
131
+ this.state.isConnected = true;
132
+ this.state.address = address;
133
+ this.state.walletType = walletType;
134
+ this.state.walletName = adapter.metadata.name;
135
+ this.state.isConnecting = false;
136
+
137
+ // Save to localStorage for auto-reconnect
138
+ this.saveConnection(walletType, address);
139
+
140
+ // Emit connect event
141
+ this.emitEvent(WalletEvent.CONNECT, { walletType, address });
142
+
143
+ return {
144
+ walletType,
145
+ address,
146
+ adapter,
147
+ };
148
+ } catch (error: any) {
149
+ this.state.isConnecting = false;
150
+ this.state.error = error.message;
151
+
152
+ if (error instanceof WalletError) {
153
+ throw error;
154
+ }
155
+
156
+ throw new WalletError(
157
+ `Failed to connect to ${adapter.metadata.name}: ${error.message}`,
158
+ WalletErrorCode.UNKNOWN_ERROR,
159
+ walletType
160
+ );
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Disconnect from current wallet
166
+ */
167
+ async disconnect(): Promise<void> {
168
+ if (!this.currentAdapter) {
169
+ return;
170
+ }
171
+
172
+ const walletType = this.state.walletType;
173
+
174
+ try {
175
+ await this.currentAdapter.disconnect();
176
+ } catch (error) {
177
+ // Ignore disconnect errors
178
+ console.warn('Error during disconnect:', error);
179
+ }
180
+
181
+ // Clear state
182
+ this.currentAdapter = null;
183
+ this.state.isConnected = false;
184
+ this.state.address = null;
185
+ this.state.walletType = null;
186
+ this.state.walletName = null;
187
+ this.state.error = null;
188
+
189
+ // Clear localStorage
190
+ this.clearConnection();
191
+
192
+ // Emit disconnect event
193
+ if (walletType) {
194
+ this.emitEvent(WalletEvent.DISCONNECT, { walletType });
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Get current connected address
200
+ */
201
+ async getAddress(): Promise<string | null> {
202
+ if (!this.currentAdapter) {
203
+ return null;
204
+ }
205
+
206
+ return await this.currentAdapter.getAddress();
207
+ }
208
+
209
+ /**
210
+ * Check if wallet is connected
211
+ */
212
+ async isConnected(): Promise<boolean> {
213
+ if (!this.currentAdapter) {
214
+ return false;
215
+ }
216
+
217
+ return await this.currentAdapter.isConnected();
218
+ }
219
+
220
+ /**
221
+ * Sign a transaction with current wallet
222
+ */
223
+ async signTransaction(
224
+ xdr: string,
225
+ options?: SignTransactionOptions
226
+ ): Promise<string> {
227
+ if (!this.currentAdapter) {
228
+ throw new WalletError(
229
+ 'No wallet connected',
230
+ WalletErrorCode.NOT_CONNECTED
231
+ );
232
+ }
233
+
234
+ return await this.currentAdapter.signTransaction(xdr, options);
235
+ }
236
+
237
+ /**
238
+ * Sign a message (if supported by wallet)
239
+ */
240
+ async signMessage(message: string): Promise<string> {
241
+ if (!this.currentAdapter) {
242
+ throw new WalletError(
243
+ 'No wallet connected',
244
+ WalletErrorCode.NOT_CONNECTED
245
+ );
246
+ }
247
+
248
+ if (!this.currentAdapter.signMessage) {
249
+ throw new WalletError(
250
+ `${this.state.walletName} does not support message signing`,
251
+ WalletErrorCode.UNSUPPORTED_METHOD,
252
+ this.state.walletType || undefined
253
+ );
254
+ }
255
+
256
+ return await this.currentAdapter.signMessage(message);
257
+ }
258
+
259
+ /**
260
+ * Get current network (if supported by wallet)
261
+ */
262
+ async getNetwork(): Promise<string> {
263
+ if (!this.currentAdapter) {
264
+ throw new WalletError(
265
+ 'No wallet connected',
266
+ WalletErrorCode.NOT_CONNECTED
267
+ );
268
+ }
269
+
270
+ if (!this.currentAdapter.getNetwork) {
271
+ throw new WalletError(
272
+ `${this.state.walletName} does not support network detection`,
273
+ WalletErrorCode.UNSUPPORTED_METHOD,
274
+ this.state.walletType || undefined
275
+ );
276
+ }
277
+
278
+ return await this.currentAdapter.getNetwork();
279
+ }
280
+
281
+ /**
282
+ * Add event listener
283
+ */
284
+ on(event: WalletEvent, listener: WalletEventListener): void {
285
+ const listeners = this.eventListeners.get(event);
286
+ if (listeners) {
287
+ listeners.add(listener);
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Remove event listener
293
+ */
294
+ off(event: WalletEvent, listener: WalletEventListener): void {
295
+ const listeners = this.eventListeners.get(event);
296
+ if (listeners) {
297
+ listeners.delete(listener);
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Emit event to all listeners
303
+ */
304
+ private emitEvent(event: WalletEvent, payload: WalletEventPayload): void {
305
+ const listeners = this.eventListeners.get(event);
306
+ if (listeners) {
307
+ listeners.forEach((listener) => listener(payload));
308
+ }
309
+ }
310
+
311
+ /**
312
+ * Save connection to localStorage for auto-reconnect
313
+ */
314
+ private saveConnection(walletType: WalletType, address: string): void {
315
+ if (typeof window === 'undefined') return;
316
+
317
+ try {
318
+ localStorage.setItem(
319
+ 'karn_wallet_connection',
320
+ JSON.stringify({ walletType, address })
321
+ );
322
+ } catch (error) {
323
+ console.warn('Failed to save wallet connection:', error);
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Clear connection from localStorage
329
+ */
330
+ private clearConnection(): void {
331
+ if (typeof window === 'undefined') return;
332
+
333
+ try {
334
+ localStorage.removeItem('karn_wallet_connection');
335
+ } catch (error) {
336
+ console.warn('Failed to clear wallet connection:', error);
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Restore previous connection from localStorage
342
+ */
343
+ private async restoreConnection(): Promise<void> {
344
+ if (typeof window === 'undefined') return;
345
+
346
+ try {
347
+ const saved = localStorage.getItem('karn_wallet_connection');
348
+ if (!saved) return;
349
+
350
+ const { walletType } = JSON.parse(saved);
351
+
352
+ // Try to reconnect silently
353
+ await this.connect(walletType);
354
+ } catch (error) {
355
+ // Silent fail - user can manually reconnect
356
+ console.debug('Failed to restore wallet connection:', error);
357
+ this.clearConnection();
358
+ }
359
+ }
360
+ }