@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,156 @@
|
|
|
1
|
+
import { rpc as StellarRpc } from '@stellar/stellar-sdk';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ExecutionConfig,
|
|
5
|
+
StellarNetworkConfig,
|
|
6
|
+
TransactionStatusUpdate,
|
|
7
|
+
TxStatus,
|
|
8
|
+
} from '@openzeppelin/ui-types';
|
|
9
|
+
import { logger, userRpcConfigService } from '@openzeppelin/ui-utils';
|
|
10
|
+
|
|
11
|
+
import { EoaExecutionStrategy } from './eoa';
|
|
12
|
+
import { ExecutionStrategy } from './execution-strategy';
|
|
13
|
+
import type { StellarTransactionData } from './formatter';
|
|
14
|
+
import { RelayerExecutionStrategy } from './relayer';
|
|
15
|
+
|
|
16
|
+
const SYSTEM_LOG_TAG = 'adapter-stellar';
|
|
17
|
+
|
|
18
|
+
// --- Helper Functions ---
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get Soroban RPC Server instance with proper configuration
|
|
22
|
+
*/
|
|
23
|
+
function getSorobanRpcServer(networkConfig: StellarNetworkConfig): StellarRpc.Server {
|
|
24
|
+
const customRpcConfig = userRpcConfigService.getUserRpcConfig(networkConfig.id);
|
|
25
|
+
const rpcUrl = customRpcConfig?.url || networkConfig.sorobanRpcUrl;
|
|
26
|
+
|
|
27
|
+
if (!rpcUrl) {
|
|
28
|
+
throw new Error(`No Soroban RPC URL available for network ${networkConfig.name}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Allow HTTP for localhost development
|
|
32
|
+
const allowHttp = new URL(rpcUrl).hostname === 'localhost';
|
|
33
|
+
|
|
34
|
+
return new StellarRpc.Server(rpcUrl, {
|
|
35
|
+
allowHttp,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Sign and broadcast a Stellar transaction using the strategy pattern.
|
|
41
|
+
* This follows the same architecture as the EVM adapter.
|
|
42
|
+
*
|
|
43
|
+
* @param transactionData - The formatted transaction data from formatStellarTransactionData
|
|
44
|
+
* @param executionConfig - Execution configuration specifying method (eoa, relayer, etc.)
|
|
45
|
+
* @param networkConfig - Stellar network configuration
|
|
46
|
+
* @param onStatusChange - Callback for status updates
|
|
47
|
+
* @param runtimeApiKey - Optional session-only API key for methods like Relayer
|
|
48
|
+
* @returns Promise resolving to the transaction hash
|
|
49
|
+
*/
|
|
50
|
+
export async function signAndBroadcastStellarTransaction(
|
|
51
|
+
transactionData: unknown,
|
|
52
|
+
executionConfig: ExecutionConfig,
|
|
53
|
+
networkConfig: StellarNetworkConfig,
|
|
54
|
+
onStatusChange?: (status: TxStatus, details: TransactionStatusUpdate) => void,
|
|
55
|
+
runtimeApiKey?: string
|
|
56
|
+
): Promise<{ txHash: string }> {
|
|
57
|
+
logger.info(
|
|
58
|
+
SYSTEM_LOG_TAG,
|
|
59
|
+
'Stellar signAndBroadcast called with executionConfig:',
|
|
60
|
+
executionConfig
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// Validate network config
|
|
64
|
+
if (!networkConfig || networkConfig.ecosystem !== 'stellar') {
|
|
65
|
+
throw new Error('Invalid Stellar network configuration provided.');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const txData = transactionData as StellarTransactionData;
|
|
69
|
+
let strategy: ExecutionStrategy;
|
|
70
|
+
|
|
71
|
+
// Select execution strategy based on method
|
|
72
|
+
switch (executionConfig.method) {
|
|
73
|
+
case 'eoa':
|
|
74
|
+
strategy = new EoaExecutionStrategy();
|
|
75
|
+
break;
|
|
76
|
+
case 'relayer':
|
|
77
|
+
strategy = new RelayerExecutionStrategy();
|
|
78
|
+
break;
|
|
79
|
+
case 'multisig':
|
|
80
|
+
// TODO: Implement MultisigExecutionStrategy when Stellar multisig support is added
|
|
81
|
+
throw new Error('Multisig execution method not yet implemented for Stellar.');
|
|
82
|
+
default: {
|
|
83
|
+
const exhaustiveCheck: never = executionConfig;
|
|
84
|
+
logger.error(SYSTEM_LOG_TAG, `Unsupported execution method encountered: ${exhaustiveCheck}`);
|
|
85
|
+
throw new Error(`Unsupported execution method: ${exhaustiveCheck}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return strategy.execute(
|
|
90
|
+
txData,
|
|
91
|
+
executionConfig,
|
|
92
|
+
networkConfig,
|
|
93
|
+
onStatusChange || (() => {}),
|
|
94
|
+
runtimeApiKey
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Waits for a transaction to be confirmed on the blockchain.
|
|
100
|
+
*
|
|
101
|
+
* @param txHash - The hash of the transaction to wait for.
|
|
102
|
+
* @param networkConfig - The network configuration.
|
|
103
|
+
* @returns A promise resolving to the final status and receipt/error.
|
|
104
|
+
*/
|
|
105
|
+
export async function waitForStellarTransactionConfirmation(
|
|
106
|
+
txHash: string,
|
|
107
|
+
networkConfig: StellarNetworkConfig
|
|
108
|
+
): Promise<{
|
|
109
|
+
status: 'success' | 'error';
|
|
110
|
+
receipt?: unknown;
|
|
111
|
+
error?: Error;
|
|
112
|
+
}> {
|
|
113
|
+
try {
|
|
114
|
+
const rpcServer = getSorobanRpcServer(networkConfig);
|
|
115
|
+
const MAX_ATTEMPTS = 20; // More attempts for confirmation
|
|
116
|
+
let attempts = 0;
|
|
117
|
+
|
|
118
|
+
while (attempts++ < MAX_ATTEMPTS) {
|
|
119
|
+
try {
|
|
120
|
+
const txResponse = await rpcServer.getTransaction(txHash);
|
|
121
|
+
|
|
122
|
+
switch (txResponse.status) {
|
|
123
|
+
case 'SUCCESS':
|
|
124
|
+
return {
|
|
125
|
+
status: 'success',
|
|
126
|
+
receipt: txResponse,
|
|
127
|
+
};
|
|
128
|
+
case 'FAILED':
|
|
129
|
+
return {
|
|
130
|
+
status: 'error',
|
|
131
|
+
error: new Error(`Transaction failed: ${JSON.stringify(txResponse.resultXdr)}`),
|
|
132
|
+
};
|
|
133
|
+
case 'NOT_FOUND':
|
|
134
|
+
// Continue waiting
|
|
135
|
+
break;
|
|
136
|
+
default:
|
|
137
|
+
// Continue waiting for other statuses
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
141
|
+
} catch (error) {
|
|
142
|
+
logger.error('waitForStellarTransactionConfirmation', `Attempt ${attempts} failed:`, error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
status: 'error',
|
|
148
|
+
error: new Error('Transaction confirmation timeout'),
|
|
149
|
+
};
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return {
|
|
152
|
+
status: 'error',
|
|
153
|
+
error: error as Error,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Re-export all types and functions for backward compatibility
|
|
2
|
+
export type {
|
|
3
|
+
SorobanArgumentValue,
|
|
4
|
+
SorobanEnumValue,
|
|
5
|
+
SorobanMapEntry,
|
|
6
|
+
SorobanComplexValue,
|
|
7
|
+
} from './parsers';
|
|
8
|
+
|
|
9
|
+
export { parseStellarInput, getScValsFromArgs, valueToScVal } from './parsers';
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { scValToNative, xdr } from '@stellar/stellar-sdk';
|
|
2
|
+
|
|
3
|
+
import type { ContractFunction } from '@openzeppelin/ui-types';
|
|
4
|
+
import { bytesToHex, logger } from '@openzeppelin/ui-utils';
|
|
5
|
+
|
|
6
|
+
import { isSerializableObject, stringifyWithBigInt } from '../utils';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Formats the result of a Stellar view function call into a user-friendly string.
|
|
10
|
+
*
|
|
11
|
+
* @param result The result value (can be ScVal, native JS value, or other types).
|
|
12
|
+
* @param functionDetails The contract function details.
|
|
13
|
+
* @returns A string representation suitable for display.
|
|
14
|
+
*/
|
|
15
|
+
export function formatStellarFunctionResult(
|
|
16
|
+
result: unknown,
|
|
17
|
+
functionDetails: ContractFunction
|
|
18
|
+
): string {
|
|
19
|
+
if (!functionDetails.outputs || !Array.isArray(functionDetails.outputs)) {
|
|
20
|
+
logger.warn(
|
|
21
|
+
'formatStellarFunctionResult',
|
|
22
|
+
`Output definition missing or invalid for function ${functionDetails.name}.`
|
|
23
|
+
);
|
|
24
|
+
return '[Error: Output definition missing]';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
let valueToFormat: unknown;
|
|
29
|
+
|
|
30
|
+
// Handle null/undefined values
|
|
31
|
+
if (result === null || result === undefined) {
|
|
32
|
+
return '(null)';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check if result is an ScVal and convert to native JS value
|
|
36
|
+
if (isScVal(result)) {
|
|
37
|
+
try {
|
|
38
|
+
// Special handling for void ScVal
|
|
39
|
+
const scVal = result as xdr.ScVal;
|
|
40
|
+
if (scVal.switch().name === 'scvVoid') {
|
|
41
|
+
return '(void)';
|
|
42
|
+
}
|
|
43
|
+
valueToFormat = scValToNative(scVal);
|
|
44
|
+
|
|
45
|
+
// Convert Buffer to Uint8Array for cross-platform compatibility
|
|
46
|
+
// scValToNative may return Buffer objects in some environments
|
|
47
|
+
if (
|
|
48
|
+
valueToFormat &&
|
|
49
|
+
typeof valueToFormat === 'object' &&
|
|
50
|
+
'constructor' in valueToFormat &&
|
|
51
|
+
valueToFormat.constructor?.name === 'Buffer'
|
|
52
|
+
) {
|
|
53
|
+
valueToFormat = new Uint8Array(valueToFormat as ArrayLike<number>);
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
logger.error('formatStellarFunctionResult', 'Failed to convert ScVal to native', {
|
|
57
|
+
functionName: functionDetails.name,
|
|
58
|
+
error,
|
|
59
|
+
});
|
|
60
|
+
return '[Error: Failed to decode ScVal]';
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
valueToFormat = result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Format based on type
|
|
67
|
+
if (typeof valueToFormat === 'bigint') {
|
|
68
|
+
return valueToFormat.toString();
|
|
69
|
+
} else if (typeof valueToFormat === 'string') {
|
|
70
|
+
return valueToFormat;
|
|
71
|
+
} else if (typeof valueToFormat === 'number') {
|
|
72
|
+
return valueToFormat.toString();
|
|
73
|
+
} else if (typeof valueToFormat === 'boolean') {
|
|
74
|
+
return String(valueToFormat);
|
|
75
|
+
} else if (valueToFormat instanceof Uint8Array) {
|
|
76
|
+
// Handle byte arrays - convert to hex string
|
|
77
|
+
return bytesToHex(valueToFormat, true);
|
|
78
|
+
} else if (Array.isArray(valueToFormat)) {
|
|
79
|
+
// Handle arrays/vectors
|
|
80
|
+
if (valueToFormat.length === 0) {
|
|
81
|
+
return '[]';
|
|
82
|
+
}
|
|
83
|
+
// Use compact formatting for simple arrays, pretty formatting for complex ones
|
|
84
|
+
if (
|
|
85
|
+
valueToFormat.every(
|
|
86
|
+
(item) =>
|
|
87
|
+
typeof item === 'string' ||
|
|
88
|
+
typeof item === 'number' ||
|
|
89
|
+
typeof item === 'boolean' ||
|
|
90
|
+
typeof item === 'bigint'
|
|
91
|
+
)
|
|
92
|
+
) {
|
|
93
|
+
return stringifyWithBigInt(valueToFormat); // No spacing for simple arrays
|
|
94
|
+
}
|
|
95
|
+
return stringifyWithBigInt(valueToFormat, 2);
|
|
96
|
+
} else if (isSerializableObject(valueToFormat)) {
|
|
97
|
+
// Handle objects/maps/structs
|
|
98
|
+
if (Object.keys(valueToFormat as object).length === 0) {
|
|
99
|
+
return '{}';
|
|
100
|
+
}
|
|
101
|
+
return stringifyWithBigInt(valueToFormat, 2);
|
|
102
|
+
} else if (valueToFormat === null || valueToFormat === undefined) {
|
|
103
|
+
return '(null)';
|
|
104
|
+
} else {
|
|
105
|
+
// Handle any other type by stringifying
|
|
106
|
+
return stringifyWithBigInt(valueToFormat, 2);
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
const errorMessage = `Error formatting result for ${functionDetails.name}: ${(error as Error).message}`;
|
|
110
|
+
logger.error('formatStellarFunctionResult', errorMessage, {
|
|
111
|
+
functionName: functionDetails.name,
|
|
112
|
+
result,
|
|
113
|
+
error,
|
|
114
|
+
});
|
|
115
|
+
return `[${errorMessage}]`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Type guard to check if a value is an ScVal
|
|
121
|
+
*/
|
|
122
|
+
function isScVal(value: unknown): boolean {
|
|
123
|
+
if (!value || typeof value !== 'object') {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
// Use instanceof check for robust type detection
|
|
129
|
+
return typeof xdr !== 'undefined' && xdr.ScVal && value instanceof xdr.ScVal;
|
|
130
|
+
} catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { nativeToScVal, xdr } from '@stellar/stellar-sdk';
|
|
2
|
+
|
|
3
|
+
import { detectBytesEncoding, stringToBytes } from '@openzeppelin/ui-utils';
|
|
4
|
+
|
|
5
|
+
import { convertStellarTypeToScValType } from '../../utils/formatting';
|
|
6
|
+
import {
|
|
7
|
+
convertEnumToScVal,
|
|
8
|
+
convertObjectToMap,
|
|
9
|
+
convertObjectToScVal,
|
|
10
|
+
convertTupleToScVal,
|
|
11
|
+
getScValFromArg,
|
|
12
|
+
getScValFromPrimitive,
|
|
13
|
+
isComplexObjectArray,
|
|
14
|
+
isEnumArgumentSet,
|
|
15
|
+
isMapArray,
|
|
16
|
+
isObjectWithTypedValues,
|
|
17
|
+
isPrimitiveArgumentSet,
|
|
18
|
+
isPrimitiveArray,
|
|
19
|
+
isPrimitiveValue,
|
|
20
|
+
isTupleArray,
|
|
21
|
+
} from '../../utils/input-parsing';
|
|
22
|
+
import type {
|
|
23
|
+
SorobanArgumentValue,
|
|
24
|
+
SorobanComplexValue,
|
|
25
|
+
SorobanEnumValue,
|
|
26
|
+
SorobanMapEntry,
|
|
27
|
+
} from './types';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Advanced ScVal generation from complex Soroban arguments.
|
|
31
|
+
* Handles Maps, Enums, Tuples, and nested structures.
|
|
32
|
+
*
|
|
33
|
+
* @param args - Complex argument structure from advanced UI forms
|
|
34
|
+
* @param scVals - Accumulator for generated ScVals (used for recursion)
|
|
35
|
+
* @returns Array of ScVals ready for contract invocation
|
|
36
|
+
*/
|
|
37
|
+
export function getScValsFromArgs(
|
|
38
|
+
args: Record<string, SorobanComplexValue> | SorobanComplexValue[],
|
|
39
|
+
scVals: xdr.ScVal[] = []
|
|
40
|
+
): xdr.ScVal[] {
|
|
41
|
+
// Handle array input (multiple arguments)
|
|
42
|
+
if (Array.isArray(args)) {
|
|
43
|
+
return args.map((arg) => {
|
|
44
|
+
if (typeof arg === 'object' && 'value' in arg && 'type' in arg) {
|
|
45
|
+
return getScValFromPrimitive(arg as SorobanArgumentValue);
|
|
46
|
+
}
|
|
47
|
+
return getScValFromArg(arg, []);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Handle primitive case - all values have type and value
|
|
52
|
+
if (isPrimitiveArgumentSet(args)) {
|
|
53
|
+
const primitiveScVals = Object.values(args).map((v) => {
|
|
54
|
+
return getScValFromPrimitive(v as SorobanArgumentValue);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return primitiveScVals;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Handle enum case - values have tag or enum properties
|
|
61
|
+
if (isEnumArgumentSet(args)) {
|
|
62
|
+
const enumScVals = Object.values(args).map((v) => {
|
|
63
|
+
return convertEnumToScVal(v as SorobanEnumValue, scVals);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return enumScVals;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Handle complex cases (maps, objects, arrays)
|
|
70
|
+
for (const argKey in args) {
|
|
71
|
+
const argValue = args[argKey];
|
|
72
|
+
|
|
73
|
+
if (Array.isArray(argValue)) {
|
|
74
|
+
// Map case - array of key-value pair objects
|
|
75
|
+
if (isMapArray(argValue)) {
|
|
76
|
+
const { mapVal, mapType } = convertObjectToMap(argValue as SorobanMapEntry[]);
|
|
77
|
+
const mapScVal = nativeToScVal(mapVal, { type: mapType });
|
|
78
|
+
scVals.push(mapScVal);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Vector case #1: array of complex objects or tuples
|
|
83
|
+
if (isComplexObjectArray(argValue)) {
|
|
84
|
+
const arrayScVals = argValue.map((v) => {
|
|
85
|
+
// Use proper type guards to ensure safe casting
|
|
86
|
+
if (typeof v === 'object' && v !== null && ('tag' in v || 'enum' in v)) {
|
|
87
|
+
return convertEnumToScVal(v as SorobanEnumValue, scVals);
|
|
88
|
+
}
|
|
89
|
+
if (
|
|
90
|
+
typeof v === 'object' &&
|
|
91
|
+
v !== null &&
|
|
92
|
+
!Array.isArray(v) &&
|
|
93
|
+
!('0' in v) &&
|
|
94
|
+
!('1' in v)
|
|
95
|
+
) {
|
|
96
|
+
return convertObjectToScVal(v as Record<string, SorobanArgumentValue>);
|
|
97
|
+
}
|
|
98
|
+
// Fallback for other types
|
|
99
|
+
return nativeToScVal(v);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const tupleScValsVec = xdr.ScVal.scvVec(arrayScVals);
|
|
103
|
+
scVals.push(tupleScValsVec);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Vector case #2: array of primitives (homogeneous type)
|
|
108
|
+
if (isPrimitiveArray(argValue)) {
|
|
109
|
+
// TypeScript now knows argValue is SorobanArgumentValue[] thanks to type predicate
|
|
110
|
+
const arrayScVals = argValue.reduce((acc: unknown[], v) => {
|
|
111
|
+
const primitive = v as unknown as SorobanArgumentValue;
|
|
112
|
+
if (primitive.type === 'bool') {
|
|
113
|
+
acc.push(primitive.value === 'true' ? true : false);
|
|
114
|
+
} else if (primitive.type === 'bytes') {
|
|
115
|
+
const encoding = detectBytesEncoding(primitive.value as string);
|
|
116
|
+
acc.push(stringToBytes(primitive.value as string, encoding));
|
|
117
|
+
} else {
|
|
118
|
+
acc.push(primitive.value);
|
|
119
|
+
}
|
|
120
|
+
return acc;
|
|
121
|
+
}, []);
|
|
122
|
+
|
|
123
|
+
const firstItem = argValue[0];
|
|
124
|
+
const scValType = convertStellarTypeToScValType(firstItem.type);
|
|
125
|
+
const typeHint = Array.isArray(scValType) ? scValType[0] : scValType;
|
|
126
|
+
const scVal = nativeToScVal(arrayScVals, {
|
|
127
|
+
type: typeHint,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
scVals.push(scVal);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Tuple case - mixed types in array
|
|
135
|
+
if (isTupleArray(argValue)) {
|
|
136
|
+
// TypeScript now knows argValue is SorobanArgumentValue[] thanks to type predicate
|
|
137
|
+
const tupleScValsVec = convertTupleToScVal(argValue);
|
|
138
|
+
scVals.push(tupleScValsVec);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Object case - structured data
|
|
144
|
+
if (isObjectWithTypedValues(argValue)) {
|
|
145
|
+
const convertedObj = convertObjectToScVal(argValue as Record<string, SorobanArgumentValue>);
|
|
146
|
+
scVals.push(convertedObj);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Single primitive value
|
|
151
|
+
if (isPrimitiveValue(argValue)) {
|
|
152
|
+
scVals.push(getScValFromPrimitive(argValue as SorobanArgumentValue));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return scVals;
|
|
157
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { isMapEntryArray } from '@openzeppelin/ui-types';
|
|
2
|
+
import { logger } from '@openzeppelin/ui-utils';
|
|
3
|
+
|
|
4
|
+
const SYSTEM_LOG_TAG = 'GenericParser';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parses a generic type string and extracts the base type and parameters
|
|
8
|
+
* @param typeString - Type like "Vec<U32>", "Map<U32,Address>", "Option<Vec<U32>>"
|
|
9
|
+
* @returns Object with baseType and parameters, or null if not generic
|
|
10
|
+
*/
|
|
11
|
+
export function parseGenericType(typeString: string): {
|
|
12
|
+
baseType: string;
|
|
13
|
+
parameters: string[];
|
|
14
|
+
} | null {
|
|
15
|
+
const match = typeString.match(/^(\w+)<(.*)>$/);
|
|
16
|
+
if (!match) return null;
|
|
17
|
+
|
|
18
|
+
const baseType = match[1];
|
|
19
|
+
const paramString = match[2];
|
|
20
|
+
|
|
21
|
+
// Handle nested generics by counting angle brackets
|
|
22
|
+
const parameters: string[] = [];
|
|
23
|
+
let current = '';
|
|
24
|
+
let depth = 0;
|
|
25
|
+
let i = 0;
|
|
26
|
+
|
|
27
|
+
while (i < paramString.length) {
|
|
28
|
+
const char = paramString[i];
|
|
29
|
+
|
|
30
|
+
if (char === '<') {
|
|
31
|
+
depth++;
|
|
32
|
+
current += char;
|
|
33
|
+
} else if (char === '>') {
|
|
34
|
+
depth--;
|
|
35
|
+
current += char;
|
|
36
|
+
} else if (char === ',' && depth === 0) {
|
|
37
|
+
parameters.push(current.trim());
|
|
38
|
+
current = '';
|
|
39
|
+
} else {
|
|
40
|
+
current += char;
|
|
41
|
+
}
|
|
42
|
+
i++;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (current.trim()) {
|
|
46
|
+
parameters.push(current.trim());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { baseType, parameters };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Parses generic types like Vec<T>, Map<K,V>, and Option<T>.
|
|
54
|
+
*
|
|
55
|
+
* @param value - The input value from the form
|
|
56
|
+
* @param parameterType - The generic parameter type (e.g., 'Vec<U32>', 'Map<Symbol,Bytes>')
|
|
57
|
+
* @param parseInnerValue - Function to recursively parse inner values
|
|
58
|
+
* @returns The parsed value suitable for further processing
|
|
59
|
+
*/
|
|
60
|
+
export function parseGeneric(
|
|
61
|
+
value: unknown,
|
|
62
|
+
parameterType: string,
|
|
63
|
+
parseInnerValue: (val: unknown, type: string) => unknown
|
|
64
|
+
): unknown {
|
|
65
|
+
try {
|
|
66
|
+
const genericInfo = parseGenericType(parameterType);
|
|
67
|
+
if (!genericInfo) {
|
|
68
|
+
return null; // Not a generic type
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const { baseType, parameters } = genericInfo;
|
|
72
|
+
|
|
73
|
+
switch (baseType) {
|
|
74
|
+
case 'Vec': {
|
|
75
|
+
// Handle Vec<T> types
|
|
76
|
+
if (!Array.isArray(value)) {
|
|
77
|
+
throw new Error(`Array expected for Vec type ${parameterType}, got ${typeof value}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const innerType = parameters[0];
|
|
81
|
+
if (!innerType) {
|
|
82
|
+
throw new Error(`Could not parse Vec inner type: ${parameterType}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return value.map((item) => parseInnerValue(item, innerType));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
case 'Map': {
|
|
89
|
+
// Handle Map<K,V> types
|
|
90
|
+
if (!isMapEntryArray(value)) {
|
|
91
|
+
throw new Error(`Array of MapEntry objects expected for Map type, got ${typeof value}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (parameters.length < 2) {
|
|
95
|
+
throw new Error(`Could not parse Map types: ${parameterType}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const mapKeyType = parameters[0];
|
|
99
|
+
const mapValueType = parameters[1];
|
|
100
|
+
|
|
101
|
+
// Convert MapEntry array to Stellar SDK expected format
|
|
102
|
+
return value.map((entry) => ({
|
|
103
|
+
0: {
|
|
104
|
+
value: entry.key,
|
|
105
|
+
type: mapKeyType,
|
|
106
|
+
},
|
|
107
|
+
1: {
|
|
108
|
+
value: entry.value,
|
|
109
|
+
type: mapValueType,
|
|
110
|
+
},
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
case 'Option': {
|
|
115
|
+
// Handle Option<T> types - empty string should be treated as null
|
|
116
|
+
if (value === null || value === undefined || value === '') {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const innerType = parameters[0];
|
|
121
|
+
if (!innerType) {
|
|
122
|
+
throw new Error(`Could not parse Option inner type: ${parameterType}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return parseInnerValue(value, innerType);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
case 'Result': {
|
|
129
|
+
// Handle Result<T,E> types (basic support)
|
|
130
|
+
if (parameters.length < 2) {
|
|
131
|
+
throw new Error(`Could not parse Result types: ${parameterType}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Result types typically come as {ok: value} or {err: error}
|
|
135
|
+
if (typeof value === 'object' && value !== null) {
|
|
136
|
+
const resultObj = value as Record<string, unknown>;
|
|
137
|
+
if ('ok' in resultObj) {
|
|
138
|
+
return {
|
|
139
|
+
ok: parseInnerValue(resultObj.ok, parameters[0]),
|
|
140
|
+
};
|
|
141
|
+
} else if ('err' in resultObj) {
|
|
142
|
+
return {
|
|
143
|
+
err: parseInnerValue(resultObj.err, parameters[1]),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return value; // Pass through if not in expected format
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
default:
|
|
152
|
+
// Unknown generic type
|
|
153
|
+
logger.warn(SYSTEM_LOG_TAG, `Unknown generic type: ${baseType}`);
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
logger.error(SYSTEM_LOG_TAG, `Failed to parse generic type ${parameterType}:`, error);
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Checks if the given parameter type is a generic type that can be handled by this parser.
|
|
164
|
+
*
|
|
165
|
+
* @param parameterType - The Stellar parameter type
|
|
166
|
+
* @returns True if this is a generic type
|
|
167
|
+
*/
|
|
168
|
+
export function isGenericType(parameterType: string): boolean {
|
|
169
|
+
const genericInfo = parseGenericType(parameterType);
|
|
170
|
+
return genericInfo !== null && ['Vec', 'Map', 'Option', 'Result'].includes(genericInfo.baseType);
|
|
171
|
+
}
|