@openzeppelin/adapter-stellar 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +272 -0
  2. package/dist/config.cjs +21 -0
  3. package/dist/config.cjs.map +1 -0
  4. package/dist/config.d.cts +8 -0
  5. package/dist/config.d.cts.map +1 -0
  6. package/dist/config.d.mts +8 -0
  7. package/dist/config.d.mts.map +1 -0
  8. package/dist/config.mjs +20 -0
  9. package/dist/config.mjs.map +1 -0
  10. package/dist/index.cjs +7564 -0
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.d.cts +261 -0
  13. package/dist/index.d.cts.map +1 -0
  14. package/dist/index.d.mts +263 -0
  15. package/dist/index.d.mts.map +1 -0
  16. package/dist/index.mjs +7529 -0
  17. package/dist/index.mjs.map +1 -0
  18. package/dist/metadata.cjs +22 -0
  19. package/dist/metadata.cjs.map +1 -0
  20. package/dist/metadata.d.cts +7 -0
  21. package/dist/metadata.d.cts.map +1 -0
  22. package/dist/metadata.d.mts +7 -0
  23. package/dist/metadata.d.mts.map +1 -0
  24. package/dist/metadata.mjs +21 -0
  25. package/dist/metadata.mjs.map +1 -0
  26. package/dist/networks-BrV516-R.d.cts +15 -0
  27. package/dist/networks-BrV516-R.d.cts.map +1 -0
  28. package/dist/networks-C0MmhJcu.d.mts +15 -0
  29. package/dist/networks-C0MmhJcu.d.mts.map +1 -0
  30. package/dist/networks-DgUFSTiC.cjs +76 -0
  31. package/dist/networks-DgUFSTiC.cjs.map +1 -0
  32. package/dist/networks-QbEPbaGT.mjs +46 -0
  33. package/dist/networks-QbEPbaGT.mjs.map +1 -0
  34. package/dist/networks.cjs +8 -0
  35. package/dist/networks.d.cts +2 -0
  36. package/dist/networks.d.mts +2 -0
  37. package/dist/networks.mjs +3 -0
  38. package/dist/vite-config.cjs +43 -0
  39. package/dist/vite-config.cjs.map +1 -0
  40. package/dist/vite-config.d.cts +35 -0
  41. package/dist/vite-config.d.cts.map +1 -0
  42. package/dist/vite-config.d.mts +35 -0
  43. package/dist/vite-config.d.mts.map +1 -0
  44. package/dist/vite-config.mjs +42 -0
  45. package/dist/vite-config.mjs.map +1 -0
  46. package/package.json +114 -0
  47. package/src/__tests__/getDefaultServiceConfig.test.ts +105 -0
  48. package/src/access-control/actions.ts +214 -0
  49. package/src/access-control/feature-detection.ts +238 -0
  50. package/src/access-control/index.ts +54 -0
  51. package/src/access-control/indexer-client.ts +1474 -0
  52. package/src/access-control/onchain-reader.ts +446 -0
  53. package/src/access-control/service.ts +1431 -0
  54. package/src/access-control/validation.ts +256 -0
  55. package/src/adapter.ts +659 -0
  56. package/src/config.ts +43 -0
  57. package/src/configuration/__tests__/explorer.test.ts +80 -0
  58. package/src/configuration/__tests__/rpc.test.ts +355 -0
  59. package/src/configuration/execution.ts +83 -0
  60. package/src/configuration/explorer.ts +105 -0
  61. package/src/configuration/index.ts +5 -0
  62. package/src/configuration/network-services.ts +210 -0
  63. package/src/configuration/rpc.ts +270 -0
  64. package/src/configuration.ts +2 -0
  65. package/src/contract/__tests__/complete-type-coverage.test.ts +78 -0
  66. package/src/contract/index.ts +3 -0
  67. package/src/contract/loader.ts +498 -0
  68. package/src/contract/transformer.ts +1 -0
  69. package/src/contract/type.ts +65 -0
  70. package/src/index.ts +23 -0
  71. package/src/mapping/constants.ts +89 -0
  72. package/src/mapping/enum-metadata.ts +237 -0
  73. package/src/mapping/field-generator.ts +296 -0
  74. package/src/mapping/index.ts +5 -0
  75. package/src/mapping/struct-fields.ts +106 -0
  76. package/src/mapping/tuple-components.ts +43 -0
  77. package/src/mapping/type-coverage-validator.ts +151 -0
  78. package/src/mapping/type-mapper.ts +203 -0
  79. package/src/metadata.ts +16 -0
  80. package/src/networks/README.md +84 -0
  81. package/src/networks/index.ts +19 -0
  82. package/src/networks/mainnet.ts +20 -0
  83. package/src/networks/testnet.ts +20 -0
  84. package/src/networks.ts +2 -0
  85. package/src/query/handler.ts +411 -0
  86. package/src/query/index.ts +4 -0
  87. package/src/query/view-checker.ts +32 -0
  88. package/src/sac/spec-cache.ts +68 -0
  89. package/src/sac/spec-source.ts +35 -0
  90. package/src/sac/xdr.ts +101 -0
  91. package/src/transaction/components/AdvancedInfo.tsx +34 -0
  92. package/src/transaction/components/FeeConfiguration.tsx +41 -0
  93. package/src/transaction/components/StellarRelayerOptions.tsx +60 -0
  94. package/src/transaction/components/TransactionTiming.tsx +77 -0
  95. package/src/transaction/components/index.ts +5 -0
  96. package/src/transaction/components/useStellarRelayerOptions.ts +114 -0
  97. package/src/transaction/eoa.ts +229 -0
  98. package/src/transaction/execution-strategy.ts +33 -0
  99. package/src/transaction/formatter.ts +296 -0
  100. package/src/transaction/index.ts +4 -0
  101. package/src/transaction/relayer.ts +575 -0
  102. package/src/transaction/sender.ts +156 -0
  103. package/src/transform/index.ts +4 -0
  104. package/src/transform/input-parser.ts +9 -0
  105. package/src/transform/output-formatter.ts +133 -0
  106. package/src/transform/parsers/complex-parser.ts +157 -0
  107. package/src/transform/parsers/generic-parser.ts +171 -0
  108. package/src/transform/parsers/index.ts +86 -0
  109. package/src/transform/parsers/primitive-parser.ts +123 -0
  110. package/src/transform/parsers/scval-converter.ts +405 -0
  111. package/src/transform/parsers/struct-parser.ts +324 -0
  112. package/src/transform/parsers/types.ts +35 -0
  113. package/src/types/__tests__/artifacts.test.ts +89 -0
  114. package/src/types/artifacts.ts +19 -0
  115. package/src/utils/__tests__/artifacts.test.ts +77 -0
  116. package/src/utils/artifacts.ts +30 -0
  117. package/src/utils/formatting.ts +122 -0
  118. package/src/utils/index.ts +6 -0
  119. package/src/utils/input-parsing.ts +336 -0
  120. package/src/utils/safe-type-parser.ts +303 -0
  121. package/src/utils/stellar-types.ts +35 -0
  122. package/src/utils/type-detection.ts +163 -0
  123. package/src/utils/xdr-ordering.ts +36 -0
  124. package/src/validation/__tests__/address.test.ts +267 -0
  125. package/src/validation/address.ts +136 -0
  126. package/src/validation/eoa.ts +33 -0
  127. package/src/validation/index.ts +3 -0
  128. package/src/validation/relayer.ts +13 -0
  129. package/src/vite-config.ts +67 -0
  130. package/src/wallet/README.md +93 -0
  131. package/src/wallet/__tests__/connection.test.ts +72 -0
  132. package/src/wallet/components/StellarWalletUiRoot.tsx +161 -0
  133. package/src/wallet/components/account/AccountDisplay.tsx +50 -0
  134. package/src/wallet/components/connect/ConnectButton.tsx +100 -0
  135. package/src/wallet/components/connect/ConnectorDialog.tsx +125 -0
  136. package/src/wallet/components/index.ts +3 -0
  137. package/src/wallet/connection.ts +151 -0
  138. package/src/wallet/context/StellarWalletContext.ts +32 -0
  139. package/src/wallet/context/index.ts +4 -0
  140. package/src/wallet/context/useStellarWalletContext.ts +17 -0
  141. package/src/wallet/hooks/facade-hooks.ts +31 -0
  142. package/src/wallet/hooks/index.ts +7 -0
  143. package/src/wallet/hooks/useStellarAccount.ts +27 -0
  144. package/src/wallet/hooks/useStellarConnect.ts +60 -0
  145. package/src/wallet/hooks/useStellarDisconnect.ts +47 -0
  146. package/src/wallet/hooks/useUiKitConfig.ts +40 -0
  147. package/src/wallet/implementation/wallets-kit-implementation.ts +379 -0
  148. package/src/wallet/index.ts +11 -0
  149. package/src/wallet/services/__tests__/configResolutionService.test.ts +163 -0
  150. package/src/wallet/services/configResolutionService.ts +65 -0
  151. package/src/wallet/stellar-wallets-kit/StellarWalletsKitConnectButton.tsx +82 -0
  152. package/src/wallet/stellar-wallets-kit/__mocks__/@creit.tech/stellar-wallets-kit.ts +48 -0
  153. package/src/wallet/stellar-wallets-kit/__tests__/export-service.test.ts +93 -0
  154. package/src/wallet/stellar-wallets-kit/__tests__/stellarUiKitManager.test.ts +0 -0
  155. package/src/wallet/stellar-wallets-kit/config-generator.ts +75 -0
  156. package/src/wallet/stellar-wallets-kit/export-service.ts +19 -0
  157. package/src/wallet/stellar-wallets-kit/index.ts +3 -0
  158. package/src/wallet/stellar-wallets-kit/stellarUiKitManager.ts +235 -0
  159. package/src/wallet/types.ts +19 -0
  160. package/src/wallet/utils/__tests__/filterWalletComponents.test.ts +150 -0
  161. package/src/wallet/utils/__tests__/uiKitService.test.ts +189 -0
  162. package/src/wallet/utils/filterWalletComponents.ts +89 -0
  163. package/src/wallet/utils/index.ts +3 -0
  164. package/src/wallet/utils/stellarWalletImplementationManager.ts +118 -0
  165. package/src/wallet/utils/uiKitService.ts +74 -0
