@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.
- package/README.md +68 -0
- package/dist/__tests__/setup.d.ts +14 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +44 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/clients/GovernorClient.d.ts +9 -0
- package/dist/clients/GovernorClient.d.ts.map +1 -0
- package/dist/clients/GovernorClient.js +18 -0
- package/dist/clients/GovernorClient.js.map +1 -0
- package/dist/clients/TreasuryClient.d.ts +9 -0
- package/dist/clients/TreasuryClient.d.ts.map +1 -0
- package/dist/clients/TreasuryClient.js +18 -0
- package/dist/clients/TreasuryClient.js.map +1 -0
- package/dist/clients/ValocracyClient.d.ts +13 -0
- package/dist/clients/ValocracyClient.d.ts.map +1 -0
- package/dist/clients/ValocracyClient.js +32 -0
- package/dist/clients/ValocracyClient.js.map +1 -0
- package/dist/clients/index.d.ts +4 -0
- package/dist/clients/index.d.ts.map +1 -0
- package/dist/clients/index.js +4 -0
- package/dist/clients/index.js.map +1 -0
- package/dist/generated/governor/src/index.d.ts +400 -0
- package/dist/generated/governor/src/index.d.ts.map +1 -0
- package/dist/generated/governor/src/index.js +63 -0
- package/dist/generated/governor/src/index.js.map +1 -0
- package/dist/generated/treasury/src/index.d.ts +474 -0
- package/dist/generated/treasury/src/index.d.ts.map +1 -0
- package/dist/generated/treasury/src/index.js +54 -0
- package/dist/generated/treasury/src/index.js.map +1 -0
- package/dist/generated/valocracy/src/index.d.ts +807 -0
- package/dist/generated/valocracy/src/index.d.ts.map +1 -0
- package/dist/generated/valocracy/src/index.js +114 -0
- package/dist/generated/valocracy/src/index.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/react/hooks/useGovernor.d.ts +24 -0
- package/dist/react/hooks/useGovernor.d.ts.map +1 -0
- package/dist/react/hooks/useGovernor.js +45 -0
- package/dist/react/hooks/useGovernor.js.map +1 -0
- package/dist/react/hooks/useMultiWallet.d.ts +35 -0
- package/dist/react/hooks/useMultiWallet.d.ts.map +1 -0
- package/dist/react/hooks/useMultiWallet.js +87 -0
- package/dist/react/hooks/useMultiWallet.js.map +1 -0
- package/dist/react/hooks/useTreasury.d.ts +14 -0
- package/dist/react/hooks/useTreasury.d.ts.map +1 -0
- package/dist/react/hooks/useTreasury.js +45 -0
- package/dist/react/hooks/useTreasury.js.map +1 -0
- package/dist/react/hooks/useValocracy.d.ts +16 -0
- package/dist/react/hooks/useValocracy.d.ts.map +1 -0
- package/dist/react/hooks/useValocracy.js +54 -0
- package/dist/react/hooks/useValocracy.js.map +1 -0
- package/dist/react/hooks/useWallet.d.ts +13 -0
- package/dist/react/hooks/useWallet.d.ts.map +1 -0
- package/dist/react/hooks/useWallet.js +51 -0
- package/dist/react/hooks/useWallet.js.map +1 -0
- package/dist/react/index.d.ts +7 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +7 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/providers/KarnProvider.d.ts +25 -0
- package/dist/react/providers/KarnProvider.d.ts.map +1 -0
- package/dist/react/providers/KarnProvider.js +25 -0
- package/dist/react/providers/KarnProvider.js.map +1 -0
- package/dist/utils/decay.d.ts +19 -0
- package/dist/utils/decay.d.ts.map +1 -0
- package/dist/utils/decay.js +33 -0
- package/dist/utils/decay.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/polling.d.ts +75 -0
- package/dist/utils/polling.d.ts.map +1 -0
- package/dist/utils/polling.js +104 -0
- package/dist/utils/polling.js.map +1 -0
- package/dist/utils/simulation.d.ts +67 -0
- package/dist/utils/simulation.d.ts.map +1 -0
- package/dist/utils/simulation.js +88 -0
- package/dist/utils/simulation.js.map +1 -0
- package/dist/wallet/WalletManager.d.ts +77 -0
- package/dist/wallet/WalletManager.d.ts.map +1 -0
- package/dist/wallet/WalletManager.js +268 -0
- package/dist/wallet/WalletManager.js.map +1 -0
- package/dist/wallet/adapters/AlbedoAdapter.d.ts +47 -0
- package/dist/wallet/adapters/AlbedoAdapter.d.ts.map +1 -0
- package/dist/wallet/adapters/AlbedoAdapter.js +84 -0
- package/dist/wallet/adapters/AlbedoAdapter.js.map +1 -0
- package/dist/wallet/adapters/FreighterAdapter.d.ts +42 -0
- package/dist/wallet/adapters/FreighterAdapter.d.ts.map +1 -0
- package/dist/wallet/adapters/FreighterAdapter.js +107 -0
- package/dist/wallet/adapters/FreighterAdapter.js.map +1 -0
- package/dist/wallet/adapters/LobstrAdapter.d.ts +34 -0
- package/dist/wallet/adapters/LobstrAdapter.d.ts.map +1 -0
- package/dist/wallet/adapters/LobstrAdapter.js +89 -0
- package/dist/wallet/adapters/LobstrAdapter.js.map +1 -0
- package/dist/wallet/adapters/RabetAdapter.d.ts +39 -0
- package/dist/wallet/adapters/RabetAdapter.d.ts.map +1 -0
- package/dist/wallet/adapters/RabetAdapter.js +104 -0
- package/dist/wallet/adapters/RabetAdapter.js.map +1 -0
- package/dist/wallet/adapters/xBullAdapter.d.ts +41 -0
- package/dist/wallet/adapters/xBullAdapter.d.ts.map +1 -0
- package/dist/wallet/adapters/xBullAdapter.js +72 -0
- package/dist/wallet/adapters/xBullAdapter.js.map +1 -0
- package/dist/wallet/index.d.ts +20 -0
- package/dist/wallet/index.d.ts.map +1 -0
- package/dist/wallet/index.js +23 -0
- package/dist/wallet/index.js.map +1 -0
- package/dist/wallet/types.d.ts +165 -0
- package/dist/wallet/types.d.ts.map +1 -0
- package/dist/wallet/types.js +50 -0
- package/dist/wallet/types.js.map +1 -0
- package/examples/basic-usage.ts +28 -0
- package/jest.config.js +37 -0
- package/package.json +58 -0
- package/src/__tests__/README.md +364 -0
- package/src/__tests__/setup.ts +57 -0
- package/src/__tests__/utils/decay.test.ts +331 -0
- package/src/__tests__/wallet/WalletManager.test.ts +410 -0
- package/src/clients/GovernorClient.ts +23 -0
- package/src/clients/TreasuryClient.ts +23 -0
- package/src/clients/ValocracyClient.ts +48 -0
- package/src/clients/index.ts +3 -0
- package/src/generated/governor/README.md +54 -0
- package/src/generated/governor/package.json +17 -0
- package/src/generated/governor/src/index.ts +428 -0
- package/src/generated/governor/tsconfig.json +98 -0
- package/src/generated/treasury/README.md +54 -0
- package/src/generated/treasury/package.json +17 -0
- package/src/generated/treasury/src/index.ts +495 -0
- package/src/generated/treasury/tsconfig.json +98 -0
- package/src/generated/valocracy/README.md +54 -0
- package/src/generated/valocracy/package.json +17 -0
- package/src/generated/valocracy/src/index.ts +831 -0
- package/src/generated/valocracy/tsconfig.json +98 -0
- package/src/index.ts +4 -0
- package/src/react/hooks/useGovernor.ts +69 -0
- package/src/react/hooks/useMultiWallet.ts +169 -0
- package/src/react/hooks/useTreasury.ts +57 -0
- package/src/react/hooks/useValocracy.ts +66 -0
- package/src/react/hooks/useWallet.ts +60 -0
- package/src/react/index.ts +6 -0
- package/src/react/providers/KarnProvider.tsx +63 -0
- package/src/utils/decay.ts +44 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/polling.ts +193 -0
- package/src/utils/simulation.ts +136 -0
- package/src/wallet/WalletManager.ts +360 -0
- package/src/wallet/adapters/AlbedoAdapter.ts +140 -0
- package/src/wallet/adapters/FreighterAdapter.ts +179 -0
- package/src/wallet/adapters/LobstrAdapter.ts +142 -0
- package/src/wallet/adapters/RabetAdapter.ts +162 -0
- package/src/wallet/adapters/xBullAdapter.ts +123 -0
- package/src/wallet/index.ts +37 -0
- package/src/wallet/types.ts +204 -0
- 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
|
+
}
|