@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.
- package/README.md +272 -0
- package/dist/config.cjs +21 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +8 -0
- package/dist/config.d.cts.map +1 -0
- package/dist/config.d.mts +8 -0
- package/dist/config.d.mts.map +1 -0
- package/dist/config.mjs +20 -0
- package/dist/config.mjs.map +1 -0
- package/dist/index.cjs +7564 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +261 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +263 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +7529 -0
- package/dist/index.mjs.map +1 -0
- package/dist/metadata.cjs +22 -0
- package/dist/metadata.cjs.map +1 -0
- package/dist/metadata.d.cts +7 -0
- package/dist/metadata.d.cts.map +1 -0
- package/dist/metadata.d.mts +7 -0
- package/dist/metadata.d.mts.map +1 -0
- package/dist/metadata.mjs +21 -0
- package/dist/metadata.mjs.map +1 -0
- package/dist/networks-BrV516-R.d.cts +15 -0
- package/dist/networks-BrV516-R.d.cts.map +1 -0
- package/dist/networks-C0MmhJcu.d.mts +15 -0
- package/dist/networks-C0MmhJcu.d.mts.map +1 -0
- package/dist/networks-DgUFSTiC.cjs +76 -0
- package/dist/networks-DgUFSTiC.cjs.map +1 -0
- package/dist/networks-QbEPbaGT.mjs +46 -0
- package/dist/networks-QbEPbaGT.mjs.map +1 -0
- package/dist/networks.cjs +8 -0
- package/dist/networks.d.cts +2 -0
- package/dist/networks.d.mts +2 -0
- package/dist/networks.mjs +3 -0
- package/dist/vite-config.cjs +43 -0
- package/dist/vite-config.cjs.map +1 -0
- package/dist/vite-config.d.cts +35 -0
- package/dist/vite-config.d.cts.map +1 -0
- package/dist/vite-config.d.mts +35 -0
- package/dist/vite-config.d.mts.map +1 -0
- package/dist/vite-config.mjs +42 -0
- package/dist/vite-config.mjs.map +1 -0
- package/package.json +114 -0
- package/src/__tests__/getDefaultServiceConfig.test.ts +105 -0
- package/src/access-control/actions.ts +214 -0
- package/src/access-control/feature-detection.ts +238 -0
- package/src/access-control/index.ts +54 -0
- package/src/access-control/indexer-client.ts +1474 -0
- package/src/access-control/onchain-reader.ts +446 -0
- package/src/access-control/service.ts +1431 -0
- package/src/access-control/validation.ts +256 -0
- package/src/adapter.ts +659 -0
- package/src/config.ts +43 -0
- package/src/configuration/__tests__/explorer.test.ts +80 -0
- package/src/configuration/__tests__/rpc.test.ts +355 -0
- package/src/configuration/execution.ts +83 -0
- package/src/configuration/explorer.ts +105 -0
- package/src/configuration/index.ts +5 -0
- package/src/configuration/network-services.ts +210 -0
- package/src/configuration/rpc.ts +270 -0
- package/src/configuration.ts +2 -0
- package/src/contract/__tests__/complete-type-coverage.test.ts +78 -0
- package/src/contract/index.ts +3 -0
- package/src/contract/loader.ts +498 -0
- package/src/contract/transformer.ts +1 -0
- package/src/contract/type.ts +65 -0
- package/src/index.ts +23 -0
- package/src/mapping/constants.ts +89 -0
- package/src/mapping/enum-metadata.ts +237 -0
- package/src/mapping/field-generator.ts +296 -0
- package/src/mapping/index.ts +5 -0
- package/src/mapping/struct-fields.ts +106 -0
- package/src/mapping/tuple-components.ts +43 -0
- package/src/mapping/type-coverage-validator.ts +151 -0
- package/src/mapping/type-mapper.ts +203 -0
- package/src/metadata.ts +16 -0
- package/src/networks/README.md +84 -0
- package/src/networks/index.ts +19 -0
- package/src/networks/mainnet.ts +20 -0
- package/src/networks/testnet.ts +20 -0
- package/src/networks.ts +2 -0
- package/src/query/handler.ts +411 -0
- package/src/query/index.ts +4 -0
- package/src/query/view-checker.ts +32 -0
- package/src/sac/spec-cache.ts +68 -0
- package/src/sac/spec-source.ts +35 -0
- package/src/sac/xdr.ts +101 -0
- package/src/transaction/components/AdvancedInfo.tsx +34 -0
- package/src/transaction/components/FeeConfiguration.tsx +41 -0
- package/src/transaction/components/StellarRelayerOptions.tsx +60 -0
- package/src/transaction/components/TransactionTiming.tsx +77 -0
- package/src/transaction/components/index.ts +5 -0
- package/src/transaction/components/useStellarRelayerOptions.ts +114 -0
- package/src/transaction/eoa.ts +229 -0
- package/src/transaction/execution-strategy.ts +33 -0
- package/src/transaction/formatter.ts +296 -0
- package/src/transaction/index.ts +4 -0
- package/src/transaction/relayer.ts +575 -0
- package/src/transaction/sender.ts +156 -0
- package/src/transform/index.ts +4 -0
- package/src/transform/input-parser.ts +9 -0
- package/src/transform/output-formatter.ts +133 -0
- package/src/transform/parsers/complex-parser.ts +157 -0
- package/src/transform/parsers/generic-parser.ts +171 -0
- package/src/transform/parsers/index.ts +86 -0
- package/src/transform/parsers/primitive-parser.ts +123 -0
- package/src/transform/parsers/scval-converter.ts +405 -0
- package/src/transform/parsers/struct-parser.ts +324 -0
- package/src/transform/parsers/types.ts +35 -0
- package/src/types/__tests__/artifacts.test.ts +89 -0
- package/src/types/artifacts.ts +19 -0
- package/src/utils/__tests__/artifacts.test.ts +77 -0
- package/src/utils/artifacts.ts +30 -0
- package/src/utils/formatting.ts +122 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/input-parsing.ts +336 -0
- package/src/utils/safe-type-parser.ts +303 -0
- package/src/utils/stellar-types.ts +35 -0
- package/src/utils/type-detection.ts +163 -0
- package/src/utils/xdr-ordering.ts +36 -0
- package/src/validation/__tests__/address.test.ts +267 -0
- package/src/validation/address.ts +136 -0
- package/src/validation/eoa.ts +33 -0
- package/src/validation/index.ts +3 -0
- package/src/validation/relayer.ts +13 -0
- package/src/vite-config.ts +67 -0
- package/src/wallet/README.md +93 -0
- package/src/wallet/__tests__/connection.test.ts +72 -0
- package/src/wallet/components/StellarWalletUiRoot.tsx +161 -0
- package/src/wallet/components/account/AccountDisplay.tsx +50 -0
- package/src/wallet/components/connect/ConnectButton.tsx +100 -0
- package/src/wallet/components/connect/ConnectorDialog.tsx +125 -0
- package/src/wallet/components/index.ts +3 -0
- package/src/wallet/connection.ts +151 -0
- package/src/wallet/context/StellarWalletContext.ts +32 -0
- package/src/wallet/context/index.ts +4 -0
- package/src/wallet/context/useStellarWalletContext.ts +17 -0
- package/src/wallet/hooks/facade-hooks.ts +31 -0
- package/src/wallet/hooks/index.ts +7 -0
- package/src/wallet/hooks/useStellarAccount.ts +27 -0
- package/src/wallet/hooks/useStellarConnect.ts +60 -0
- package/src/wallet/hooks/useStellarDisconnect.ts +47 -0
- package/src/wallet/hooks/useUiKitConfig.ts +40 -0
- package/src/wallet/implementation/wallets-kit-implementation.ts +379 -0
- package/src/wallet/index.ts +11 -0
- package/src/wallet/services/__tests__/configResolutionService.test.ts +163 -0
- package/src/wallet/services/configResolutionService.ts +65 -0
- package/src/wallet/stellar-wallets-kit/StellarWalletsKitConnectButton.tsx +82 -0
- package/src/wallet/stellar-wallets-kit/__mocks__/@creit.tech/stellar-wallets-kit.ts +48 -0
- package/src/wallet/stellar-wallets-kit/__tests__/export-service.test.ts +93 -0
- package/src/wallet/stellar-wallets-kit/__tests__/stellarUiKitManager.test.ts +0 -0
- package/src/wallet/stellar-wallets-kit/config-generator.ts +75 -0
- package/src/wallet/stellar-wallets-kit/export-service.ts +19 -0
- package/src/wallet/stellar-wallets-kit/index.ts +3 -0
- package/src/wallet/stellar-wallets-kit/stellarUiKitManager.ts +235 -0
- package/src/wallet/types.ts +19 -0
- package/src/wallet/utils/__tests__/filterWalletComponents.test.ts +150 -0
- package/src/wallet/utils/__tests__/uiKitService.test.ts +189 -0
- package/src/wallet/utils/filterWalletComponents.ts +89 -0
- package/src/wallet/utils/index.ts +3 -0
- package/src/wallet/utils/stellarWalletImplementationManager.ts +118 -0
- 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
|
+
}
|