@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,575 @@
1
+ import {
2
+ Account,
3
+ BASE_FEE,
4
+ Contract,
5
+ rpc as StellarRpc,
6
+ TransactionBuilder,
7
+ xdr,
8
+ } from '@stellar/stellar-sdk';
9
+
10
+ import {
11
+ Configuration,
12
+ RelayersApi,
13
+ type ApiResponseRelayerResponseData,
14
+ type ApiResponseRelayerStatusDataOneOf1,
15
+ type ScVal,
16
+ type StellarTransactionRequest,
17
+ type StellarTransactionResponse,
18
+ } from '@openzeppelin/relayer-sdk';
19
+ import type {
20
+ ExecutionConfig,
21
+ RelayerDetails,
22
+ RelayerDetailsRich,
23
+ RelayerExecutionConfig,
24
+ StellarNetworkConfig,
25
+ TransactionStatusUpdate,
26
+ TxStatus,
27
+ } from '@openzeppelin/ui-types';
28
+ import { logger } from '@openzeppelin/ui-utils';
29
+
30
+ import { CALLER_PLACEHOLDER } from '../access-control/actions';
31
+ import { valueToScVal } from '../transform/input-parser';
32
+ import { getStellarWalletConnectionStatus, signTransaction } from '../wallet/connection';
33
+ import { ExecutionStrategy } from './execution-strategy';
34
+ import type { StellarTransactionData } from './formatter';
35
+
36
+ /**
37
+ * Stellar-specific transaction options for the OpenZeppelin Relayer.
38
+ * These options map directly to the StellarTransactionRequest parameters in the SDK.
39
+ */
40
+ export interface StellarRelayerTransactionOptions {
41
+ // Basic options
42
+ maxFee?: number;
43
+
44
+ // Transaction expiration
45
+ validUntil?: string; // ISO 8601 date string
46
+
47
+ // Fee bump for stuck transactions
48
+ feeBump?: boolean;
49
+ }
50
+
51
+ /**
52
+ * Implements the ExecutionStrategy for the OpenZeppelin Relayer for Stellar networks.
53
+ * This strategy sends the transaction to the relayer service, which then handles
54
+ * fee payment, signing, and broadcasting on Stellar/Soroban. It includes a polling
55
+ * mechanism to wait for the transaction to be confirmed and return the final hash.
56
+ */
57
+ export class RelayerExecutionStrategy implements ExecutionStrategy {
58
+ public async execute(
59
+ transactionData: StellarTransactionData,
60
+ executionConfig: ExecutionConfig,
61
+ networkConfig: StellarNetworkConfig,
62
+ onStatusChange: (status: TxStatus, details: TransactionStatusUpdate) => void,
63
+ runtimeApiKey?: string
64
+ ): Promise<{ txHash: string }> {
65
+ const relayerConfig = executionConfig as RelayerExecutionConfig;
66
+
67
+ if (!runtimeApiKey) {
68
+ throw new Error('API Key is required for Relayer execution.');
69
+ }
70
+
71
+ const { transactionId } = await this.sendTransactionViaRelayer(
72
+ transactionData,
73
+ relayerConfig,
74
+ networkConfig,
75
+ runtimeApiKey
76
+ );
77
+
78
+ onStatusChange('pendingRelayer', { transactionId });
79
+
80
+ const sdkConfig = new Configuration({
81
+ basePath: relayerConfig.serviceUrl,
82
+ accessToken: runtimeApiKey,
83
+ });
84
+
85
+ const txHash = await this.pollForTransactionHash(
86
+ relayerConfig.relayer.relayerId,
87
+ transactionId,
88
+ sdkConfig
89
+ );
90
+
91
+ return { txHash };
92
+ }
93
+
94
+ /**
95
+ * Fetches and filters relayers for Stellar networks from the OpenZeppelin Relayer service.
96
+ * This function handles pagination to retrieve all available relayers.
97
+ *
98
+ * @param serviceUrl The base URL of the relayer service.
99
+ * @param accessToken The session-based API key for authentication.
100
+ * @param networkConfig The Stellar network configuration to filter relayers by.
101
+ * @returns A promise that resolves to an array of compatible relayer details.
102
+ * @throws If the API call fails or returns an unsuccessful response.
103
+ */
104
+ public async getStellarRelayers(
105
+ serviceUrl: string,
106
+ accessToken: string,
107
+ networkConfig: StellarNetworkConfig
108
+ ): Promise<RelayerDetails[]> {
109
+ logger.info(
110
+ '[StellarRelayer] Getting relayers with access token',
111
+ accessToken.slice(0, 5).padEnd(accessToken.length, '*')
112
+ );
113
+ const sdkConfig = new Configuration({
114
+ basePath: serviceUrl,
115
+ accessToken,
116
+ });
117
+ const relayersApi = new RelayersApi(sdkConfig);
118
+
119
+ let allRelayers: ApiResponseRelayerResponseData[] = [];
120
+ let currentPage = 1;
121
+ let totalItems = 0;
122
+ let hasMore = true;
123
+
124
+ do {
125
+ const { data } = await relayersApi.listRelayers(currentPage, 100);
126
+
127
+ if (!data.success || !data.data) {
128
+ throw new Error(`Failed to fetch relayers on page ${currentPage}.`);
129
+ }
130
+
131
+ allRelayers = [...allRelayers, ...data.data];
132
+ totalItems = data.pagination?.total_items || 0;
133
+
134
+ if (allRelayers.length >= totalItems) {
135
+ hasMore = false;
136
+ } else {
137
+ currentPage++;
138
+ }
139
+ } while (hasMore);
140
+
141
+ return allRelayers
142
+ .filter(
143
+ (r: ApiResponseRelayerResponseData) =>
144
+ r.network_type === 'stellar' && networkConfig.id.includes(r.network)
145
+ )
146
+ .map((r: ApiResponseRelayerResponseData) => ({
147
+ relayerId: r.id,
148
+ name: r.name,
149
+ address: r.address || '',
150
+ network: r.network,
151
+ paused: r.paused || false,
152
+ }));
153
+ }
154
+
155
+ /**
156
+ * Fetches comprehensive information about a specific Stellar relayer including balance and status.
157
+ * This function combines multiple SDK API calls to provide rich relayer details.
158
+ *
159
+ * @param serviceUrl The base URL of the relayer service.
160
+ * @param accessToken The session-based API key for authentication.
161
+ * @param relayerId The unique identifier of the relayer.
162
+ * @param networkConfig The Stellar network configuration for context.
163
+ * @returns A promise that resolves to enhanced relayer details including balance and status.
164
+ * @throws If any API call fails or returns an unsuccessful response.
165
+ */
166
+ public async getStellarRelayer(
167
+ serviceUrl: string,
168
+ accessToken: string,
169
+ relayerId: string,
170
+ _networkConfig: StellarNetworkConfig
171
+ ): Promise<RelayerDetailsRich> {
172
+ logger.info('[StellarRelayer] Getting detailed relayer info', relayerId);
173
+
174
+ const sdkConfig = new Configuration({
175
+ basePath: serviceUrl,
176
+ accessToken,
177
+ });
178
+ const relayersApi = new RelayersApi(sdkConfig);
179
+
180
+ try {
181
+ // Fetch basic relayer details, balance, and status in parallel
182
+ const [relayerResponse, balanceResponse, statusResponse] = await Promise.all([
183
+ relayersApi.getRelayer(relayerId),
184
+ relayersApi.getRelayerBalance(relayerId).catch((err) => {
185
+ logger.warn('[StellarRelayer] Failed to fetch balance', err);
186
+ return null;
187
+ }),
188
+ relayersApi.getRelayerStatus(relayerId).catch((err) => {
189
+ logger.warn('[StellarRelayer] Failed to fetch status', err);
190
+ return null;
191
+ }),
192
+ ]);
193
+
194
+ if (!relayerResponse.data.success || !relayerResponse.data.data) {
195
+ throw new Error(`Failed to fetch relayer details for ID: ${relayerId}`);
196
+ }
197
+
198
+ const relayerData = relayerResponse.data.data;
199
+
200
+ // Build enhanced relayer details object
201
+ const enhancedDetails: RelayerDetailsRich = {
202
+ relayerId: relayerData.id,
203
+ name: relayerData.name,
204
+ address: relayerData.address || '',
205
+ network: relayerData.network,
206
+ paused: relayerData.paused || false,
207
+ systemDisabled: relayerData.system_disabled || false,
208
+ };
209
+
210
+ // Add balance if available (Stellar native balance in lumens)
211
+ if (balanceResponse?.data?.success && balanceResponse.data.data?.balance) {
212
+ try {
213
+ // Stellar balance is in stroops (1 XLM = 10,000,000 stroops)
214
+ const balanceInStroops = Number(balanceResponse.data.data.balance);
215
+ const balanceInXlm = balanceInStroops / 10000000;
216
+ enhancedDetails.balance = `${balanceInXlm.toFixed(7)} XLM`;
217
+ } catch (error) {
218
+ logger.warn('[StellarRelayer] Failed to format balance, using raw value', String(error));
219
+ enhancedDetails.balance = String(balanceResponse.data.data.balance);
220
+ }
221
+ }
222
+
223
+ // Add status details if available
224
+ if (statusResponse?.data?.success && statusResponse.data.data) {
225
+ const statusData = statusResponse.data.data;
226
+ if (statusData.network_type === 'stellar') {
227
+ // Type guard to ensure we have Stellar-specific fields
228
+ const stellarStatusData = statusData as ApiResponseRelayerStatusDataOneOf1;
229
+ if (
230
+ stellarStatusData.sequence_number !== undefined &&
231
+ stellarStatusData.sequence_number !== null
232
+ ) {
233
+ enhancedDetails.nonce = String(stellarStatusData.sequence_number);
234
+ }
235
+ if (stellarStatusData.pending_transactions_count !== undefined) {
236
+ enhancedDetails.pendingTransactionsCount = stellarStatusData.pending_transactions_count;
237
+ }
238
+ if (stellarStatusData.last_confirmed_transaction_timestamp) {
239
+ enhancedDetails.lastConfirmedTransactionTimestamp =
240
+ stellarStatusData.last_confirmed_transaction_timestamp;
241
+ }
242
+ }
243
+ }
244
+
245
+ logger.info(
246
+ '[StellarRelayer] Retrieved enhanced relayer details',
247
+ JSON.stringify(enhancedDetails)
248
+ );
249
+ return enhancedDetails;
250
+ } catch (error) {
251
+ logger.error(
252
+ '[StellarRelayer] Failed to get relayer details',
253
+ error instanceof Error ? error.message : String(error)
254
+ );
255
+ throw error;
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Submits a Stellar transaction to the relayer service for asynchronous processing.
261
+ * @param transactionData The Stellar contract transaction data.
262
+ * @param executionConfig The relayer-specific execution configuration.
263
+ * @param networkConfig The Stellar network configuration.
264
+ * @param runtimeApiKey The user's session-only API key.
265
+ * @returns A promise that resolves to an object containing the transaction ID assigned by the relayer.
266
+ */
267
+ private async sendTransactionViaRelayer(
268
+ transactionData: StellarTransactionData,
269
+ executionConfig: RelayerExecutionConfig,
270
+ _networkConfig: StellarNetworkConfig,
271
+ runtimeApiKey: string
272
+ ): Promise<{ transactionId: string }> {
273
+ // Type-safe extraction of Stellar-specific options
274
+ const stellarOptions = executionConfig.transactionOptions as
275
+ | StellarRelayerTransactionOptions
276
+ | undefined;
277
+
278
+ // If fee bump is requested, use signed XDR mode per relayer docs
279
+ // Only valid when providing a signed transaction_xdr
280
+ let relayerTxRequest: StellarTransactionRequest;
281
+ if (stellarOptions?.feeBump) {
282
+ const signedInnerXdr = await this.buildSignedInnerTransactionXdr(
283
+ transactionData,
284
+ _networkConfig,
285
+ stellarOptions
286
+ );
287
+
288
+ relayerTxRequest = {
289
+ network: executionConfig.relayer.network,
290
+ transaction_xdr: signedInnerXdr,
291
+ fee_bump: true,
292
+ ...(stellarOptions?.maxFee !== undefined && { max_fee: stellarOptions.maxFee }),
293
+ ...(stellarOptions?.validUntil !== undefined && { valid_until: stellarOptions.validUntil }),
294
+ };
295
+ } else {
296
+ // Default operations-based mode
297
+ relayerTxRequest = {
298
+ network: executionConfig.relayer.network, // Use relayer's network (e.g., 'testnet', 'mainnet')
299
+ source_account: executionConfig.relayer.address, // Use relayer's address as source account
300
+ operations: [
301
+ {
302
+ type: 'invoke_contract',
303
+ contract_address: transactionData.contractAddress,
304
+ function_name: transactionData.functionName,
305
+ args: this.convertArgsToScVal(transactionData),
306
+ // No auth field needed - using source_account at top level
307
+ },
308
+ ],
309
+ // Include optional parameters if provided
310
+ ...(stellarOptions?.maxFee !== undefined && { max_fee: stellarOptions.maxFee }),
311
+ ...(stellarOptions?.validUntil !== undefined && { valid_until: stellarOptions.validUntil }),
312
+ // Note: fee_bump is not supported by the relayer service in operations mode
313
+ // Memos are not supported for Soroban contract operations
314
+ };
315
+ }
316
+
317
+ const sdkConfig = new Configuration({
318
+ basePath: executionConfig.serviceUrl,
319
+ accessToken: runtimeApiKey,
320
+ });
321
+ const relayersApi = new RelayersApi(sdkConfig);
322
+
323
+ const result = await relayersApi.sendTransaction(
324
+ executionConfig.relayer.relayerId,
325
+ relayerTxRequest
326
+ );
327
+
328
+ if (!result.data.success || !result.data.data?.id) {
329
+ throw new Error(`Relayer API failed to return a transaction ID. Error: ${result.data.error}`);
330
+ }
331
+
332
+ return { transactionId: result.data.data.id };
333
+ }
334
+
335
+ /**
336
+ * Converts Stellar transaction arguments to ScVal format for the relayer.
337
+ * Uses the same comprehensive conversion utility as the EOA execution strategy.
338
+ */
339
+ private convertArgsToScVal(transactionData: StellarTransactionData): ScVal[] {
340
+ // Use the same comprehensive conversion as EOA execution strategy
341
+ return transactionData.args.map((arg, index) => {
342
+ const argType = transactionData.argTypes[index];
343
+ const argSchema = transactionData.argSchema?.[index]; // Pass schema for struct field type resolution
344
+
345
+ const scVal = valueToScVal(arg, argType, argSchema);
346
+
347
+ // Convert Stellar SDK ScVal to relayer SDK ScVal format
348
+ return this.stellarScValToRelayerScVal(scVal);
349
+ });
350
+ }
351
+
352
+ /**
353
+ * Build and sign the inner transaction using the connected wallet.
354
+ * Returns the signed inner transaction XDR to be wrapped by the relayer as a fee bump.
355
+ */
356
+ private async buildSignedInnerTransactionXdr(
357
+ txData: StellarTransactionData,
358
+ stellarConfig: StellarNetworkConfig,
359
+ _options?: StellarRelayerTransactionOptions
360
+ ): Promise<string> {
361
+ const rpcServer = this.getSorobanRpcServer(stellarConfig);
362
+ const connectedAddress = this.getConnectedWalletAddress();
363
+
364
+ // Fetch sequence for the connected address
365
+ const accountResponse = await rpcServer.getAccount(connectedAddress);
366
+ const sourceAccount = new Account(connectedAddress, accountResponse.sequenceNumber());
367
+
368
+ // Build a contract invocation transaction
369
+ const contract = new Contract(txData.contractAddress);
370
+ const transactionBuilder = new TransactionBuilder(sourceAccount, {
371
+ fee: BASE_FEE,
372
+ networkPassphrase: stellarConfig.networkPassphrase,
373
+ });
374
+
375
+ // Replace CALLER_PLACEHOLDER with the connected wallet address
376
+ // This supports OpenZeppelin Stellar access control functions that require a caller parameter
377
+ const resolvedArgs = txData.args.map((arg) =>
378
+ arg === CALLER_PLACEHOLDER ? connectedAddress : arg
379
+ );
380
+
381
+ const scValArgs = resolvedArgs.map((arg, index) => {
382
+ const argType = txData.argTypes[index];
383
+ const argSchema = txData.argSchema?.[index];
384
+ return valueToScVal(arg, argType, argSchema);
385
+ });
386
+
387
+ transactionBuilder.addOperation(contract.call(txData.functionName, ...scValArgs));
388
+
389
+ // Note: Soroban contract transactions do not support memos; do not attach
390
+
391
+ // Timeout: keep short; relayer will wrap in fee bump and submit
392
+ transactionBuilder.setTimeout(30);
393
+
394
+ // Build → simulate → prepare
395
+ let transaction = transactionBuilder.build();
396
+
397
+ const simulation = await rpcServer.simulateTransaction(transaction);
398
+ if (StellarRpc.Api.isSimulationError(simulation)) {
399
+ throw new Error(`Transaction simulation failed: ${simulation.error}`);
400
+ }
401
+ transaction = await rpcServer.prepareTransaction(transaction);
402
+
403
+ // Sign with connected wallet
404
+ const signResult = await signTransaction(transaction.toXDR(), connectedAddress);
405
+ const signedTx = TransactionBuilder.fromXDR(
406
+ signResult.signedTxXdr,
407
+ stellarConfig.networkPassphrase
408
+ );
409
+
410
+ // Ensure we have a standard Transaction (not FeeBumpTransaction)
411
+ if ('memo' in signedTx && 'sequence' in signedTx) {
412
+ return signedTx.toXDR();
413
+ }
414
+ throw new Error('Unexpected transaction type returned from signing');
415
+ }
416
+
417
+ /**
418
+ * Get Soroban RPC Server instance with current configuration and user overrides.
419
+ */
420
+ private getSorobanRpcServer(networkConfig: StellarNetworkConfig): StellarRpc.Server {
421
+ // Allow HTTP for localhost development
422
+ const rpcUrl = networkConfig.sorobanRpcUrl;
423
+ if (!rpcUrl) {
424
+ throw new Error(`No Soroban RPC URL available for network ${networkConfig.name}`);
425
+ }
426
+ const allowHttp = new URL(rpcUrl).hostname === 'localhost';
427
+ return new StellarRpc.Server(rpcUrl, { allowHttp });
428
+ }
429
+
430
+ private getConnectedWalletAddress(): string {
431
+ const connectionStatus = getStellarWalletConnectionStatus();
432
+ if (!connectionStatus.isConnected || !connectionStatus.address) {
433
+ throw new Error('No connected wallet found. Please connect your Stellar wallet first.');
434
+ }
435
+ return connectionStatus.address;
436
+ }
437
+
438
+ /**
439
+ * Converts a Stellar SDK ScVal to the relayer SDK ScVal format.
440
+ * The relayer SDK uses a simplified ScVal representation compared to Stellar SDK's XDR types.
441
+ */
442
+ private stellarScValToRelayerScVal(stellarScVal: xdr.ScVal): ScVal {
443
+ const scValType = stellarScVal.switch();
444
+
445
+ switch (scValType.name) {
446
+ case 'scvBool':
447
+ return { bool: stellarScVal.b() };
448
+ case 'scvVoid':
449
+ return { bool: false }; // Fallback for void
450
+ case 'scvU32':
451
+ return { u32: stellarScVal.u32() };
452
+ case 'scvI32':
453
+ return { i32: stellarScVal.i32() };
454
+ case 'scvU64':
455
+ return { u64: stellarScVal.u64().toString() };
456
+ case 'scvI64':
457
+ return { i64: stellarScVal.i64().toString() };
458
+ case 'scvU128': {
459
+ const u128Parts = stellarScVal.u128();
460
+ return {
461
+ u128: {
462
+ hi: u128Parts.hi().toString(),
463
+ lo: u128Parts.lo().toString(),
464
+ },
465
+ };
466
+ }
467
+ case 'scvI128': {
468
+ const i128Parts = stellarScVal.i128();
469
+ return {
470
+ i128: {
471
+ hi: i128Parts.hi().toString(),
472
+ lo: i128Parts.lo().toString(),
473
+ },
474
+ };
475
+ }
476
+ case 'scvU256': {
477
+ const u256Parts = stellarScVal.u256();
478
+ return {
479
+ u256: {
480
+ hi_hi: u256Parts.hiHi().toString(),
481
+ hi_lo: u256Parts.hiLo().toString(),
482
+ lo_hi: u256Parts.loHi().toString(),
483
+ lo_lo: u256Parts.loLo().toString(),
484
+ },
485
+ };
486
+ }
487
+ case 'scvI256': {
488
+ const i256Parts = stellarScVal.i256();
489
+ return {
490
+ i256: {
491
+ hi_hi: i256Parts.hiHi().toString(),
492
+ hi_lo: i256Parts.hiLo().toString(),
493
+ lo_hi: i256Parts.loHi().toString(),
494
+ lo_lo: i256Parts.loLo().toString(),
495
+ },
496
+ };
497
+ }
498
+ case 'scvBytes':
499
+ return { bytes: stellarScVal.bytes().toString('hex') };
500
+ case 'scvString':
501
+ return { string: stellarScVal.str().toString() };
502
+ case 'scvSymbol':
503
+ return { symbol: stellarScVal.sym().toString() };
504
+ case 'scvVec':
505
+ return {
506
+ vec: stellarScVal.vec()?.map((val) => this.stellarScValToRelayerScVal(val)) || [],
507
+ };
508
+ case 'scvMap': {
509
+ const mapEntries = stellarScVal.map() || [];
510
+ return {
511
+ map: mapEntries.map((entry) => ({
512
+ key: this.stellarScValToRelayerScVal(entry.key()),
513
+ val: this.stellarScValToRelayerScVal(entry.val()),
514
+ })),
515
+ };
516
+ }
517
+ case 'scvAddress':
518
+ return { address: stellarScVal.address().toString() };
519
+ default:
520
+ // For any unhandled types, convert to string representation as fallback
521
+ return { string: stellarScVal.toString() };
522
+ }
523
+ }
524
+
525
+ /**
526
+ * Polls the relayer for a Stellar transaction's status until it is confirmed and has a hash, or fails.
527
+ * @param relayerId The ID of the relayer processing the transaction.
528
+ * @param transactionId The ID of the transaction to poll.
529
+ * @param sdkConfig The SDK configuration containing the necessary authentication.
530
+ * @returns A promise that resolves to the final transaction hash.
531
+ * @throws If the transaction fails or polling times out.
532
+ */
533
+ private async pollForTransactionHash(
534
+ relayerId: string,
535
+ transactionId: string,
536
+ sdkConfig: Configuration
537
+ ): Promise<string> {
538
+ const relayersApi = new RelayersApi(sdkConfig);
539
+ const POLLING_INTERVAL = 2000;
540
+ const POLLING_TIMEOUT = 300000; // 5 minutes in milliseconds
541
+ const startTime = Date.now();
542
+
543
+ while (Date.now() - startTime < POLLING_TIMEOUT) {
544
+ const { data } = await relayersApi.getTransactionById(relayerId, transactionId);
545
+
546
+ if (!data.success || !data.data) {
547
+ throw new Error(`Failed to get transaction status for ID: ${transactionId}`);
548
+ }
549
+
550
+ const txResponse = data.data as StellarTransactionResponse;
551
+
552
+ if (txResponse.status === 'mined' || txResponse.status === 'confirmed') {
553
+ if (!txResponse.hash) {
554
+ throw new Error(
555
+ `Transaction is confirmed but no hash was returned for ID: ${transactionId}`
556
+ );
557
+ }
558
+ return txResponse.hash;
559
+ }
560
+
561
+ if (
562
+ txResponse.status === 'failed' ||
563
+ txResponse.status === 'canceled' ||
564
+ txResponse.status === 'expired'
565
+ ) {
566
+ throw new Error(`Transaction ${txResponse.status}`);
567
+ }
568
+
569
+ // Continue polling for 'pending' or 'sent' statuses
570
+ await new Promise((resolve) => setTimeout(resolve, POLLING_INTERVAL));
571
+ }
572
+
573
+ throw new Error(`Polling for transaction hash timed out for ID: ${transactionId}`);
574
+ }
575
+ }