diff --git a/.changeset/lucky-pandas-wander.md b/.changeset/lucky-pandas-wander.md new file mode 100644 index 00000000..5ed08926 --- /dev/null +++ b/.changeset/lucky-pandas-wander.md @@ -0,0 +1,9 @@ +--- +'@smartcontractkit/operator-ui': minor +--- + +feat: Add stellar + +Requires a node exposing the `stellarKeys` GraphQL query; against older nodes the +account address dropdown will be empty. No account public key field, as a Stellar +account address already encodes the ed25519 public key. diff --git a/src/components/Form/ChainConfigurationForm.test.tsx b/src/components/Form/ChainConfigurationForm.test.tsx index 5d999b82..3cec611f 100644 --- a/src/components/Form/ChainConfigurationForm.test.tsx +++ b/src/components/Form/ChainConfigurationForm.test.tsx @@ -214,6 +214,9 @@ describe('ChainConfigurationForm', () => { suiKeys: { results: [], }, + stellarKeys: { + results: [], + }, }) await selectOptionOnUI(/chain type/i, 'APTOS') @@ -326,6 +329,9 @@ describe('ChainConfigurationForm', () => { suiKeys: { results: [], }, + stellarKeys: { + results: [], + }, }) await selectOptionOnUI(/chain type/i, 'SUI') @@ -369,6 +375,84 @@ describe('ChainConfigurationForm', () => { }) }) + test('should able to create STELLAR chain config (with manual input)', async () => { + const handleSubmit = jest.fn() + const initialValues = emptyFormValues() + initialValues.chainType = ChainTypes.EVM + + renderChainConfigurationForm(initialValues, handleSubmit, [], { + aptosKeys: { + results: [], + }, + solanaKeys: { + results: [], + }, + starknetKeys: { + results: [], + }, + tronKeys: { + results: [], + }, + tonKeys: { + results: [], + }, + suiKeys: { + results: [], + }, + stellarKeys: { + results: [], + }, + }) + + await selectOptionOnUI(/chain type/i, 'STELLAR') + + // A Stellar chain ID is the SHA-256 of the network passphrase. + const chainIdTextBox = getByRole('textbox', { name: /chain id/i }) + userEvent.type( + chainIdTextBox, + 'cee0302d59844d32bdca915c8203dd44b33fbb7edc19051ea37abedf28ecd472', + ) + + const accountAddrTextBox = getByRole('textbox', { + name: /account address/i, + }) + userEvent.type( + accountAddrTextBox, + 'GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFSHONUCEOASW7QC7OX2H', + ) + + await userEvent.click(getByRole('button', { name: /submit/i })) + + await waitFor(() => { + expect(handleSubmit).toHaveBeenCalledWith({ + accountAddr: 'GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFSHONUCEOASW7QC7OX2H', + accountAddrPubKey: '', + adminAddr: '', + chainID: + 'cee0302d59844d32bdca915c8203dd44b33fbb7edc19051ea37abedf28ecd472', + chainType: 'STELLAR', + fluxMonitorEnabled: false, + ocr1Enabled: false, + ocr1IsBootstrap: false, + ocr1KeyBundleID: '', + ocr1Multiaddr: '', + ocr1P2PPeerID: '', + ocr2CommitPluginEnabled: false, + ocr2Enabled: false, + ocr2ExecutePluginEnabled: false, + ocr2ForwarderAddress: '', + ocr2IsBootstrap: false, + ocr2KeyBundleID: '', + ocr2MedianPluginEnabled: false, + ocr2MercuryPluginEnabled: false, + ocr2Multiaddr: '', + ocr2P2PPeerID: '', + ocr2RebalancerPluginEnabled: false, + }) + expect(handleSubmit).toHaveBeenCalledTimes(1) + }) + }) + test('should able to create Solana chain config', async () => { const handleSubmit = jest.fn() const initialValues = emptyFormValues() @@ -529,6 +613,9 @@ test('should be able to select OCR2 Job Type with Key Bundle ID', async () => { suiKeys: { results: [], }, + stellarKeys: { + results: [], + }, }) const ocr2CheckBox = screen.getByText(/ocr2/i) @@ -618,6 +705,14 @@ function renderChainConfigurationForm( suiKeys: { results: [{ account: '0x123', id: '6666' }], }, + stellarKeys: { + results: [ + { + account: 'GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFSHONUCEOASW7QC7OX2H', + id: '7777', + }, + ], + }, }, ) { return render( diff --git a/src/components/Form/ChainConfigurationForm.tsx b/src/components/Form/ChainConfigurationForm.tsx index 09ea90f1..5ad356dd 100644 --- a/src/components/Form/ChainConfigurationForm.tsx +++ b/src/components/Form/ChainConfigurationForm.tsx @@ -360,6 +360,11 @@ export const ChainConfigurationForm = withStyles(styles)(({ chainPublicKeys = accountsNonEvm?.suiKeys.results.map((acc) => acc.id) ?? [] break + case ChainTypes.STELLAR: + chainAccountAddresses = + accountsNonEvm?.stellarKeys.results.map((acc) => acc.account) ?? + [] + break default: chainAccountAddresses = [] } @@ -404,6 +409,9 @@ export const ChainConfigurationForm = withStyles(styles)(({ + diff --git a/src/components/Form/ChainTypes.ts b/src/components/Form/ChainTypes.ts index 1d98292c..626588a7 100644 --- a/src/components/Form/ChainTypes.ts +++ b/src/components/Form/ChainTypes.ts @@ -7,4 +7,5 @@ export const ChainTypes = { TRON: 'TRON', TON: 'TON', SUI: 'SUI', + STELLAR: 'STELLAR', } diff --git a/src/hooks/queries/useNonEvmAccountsQuery.test.tsx b/src/hooks/queries/useNonEvmAccountsQuery.test.tsx index 6ef92bbb..466d4ab8 100644 --- a/src/hooks/queries/useNonEvmAccountsQuery.test.tsx +++ b/src/hooks/queries/useNonEvmAccountsQuery.test.tsx @@ -34,6 +34,10 @@ const mockData = { { __typename: 'SuiKey', account: 'account7', id: '7' }, ], }, + stellarKeys: { + __typename: 'StellarKeys', + results: [{ __typename: 'StellarKey', account: 'account8', id: '8' }], + }, }, } @@ -85,6 +89,13 @@ const TestComponent: React.FC = () => {
Sui ID: {key.id}
))} + + {data?.stellarKeys.results.map((key, i) => ( +Account: {key.account}
+Stellar ID: {key.id}
+