@@ -0,0 +1,446 @@
1
+ /**
2
+ * On-Chain Reader Module
3
+ *
4
+ * Reads current access control state (ownership and roles) from Stellar (Soroban) contracts
5
+ * using on-chain queries. Supports both Ownable and AccessControl patterns.
6
+ */
7
+
8
+ import { rpc as StellarRpc } from '@stellar/stellar-sdk';
9
+
10
+ import type {
11
+ ContractSchema,
12
+ OwnershipInfo,
13
+ RoleAssignment,
14
+ RoleIdentifier,
15
+ StellarNetworkConfig,
16
+ } from '@openzeppelin/ui-types';
17
+ import { OperationFailed } from '@openzeppelin/ui-types';
18
+ import { DEFAULT_CONCURRENCY_LIMIT, logger, promiseAllWithLimit } from '@openzeppelin/ui-utils';
19
+
20
+ import { queryStellarViewFunction } from '../query/handler';
21
+
22
+ /**
23
+ * Helper to load a minimal contract schema for access control functions
24
+ * This allows us to use the existing query infrastructure
25
+ */
26
+ function createMinimalSchema(
27
+ contractAddress: string,
28
+ functionName: string,
29
+ inputs: Array<{ name: string; type: string }> = [],
30
+ outputType = 'Val'
31
+ ): ContractSchema {
32
+ return {
33
+ ecosystem: 'stellar',
34
+ address: contractAddress,
35
+ functions: [
36
+ {
37
+ id: functionName,
38
+ name: functionName,
39
+ displayName: functionName,
40
+ type: 'function',
41
+ inputs,
42
+ outputs: [{ name: 'result', type: outputType }],
43
+ modifiesState: false,
44
+ stateMutability: 'view',
45
+ },
46
+ ],
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Simple wrapper around queryStellarViewFunction for access control queries
52
+ */
53
+ async function queryAccessControlFunction(
54
+ contractAddress: string,
55
+ functionName: string,
56
+ params: unknown[],
57
+ networkConfig: StellarNetworkConfig,
58
+ inputs: Array<{ name: string; type: string }> = []
59
+ ): Promise<unknown> {
60
+ const schema = createMinimalSchema(contractAddress, functionName, inputs);
61
+ return queryStellarViewFunction(contractAddress, functionName, networkConfig, params, schema);
62
+ }
63
+
64
+ /**
65
+ * Reads the current owner from an Ownable contract
66
+ *
67
+ * @param contractAddress The contract address
68
+ * @param networkConfig The network configuration
69
+ * @returns Ownership information
70
+ */
71
+ export async function readOwnership(
72
+ contractAddress: string,
73
+ networkConfig: StellarNetworkConfig
74
+ ): Promise<OwnershipInfo> {
75
+ logger.info('readOwnership', `Reading owner for contract ${contractAddress}`);
76
+
77
+ try {
78
+ const result = await queryAccessControlFunction(
79
+ contractAddress,
80
+ 'get_owner',
81
+ [],
82
+ networkConfig
83
+ );
84
+
85
+ // get_owner returns Option<Address>
86
+ if (result === undefined || result === null) {
87
+ return { owner: null };
88
+ }
89
+
90
+ const ownerAddress = typeof result === 'string' ? result : String(result);
91
+ logger.debug('readOwnership', `Owner: ${ownerAddress}`);
92
+
93
+ return { owner: ownerAddress };
94
+ } catch (error) {
95
+ logger.error('readOwnership', 'Failed to read ownership:', error);
96
+ throw new OperationFailed(
97
+ `Failed to read ownership: ${(error as Error).message}`,
98
+ contractAddress,
99
+ 'readOwnership',
100
+ error as Error
101
+ );
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Checks if an account has a specific role
107
+ *
108
+ * @param contractAddress The contract address
109
+ * @param roleId The role identifier (Symbol)
110
+ * @param account The account address to check
111
+ * @param networkConfig The network configuration
112
+ * @returns True if the account has the role, false otherwise
113
+ */
114
+ export async function hasRole(
115
+ contractAddress: string,
116
+ roleId: string,
117
+ account: string,
118
+ networkConfig: StellarNetworkConfig
119
+ ): Promise<boolean> {
120
+ logger.debug('hasRole', `Checking role ${roleId} for ${account}`);
121
+
122
+ try {
123
+ const inputs = [
124
+ { name: 'account', type: 'Address' },
125
+ { name: 'role', type: 'Symbol' },
126
+ ];
127
+ const result = await queryAccessControlFunction(
128
+ contractAddress,
129
+ 'has_role',
130
+ [account, roleId],
131
+ networkConfig,
132
+ inputs
133
+ );
134
+
135
+ // has_role returns Option<u32> (Some(index) if has role, None otherwise)
136
+ return typeof result === 'number';
137
+ } catch (error) {
138
+ logger.error('hasRole', `Failed to check role ${roleId}:`, error);
139
+ return false;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Gets the count of members for a specific role
145
+ *
146
+ * @param contractAddress The contract address
147
+ * @param roleId The role identifier (Symbol)
148
+ * @param networkConfig The network configuration
149
+ * @returns The count of role members
150
+ */
151
+ export async function getRoleMemberCount(
152
+ contractAddress: string,
153
+ roleId: string,
154
+ networkConfig: StellarNetworkConfig
155
+ ): Promise<number> {
156
+ logger.debug('getRoleMemberCount', `Getting member count for role ${roleId}`);
157
+
158
+ try {
159
+ const inputs = [{ name: 'role', type: 'Symbol' }];
160
+ const result = await queryAccessControlFunction(
161
+ contractAddress,
162
+ 'get_role_member_count',
163
+ [roleId],
164
+ networkConfig,
165
+ inputs
166
+ );
167
+
168
+ // Handle both number and string results (formatter may return string for large numbers)
169
+ if (typeof result === 'number') {
170
+ return result;
171
+ }
172
+ if (typeof result === 'string') {
173
+ const parsed = parseInt(result, 10);
174
+ return isNaN(parsed) ? 0 : parsed;
175
+ }
176
+ return 0;
177
+ } catch (error) {
178
+ logger.error('getRoleMemberCount', `Failed to get member count for role ${roleId}:`, error);
179
+ return 0;
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Gets the member address at a specific index for a role
185
+ *
186
+ * @param contractAddress The contract address
187
+ * @param roleId The role identifier (Symbol)
188
+ * @param index The index of the member to retrieve
189
+ * @param networkConfig The network configuration
190
+ * @returns The member address, or null if index out of bounds
191
+ */
192
+ export async function getRoleMember(
193
+ contractAddress: string,
194
+ roleId: string,
195
+ index: number,
196
+ networkConfig: StellarNetworkConfig
197
+ ): Promise<string | null> {
198
+ logger.debug('getRoleMember', `Getting member at index ${index} for role ${roleId}`);
199
+
200
+ try {
201
+ const inputs = [
202
+ { name: 'role', type: 'Symbol' },
203
+ { name: 'index', type: 'u32' },
204
+ ];
205
+ const result = await queryAccessControlFunction(
206
+ contractAddress,
207
+ 'get_role_member',
208
+ [roleId, index],
209
+ networkConfig,
210
+ inputs
211
+ );
212
+
213
+ if (result === undefined || result === null) {
214
+ return null;
215
+ }
216
+
217
+ return String(result);
218
+ } catch (error) {
219
+ logger.error('getRoleMember', `Failed to get role member at index ${index}:`, error);
220
+ return null;
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Enumerates all members of a specific role
226
+ *
227
+ * Fetches all members in parallel with controlled concurrency for improved
228
+ * performance while avoiding overwhelming RPC endpoints.
229
+ *
230
+ * @param contractAddress The contract address
231
+ * @param roleId The role identifier (Symbol)
232
+ * @param networkConfig The network configuration
233
+ * @returns Array of member addresses
234
+ */
235
+ export async function enumerateRoleMembers(
236
+ contractAddress: string,
237
+ roleId: string,
238
+ networkConfig: StellarNetworkConfig
239
+ ): Promise<string[]> {
240
+ logger.info('enumerateRoleMembers', `Enumerating members for role ${roleId}`);
241
+
242
+ try {
243
+ // Get the count of members
244
+ const count = await getRoleMemberCount(contractAddress, roleId, networkConfig);
245
+
246
+ logger.debug('enumerateRoleMembers', `Role ${roleId} has ${count} members`);
247
+
248
+ if (count === 0) {
249
+ return [];
250
+ }
251
+
252
+ // Create task functions for controlled concurrent execution
253
+ const memberTasks = Array.from(
254
+ { length: count },
255
+ (_, i) => () => getRoleMember(contractAddress, roleId, i, networkConfig)
256
+ );
257
+
258
+ // Fetch members with concurrency limit to avoid overwhelming RPC endpoints
259
+ const results = await promiseAllWithLimit(memberTasks, DEFAULT_CONCURRENCY_LIMIT);
260
+
261
+ // Filter out null results and return valid members
262
+ const members = results.filter((m): m is string => m !== null);
263
+
264
+ logger.debug('enumerateRoleMembers', `Retrieved ${members.length} members for role ${roleId}`);
265
+
266
+ return members;
267
+ } catch (error) {
268
+ logger.error('enumerateRoleMembers', `Failed to enumerate role ${roleId}:`, error);
269
+ throw new OperationFailed(
270
+ `Failed to enumerate role members: ${(error as Error).message}`,
271
+ contractAddress,
272
+ 'enumerateRoleMembers',
273
+ error as Error
274
+ );
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Reads all current role assignments for a contract
280
+ *
281
+ * Fetches all roles in parallel for improved performance.
282
+ *
283
+ * @param contractAddress The contract address
284
+ * @param roleIds Array of role identifiers to query
285
+ * @param networkConfig The network configuration
286
+ * @returns Array of role assignments
287
+ */
288
+ export async function readCurrentRoles(
289
+ contractAddress: string,
290
+ roleIds: string[],
291
+ networkConfig: StellarNetworkConfig
292
+ ): Promise<RoleAssignment[]> {
293
+ logger.info(
294
+ 'readCurrentRoles',
295
+ `Reading ${roleIds.length} roles for contract ${contractAddress}`
296
+ );
297
+
298
+ if (roleIds.length === 0) {
299
+ return [];
300
+ }
301
+
302
+ // Process all roles in parallel for improved performance
303
+ const assignmentPromises = roleIds.map(async (roleId) => {
304
+ const role: RoleIdentifier = {
305
+ id: roleId,
306
+ label: roleId.replace(/_/g, ' ').toLowerCase(),
307
+ };
308
+
309
+ try {
310
+ const members = await enumerateRoleMembers(contractAddress, roleId, networkConfig);
311
+
312
+ logger.debug('readCurrentRoles', `Role ${roleId} has ${members.length} members`);
313
+
314
+ return {
315
+ role,
316
+ members,
317
+ };
318
+ } catch (error) {
319
+ logger.warn('readCurrentRoles', `Failed to read role ${roleId}:`, error);
320
+ // Return role with empty members array to maintain array length consistency
321
+ return {
322
+ role,
323
+ members: [],
324
+ };
325
+ }
326
+ });
327
+
328
+ const assignments = await Promise.all(assignmentPromises);
329
+
330
+ logger.info(
331
+ 'readCurrentRoles',
332
+ `Completed reading ${assignments.length} roles with ${assignments.reduce((sum, a) => sum + a.members.length, 0)} total members`
333
+ );
334
+
335
+ return assignments;
336
+ }
337
+
338
+ /**
339
+ * Gets the admin role for a specific role
340
+ *
341
+ * @param contractAddress The contract address
342
+ * @param roleId The role identifier (Symbol)
343
+ * @param networkConfig The network configuration
344
+ * @returns The admin role identifier, or null if no admin role set
345
+ */
346
+ export async function getRoleAdmin(
347
+ contractAddress: string,
348
+ roleId: string,
349
+ networkConfig: StellarNetworkConfig
350
+ ): Promise<string | null> {
351
+ logger.debug('getRoleAdmin', `Getting admin role for ${roleId}`);
352
+
353
+ try {
354
+ const inputs = [{ name: 'role', type: 'Symbol' }];
355
+ const result = await queryAccessControlFunction(
356
+ contractAddress,
357
+ 'get_role_admin',
358
+ [roleId],
359
+ networkConfig,
360
+ inputs
361
+ );
362
+
363
+ // get_role_admin returns Option<Symbol>
364
+ if (result === undefined || result === null) {
365
+ return null;
366
+ }
367
+
368
+ return String(result);
369
+ } catch (error) {
370
+ logger.error('getRoleAdmin', `Failed to get admin role for ${roleId}:`, error);
371
+ return null;
372
+ }
373
+ }
374
+
375
+ /**
376
+ * Gets the top-level admin account
377
+ *
378
+ * @param contractAddress The contract address
379
+ * @param networkConfig The network configuration
380
+ * @returns The admin address, or null if no admin set
381
+ */
382
+ export async function getAdmin(
383
+ contractAddress: string,
384
+ networkConfig: StellarNetworkConfig
385
+ ): Promise<string | null> {
386
+ logger.info('getAdmin', `Reading admin for contract ${contractAddress}`);
387
+
388
+ try {
389
+ const result = await queryAccessControlFunction(
390
+ contractAddress,
391
+ 'get_admin',
392
+ [],
393
+ networkConfig
394
+ );
395
+
396
+ // get_admin returns Option<Address>
397
+ if (result === undefined || result === null) {
398
+ return null;
399
+ }
400
+
401
+ return String(result);
402
+ } catch (error) {
403
+ logger.error('getAdmin', 'Failed to read admin:', error);
404
+ return null;
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Gets the current ledger sequence number from the Soroban RPC
410
+ *
411
+ * Used for two-step Ownable contracts to:
412
+ * - Calculate appropriate expiration ledgers for ownership transfers
413
+ * - Validate expiration ledgers before submitting transactions
414
+ * - Determine if pending ownership transfers have expired
415
+ *
416
+ * @param networkConfig The network configuration containing the Soroban RPC URL
417
+ * @returns Promise resolving to the current ledger sequence number
418
+ * @throws OperationFailed if the RPC call fails
419
+ *
420
+ * @example
421
+ * ```typescript
422
+ * const currentLedger = await getCurrentLedger(networkConfig);
423
+ * // Set expiration to ~1 hour from now (~720 ledgers at 5s/ledger)
424
+ * const expirationLedger = currentLedger + 720;
425
+ * ```
426
+ */
427
+ export async function getCurrentLedger(networkConfig: StellarNetworkConfig): Promise<number> {
428
+ logger.info('getCurrentLedger', `Fetching current ledger from ${networkConfig.sorobanRpcUrl}`);
429
+
430
+ try {
431
+ const server = new StellarRpc.Server(networkConfig.sorobanRpcUrl);
432
+ const latestLedger = await server.getLatestLedger();
433
+
434
+ logger.debug('getCurrentLedger', `Current ledger: ${latestLedger.sequence}`);
435
+
436
+ return latestLedger.sequence;
437
+ } catch (error) {
438
+ logger.error('getCurrentLedger', 'Failed to fetch current ledger:', error);
439
+ throw new OperationFailed(
440
+ `Failed to get current ledger: ${(error as Error).message}`,
441
+ networkConfig.sorobanRpcUrl,
442
+ 'getCurrentLedger',
443
+ error as Error
444
+ );
445
+ }
446
+ }