@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,411 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Account,
|
|
3
|
+
Address,
|
|
4
|
+
BASE_FEE,
|
|
5
|
+
Contract,
|
|
6
|
+
nativeToScVal,
|
|
7
|
+
rpc as StellarRpc,
|
|
8
|
+
TransactionBuilder,
|
|
9
|
+
xdr,
|
|
10
|
+
} from '@stellar/stellar-sdk';
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
ContractSchema,
|
|
14
|
+
FunctionParameter,
|
|
15
|
+
NetworkConfig,
|
|
16
|
+
StellarNetworkConfig,
|
|
17
|
+
} from '@openzeppelin/ui-types';
|
|
18
|
+
import { logger, userRpcConfigService } from '@openzeppelin/ui-utils';
|
|
19
|
+
|
|
20
|
+
import { parseStellarInput } from '../transform';
|
|
21
|
+
import { formatStellarFunctionResult } from '../transform/output-formatter';
|
|
22
|
+
import { convertStellarTypeToScValType } from '../utils';
|
|
23
|
+
import { isStellarViewFunction } from './view-checker';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Private helper to get a Soroban RPC Server instance for view queries.
|
|
27
|
+
* Prioritizes custom RPC configuration, then falls back to network default.
|
|
28
|
+
*/
|
|
29
|
+
function getSorobanRpcServer(networkConfig: StellarNetworkConfig): StellarRpc.Server {
|
|
30
|
+
// Check if there's a custom RPC configuration
|
|
31
|
+
const customRpcConfig = userRpcConfigService.getUserRpcConfig(networkConfig.id);
|
|
32
|
+
const rpcUrl = customRpcConfig?.url || networkConfig.sorobanRpcUrl;
|
|
33
|
+
|
|
34
|
+
if (!rpcUrl) {
|
|
35
|
+
throw new Error(`No Soroban RPC URL available for network ${networkConfig.name}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
logger.info(
|
|
39
|
+
'getSorobanRpcServer',
|
|
40
|
+
`Creating Soroban RPC server for ${networkConfig.name} using RPC: ${rpcUrl}`
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Allow HTTP for localhost development
|
|
44
|
+
const allowHttp = new URL(rpcUrl).hostname === 'localhost';
|
|
45
|
+
|
|
46
|
+
return new StellarRpc.Server(rpcUrl, {
|
|
47
|
+
allowHttp,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Creates a dummy transaction to simulate a contract function call.
|
|
53
|
+
* This is used for view functions that don't modify state.
|
|
54
|
+
*/
|
|
55
|
+
async function createSimulationTransaction(
|
|
56
|
+
contractAddress: string,
|
|
57
|
+
functionName: string,
|
|
58
|
+
args: unknown[],
|
|
59
|
+
paramTypes: string[],
|
|
60
|
+
networkConfig: StellarNetworkConfig
|
|
61
|
+
): Promise<TransactionBuilder> {
|
|
62
|
+
try {
|
|
63
|
+
// Create a dummy source account for simulation
|
|
64
|
+
// For simulations, we only need a valid public key - no private key required.
|
|
65
|
+
// Using a hardcoded test public key avoids Keypair.random() issues in test environments
|
|
66
|
+
// where @noble/curves crypto may not work correctly.
|
|
67
|
+
// This is a valid Stellar public key (the "zero" key derived from 32 zero bytes).
|
|
68
|
+
const SIMULATION_PUBLIC_KEY = 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF';
|
|
69
|
+
const sourceAccount = new Account(SIMULATION_PUBLIC_KEY, '0');
|
|
70
|
+
|
|
71
|
+
// Create contract instance
|
|
72
|
+
const contract = new Contract(contractAddress);
|
|
73
|
+
|
|
74
|
+
// Convert args to ScVal with proper type hints
|
|
75
|
+
const scValArgs = args.map((arg, index) => {
|
|
76
|
+
const paramType = paramTypes[index];
|
|
77
|
+
if (!paramType) {
|
|
78
|
+
// Fallback to direct conversion if type info missing
|
|
79
|
+
return nativeToScVal(arg);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Special cases that don't need type hints
|
|
83
|
+
if (paramType === 'Bool' || paramType === 'Bytes' || paramType.match(/^BytesN<\d+>$/)) {
|
|
84
|
+
return nativeToScVal(arg);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Use common utility for type conversion with hints
|
|
88
|
+
const typeHint = convertStellarTypeToScValType(paramType);
|
|
89
|
+
return nativeToScVal(arg, { type: typeHint });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Build the transaction for simulation
|
|
93
|
+
const transaction = new TransactionBuilder(sourceAccount, {
|
|
94
|
+
fee: BASE_FEE,
|
|
95
|
+
networkPassphrase: networkConfig.networkPassphrase,
|
|
96
|
+
})
|
|
97
|
+
.addOperation(contract.call(functionName, ...scValArgs))
|
|
98
|
+
.setTimeout(30);
|
|
99
|
+
|
|
100
|
+
return transaction;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
logger.error('createSimulationTransaction', 'Failed to create simulation transaction:', error);
|
|
103
|
+
throw new Error(`Failed to create simulation transaction: ${(error as Error).message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Determines if a Stellar contract function modifies state by simulating it
|
|
109
|
+
* and checking for state changes.
|
|
110
|
+
*
|
|
111
|
+
* @param contractAddress Address of the contract.
|
|
112
|
+
* @param functionName Name of the function to check.
|
|
113
|
+
* @param networkConfig The specific network configuration.
|
|
114
|
+
* @param inputTypes Parameter types for the function (empty array for parameterless functions).
|
|
115
|
+
* @returns True if the function modifies state, false if it's read-only.
|
|
116
|
+
*/
|
|
117
|
+
export async function checkStellarFunctionStateMutability(
|
|
118
|
+
contractAddress: string,
|
|
119
|
+
functionName: string,
|
|
120
|
+
networkConfig: StellarNetworkConfig,
|
|
121
|
+
inputTypes: string[] = []
|
|
122
|
+
): Promise<boolean> {
|
|
123
|
+
logger.info(
|
|
124
|
+
'checkStellarFunctionStateMutability',
|
|
125
|
+
`Checking state mutability for function: ${functionName} on ${contractAddress}`
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
// --- Validate Contract Address --- //
|
|
130
|
+
try {
|
|
131
|
+
Address.fromString(contractAddress);
|
|
132
|
+
} catch {
|
|
133
|
+
throw new Error(`Invalid Stellar contract address provided: ${contractAddress}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// --- Get Soroban RPC Server --- //
|
|
137
|
+
const rpcServer = getSorobanRpcServer(networkConfig);
|
|
138
|
+
|
|
139
|
+
// --- Create dummy parameters for simulation --- //
|
|
140
|
+
// For state mutability detection, we can use dummy values since we only care about state changes
|
|
141
|
+
const dummyArgs = inputTypes.map((paramType) => {
|
|
142
|
+
// Create minimal dummy values for different types
|
|
143
|
+
switch (paramType) {
|
|
144
|
+
case 'Bool':
|
|
145
|
+
return false;
|
|
146
|
+
case 'I32':
|
|
147
|
+
case 'U32':
|
|
148
|
+
case 'I64':
|
|
149
|
+
case 'U64':
|
|
150
|
+
case 'I128':
|
|
151
|
+
case 'U128':
|
|
152
|
+
case 'I256':
|
|
153
|
+
case 'U256':
|
|
154
|
+
return 0;
|
|
155
|
+
case 'String':
|
|
156
|
+
return '';
|
|
157
|
+
case 'Bytes':
|
|
158
|
+
return Buffer.alloc(0);
|
|
159
|
+
case 'Address':
|
|
160
|
+
// Use the contract address as a dummy address
|
|
161
|
+
return contractAddress;
|
|
162
|
+
default:
|
|
163
|
+
// For complex types, use null which often gets converted to appropriate defaults
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// --- Create Simulation Transaction --- //
|
|
169
|
+
const transactionBuilder = await createSimulationTransaction(
|
|
170
|
+
contractAddress,
|
|
171
|
+
functionName,
|
|
172
|
+
dummyArgs,
|
|
173
|
+
inputTypes,
|
|
174
|
+
networkConfig
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const transaction = transactionBuilder.build();
|
|
178
|
+
|
|
179
|
+
logger.debug(
|
|
180
|
+
'checkStellarFunctionStateMutability',
|
|
181
|
+
`[Check ${functionName}] Simulating transaction for state mutability check`
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// --- Simulate Transaction --- //
|
|
185
|
+
let simulationResult: StellarRpc.Api.SimulateTransactionResponse;
|
|
186
|
+
try {
|
|
187
|
+
simulationResult = await rpcServer.simulateTransaction(transaction);
|
|
188
|
+
} catch (simulationError) {
|
|
189
|
+
logger.warn(
|
|
190
|
+
'checkStellarFunctionStateMutability',
|
|
191
|
+
`[Check ${functionName}] Simulation failed, assuming function modifies state:`,
|
|
192
|
+
simulationError
|
|
193
|
+
);
|
|
194
|
+
// If simulation fails, err on the side of caution and assume it modifies state
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// --- Process Simulation Result --- //
|
|
199
|
+
if (StellarRpc.Api.isSimulationError(simulationResult)) {
|
|
200
|
+
logger.warn(
|
|
201
|
+
'checkStellarFunctionStateMutability',
|
|
202
|
+
`[Check ${functionName}] Simulation error, assuming function modifies state:`,
|
|
203
|
+
simulationResult.error
|
|
204
|
+
);
|
|
205
|
+
// If there's a simulation error, assume it modifies state for safety
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// --- Check State Changes --- //
|
|
210
|
+
// Filter out infrastructure state changes that occur for every invocation:
|
|
211
|
+
// 1. CONTRACT_CODE entries (WASM code TTL bumps)
|
|
212
|
+
// 2. CONTRACT_DATA entries with scvLedgerKeyContractInstance key (instance TTL bumps)
|
|
213
|
+
// Only remaining entries represent actual contract storage modifications.
|
|
214
|
+
const storageChanges = (simulationResult.stateChanges ?? []).filter((change) => {
|
|
215
|
+
try {
|
|
216
|
+
const keyType = change.key.switch();
|
|
217
|
+
|
|
218
|
+
if (keyType === xdr.LedgerEntryType.contractCode()) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (keyType === xdr.LedgerEntryType.contractData()) {
|
|
223
|
+
const dataKey = change.key.contractData().key();
|
|
224
|
+
if (dataKey.switch() === xdr.ScValType.scvLedgerKeyContractInstance()) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return true;
|
|
230
|
+
} catch {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const hasStorageChanges = storageChanges.length > 0;
|
|
236
|
+
|
|
237
|
+
logger.info(
|
|
238
|
+
'checkStellarFunctionStateMutability',
|
|
239
|
+
`[Check ${functionName}] State mutability check complete:`,
|
|
240
|
+
{
|
|
241
|
+
totalStateChanges: simulationResult.stateChanges?.length || 0,
|
|
242
|
+
storageChangesCount: storageChanges.length,
|
|
243
|
+
modifiesState: hasStorageChanges,
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
return hasStorageChanges;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
logger.warn(
|
|
250
|
+
'checkStellarFunctionStateMutability',
|
|
251
|
+
`Failed to check state mutability for ${functionName}, assuming it modifies state:`,
|
|
252
|
+
error
|
|
253
|
+
);
|
|
254
|
+
// If anything goes wrong, assume the function modifies state as a safe default
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Core logic for querying a Stellar view function.
|
|
261
|
+
*
|
|
262
|
+
* @param contractAddress Address of the contract.
|
|
263
|
+
* @param functionId ID of the function to query.
|
|
264
|
+
* @param networkConfig The specific network configuration.
|
|
265
|
+
* @param params Raw parameters for the function call.
|
|
266
|
+
* @param contractSchema Optional pre-loaded contract schema.
|
|
267
|
+
* @param loadContractFn Function reference to load contract schema if not provided.
|
|
268
|
+
* @returns The decoded result of the view function call.
|
|
269
|
+
*/
|
|
270
|
+
export async function queryStellarViewFunction(
|
|
271
|
+
contractAddress: string,
|
|
272
|
+
functionId: string,
|
|
273
|
+
networkConfig: NetworkConfig,
|
|
274
|
+
params: unknown[] = [],
|
|
275
|
+
contractSchema?: ContractSchema,
|
|
276
|
+
loadContractFn?: (source: string) => Promise<ContractSchema>
|
|
277
|
+
): Promise<unknown> {
|
|
278
|
+
logger.info(
|
|
279
|
+
'queryStellarViewFunction',
|
|
280
|
+
`Querying Stellar view function: ${functionId} on ${contractAddress} (${networkConfig.name})`,
|
|
281
|
+
{ params }
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
if (networkConfig.ecosystem !== 'stellar') {
|
|
285
|
+
throw new Error('Invalid network configuration for Stellar query.');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const stellarConfig = networkConfig as StellarNetworkConfig;
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
// --- Validate Contract Address --- //
|
|
292
|
+
try {
|
|
293
|
+
Address.fromString(contractAddress);
|
|
294
|
+
} catch {
|
|
295
|
+
throw new Error(`Invalid Stellar contract address provided: ${contractAddress}`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// --- Get Soroban RPC Server --- //
|
|
299
|
+
const rpcServer = getSorobanRpcServer(stellarConfig);
|
|
300
|
+
|
|
301
|
+
// --- Get Schema & Function Details --- //
|
|
302
|
+
let schema = contractSchema;
|
|
303
|
+
if (!schema && loadContractFn) {
|
|
304
|
+
schema = await loadContractFn(contractAddress);
|
|
305
|
+
}
|
|
306
|
+
if (!schema) {
|
|
307
|
+
throw new Error(
|
|
308
|
+
`Contract schema not provided and loadContractFn not available for ${contractAddress}`
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const functionDetails = schema.functions.find((fn) => fn.id === functionId);
|
|
313
|
+
if (!functionDetails) {
|
|
314
|
+
throw new Error(`Function with ID ${functionId} not found in contract schema.`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (!isStellarViewFunction(functionDetails)) {
|
|
318
|
+
throw new Error(`Function ${functionDetails.name} is not a view function.`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// --- Parse Input Parameters --- //
|
|
322
|
+
const expectedInputs: readonly FunctionParameter[] = functionDetails.inputs;
|
|
323
|
+
if (params.length !== expectedInputs.length) {
|
|
324
|
+
throw new Error(
|
|
325
|
+
`Incorrect number of parameters provided for ${functionDetails.name}. Expected ${expectedInputs.length}, got ${params.length}.`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const args = expectedInputs.map((inputParam: FunctionParameter, index: number) => {
|
|
330
|
+
const rawValue = params[index];
|
|
331
|
+
return parseStellarInput(rawValue, inputParam.type);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
logger.debug('queryStellarViewFunction', 'Parsed Args for contract call:', args);
|
|
335
|
+
|
|
336
|
+
// --- Create Simulation Transaction --- //
|
|
337
|
+
const paramTypes = expectedInputs.map((input) => input.type);
|
|
338
|
+
const transactionBuilder = await createSimulationTransaction(
|
|
339
|
+
contractAddress,
|
|
340
|
+
functionDetails.name,
|
|
341
|
+
args,
|
|
342
|
+
paramTypes,
|
|
343
|
+
stellarConfig
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
const transaction = transactionBuilder.build();
|
|
347
|
+
|
|
348
|
+
logger.debug(
|
|
349
|
+
'queryStellarViewFunction',
|
|
350
|
+
`[Query ${functionDetails.name}] Simulating transaction:`,
|
|
351
|
+
transaction.toXDR()
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
// --- Simulate Transaction --- //
|
|
355
|
+
let simulationResult: StellarRpc.Api.SimulateTransactionResponse;
|
|
356
|
+
try {
|
|
357
|
+
simulationResult = await rpcServer.simulateTransaction(transaction);
|
|
358
|
+
} catch (simulationError) {
|
|
359
|
+
logger.error(
|
|
360
|
+
'queryStellarViewFunction',
|
|
361
|
+
`[Query ${functionDetails.name}] Simulation failed:`,
|
|
362
|
+
simulationError
|
|
363
|
+
);
|
|
364
|
+
throw new Error(
|
|
365
|
+
`Soroban RPC simulation failed for ${functionDetails.name}: ${(simulationError as Error).message}`
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// --- Process Simulation Result --- //
|
|
370
|
+
if (StellarRpc.Api.isSimulationError(simulationResult)) {
|
|
371
|
+
logger.error(
|
|
372
|
+
'queryStellarViewFunction',
|
|
373
|
+
`[Query ${functionDetails.name}] Simulation error:`,
|
|
374
|
+
simulationResult.error
|
|
375
|
+
);
|
|
376
|
+
throw new Error(`Contract simulation failed: ${simulationResult.error}`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (!simulationResult.result) {
|
|
380
|
+
throw new Error(`No result returned from contract simulation for ${functionDetails.name}`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const rawResult = simulationResult.result.retval;
|
|
384
|
+
logger.debug(
|
|
385
|
+
'queryStellarViewFunction',
|
|
386
|
+
`[Query ${functionDetails.name}] Raw simulation result:`,
|
|
387
|
+
rawResult
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
// --- Format Result --- //
|
|
391
|
+
const formattedResult = formatStellarFunctionResult(rawResult, functionDetails);
|
|
392
|
+
|
|
393
|
+
logger.info(
|
|
394
|
+
'queryStellarViewFunction',
|
|
395
|
+
`[Query ${functionDetails.name}] Formatted result:`,
|
|
396
|
+
formattedResult
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
return formattedResult;
|
|
400
|
+
} catch (error) {
|
|
401
|
+
const errorMessage = `Failed to query Stellar view function ${functionId} on network ${networkConfig.name}: ${(error as Error).message}`;
|
|
402
|
+
logger.error('queryStellarViewFunction', errorMessage, {
|
|
403
|
+
contractAddress,
|
|
404
|
+
functionId,
|
|
405
|
+
params,
|
|
406
|
+
networkConfig,
|
|
407
|
+
error,
|
|
408
|
+
});
|
|
409
|
+
throw new Error(errorMessage);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ContractFunction, ContractSchema } from '@openzeppelin/ui-types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Determines if a function is a view/pure function (read-only) for Stellar contracts.
|
|
5
|
+
* This function works with simulation-based state mutability detection results
|
|
6
|
+
* from the contract loader, following the same approach as the official Stellar Laboratory.
|
|
7
|
+
*
|
|
8
|
+
* @param functionDetails The function details from the contract schema (with simulation-based mutability info).
|
|
9
|
+
* @returns True if the function is read-only, false otherwise.
|
|
10
|
+
*/
|
|
11
|
+
export function isStellarViewFunction(functionDetails: ContractFunction): boolean {
|
|
12
|
+
// First check stateMutability if available (set by simulation-based detection)
|
|
13
|
+
if (functionDetails.stateMutability) {
|
|
14
|
+
return functionDetails.stateMutability === 'view' || functionDetails.stateMutability === 'pure';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Fallback to modifiesState if stateMutability is not available
|
|
18
|
+
// (also set by simulation-based detection in the loader)
|
|
19
|
+
return !functionDetails.modifiesState;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get only the functions that modify state (writable functions) for Stellar contracts.
|
|
24
|
+
* Uses simulation-based state mutability detection results to accurately filter functions.
|
|
25
|
+
* @param contractSchema The contract schema to filter (with simulation-based mutability info).
|
|
26
|
+
* @returns Array of writable functions
|
|
27
|
+
*/
|
|
28
|
+
export function getStellarWritableFunctions(
|
|
29
|
+
contractSchema: ContractSchema
|
|
30
|
+
): ContractSchema['functions'] {
|
|
31
|
+
return contractSchema.functions.filter((func) => !isStellarViewFunction(func));
|
|
32
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { xdr } from '@stellar/stellar-sdk';
|
|
2
|
+
|
|
3
|
+
import { logger } from '@openzeppelin/ui-utils';
|
|
4
|
+
|
|
5
|
+
import type { SacSpecSourceConfig } from './spec-source';
|
|
6
|
+
import { fetchSacSpecJson, getSacSpecUrl } from './spec-source';
|
|
7
|
+
import { encodeSacSpecEntries, toScSpecEntries } from './xdr';
|
|
8
|
+
|
|
9
|
+
interface SacSpecCacheEntry {
|
|
10
|
+
base64Entries: string[];
|
|
11
|
+
specEntries: xdr.ScSpecEntry[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const sacSpecCache = new Map<string, SacSpecCacheEntry>();
|
|
15
|
+
const sacSpecInflight = new Map<string, Promise<SacSpecCacheEntry>>();
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Returns cached SAC spec artifacts (base64 and decoded entries) for the given source config.
|
|
19
|
+
* Ensures fetch/encode work is performed at most once per config.
|
|
20
|
+
*/
|
|
21
|
+
export async function getSacSpecArtifacts(
|
|
22
|
+
cfg: SacSpecSourceConfig = {}
|
|
23
|
+
): Promise<SacSpecCacheEntry> {
|
|
24
|
+
const cacheKey = getSacSpecUrl(cfg);
|
|
25
|
+
|
|
26
|
+
const cached = sacSpecCache.get(cacheKey);
|
|
27
|
+
if (cached) {
|
|
28
|
+
logger.debug('stellar:sac:spec-cache', 'Returning cached SAC spec artifacts');
|
|
29
|
+
return cached;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const inflight = sacSpecInflight.get(cacheKey);
|
|
33
|
+
if (inflight) {
|
|
34
|
+
return inflight;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const promise = (async () => {
|
|
38
|
+
const json = await fetchSacSpecJson(cfg);
|
|
39
|
+
const base64Entries = await encodeSacSpecEntries(json);
|
|
40
|
+
const specEntries = toScSpecEntries(base64Entries);
|
|
41
|
+
|
|
42
|
+
const entry: SacSpecCacheEntry = {
|
|
43
|
+
base64Entries,
|
|
44
|
+
specEntries,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
sacSpecCache.set(cacheKey, entry);
|
|
48
|
+
sacSpecInflight.delete(cacheKey);
|
|
49
|
+
|
|
50
|
+
logger.debug('stellar:sac:spec-cache', 'Cached SAC spec artifacts for future re-use');
|
|
51
|
+
|
|
52
|
+
return entry;
|
|
53
|
+
})().catch((error) => {
|
|
54
|
+
sacSpecInflight.delete(cacheKey);
|
|
55
|
+
throw error;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
sacSpecInflight.set(cacheKey, promise);
|
|
59
|
+
return promise;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Clears cached SAC spec artifacts. Intended for test environments.
|
|
64
|
+
*/
|
|
65
|
+
export function clearSacSpecArtifactsCache(): void {
|
|
66
|
+
sacSpecCache.clear();
|
|
67
|
+
sacSpecInflight.clear();
|
|
68
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { logger } from '@openzeppelin/ui-utils';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_SPEC = {
|
|
4
|
+
repo: 'stellar/stellar-asset-contract-spec',
|
|
5
|
+
path: 'refs/heads/main',
|
|
6
|
+
file: 'stellar-asset-spec.json',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export interface SacSpecSourceConfig {
|
|
10
|
+
repo?: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
file?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getSacSpecUrl(cfg: SacSpecSourceConfig = {}): string {
|
|
16
|
+
const repo = cfg.repo || DEFAULT_SPEC.repo;
|
|
17
|
+
const path = cfg.path || DEFAULT_SPEC.path;
|
|
18
|
+
const file = cfg.file || DEFAULT_SPEC.file;
|
|
19
|
+
return `https://raw.githubusercontent.com/${repo}/${path}/${file}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Fetches the SAC JSON spec from GitHub raw. */
|
|
23
|
+
export async function fetchSacSpecJson(cfg: SacSpecSourceConfig = {}): Promise<string> {
|
|
24
|
+
const url = getSacSpecUrl(cfg);
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch(url);
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
throw new Error(`HTTP ${res.status}`);
|
|
29
|
+
}
|
|
30
|
+
return await res.text();
|
|
31
|
+
} catch (error) {
|
|
32
|
+
logger.error('stellar:sac:spec-source', 'Failed to fetch SAC spec:', url, error);
|
|
33
|
+
throw new Error('Failed to load Stellar Asset Contract spec. Please try again later.');
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/sac/xdr.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { xdr } from '@stellar/stellar-sdk';
|
|
2
|
+
import stellarXdrJsonPackage from '@stellar/stellar-xdr-json/package.json' with { type: 'json' };
|
|
3
|
+
import { parse, stringify } from 'lossless-json';
|
|
4
|
+
|
|
5
|
+
import { logger } from '@openzeppelin/ui-utils';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* CDN URL for the stellar-xdr-json WASM module.
|
|
9
|
+
*
|
|
10
|
+
* Why CDN instead of bundling?
|
|
11
|
+
* 1. Vite bundling issues: The WASM file requires special handling in Vite. We tried multiple
|
|
12
|
+
* approaches including vite-plugin-wasm, vite-plugin-top-level-await, ?url imports, and
|
|
13
|
+
* various configurations, but consistently hit WebAssembly.instantiate errors where the
|
|
14
|
+
* browser received HTML instead of the WASM binary (magic word 00 61 73 6d expected,
|
|
15
|
+
* but got 3c 21 64 6f which is "<!do" in HTML).
|
|
16
|
+
* 2. Bundle size: The WASM file is ~3MB, which would significantly increase bundle size
|
|
17
|
+
* for all users, even those who never use SAC contracts.
|
|
18
|
+
* 3. Dynamic loading: With CDN + dynamic imports, the WASM only loads when actually needed
|
|
19
|
+
* (when a SAC contract is detected), keeping the initial bundle lean.
|
|
20
|
+
* 4. Simplicity: After trying complex Vite configurations and WASM plugins without success,
|
|
21
|
+
* the CDN approach is simpler and more reliable.
|
|
22
|
+
*
|
|
23
|
+
* Trade-off: Requires internet connection for SAC contracts, but this is acceptable since
|
|
24
|
+
* SAC specs are already fetched from GitHub anyway.
|
|
25
|
+
*/
|
|
26
|
+
type PackageJson = { version?: string };
|
|
27
|
+
|
|
28
|
+
const stellarXdrJsonVersion = (stellarXdrJsonPackage as PackageJson).version ?? '23.0.0';
|
|
29
|
+
|
|
30
|
+
const CDN_WASM_URL = `https://unpkg.com/@stellar/stellar-xdr-json@${stellarXdrJsonVersion}/stellar_xdr_json_bg.wasm`;
|
|
31
|
+
|
|
32
|
+
// Dynamically import the WASM module only when needed
|
|
33
|
+
let initialized = false;
|
|
34
|
+
let encode: ((type: string, value: string) => string) | null = null;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Initializes the stellar-xdr-json WASM module for XDR encoding.
|
|
38
|
+
*
|
|
39
|
+
* This uses dynamic imports to only load the WASM when SAC contracts are actually used,
|
|
40
|
+
* avoiding the 3MB overhead for users who don't need SAC support.
|
|
41
|
+
*
|
|
42
|
+
* The WASM is loaded from CDN to keep bundle size minimal.
|
|
43
|
+
*/
|
|
44
|
+
export async function ensureXdrJsonInitialized(): Promise<void> {
|
|
45
|
+
if (initialized) return;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// Dynamic import - only loads when SAC is actually used
|
|
49
|
+
const stellarXdrJson = await import('@stellar/stellar-xdr-json');
|
|
50
|
+
const init = stellarXdrJson.default;
|
|
51
|
+
|
|
52
|
+
// Load WASM from CDN
|
|
53
|
+
await init(CDN_WASM_URL);
|
|
54
|
+
encode = stellarXdrJson.encode;
|
|
55
|
+
initialized = true;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
logger.error('stellar:sac:xdr', 'Failed to initialize WASM module:', error);
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Converts the SAC JSON spec content into Base64 XDR entries for ScSpecEntry[]
|
|
64
|
+
*/
|
|
65
|
+
export async function encodeSacSpecEntries(jsonString: string): Promise<string[]> {
|
|
66
|
+
await ensureXdrJsonInitialized();
|
|
67
|
+
|
|
68
|
+
if (!encode) {
|
|
69
|
+
throw new Error('WASM module not properly initialized');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const jsonData = parse(jsonString) as unknown[];
|
|
74
|
+
const result: string[] = [];
|
|
75
|
+
for (const entry of jsonData) {
|
|
76
|
+
const stringified = stringify(entry);
|
|
77
|
+
if (!stringified) {
|
|
78
|
+
throw new Error('Failed to stringify SAC spec entry before XDR encoding.');
|
|
79
|
+
}
|
|
80
|
+
const encoded = encode('ScSpecEntry', stringified);
|
|
81
|
+
result.push(encoded);
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
logger.error('stellar:sac:xdr', 'Failed to encode SAC spec to XDR', error);
|
|
86
|
+
throw new Error('Failed to process SAC spec.');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Utility to convert base64 strings to xdr.ScSpecEntry[] */
|
|
91
|
+
export function toScSpecEntries(base64Entries: string[]): xdr.ScSpecEntry[] {
|
|
92
|
+
return base64Entries.map((b64) => xdr.ScSpecEntry.fromXDR(b64, 'base64'));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Reset the initialization state (mainly for testing)
|
|
97
|
+
*/
|
|
98
|
+
export function resetXdrInitialization(): void {
|
|
99
|
+
initialized = false;
|
|
100
|
+
encode = null;
|
|
101
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Info } from 'lucide-react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { Button } from '@openzeppelin/ui-components';
|
|
5
|
+
|
|
6
|
+
interface AdvancedInfoProps {
|
|
7
|
+
showAdvancedInfo: boolean;
|
|
8
|
+
onToggle: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const AdvancedInfo: React.FC<AdvancedInfoProps> = ({ showAdvancedInfo, onToggle }) => {
|
|
12
|
+
return (
|
|
13
|
+
<div className="space-y-2">
|
|
14
|
+
<div className="flex items-center justify-between">
|
|
15
|
+
<label className="text-base font-medium">Stellar Transaction Configuration</label>
|
|
16
|
+
<Button variant="ghost" size="sm" onClick={onToggle} className="text-xs" type="button">
|
|
17
|
+
<Info className="h-3 w-3 mr-1" />
|
|
18
|
+
Stellar Options
|
|
19
|
+
</Button>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
{showAdvancedInfo && (
|
|
23
|
+
<div className="mt-3 rounded-lg bg-muted/30 p-4">
|
|
24
|
+
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
25
|
+
Configure Stellar-specific transaction parameters: <strong>maxFee</strong> sets the
|
|
26
|
+
maximum fee in stroops you're willing to pay, <strong>validUntil</strong> sets
|
|
27
|
+
transaction expiration, and <strong>feeBump</strong> enables automatic fee increases for
|
|
28
|
+
stuck transactions.
|
|
29
|
+
</p>
|
|
30
|
+
</div>
|
|
31
|
+
)}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
};
|