@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,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Control } from 'react-hook-form';
|
|
3
|
+
|
|
4
|
+
import { BooleanField, NumberField } from '@openzeppelin/ui-components';
|
|
5
|
+
|
|
6
|
+
import type { StellarRelayerFormData } from './useStellarRelayerOptions';
|
|
7
|
+
|
|
8
|
+
interface FeeConfigurationProps {
|
|
9
|
+
control: Control<StellarRelayerFormData>;
|
|
10
|
+
showBasicFeeOnly: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const FeeConfiguration: React.FC<FeeConfigurationProps> = ({
|
|
14
|
+
control,
|
|
15
|
+
showBasicFeeOnly,
|
|
16
|
+
}) => {
|
|
17
|
+
return (
|
|
18
|
+
<div className="space-y-4">
|
|
19
|
+
<NumberField
|
|
20
|
+
id="maxFee"
|
|
21
|
+
label="Maximum Fee (stroops)"
|
|
22
|
+
name="transactionOptions.maxFee"
|
|
23
|
+
control={control}
|
|
24
|
+
placeholder="e.g., 1000000 (0.1 XLM)"
|
|
25
|
+
helperText="Maximum fee you're willing to pay in stroops (1 XLM = 10,000,000 stroops). Leave empty to use network defaults."
|
|
26
|
+
min={0}
|
|
27
|
+
step={1}
|
|
28
|
+
/>
|
|
29
|
+
|
|
30
|
+
{!showBasicFeeOnly && (
|
|
31
|
+
<BooleanField
|
|
32
|
+
id="feeBump"
|
|
33
|
+
label="Enable Fee Bump"
|
|
34
|
+
name="transactionOptions.feeBump"
|
|
35
|
+
control={control}
|
|
36
|
+
helperText="Automatically increase fee if transaction gets stuck in the network."
|
|
37
|
+
/>
|
|
38
|
+
)}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@openzeppelin/ui-components';
|
|
4
|
+
|
|
5
|
+
import { AdvancedInfo } from './AdvancedInfo';
|
|
6
|
+
import { FeeConfiguration } from './FeeConfiguration';
|
|
7
|
+
import { TransactionTiming } from './TransactionTiming';
|
|
8
|
+
import { useStellarRelayerOptions } from './useStellarRelayerOptions';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Stellar-specific relayer transaction options component.
|
|
12
|
+
*
|
|
13
|
+
* Provides configuration for Stellar transaction parameters:
|
|
14
|
+
* - Basic: Simple max fee configuration with reasonable defaults
|
|
15
|
+
* - Advanced: Full control over maxFee, validUntil, and feeBump options
|
|
16
|
+
*
|
|
17
|
+
* Stellar transactions have different parameters compared to EVM:
|
|
18
|
+
* - maxFee: Maximum fee willing to pay (in stroops)
|
|
19
|
+
* - validUntil: Transaction expiration time
|
|
20
|
+
* - feeBump: Enable fee bump for stuck transactions
|
|
21
|
+
*/
|
|
22
|
+
export const StellarRelayerOptions: React.FC<{
|
|
23
|
+
options: Record<string, unknown>;
|
|
24
|
+
onChange: (options: Record<string, unknown>) => void;
|
|
25
|
+
}> = ({ options, onChange }) => {
|
|
26
|
+
const [showAdvancedInfo, setShowAdvancedInfo] = React.useState(false);
|
|
27
|
+
|
|
28
|
+
const { control, configMode, handleModeChange } = useStellarRelayerOptions({
|
|
29
|
+
options,
|
|
30
|
+
onChange,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="space-y-4">
|
|
35
|
+
<AdvancedInfo
|
|
36
|
+
showAdvancedInfo={showAdvancedInfo}
|
|
37
|
+
onToggle={() => setShowAdvancedInfo(!showAdvancedInfo)}
|
|
38
|
+
/>
|
|
39
|
+
|
|
40
|
+
<Tabs value={configMode} onValueChange={handleModeChange}>
|
|
41
|
+
<TabsList className="grid w-full grid-cols-2">
|
|
42
|
+
<TabsTrigger value="basic">Basic</TabsTrigger>
|
|
43
|
+
<TabsTrigger value="advanced">Advanced</TabsTrigger>
|
|
44
|
+
</TabsList>
|
|
45
|
+
|
|
46
|
+
<TabsContent value="basic" className="space-y-4">
|
|
47
|
+
<FeeConfiguration control={control} showBasicFeeOnly={true} />
|
|
48
|
+
</TabsContent>
|
|
49
|
+
|
|
50
|
+
<TabsContent value="advanced" className="space-y-4">
|
|
51
|
+
<div className="space-y-6">
|
|
52
|
+
<FeeConfiguration control={control} showBasicFeeOnly={false} />
|
|
53
|
+
|
|
54
|
+
<TransactionTiming control={control} />
|
|
55
|
+
</div>
|
|
56
|
+
</TabsContent>
|
|
57
|
+
</Tabs>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Control, Controller } from 'react-hook-form';
|
|
3
|
+
|
|
4
|
+
import { Button, DateTimeField } from '@openzeppelin/ui-components';
|
|
5
|
+
|
|
6
|
+
import type { StellarRelayerFormData } from './useStellarRelayerOptions';
|
|
7
|
+
|
|
8
|
+
interface TransactionTimingProps {
|
|
9
|
+
control: Control<StellarRelayerFormData>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const TransactionTiming: React.FC<TransactionTimingProps> = ({ control }) => {
|
|
13
|
+
// Helper function to generate a datetime-local value for 1 hour from now
|
|
14
|
+
const getOneHourFromNow = () => {
|
|
15
|
+
const now = new Date();
|
|
16
|
+
now.setHours(now.getHours() + 1);
|
|
17
|
+
// Format for datetime-local input (YYYY-MM-DDTHH:mm)
|
|
18
|
+
return now.toISOString().slice(0, 16);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Helper function to generate a datetime-local value for 24 hours from now
|
|
22
|
+
const getTwentyFourHoursFromNow = () => {
|
|
23
|
+
const now = new Date();
|
|
24
|
+
now.setHours(now.getHours() + 24);
|
|
25
|
+
return now.toISOString().slice(0, 16);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="space-y-4">
|
|
30
|
+
<DateTimeField
|
|
31
|
+
id="validUntil"
|
|
32
|
+
label="Transaction Expiration"
|
|
33
|
+
name="transactionOptions.validUntil"
|
|
34
|
+
control={control}
|
|
35
|
+
placeholder="YYYY-MM-DDTHH:mm"
|
|
36
|
+
helperText="Set when this transaction should expire. Leave empty for no expiration limit."
|
|
37
|
+
/>
|
|
38
|
+
<div className="flex gap-2 pt-1">
|
|
39
|
+
<Controller
|
|
40
|
+
name="transactionOptions.validUntil"
|
|
41
|
+
control={control}
|
|
42
|
+
render={({ field }) => (
|
|
43
|
+
<>
|
|
44
|
+
<Button
|
|
45
|
+
type="button"
|
|
46
|
+
variant="outline"
|
|
47
|
+
size="sm"
|
|
48
|
+
onClick={() => field.onChange(new Date(getOneHourFromNow()).toISOString())}
|
|
49
|
+
className="text-xs"
|
|
50
|
+
>
|
|
51
|
+
+1 Hour
|
|
52
|
+
</Button>
|
|
53
|
+
<Button
|
|
54
|
+
type="button"
|
|
55
|
+
variant="outline"
|
|
56
|
+
size="sm"
|
|
57
|
+
onClick={() => field.onChange(new Date(getTwentyFourHoursFromNow()).toISOString())}
|
|
58
|
+
className="text-xs"
|
|
59
|
+
>
|
|
60
|
+
+24 Hours
|
|
61
|
+
</Button>
|
|
62
|
+
<Button
|
|
63
|
+
type="button"
|
|
64
|
+
variant="outline"
|
|
65
|
+
size="sm"
|
|
66
|
+
onClick={() => field.onChange('')}
|
|
67
|
+
className="text-xs"
|
|
68
|
+
>
|
|
69
|
+
Clear
|
|
70
|
+
</Button>
|
|
71
|
+
</>
|
|
72
|
+
)}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { StellarRelayerOptions } from './StellarRelayerOptions';
|
|
2
|
+
export { useStellarRelayerOptions } from './useStellarRelayerOptions';
|
|
3
|
+
export { AdvancedInfo } from './AdvancedInfo';
|
|
4
|
+
export { FeeConfiguration } from './FeeConfiguration';
|
|
5
|
+
export { TransactionTiming } from './TransactionTiming';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useForm } from 'react-hook-form';
|
|
3
|
+
|
|
4
|
+
import type { StellarRelayerTransactionOptions } from '../relayer';
|
|
5
|
+
|
|
6
|
+
export interface StellarRelayerFormData {
|
|
7
|
+
transactionOptions: StellarRelayerTransactionOptions;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface UseStellarRelayerOptionsProps {
|
|
11
|
+
options: Record<string, unknown>;
|
|
12
|
+
onChange: (options: Record<string, unknown>) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const useStellarRelayerOptions = ({ options, onChange }: UseStellarRelayerOptionsProps) => {
|
|
16
|
+
// Store the latest onChange callback in a ref to avoid dependency issues
|
|
17
|
+
const onChangeRef = useRef(onChange);
|
|
18
|
+
onChangeRef.current = onChange;
|
|
19
|
+
|
|
20
|
+
// Initialize form with options from parent (only on mount to prevent loops)
|
|
21
|
+
const initialOptions: StellarRelayerTransactionOptions = {
|
|
22
|
+
maxFee: options.maxFee as number | undefined,
|
|
23
|
+
validUntil: options.validUntil as string | undefined,
|
|
24
|
+
feeBump: options.feeBump as boolean | undefined,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const { control, setValue, watch } = useForm<StellarRelayerFormData>({
|
|
28
|
+
defaultValues: {
|
|
29
|
+
transactionOptions: initialOptions,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const formValues = watch('transactionOptions');
|
|
34
|
+
const isInitialMount = useRef(true);
|
|
35
|
+
|
|
36
|
+
// Track user's intended mode (separate from auto-detection)
|
|
37
|
+
const [userMode, setUserMode] = useState<'basic' | 'advanced'>(() => {
|
|
38
|
+
// Determine initial mode based on existing settings
|
|
39
|
+
const hasAdvancedSettings = Boolean(formValues.validUntil || formValues.feeBump);
|
|
40
|
+
return hasAdvancedSettings ? 'advanced' : 'basic';
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const configMode = userMode;
|
|
44
|
+
|
|
45
|
+
// Handle initial mount - ensure any default values are communicated to parent
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (isInitialMount.current) {
|
|
48
|
+
isInitialMount.current = false;
|
|
49
|
+
// No default values to set for Stellar (unlike EVM's Speed.FAST)
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
// Notify parent of changes after initial mount - watch specific fields
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (isInitialMount.current) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const timeoutId = setTimeout(() => {
|
|
61
|
+
const newOptions: StellarRelayerTransactionOptions = {};
|
|
62
|
+
|
|
63
|
+
// Set maxFee if provided
|
|
64
|
+
if (formValues.maxFee !== undefined && formValues.maxFee !== null) {
|
|
65
|
+
newOptions.maxFee = formValues.maxFee;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Set validUntil if provided (check for actual date string, not just truthy)
|
|
69
|
+
if (formValues.validUntil && formValues.validUntil.trim() !== '') {
|
|
70
|
+
newOptions.validUntil = formValues.validUntil;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Set feeBump if enabled
|
|
74
|
+
if (formValues.feeBump !== undefined) {
|
|
75
|
+
newOptions.feeBump = formValues.feeBump;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Soroban transactions do not support memos; do not propagate any
|
|
79
|
+
|
|
80
|
+
onChangeRef.current(newOptions as Record<string, unknown>);
|
|
81
|
+
}, 100);
|
|
82
|
+
|
|
83
|
+
return () => clearTimeout(timeoutId);
|
|
84
|
+
}, [formValues.maxFee, formValues.validUntil, formValues.feeBump]);
|
|
85
|
+
|
|
86
|
+
// Event handlers
|
|
87
|
+
const handleModeChange = (mode: string) => {
|
|
88
|
+
const newMode = mode as 'basic' | 'advanced';
|
|
89
|
+
setUserMode(newMode);
|
|
90
|
+
|
|
91
|
+
if (newMode === 'basic') {
|
|
92
|
+
// Reset advanced options when switching to basic mode
|
|
93
|
+
setValue('transactionOptions', {
|
|
94
|
+
...formValues,
|
|
95
|
+
validUntil: undefined,
|
|
96
|
+
feeBump: undefined,
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
// When switching to advanced mode, keep existing values or set reasonable defaults
|
|
100
|
+
setValue('transactionOptions', {
|
|
101
|
+
...formValues,
|
|
102
|
+
validUntil: formValues.validUntil || undefined,
|
|
103
|
+
feeBump: formValues.feeBump || false,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
control,
|
|
110
|
+
formValues,
|
|
111
|
+
configMode,
|
|
112
|
+
handleModeChange,
|
|
113
|
+
};
|
|
114
|
+
};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Account,
|
|
3
|
+
BASE_FEE,
|
|
4
|
+
Contract,
|
|
5
|
+
rpc as StellarRpc,
|
|
6
|
+
TransactionBuilder,
|
|
7
|
+
} from '@stellar/stellar-sdk';
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
ExecutionConfig,
|
|
11
|
+
StellarNetworkConfig,
|
|
12
|
+
TransactionStatusUpdate,
|
|
13
|
+
TxStatus,
|
|
14
|
+
} from '@openzeppelin/ui-types';
|
|
15
|
+
import { logger, userRpcConfigService } from '@openzeppelin/ui-utils';
|
|
16
|
+
|
|
17
|
+
import { CALLER_PLACEHOLDER } from '../access-control/actions';
|
|
18
|
+
import { valueToScVal } from '../transform/input-parser';
|
|
19
|
+
import { getStellarWalletConnectionStatus, signTransaction } from '../wallet/connection';
|
|
20
|
+
import { ExecutionStrategy } from './execution-strategy';
|
|
21
|
+
import type { StellarTransactionData } from './formatter';
|
|
22
|
+
|
|
23
|
+
const SYSTEM_LOG_TAG = 'EoaExecutionStrategy';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get Soroban RPC Server instance with proper configuration
|
|
27
|
+
*/
|
|
28
|
+
function getSorobanRpcServer(networkConfig: StellarNetworkConfig): StellarRpc.Server {
|
|
29
|
+
const customRpcConfig = userRpcConfigService.getUserRpcConfig(networkConfig.id);
|
|
30
|
+
const rpcUrl = customRpcConfig?.url || networkConfig.sorobanRpcUrl;
|
|
31
|
+
|
|
32
|
+
if (!rpcUrl) {
|
|
33
|
+
throw new Error(`No Soroban RPC URL available for network ${networkConfig.name}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Allow HTTP for localhost development
|
|
37
|
+
const allowHttp = new URL(rpcUrl).hostname === 'localhost';
|
|
38
|
+
|
|
39
|
+
return new StellarRpc.Server(rpcUrl, {
|
|
40
|
+
allowHttp,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Implements the ExecutionStrategy for a standard Externally Owned Account (EOA).
|
|
46
|
+
* This strategy involves signing and broadcasting a transaction directly from the user's
|
|
47
|
+
* connected Stellar wallet, which is the most common way of interacting with Stellar/Soroban contracts.
|
|
48
|
+
*/
|
|
49
|
+
export class EoaExecutionStrategy implements ExecutionStrategy {
|
|
50
|
+
public async execute(
|
|
51
|
+
transactionData: StellarTransactionData,
|
|
52
|
+
executionConfig: ExecutionConfig,
|
|
53
|
+
networkConfig: StellarNetworkConfig,
|
|
54
|
+
onStatusChange: (status: TxStatus, details: TransactionStatusUpdate) => void,
|
|
55
|
+
// runtimeApiKey is unused in EOA strategy but required by the interface
|
|
56
|
+
_runtimeApiKey?: string
|
|
57
|
+
): Promise<{ txHash: string }> {
|
|
58
|
+
logger.info(SYSTEM_LOG_TAG, 'Using Stellar EOA execution strategy');
|
|
59
|
+
|
|
60
|
+
// Validate execution config is for EOA
|
|
61
|
+
if (executionConfig.method !== 'eoa') {
|
|
62
|
+
throw new Error(`Expected EOA execution config, got: ${executionConfig.method}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return this.executeEoaTransaction(transactionData, networkConfig, onStatusChange);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private async executeEoaTransaction(
|
|
69
|
+
txData: StellarTransactionData,
|
|
70
|
+
stellarConfig: StellarNetworkConfig,
|
|
71
|
+
onStatusChange: (status: TxStatus, details: TransactionStatusUpdate) => void
|
|
72
|
+
): Promise<{ txHash: string }> {
|
|
73
|
+
try {
|
|
74
|
+
// --- Step 1: Get RPC Server and Connected Wallet Address --- //
|
|
75
|
+
const rpcServer = getSorobanRpcServer(stellarConfig);
|
|
76
|
+
const connectedAddress = this.getConnectedWalletAddress();
|
|
77
|
+
|
|
78
|
+
logger.info(SYSTEM_LOG_TAG, `Connected address: ${connectedAddress}`);
|
|
79
|
+
|
|
80
|
+
// --- Step 2: Get Account Details --- //
|
|
81
|
+
let sourceAccount: Account;
|
|
82
|
+
try {
|
|
83
|
+
const accountResponse = await rpcServer.getAccount(connectedAddress);
|
|
84
|
+
sourceAccount = new Account(connectedAddress, accountResponse.sequenceNumber());
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new Error(`Failed to load account details: ${(error as Error).message}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- Step 3: Build Transaction --- //
|
|
90
|
+
const contract = new Contract(txData.contractAddress);
|
|
91
|
+
|
|
92
|
+
const transactionBuilder = new TransactionBuilder(sourceAccount, {
|
|
93
|
+
fee: BASE_FEE,
|
|
94
|
+
networkPassphrase: stellarConfig.networkPassphrase,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Replace CALLER_PLACEHOLDER with the connected wallet address
|
|
98
|
+
// This supports OpenZeppelin Stellar access control functions that require a caller parameter
|
|
99
|
+
const resolvedArgs = txData.args.map((arg) =>
|
|
100
|
+
arg === CALLER_PLACEHOLDER ? connectedAddress : arg
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// Add the contract call operation (convert args to ScVal with comprehensive type support)
|
|
104
|
+
const scValArgs = resolvedArgs.map((arg, index) => {
|
|
105
|
+
const argType = txData.argTypes[index];
|
|
106
|
+
const argSchema = txData.argSchema?.[index]; // Pass schema for struct field type resolution
|
|
107
|
+
|
|
108
|
+
return valueToScVal(arg, argType, argSchema);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
transactionBuilder.addOperation(contract.call(txData.functionName, ...scValArgs));
|
|
112
|
+
|
|
113
|
+
// Set timeout (default 30 seconds)
|
|
114
|
+
transactionBuilder.setTimeout(30);
|
|
115
|
+
|
|
116
|
+
let transaction = transactionBuilder.build();
|
|
117
|
+
|
|
118
|
+
// --- Step 4: Simulate Transaction First --- //
|
|
119
|
+
try {
|
|
120
|
+
const simulation = await rpcServer.simulateTransaction(transaction);
|
|
121
|
+
|
|
122
|
+
if (StellarRpc.Api.isSimulationError(simulation)) {
|
|
123
|
+
throw new Error(`Transaction simulation failed: ${simulation.error}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Prepare the transaction with simulation results
|
|
127
|
+
transaction = await rpcServer.prepareTransaction(transaction);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
throw new Error(`Transaction simulation/preparation failed: ${(error as Error).message}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
onStatusChange('pendingSignature', {});
|
|
133
|
+
|
|
134
|
+
// --- Step 5: Sign Transaction --- //
|
|
135
|
+
try {
|
|
136
|
+
const signResult = await signTransaction(transaction.toXDR(), connectedAddress);
|
|
137
|
+
const signedTx = TransactionBuilder.fromXDR(
|
|
138
|
+
signResult.signedTxXdr,
|
|
139
|
+
stellarConfig.networkPassphrase
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// Type guard to ensure we have a regular Transaction, not a FeeBumpTransaction
|
|
143
|
+
if ('memo' in signedTx && 'sequence' in signedTx) {
|
|
144
|
+
transaction = signedTx;
|
|
145
|
+
} else {
|
|
146
|
+
throw new Error('Unexpected transaction type returned from signing');
|
|
147
|
+
}
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if ((error as Error).message.includes('User declined')) {
|
|
150
|
+
throw new Error('Transaction was rejected by user');
|
|
151
|
+
}
|
|
152
|
+
throw new Error(`Failed to sign transaction: ${(error as Error).message}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
onStatusChange('pendingConfirmation', {});
|
|
156
|
+
|
|
157
|
+
// --- Step 6: Send Transaction --- //
|
|
158
|
+
let sendResult: StellarRpc.Api.SendTransactionResponse;
|
|
159
|
+
try {
|
|
160
|
+
sendResult = await rpcServer.sendTransaction(transaction);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
throw new Error(`Failed to broadcast transaction: ${(error as Error).message}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (sendResult.status !== 'PENDING') {
|
|
166
|
+
throw new Error(`Transaction failed to submit: ${sendResult.status}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const txHash = sendResult.hash;
|
|
170
|
+
logger.info(SYSTEM_LOG_TAG, `Transaction submitted successfully: ${txHash}`);
|
|
171
|
+
|
|
172
|
+
// Status remains 'pendingConfirmation' during confirmation wait
|
|
173
|
+
|
|
174
|
+
// --- Step 7: Wait for Confirmation --- //
|
|
175
|
+
try {
|
|
176
|
+
let txResponse;
|
|
177
|
+
const MAX_ATTEMPTS = 10;
|
|
178
|
+
let attempts = 0;
|
|
179
|
+
|
|
180
|
+
while (attempts++ < MAX_ATTEMPTS && txResponse?.status !== 'SUCCESS') {
|
|
181
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
182
|
+
txResponse = await rpcServer.getTransaction(txHash);
|
|
183
|
+
|
|
184
|
+
switch (txResponse.status) {
|
|
185
|
+
case 'FAILED':
|
|
186
|
+
throw new Error(`Transaction failed: ${JSON.stringify(txResponse.resultXdr)}`);
|
|
187
|
+
case 'NOT_FOUND':
|
|
188
|
+
continue;
|
|
189
|
+
case 'SUCCESS':
|
|
190
|
+
break;
|
|
191
|
+
default:
|
|
192
|
+
// Continue waiting
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (attempts >= MAX_ATTEMPTS || txResponse?.status !== 'SUCCESS') {
|
|
197
|
+
logger.warn(SYSTEM_LOG_TAG, `Transaction confirmation timeout for ${txHash}`);
|
|
198
|
+
// Don't throw error, just return the hash - transaction might still succeed
|
|
199
|
+
}
|
|
200
|
+
} catch (confirmError) {
|
|
201
|
+
// Log the error but don't fail the transaction - it was already submitted
|
|
202
|
+
logger.error(SYSTEM_LOG_TAG, 'Error waiting for confirmation:', confirmError);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
onStatusChange('success', {
|
|
206
|
+
txHash,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return { txHash };
|
|
210
|
+
} catch (error) {
|
|
211
|
+
const errorMessage = `Failed to execute Stellar EOA transaction: ${(error as Error).message}`;
|
|
212
|
+
logger.error(SYSTEM_LOG_TAG, errorMessage, error);
|
|
213
|
+
|
|
214
|
+
onStatusChange('error', {});
|
|
215
|
+
|
|
216
|
+
throw new Error(errorMessage);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private getConnectedWalletAddress(): string {
|
|
221
|
+
const connectionStatus = getStellarWalletConnectionStatus();
|
|
222
|
+
|
|
223
|
+
if (!connectionStatus.isConnected || !connectionStatus.address) {
|
|
224
|
+
throw new Error('No connected wallet found. Please connect your Stellar wallet first.');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return connectionStatus.address;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ExecutionConfig,
|
|
3
|
+
StellarNetworkConfig,
|
|
4
|
+
TransactionStatusUpdate,
|
|
5
|
+
TxStatus,
|
|
6
|
+
} from '@openzeppelin/ui-types';
|
|
7
|
+
|
|
8
|
+
import type { StellarTransactionData } from './formatter';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Defines a common interface for different transaction execution strategies.
|
|
12
|
+
* This allows the adapter to remain a lean orchestrator that selects the appropriate strategy
|
|
13
|
+
* at runtime based on the user's configuration.
|
|
14
|
+
*/
|
|
15
|
+
export interface ExecutionStrategy {
|
|
16
|
+
/**
|
|
17
|
+
* Executes a transaction according to the specific strategy.
|
|
18
|
+
*
|
|
19
|
+
* @param transactionData The contract call parameters, including contract address, function name, and args.
|
|
20
|
+
* @param executionConfig The configuration for the selected execution method.
|
|
21
|
+
* @param networkConfig The Stellar network configuration to use for the transaction.
|
|
22
|
+
* @param onStatusChange A callback to report real-time status updates to the UI.
|
|
23
|
+
* @param runtimeApiKey Optional session-only API key for methods like Relayer.
|
|
24
|
+
* @returns A promise that resolves to an object containing the final transaction hash.
|
|
25
|
+
*/
|
|
26
|
+
execute(
|
|
27
|
+
transactionData: StellarTransactionData,
|
|
28
|
+
executionConfig: ExecutionConfig,
|
|
29
|
+
networkConfig: StellarNetworkConfig,
|
|
30
|
+
onStatusChange: (status: TxStatus, details: TransactionStatusUpdate) => void,
|
|
31
|
+
runtimeApiKey?: string
|
|
32
|
+
): Promise<{ txHash: string }>;
|
|
33
|
+
}
|