@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,498 @@
|
|
|
1
|
+
import * as StellarSdk from '@stellar/stellar-sdk';
|
|
2
|
+
import { xdr } from '@stellar/stellar-sdk';
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
ContractFunction,
|
|
6
|
+
ContractSchema,
|
|
7
|
+
FunctionParameter,
|
|
8
|
+
StellarNetworkConfig,
|
|
9
|
+
} from '@openzeppelin/ui-types';
|
|
10
|
+
import { logger } from '@openzeppelin/ui-utils';
|
|
11
|
+
|
|
12
|
+
import { getStellarExplorerAddressUrl } from '../configuration/explorer';
|
|
13
|
+
import { extractStructFields, isStructType } from '../mapping/struct-fields';
|
|
14
|
+
import { checkStellarFunctionStateMutability } from '../query/handler';
|
|
15
|
+
import { getSacSpecArtifacts } from '../sac/spec-cache';
|
|
16
|
+
import type { StellarContractArtifacts } from '../types/artifacts';
|
|
17
|
+
import { extractSorobanTypeFromScSpec } from '../utils/type-detection';
|
|
18
|
+
import { getStellarContractType } from './type';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Load a Stellar contract using the official Stellar SDK approach
|
|
22
|
+
* Based on the patterns from the official Stellar laboratory
|
|
23
|
+
*/
|
|
24
|
+
export async function loadStellarContractFromAddress(
|
|
25
|
+
contractAddress: string,
|
|
26
|
+
networkConfig: StellarNetworkConfig
|
|
27
|
+
): Promise<ContractSchema> {
|
|
28
|
+
logger.info('loadStellarContractFromAddress', 'Loading contract:', {
|
|
29
|
+
contractAddress,
|
|
30
|
+
network: networkConfig.name,
|
|
31
|
+
rpcUrl: networkConfig.sorobanRpcUrl,
|
|
32
|
+
networkPassphrase: networkConfig.networkPassphrase,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// Validate contract address
|
|
37
|
+
if (!StellarSdk.StrKey.isValidContract(contractAddress)) {
|
|
38
|
+
throw new Error(`Invalid contract address: ${contractAddress}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Special-case: detect SAC and construct spec locally
|
|
42
|
+
try {
|
|
43
|
+
const execType = await getStellarContractType(contractAddress, networkConfig);
|
|
44
|
+
|
|
45
|
+
if (execType === 'contractExecutableStellarAsset') {
|
|
46
|
+
const { base64Entries, specEntries } = await getSacSpecArtifacts();
|
|
47
|
+
const spec = new StellarSdk.contract.Spec(base64Entries);
|
|
48
|
+
|
|
49
|
+
const functions = await extractFunctionsFromSpec(
|
|
50
|
+
spec,
|
|
51
|
+
contractAddress,
|
|
52
|
+
specEntries,
|
|
53
|
+
networkConfig
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
name: `Stellar Asset Contract ${contractAddress.slice(0, 8)}...`,
|
|
58
|
+
ecosystem: 'stellar',
|
|
59
|
+
functions,
|
|
60
|
+
metadata: {
|
|
61
|
+
specEntries,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
} catch (e) {
|
|
66
|
+
// If detection path fails unexpectedly, fall back to SDK client path below
|
|
67
|
+
logger.warn(
|
|
68
|
+
'loadStellarContractFromAddress',
|
|
69
|
+
'SAC detection failed, falling back to regular client:',
|
|
70
|
+
e
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Create contract client using the official Stellar SDK approach
|
|
75
|
+
// Laboratory note: some contracts may be missing Wasm/definition retrievable via RPC.
|
|
76
|
+
// In that case the SDK can throw an internal error like
|
|
77
|
+
// "Cannot destructure property 'length'...". Detect and surface an explicit error.
|
|
78
|
+
let contractClient: StellarSdk.contract.Client;
|
|
79
|
+
try {
|
|
80
|
+
contractClient = await StellarSdk.contract.Client.from({
|
|
81
|
+
contractId: contractAddress,
|
|
82
|
+
networkPassphrase: networkConfig.networkPassphrase,
|
|
83
|
+
rpcUrl: networkConfig.sorobanRpcUrl,
|
|
84
|
+
});
|
|
85
|
+
} catch (e) {
|
|
86
|
+
const message = (e as Error)?.message || String(e);
|
|
87
|
+
if (message.includes("Cannot destructure property 'length'")) {
|
|
88
|
+
const friendly =
|
|
89
|
+
'Unable to fetch contract metadata from RPC. The contract appears to have no published Wasm/definition on this network.';
|
|
90
|
+
logger.error('loadStellarContractFromAddress', friendly);
|
|
91
|
+
throw new Error(`NO_WASM: ${friendly}`);
|
|
92
|
+
}
|
|
93
|
+
throw e;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
logger.info('loadStellarContractFromAddress', 'Contract client created successfully');
|
|
97
|
+
|
|
98
|
+
// Get spec entries - try different approaches to access them
|
|
99
|
+
let specEntries: xdr.ScSpecEntry[] = [];
|
|
100
|
+
try {
|
|
101
|
+
// Access spec entries from the spec object
|
|
102
|
+
|
|
103
|
+
// Try to access spec entries through different possible properties/methods
|
|
104
|
+
if (contractClient.spec && typeof contractClient.spec === 'object') {
|
|
105
|
+
const spec = contractClient.spec as unknown as Record<string, unknown>;
|
|
106
|
+
|
|
107
|
+
// Try common property names
|
|
108
|
+
if (Array.isArray(spec.entries)) {
|
|
109
|
+
specEntries = spec.entries as xdr.ScSpecEntry[];
|
|
110
|
+
} else if (Array.isArray(spec._entries)) {
|
|
111
|
+
specEntries = spec._entries as xdr.ScSpecEntry[];
|
|
112
|
+
} else if (Array.isArray(spec.specEntries)) {
|
|
113
|
+
specEntries = spec.specEntries as xdr.ScSpecEntry[];
|
|
114
|
+
} else if (typeof spec.entries === 'function') {
|
|
115
|
+
// Maybe it's a method after all, but with different signature
|
|
116
|
+
try {
|
|
117
|
+
specEntries = (spec.entries as () => xdr.ScSpecEntry[])();
|
|
118
|
+
} catch (e) {
|
|
119
|
+
logger.warn('loadStellarContractFromAddress', 'entries() method failed:', e);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Try the method directly on spec if it has the method
|
|
124
|
+
if (specEntries.length === 0 && typeof spec.entries === 'function') {
|
|
125
|
+
try {
|
|
126
|
+
specEntries = (spec.entries as () => xdr.ScSpecEntry[])();
|
|
127
|
+
} catch (e) {
|
|
128
|
+
logger.warn('loadStellarContractFromAddress', 'direct entries() method failed:', e);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
logger.info('loadStellarContractFromAddress', `Found ${specEntries.length} spec entries`);
|
|
133
|
+
}
|
|
134
|
+
} catch (specError) {
|
|
135
|
+
logger.warn('loadStellarContractFromAddress', 'Could not extract spec entries:', specError);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Extract functions using the official laboratory approach with spec entries for struct extraction
|
|
139
|
+
const functions = await extractFunctionsFromSpec(
|
|
140
|
+
contractClient.spec,
|
|
141
|
+
contractAddress,
|
|
142
|
+
specEntries,
|
|
143
|
+
networkConfig
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
logger.info(
|
|
147
|
+
'loadStellarContractFromAddress',
|
|
148
|
+
`Successfully extracted ${functions.length} functions`
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
name: `Soroban Contract ${contractAddress.slice(0, 8)}...`,
|
|
153
|
+
ecosystem: 'stellar',
|
|
154
|
+
functions,
|
|
155
|
+
metadata: {
|
|
156
|
+
specEntries,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
} catch (error) {
|
|
160
|
+
const msg = (error as Error)?.message || String(error);
|
|
161
|
+
// Preserve explicit NO_WASM error so downstream can surface it to the user
|
|
162
|
+
if (msg.startsWith('NO_WASM:')) {
|
|
163
|
+
logger.error('loadStellarContractFromAddress', msg);
|
|
164
|
+
throw new Error(msg);
|
|
165
|
+
}
|
|
166
|
+
logger.error('loadStellarContractFromAddress', 'Failed to load contract:', error);
|
|
167
|
+
throw new Error(`Failed to load contract: ${msg}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Extract functions from contract spec using the official Stellar laboratory approach
|
|
173
|
+
* with simulation-based state mutability detection
|
|
174
|
+
*/
|
|
175
|
+
async function extractFunctionsFromSpec(
|
|
176
|
+
spec: StellarSdk.contract.Spec,
|
|
177
|
+
contractAddress: string,
|
|
178
|
+
specEntries?: xdr.ScSpecEntry[],
|
|
179
|
+
networkConfig?: StellarNetworkConfig
|
|
180
|
+
): Promise<ContractFunction[]> {
|
|
181
|
+
try {
|
|
182
|
+
// Get all functions using the official SDK method
|
|
183
|
+
const specFunctions = spec.funcs();
|
|
184
|
+
|
|
185
|
+
logger.info('extractFunctionsFromSpec', `Found ${specFunctions.length} functions in spec`);
|
|
186
|
+
|
|
187
|
+
return await Promise.all(
|
|
188
|
+
specFunctions.map(async (func, index) => {
|
|
189
|
+
try {
|
|
190
|
+
// Extract function name using the official SDK method
|
|
191
|
+
const functionName = func.name().toString();
|
|
192
|
+
|
|
193
|
+
logger.info('extractFunctionsFromSpec', `Processing function: ${functionName}`);
|
|
194
|
+
|
|
195
|
+
// Get function inputs and outputs using the official SDK methods
|
|
196
|
+
const inputs: FunctionParameter[] = func.inputs().map((input, inputIndex) => {
|
|
197
|
+
try {
|
|
198
|
+
const inputName = input.name().toString();
|
|
199
|
+
const inputType = extractSorobanTypeFromScSpec(input.type());
|
|
200
|
+
|
|
201
|
+
if (inputType === 'unknown') {
|
|
202
|
+
logger.warn(
|
|
203
|
+
'extractFunctionsFromSpec',
|
|
204
|
+
`Unknown type for parameter "${inputName}" in function "${functionName}"`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check if this is a struct type and extract components
|
|
209
|
+
let components: FunctionParameter[] | undefined;
|
|
210
|
+
if (specEntries && specEntries.length > 0 && isStructType(specEntries, inputType)) {
|
|
211
|
+
const structFields = extractStructFields(specEntries, inputType);
|
|
212
|
+
if (structFields && structFields.length > 0) {
|
|
213
|
+
components = structFields;
|
|
214
|
+
logger.debug(
|
|
215
|
+
'extractFunctionsFromSpec',
|
|
216
|
+
`Extracted ${structFields.length} fields for struct type "${inputType}": ${structFields.map((f) => `${f.name}:${f.type}`).join(', ')}`
|
|
217
|
+
);
|
|
218
|
+
} else {
|
|
219
|
+
logger.warn(
|
|
220
|
+
'extractFunctionsFromSpec',
|
|
221
|
+
`No fields extracted for struct "${inputType}"`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
name: inputName || `param_${inputIndex}`,
|
|
228
|
+
type: inputType,
|
|
229
|
+
...(components && { components }),
|
|
230
|
+
};
|
|
231
|
+
} catch (error) {
|
|
232
|
+
logger.warn(
|
|
233
|
+
'extractFunctionsFromSpec',
|
|
234
|
+
`Failed to parse input ${inputIndex}:`,
|
|
235
|
+
error
|
|
236
|
+
);
|
|
237
|
+
return {
|
|
238
|
+
name: `param_${inputIndex}`,
|
|
239
|
+
type: 'unknown',
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const outputs: FunctionParameter[] = func.outputs().map((output, outputIndex) => {
|
|
245
|
+
try {
|
|
246
|
+
// Outputs are ScSpecTypeDef objects, they don't have names, only types
|
|
247
|
+
const outputType = extractSorobanTypeFromScSpec(output);
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
name: `result_${outputIndex}`,
|
|
251
|
+
type: outputType,
|
|
252
|
+
};
|
|
253
|
+
} catch (error) {
|
|
254
|
+
logger.warn(
|
|
255
|
+
'extractFunctionsFromSpec',
|
|
256
|
+
`Failed to parse output ${outputIndex}:`,
|
|
257
|
+
error
|
|
258
|
+
);
|
|
259
|
+
return {
|
|
260
|
+
name: `result_${outputIndex}`,
|
|
261
|
+
type: 'unknown',
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Determine if function is read-only (view function) using simulation-based detection
|
|
267
|
+
// This follows the same approach as the official Stellar Laboratory
|
|
268
|
+
let modifiesState = true; // Default assumption for safety
|
|
269
|
+
let stateMutability: 'view' | 'pure' | 'nonpayable' = 'nonpayable';
|
|
270
|
+
|
|
271
|
+
if (networkConfig) {
|
|
272
|
+
try {
|
|
273
|
+
// Extract input types for simulation
|
|
274
|
+
const inputTypes = inputs.map((input) => input.type);
|
|
275
|
+
|
|
276
|
+
logger.debug(
|
|
277
|
+
'extractFunctionsFromSpec',
|
|
278
|
+
`Checking state mutability for ${functionName} with input types: ${inputTypes.join(', ')}`
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
// Use simulation-based state mutability detection
|
|
282
|
+
modifiesState = await checkStellarFunctionStateMutability(
|
|
283
|
+
contractAddress,
|
|
284
|
+
functionName,
|
|
285
|
+
networkConfig,
|
|
286
|
+
inputTypes
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
stateMutability = modifiesState ? 'nonpayable' : 'view';
|
|
290
|
+
|
|
291
|
+
logger.info(
|
|
292
|
+
'extractFunctionsFromSpec',
|
|
293
|
+
`Function ${functionName} state mutability determined:`,
|
|
294
|
+
{ modifiesState, stateMutability }
|
|
295
|
+
);
|
|
296
|
+
} catch (error) {
|
|
297
|
+
logger.warn(
|
|
298
|
+
'extractFunctionsFromSpec',
|
|
299
|
+
`Failed to determine state mutability for ${functionName}, assuming it modifies state:`,
|
|
300
|
+
error
|
|
301
|
+
);
|
|
302
|
+
// Keep defaults: modifiesState = true, stateMutability = 'nonpayable'
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
logger.warn(
|
|
306
|
+
'extractFunctionsFromSpec',
|
|
307
|
+
`No network config provided for ${functionName}, assuming it modifies state`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Generate a unique ID for the function
|
|
312
|
+
const functionId = `${functionName}_${inputs.map((i) => i.type).join('_')}`;
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
id: functionId,
|
|
316
|
+
name: functionName,
|
|
317
|
+
displayName:
|
|
318
|
+
functionName.charAt(0).toUpperCase() + functionName.slice(1).replace(/_/g, ' '),
|
|
319
|
+
description: `Soroban function: ${functionName}`,
|
|
320
|
+
inputs,
|
|
321
|
+
outputs,
|
|
322
|
+
type: 'function',
|
|
323
|
+
modifiesState,
|
|
324
|
+
stateMutability,
|
|
325
|
+
};
|
|
326
|
+
} catch (error) {
|
|
327
|
+
logger.error('extractFunctionsFromSpec', `Failed to process function ${index}:`, error);
|
|
328
|
+
|
|
329
|
+
// Return a basic function entry for failed parsing
|
|
330
|
+
return {
|
|
331
|
+
id: `function_${index}`,
|
|
332
|
+
name: `function_${index}`,
|
|
333
|
+
displayName: `Function ${index}`,
|
|
334
|
+
description: `Failed to parse function ${index}: ${(error as Error).message}`,
|
|
335
|
+
inputs: [],
|
|
336
|
+
outputs: [],
|
|
337
|
+
type: 'function',
|
|
338
|
+
modifiesState: true,
|
|
339
|
+
stateMutability: 'nonpayable',
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
})
|
|
343
|
+
);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
logger.error('extractFunctionsFromSpec', 'Failed to extract functions from spec:', error);
|
|
346
|
+
throw new Error(`Failed to extract functions: ${(error as Error).message}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Enhanced result type for Stellar contract loading with metadata
|
|
352
|
+
*/
|
|
353
|
+
export interface StellarContractLoadResult {
|
|
354
|
+
schema: ContractSchema;
|
|
355
|
+
source: 'fetched' | 'manual';
|
|
356
|
+
contractDefinitionOriginal?: string;
|
|
357
|
+
metadata?: {
|
|
358
|
+
fetchedFrom?: string;
|
|
359
|
+
contractName?: string;
|
|
360
|
+
fetchTimestamp?: Date;
|
|
361
|
+
definitionHash?: string;
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Load Stellar contract with basic metadata
|
|
367
|
+
*/
|
|
368
|
+
export async function loadStellarContract(
|
|
369
|
+
artifacts: StellarContractArtifacts,
|
|
370
|
+
networkConfig: StellarNetworkConfig
|
|
371
|
+
): Promise<StellarContractLoadResult> {
|
|
372
|
+
if (typeof artifacts.contractAddress !== 'string') {
|
|
373
|
+
throw new Error('A contract address must be provided.');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const schema = await loadStellarContractFromAddress(artifacts.contractAddress, networkConfig);
|
|
377
|
+
|
|
378
|
+
const schemaWithAddress = { ...schema, address: artifacts.contractAddress };
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
schema: schemaWithAddress,
|
|
382
|
+
source: 'fetched',
|
|
383
|
+
contractDefinitionOriginal: JSON.stringify(schemaWithAddress),
|
|
384
|
+
metadata: {
|
|
385
|
+
fetchedFrom:
|
|
386
|
+
getStellarExplorerAddressUrl(artifacts.contractAddress, networkConfig) ||
|
|
387
|
+
networkConfig.sorobanRpcUrl,
|
|
388
|
+
contractName: schema.name,
|
|
389
|
+
fetchTimestamp: new Date(),
|
|
390
|
+
},
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Load Stellar contract with extended metadata
|
|
396
|
+
*/
|
|
397
|
+
export async function loadStellarContractWithMetadata(
|
|
398
|
+
artifacts: StellarContractArtifacts,
|
|
399
|
+
networkConfig: StellarNetworkConfig
|
|
400
|
+
): Promise<StellarContractLoadResult> {
|
|
401
|
+
if (typeof artifacts.contractAddress !== 'string') {
|
|
402
|
+
throw new Error('A contract address must be provided.');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
const contractData = await loadStellarContractFromAddress(
|
|
407
|
+
artifacts.contractAddress,
|
|
408
|
+
networkConfig
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
const schema = {
|
|
412
|
+
...contractData,
|
|
413
|
+
address: artifacts.contractAddress,
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
schema,
|
|
418
|
+
source: 'fetched',
|
|
419
|
+
contractDefinitionOriginal: JSON.stringify(schema),
|
|
420
|
+
metadata: {
|
|
421
|
+
fetchedFrom:
|
|
422
|
+
getStellarExplorerAddressUrl(artifacts.contractAddress, networkConfig) ||
|
|
423
|
+
networkConfig.sorobanRpcUrl,
|
|
424
|
+
contractName: schema.name,
|
|
425
|
+
fetchTimestamp: new Date(),
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
} catch (error) {
|
|
429
|
+
// Check if this is a network/connection error
|
|
430
|
+
const errorMessage = (error as Error).message || '';
|
|
431
|
+
// Surface Laboratory-style explicit message if Wasm is missing
|
|
432
|
+
if (errorMessage.startsWith('NO_WASM:')) {
|
|
433
|
+
// Re-throw without swallowing details so UI can show this immediately
|
|
434
|
+
throw new Error(errorMessage.replace(/^NO_WASM:\s*/, ''));
|
|
435
|
+
}
|
|
436
|
+
if (errorMessage.includes('Failed to load contract')) {
|
|
437
|
+
throw new Error(
|
|
438
|
+
`Contract at ${artifacts.contractAddress} could not be loaded from the network. ` +
|
|
439
|
+
`Please verify the contract ID is correct and the network is accessible.`
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Re-throw other errors
|
|
444
|
+
throw error;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Integration points for manual contract definition input (future work):
|
|
450
|
+
*
|
|
451
|
+
* Single Input with Auto-Detection (simplified UX):
|
|
452
|
+
* - Add a new loader path: `loadStellarContractFromDefinition(definition, networkConfig)`
|
|
453
|
+
* - Auto-detect content type using magic bytes and structure:
|
|
454
|
+
* - Wasm binary: starts with magic bytes `[0x00, 0x61, 0x73, 0x6D]` (`\0asm`)
|
|
455
|
+
* - JSON spec: valid JSON array with Soroban spec entry objects
|
|
456
|
+
* - For JSON: Parse and validate, use `transformStellarSpecToSchema()` to build schema
|
|
457
|
+
* - For Wasm: Extract embedded spec from binary locally (no RPC), then build schema
|
|
458
|
+
* - Return `{ schema, source: 'manual' }` with `contractDefinitionOriginal` set to
|
|
459
|
+
* the raw input (JSON string or Wasm binary) for auto-save restoration
|
|
460
|
+
*
|
|
461
|
+
* The builder UI provides a single input field (code editor with file upload support)
|
|
462
|
+
* that accepts either format, eliminating user confusion about format selection.
|
|
463
|
+
* The auto-save system will store the resulting schema and `contractDefinitionOriginal`
|
|
464
|
+
* so the configuration restores seamlessly.
|
|
465
|
+
*/
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Transform Stellar contract spec to our internal schema format.
|
|
469
|
+
*
|
|
470
|
+
* This function is intentionally minimal at the moment and primarily used by
|
|
471
|
+
* tests. The production load path derives function metadata using the
|
|
472
|
+
* Stellar SDK via `loadStellarContractFromAddress`/`extractFunctionsFromSpec`.
|
|
473
|
+
* A full spec-to-schema converter (with robust type mapping for inputs/outputs
|
|
474
|
+
* and state mutability inference) is planned under the upcoming
|
|
475
|
+
* "Type Mapping and Data Transformation" work in
|
|
476
|
+
* `.agent-os/specs/2025-08-20-stellar-adapter-integration/tasks.md`.
|
|
477
|
+
*/
|
|
478
|
+
export function transformStellarSpecToSchema(
|
|
479
|
+
contractSpec: Record<string, unknown>,
|
|
480
|
+
contractAddress: string,
|
|
481
|
+
ecosystem: 'stellar' = 'stellar'
|
|
482
|
+
): ContractSchema {
|
|
483
|
+
logger.info('transformStellarSpecToSchema', 'Transforming Stellar spec to schema format');
|
|
484
|
+
|
|
485
|
+
const schema: ContractSchema = {
|
|
486
|
+
name: (contractSpec.name as string) || `Soroban Contract ${contractAddress.slice(0, 8)}...`,
|
|
487
|
+
ecosystem,
|
|
488
|
+
functions: (contractSpec.functions as ContractFunction[]) || [],
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
logger.info('transformStellarSpecToSchema', 'Generated schema:', {
|
|
492
|
+
name: schema.name,
|
|
493
|
+
ecosystem: schema.ecosystem,
|
|
494
|
+
functionCount: Array.isArray(schema.functions) ? schema.functions.length : 0,
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
return schema;
|
|
498
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Contract, rpc as StellarRpc, xdr } from '@stellar/stellar-sdk';
|
|
2
|
+
|
|
3
|
+
import type { StellarNetworkConfig } from '@openzeppelin/ui-types';
|
|
4
|
+
import { logger, userRpcConfigService } from '@openzeppelin/ui-utils';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns a Soroban RPC server instance honoring user overrides.
|
|
8
|
+
*/
|
|
9
|
+
function getSorobanRpcServer(networkConfig: StellarNetworkConfig): StellarRpc.Server {
|
|
10
|
+
const customRpcConfig = userRpcConfigService.getUserRpcConfig(networkConfig.id);
|
|
11
|
+
const rpcUrl = customRpcConfig?.url || networkConfig.sorobanRpcUrl;
|
|
12
|
+
if (!rpcUrl) {
|
|
13
|
+
throw new Error(`No Soroban RPC URL available for network ${networkConfig.name}`);
|
|
14
|
+
}
|
|
15
|
+
const allowHttp = new URL(rpcUrl).hostname === 'localhost';
|
|
16
|
+
return new StellarRpc.Server(rpcUrl, { allowHttp });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type StellarContractExecutableType =
|
|
20
|
+
| 'contractExecutableWasm'
|
|
21
|
+
| 'contractExecutableStellarAsset'
|
|
22
|
+
| null;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Detects executable type for a given Stellar contract ID via RPC ledger entries.
|
|
26
|
+
*/
|
|
27
|
+
export async function getStellarContractType(
|
|
28
|
+
contractId: string,
|
|
29
|
+
networkConfig: StellarNetworkConfig
|
|
30
|
+
): Promise<StellarContractExecutableType> {
|
|
31
|
+
try {
|
|
32
|
+
if (!contractId) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const rpcServer = getSorobanRpcServer(networkConfig);
|
|
37
|
+
|
|
38
|
+
// Build ledger key footprint for the contract
|
|
39
|
+
const ledgerKey = new Contract(contractId).getFootprint();
|
|
40
|
+
const ledgerEntries = await rpcServer.getLedgerEntries(ledgerKey);
|
|
41
|
+
|
|
42
|
+
const first = ledgerEntries?.entries?.[0]?.val;
|
|
43
|
+
if (!first) {
|
|
44
|
+
throw new Error('Could not obtain contract data from server.');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const executable = first.contractData()?.val()?.instance()?.executable();
|
|
48
|
+
if (!executable) {
|
|
49
|
+
throw new Error('Could not get executable from contract data.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const execWasmType = xdr.ContractExecutableType.contractExecutableWasm().name;
|
|
53
|
+
const execStellarAssetType = xdr.ContractExecutableType.contractExecutableStellarAsset().name;
|
|
54
|
+
const detected = executable.switch()?.name as string | undefined;
|
|
55
|
+
|
|
56
|
+
if (detected === execWasmType) return 'contractExecutableWasm';
|
|
57
|
+
if (detected === execStellarAssetType) return 'contractExecutableStellarAsset';
|
|
58
|
+
return null;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
logger.error('stellar:contract-type', 'Failed to detect contract type:', error);
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Something went wrong getting contract type by contract ID. ${(error as Error).message}`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { EcosystemExport, StellarNetworkConfig } from '@openzeppelin/ui-types';
|
|
2
|
+
|
|
3
|
+
import { StellarAdapter } from './adapter';
|
|
4
|
+
import { stellarAdapterConfig } from './config';
|
|
5
|
+
import { ecosystemMetadata } from './metadata';
|
|
6
|
+
import { stellarNetworks } from './networks';
|
|
7
|
+
|
|
8
|
+
export { ecosystemMetadata } from './metadata';
|
|
9
|
+
export { StellarAdapter } from './adapter';
|
|
10
|
+
|
|
11
|
+
export const ecosystemDefinition: EcosystemExport = {
|
|
12
|
+
...ecosystemMetadata,
|
|
13
|
+
networks: stellarNetworks,
|
|
14
|
+
createAdapter: (config) => new StellarAdapter(config as StellarNetworkConfig),
|
|
15
|
+
adapterConfig: stellarAdapterConfig,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Adapter-specific types
|
|
19
|
+
export type { StellarContractArtifacts } from './types/artifacts';
|
|
20
|
+
export { isStellarContractArtifacts } from './types/artifacts';
|
|
21
|
+
|
|
22
|
+
// Individual network exports
|
|
23
|
+
export { stellarPublic, stellarTestnet } from './networks';
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { FieldType, TypeMappingInfo } from '@openzeppelin/ui-types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stellar/Soroban-specific type mapping to default form field types.
|
|
5
|
+
* Based on Soroban type system: https://developers.stellar.org/docs/learn/fundamentals/contract-development/types
|
|
6
|
+
*
|
|
7
|
+
* Note: Large integer types (U64, U128, U256, I64, I128, I256) are mapped to 'bigint'
|
|
8
|
+
* instead of 'number' to avoid JavaScript's Number precision limitations.
|
|
9
|
+
* JavaScript's Number type can only safely represent integers up to 2^53 - 1,
|
|
10
|
+
* but these types can hold much larger values. The BigIntField component stores values
|
|
11
|
+
* as strings and the Stellar adapter handles conversion automatically.
|
|
12
|
+
*/
|
|
13
|
+
export const STELLAR_TYPE_TO_FIELD_TYPE: Record<string, FieldType> = {
|
|
14
|
+
// Address types
|
|
15
|
+
Address: 'blockchain-address',
|
|
16
|
+
MuxedAddress: 'blockchain-address',
|
|
17
|
+
|
|
18
|
+
// String types
|
|
19
|
+
ScString: 'text',
|
|
20
|
+
ScSymbol: 'text',
|
|
21
|
+
|
|
22
|
+
// Numeric types - unsigned integers
|
|
23
|
+
U32: 'number',
|
|
24
|
+
U64: 'bigint',
|
|
25
|
+
U128: 'bigint',
|
|
26
|
+
U256: 'bigint',
|
|
27
|
+
|
|
28
|
+
// Numeric types - signed integers
|
|
29
|
+
I32: 'number',
|
|
30
|
+
I64: 'bigint',
|
|
31
|
+
I128: 'bigint',
|
|
32
|
+
I256: 'bigint',
|
|
33
|
+
|
|
34
|
+
// Boolean type
|
|
35
|
+
Bool: 'checkbox',
|
|
36
|
+
|
|
37
|
+
// Byte types
|
|
38
|
+
Bytes: 'bytes',
|
|
39
|
+
DataUrl: 'bytes',
|
|
40
|
+
|
|
41
|
+
// Collection types
|
|
42
|
+
Vec: 'array',
|
|
43
|
+
Map: 'map',
|
|
44
|
+
|
|
45
|
+
// Complex types
|
|
46
|
+
Tuple: 'object',
|
|
47
|
+
Enum: 'select',
|
|
48
|
+
|
|
49
|
+
// Instance types (for compatibility)
|
|
50
|
+
Instance: 'object',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Stellar dynamic type patterns handled through pattern matching.
|
|
55
|
+
*/
|
|
56
|
+
const STELLAR_DYNAMIC_PATTERNS: TypeMappingInfo['dynamicPatterns'] = [
|
|
57
|
+
{ name: 'vec', syntax: 'Vec<T>', mapsTo: null, description: 'Array (maps based on inner type)' },
|
|
58
|
+
{ name: 'map', syntax: 'Map<K,V>', mapsTo: 'map', description: 'Key-value map' },
|
|
59
|
+
{
|
|
60
|
+
name: 'option',
|
|
61
|
+
syntax: 'Option<T>',
|
|
62
|
+
mapsTo: 'unwrap',
|
|
63
|
+
description: 'Optional, resolves to inner type',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'result',
|
|
67
|
+
syntax: 'Result<T>',
|
|
68
|
+
mapsTo: 'unwrap',
|
|
69
|
+
description: 'Result, resolves to inner type',
|
|
70
|
+
},
|
|
71
|
+
{ name: 'bytes-n', syntax: 'BytesN<N>', mapsTo: 'bytes', description: 'Fixed-size byte array' },
|
|
72
|
+
{
|
|
73
|
+
name: 'struct',
|
|
74
|
+
syntax: 'StructName',
|
|
75
|
+
mapsTo: 'object',
|
|
76
|
+
description: 'Custom struct (PascalCase)',
|
|
77
|
+
},
|
|
78
|
+
{ name: 'enum', syntax: 'EnumName', mapsTo: 'select', description: 'Enum type' },
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Returns complete type mapping information for Stellar.
|
|
83
|
+
*/
|
|
84
|
+
export function getStellarTypeMappingInfo(): TypeMappingInfo {
|
|
85
|
+
return {
|
|
86
|
+
primitives: { ...STELLAR_TYPE_TO_FIELD_TYPE },
|
|
87
|
+
dynamicPatterns: STELLAR_DYNAMIC_PATTERNS,
|
|
88
|
+
};
|
|
89
|
+
}
|