feat(trading): add and show key aliases (#5819)

This commit is contained in:
Matthew Russell 2024-03-06 10:47:16 +00:00 committed by GitHub
parent 38d13085fb
commit ad6f0c5798
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 381 additions and 44 deletions

View File

@ -0,0 +1 @@
export { ProfileDialog } from './profile-dialog';

View File

@ -0,0 +1,150 @@
import {
Dialog,
FormGroup,
Input,
InputError,
Intent,
TradingButton,
} from '@vegaprotocol/ui-toolkit';
import { useProfileDialogStore } from '../../stores/profile-dialog-store';
import { useForm } from 'react-hook-form';
import { useT } from '../../lib/use-t';
import { useRequired } from '@vegaprotocol/utils';
import {
useSimpleTransaction,
type Status,
useVegaWallet,
} from '@vegaprotocol/wallet-react';
import {
usePartyProfilesQuery,
type PartyProfilesQuery,
} from '../vega-wallet-connect-button/__generated__/PartyProfiles';
export const ProfileDialog = () => {
const t = useT();
const { pubKeys } = useVegaWallet();
const { data, refetch } = usePartyProfilesQuery({
variables: { partyIds: pubKeys.map((pk) => pk.publicKey) },
skip: pubKeys.length <= 0,
});
const open = useProfileDialogStore((store) => store.open);
const pubKey = useProfileDialogStore((store) => store.pubKey);
const setOpen = useProfileDialogStore((store) => store.setOpen);
const { send, status, error, reset } = useSimpleTransaction({
onSuccess: () => {
refetch();
},
});
const profileEdge = data?.partiesProfilesConnection?.edges.find(
(e) => e.node.partyId === pubKey
);
const sendTx = (field: FormFields) => {
send({
updatePartyProfile: {
alias: field.alias,
metadata: [],
},
});
};
return (
<Dialog
open={open}
onChange={() => {
setOpen(undefined);
reset();
}}
title={t('Edit profile')}
>
<ProfileForm
profile={profileEdge?.node}
status={status}
error={error}
onSubmit={sendTx}
/>
</Dialog>
);
};
interface FormFields {
alias: string;
}
type Profile = NonNullable<
PartyProfilesQuery['partiesProfilesConnection']
>['edges'][number]['node'];
const ProfileForm = ({
profile,
onSubmit,
status,
error,
}: {
profile: Profile | undefined;
onSubmit: (fields: FormFields) => void;
status: Status;
error: string | undefined;
}) => {
const t = useT();
const required = useRequired();
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormFields>({
defaultValues: {
alias: profile?.alias,
},
});
const renderButtonText = () => {
if (status === 'requested') {
return t('Confirm in wallet...');
}
if (status === 'pending') {
return t('Confirming transaction...');
}
return t('Submit');
};
const errorMessage = errors.alias?.message || error;
return (
<form onSubmit={handleSubmit(onSubmit)} className="mt-3">
<FormGroup label="Alias" labelFor="alias">
<Input
{...register('alias', {
validate: {
required,
},
})}
/>
{errorMessage && (
<InputError>
<p className="break-words max-w-full first-letter:uppercase">
{errorMessage}
</p>
</InputError>
)}
{status === 'confirmed' && (
<p className="mt-2 mb-4 text-sm text-success">
{t('Profile updated')}
</p>
)}
</FormGroup>
<TradingButton
type="submit"
intent={Intent.Info}
disabled={status === 'requested' || status === 'pending'}
>
{renderButtonText()}
</TradingButton>
</form>
);
};

View File

@ -0,0 +1,14 @@
query PartyProfiles($partyIds: [ID!]) {
partiesProfilesConnection(ids: $partyIds) {
edges {
node {
partyId
alias
metadata {
key
value
}
}
}
}
}

View File

@ -0,0 +1,57 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type PartyProfilesQueryVariables = Types.Exact<{
partyIds?: Types.InputMaybe<Array<Types.Scalars['ID']> | Types.Scalars['ID']>;
}>;
export type PartyProfilesQuery = { __typename?: 'Query', partiesProfilesConnection?: { __typename?: 'PartiesProfilesConnection', edges: Array<{ __typename?: 'PartyProfileEdge', node: { __typename?: 'PartyProfile', partyId: string, alias: string, metadata: Array<{ __typename?: 'Metadata', key: string, value: string }> } }> } | null };
export const PartyProfilesDocument = gql`
query PartyProfiles($partyIds: [ID!]) {
partiesProfilesConnection(ids: $partyIds) {
edges {
node {
partyId
alias
metadata {
key
value
}
}
}
}
}
`;
/**
* __usePartyProfilesQuery__
*
* To run a query within a React component, call `usePartyProfilesQuery` and pass it any options that fit your needs.
* When your component renders, `usePartyProfilesQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = usePartyProfilesQuery({
* variables: {
* partyIds: // value for 'partyIds'
* },
* });
*/
export function usePartyProfilesQuery(baseOptions?: Apollo.QueryHookOptions<PartyProfilesQuery, PartyProfilesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<PartyProfilesQuery, PartyProfilesQueryVariables>(PartyProfilesDocument, options);
}
export function usePartyProfilesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<PartyProfilesQuery, PartyProfilesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<PartyProfilesQuery, PartyProfilesQueryVariables>(PartyProfilesDocument, options);
}
export type PartyProfilesQueryHookResult = ReturnType<typeof usePartyProfilesQuery>;
export type PartyProfilesLazyQueryHookResult = ReturnType<typeof usePartyProfilesLazyQuery>;
export type PartyProfilesQueryResult = Apollo.QueryResult<PartyProfilesQuery, PartyProfilesQueryVariables>;

View File

@ -1,21 +1,57 @@
import { act, fireEvent, render, screen } from '@testing-library/react'; import { act, fireEvent, render, screen, within } from '@testing-library/react';
import { VegaWalletConnectButton } from './vega-wallet-connect-button'; import { VegaWalletConnectButton } from './vega-wallet-connect-button';
import { truncateByChars } from '@vegaprotocol/utils';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { import {
mockConfig, mockConfig,
MockedWalletProvider, MockedWalletProvider,
} from '@vegaprotocol/wallet-react/testing'; } from '@vegaprotocol/wallet-react/testing';
import { MockedProvider, type MockedResponse } from '@apollo/react-testing';
import {
PartyProfilesDocument,
type PartyProfilesQuery,
} from './__generated__/PartyProfiles';
jest.mock('../../lib/hooks/use-get-current-route-id', () => ({ jest.mock('../../lib/hooks/use-get-current-route-id', () => ({
useGetCurrentRouteId: jest.fn().mockReturnValue('current-route-id'), useGetCurrentRouteId: jest.fn().mockReturnValue('current-route-id'),
})); }));
const key = { publicKey: '123456__123456', name: 'test' };
const key2 = { publicKey: 'abcdef__abcdef', name: 'test2' };
const keys = [key, key2];
const keyProfile = {
__typename: 'PartyProfile' as const,
partyId: key.publicKey,
alias: `${key.name} alias`,
metadata: [],
};
const renderComponent = (mockOnClick = jest.fn()) => { const renderComponent = (mockOnClick = jest.fn()) => {
const partyProfilesMock: MockedResponse<PartyProfilesQuery> = {
request: {
query: PartyProfilesDocument,
variables: { partyIds: keys.map((k) => k.publicKey) },
},
result: {
data: {
partiesProfilesConnection: {
__typename: 'PartiesProfilesConnection',
edges: [
{
__typename: 'PartyProfileEdge',
node: keyProfile,
},
],
},
},
},
};
return ( return (
<MockedProvider mocks={[partyProfilesMock]}>
<MockedWalletProvider> <MockedWalletProvider>
<VegaWalletConnectButton onClick={mockOnClick} /> <VegaWalletConnectButton onClick={mockOnClick} />
</MockedWalletProvider> </MockedWalletProvider>
</MockedProvider>
); );
}; };
@ -43,10 +79,6 @@ describe('VegaWalletConnectButton', () => {
}); });
it('should open dropdown and refresh keys when connected', async () => { it('should open dropdown and refresh keys when connected', async () => {
const key = { publicKey: '123456__123456', name: 'test' };
const key2 = { publicKey: 'abcdef__abcdef', name: 'test2' };
const keys = [key, key2];
mockConfig.store.setState({ mockConfig.store.setState({
status: 'connected', status: 'connected',
keys, keys,
@ -61,14 +93,22 @@ describe('VegaWalletConnectButton', () => {
expect(screen.queryByTestId('connect-vega-wallet')).not.toBeInTheDocument(); expect(screen.queryByTestId('connect-vega-wallet')).not.toBeInTheDocument();
const button = screen.getByTestId('manage-vega-wallet'); const button = screen.getByTestId('manage-vega-wallet');
expect(button).toHaveTextContent(truncateByChars(key.publicKey)); expect(button).toHaveTextContent(key.name);
fireEvent.click(button); fireEvent.click(button);
expect(await screen.findByRole('menu')).toBeInTheDocument(); expect(await screen.findByRole('menu')).toBeInTheDocument();
expect(await screen.findAllByRole('menuitemradio')).toHaveLength( const menuItems = await screen.findAllByRole('menuitemradio');
keys.length expect(menuItems).toHaveLength(keys.length);
expect(within(menuItems[0]).getByTestId('alias')).toHaveTextContent(
keyProfile.alias
); );
expect(within(menuItems[1]).getByTestId('alias')).toHaveTextContent(
'No alias'
);
expect(refreshKeys).toHaveBeenCalled(); expect(refreshKeys).toHaveBeenCalled();
fireEvent.click(screen.getByTestId(`key-${key2.publicKey}`)); fireEvent.click(screen.getByTestId(`key-${key2.publicKey}`));

View File

@ -14,6 +14,7 @@ import {
TradingDropdownItem, TradingDropdownItem,
TradingDropdownRadioItem, TradingDropdownRadioItem,
TradingDropdownItemIndicator, TradingDropdownItemIndicator,
Tooltip,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { isBrowserWalletInstalled, type Key } from '@vegaprotocol/wallet'; import { isBrowserWalletInstalled, type Key } from '@vegaprotocol/wallet';
import { useDialogStore, useVegaWallet } from '@vegaprotocol/wallet-react'; import { useDialogStore, useVegaWallet } from '@vegaprotocol/wallet-react';
@ -22,6 +23,8 @@ import classNames from 'classnames';
import { ViewType, useSidebar } from '../sidebar'; import { ViewType, useSidebar } from '../sidebar';
import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id'; import { useGetCurrentRouteId } from '../../lib/hooks/use-get-current-route-id';
import { useT } from '../../lib/use-t'; import { useT } from '../../lib/use-t';
import { usePartyProfilesQuery } from './__generated__/PartyProfiles';
import { useProfileDialogStore } from '../../stores/profile-dialog-store';
export const VegaWalletConnectButton = ({ export const VegaWalletConnectButton = ({
intent = Intent.None, intent = Intent.None,
@ -68,10 +71,10 @@ export const VegaWalletConnectButton = ({
{activeKey ? ( {activeKey ? (
<> <>
{activeKey && ( {activeKey && (
<span className="uppercase">{activeKey.name}</span> <span className="uppercase">
{activeKey.name ? activeKey.name : t('Unnamed key')}
</span>
)} )}
{' | '}
{truncateByChars(activeKey.publicKey)}
</> </>
) : ( ) : (
<>{'Select key'}</> <>{'Select key'}</>
@ -88,20 +91,11 @@ export const VegaWalletConnectButton = ({
onEscapeKeyDown={() => setDropdownOpen(false)} onEscapeKeyDown={() => setDropdownOpen(false)}
> >
<div className="min-w-[340px]" data-testid="keypair-list"> <div className="min-w-[340px]" data-testid="keypair-list">
<TradingDropdownRadioGroup <KeypairRadioGroup
value={pubKey || undefined} pubKey={pubKey}
onValueChange={(value) => { pubKeys={pubKeys}
selectPubKey(value); onSelect={selectPubKey}
}}
>
{pubKeys.map((pk) => (
<KeypairItem
key={pk.publicKey}
pk={pk}
active={pk.publicKey === pubKey}
/> />
))}
</TradingDropdownRadioGroup>
<TradingDropdownSeparator /> <TradingDropdownSeparator />
{!isReadOnly && ( {!isReadOnly && (
<TradingDropdownItem <TradingDropdownItem
@ -141,28 +135,52 @@ export const VegaWalletConnectButton = ({
); );
}; };
const KeypairItem = ({ pk, active }: { pk: Key; active: boolean }) => { const KeypairRadioGroup = ({
pubKey,
pubKeys,
onSelect,
}: {
pubKey: string | undefined;
pubKeys: Key[];
onSelect: (pubKey: string) => void;
}) => {
const { data } = usePartyProfilesQuery({
variables: { partyIds: pubKeys.map((pk) => pk.publicKey) },
skip: pubKeys.length <= 0,
});
return (
<TradingDropdownRadioGroup value={pubKey} onValueChange={onSelect}>
{pubKeys.map((pk) => {
const profile = data?.partiesProfilesConnection?.edges.find(
(e) => e.node.partyId === pk.publicKey
);
return (
<KeypairItem key={pk.publicKey} pk={pk} alias={profile?.node.alias} />
);
})}
</TradingDropdownRadioGroup>
);
};
const KeypairItem = ({ pk, alias }: { pk: Key; alias: string | undefined }) => {
const t = useT(); const t = useT();
const [copied, setCopied] = useCopyTimeout(); const [copied, setCopied] = useCopyTimeout();
const setOpen = useProfileDialogStore((store) => store.setOpen);
return ( return (
<TradingDropdownRadioItem value={pk.publicKey}> <TradingDropdownRadioItem value={pk.publicKey}>
<div <div>
className={classNames('flex-1 mr-2', { <div className="flex items-center gap-2">
'text-default': active, <span>{pk.name ? pk.name : t('Unnamed key')}</span>
'text-muted': !active,
})}
data-testid={`key-${pk.publicKey}`}
>
<span className={classNames('mr-2 uppercase')}>
{pk.name}
{' | '} {' | '}
{truncateByChars(pk.publicKey)} <span className="font-mono">
{truncateByChars(pk.publicKey, 3, 3)}
</span> </span>
<span className="inline-flex items-center gap-1">
<CopyToClipboard text={pk.publicKey} onCopy={() => setCopied(true)}> <CopyToClipboard text={pk.publicKey} onCopy={() => setCopied(true)}>
<button <button
data-testid="copy-vega-public-key" data-testid="copy-vega-public-key"
className="relative -top-px"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<span className="sr-only">{t('Copy')}</span> <span className="sr-only">{t('Copy')}</span>
@ -170,7 +188,17 @@ const KeypairItem = ({ pk, active }: { pk: Key; active: boolean }) => {
</button> </button>
</CopyToClipboard> </CopyToClipboard>
{copied && <span className="text-xs">{t('Copied')}</span>} {copied && <span className="text-xs">{t('Copied')}</span>}
</span> </div>
<div
className={classNames('flex-1 mr-2 text-secondary text-sm')}
data-testid={`key-${pk.publicKey}`}
>
<Tooltip description={t('Public facing key alias. Click to edit')}>
<button data-testid="alias" onClick={() => setOpen(pk.publicKey)}>
{alias ? alias : t('No alias')}
</button>
</Tooltip>
</div>
</div> </div>
<TradingDropdownItemIndicator /> <TradingDropdownItemIndicator />
</TradingDropdownRadioItem> </TradingDropdownRadioItem>

View File

@ -8,6 +8,7 @@ import {
} from '@vegaprotocol/web3'; } from '@vegaprotocol/web3';
import { WelcomeDialog } from '../components/welcome-dialog'; import { WelcomeDialog } from '../components/welcome-dialog';
import { VegaWalletConnectDialog } from '../components/vega-wallet-connect-dialog'; import { VegaWalletConnectDialog } from '../components/vega-wallet-connect-dialog';
import { ProfileDialog } from '../components/profile-dialog';
const DialogsContainer = () => { const DialogsContainer = () => {
const { isOpen, id, trigger, setOpen } = useAssetDetailsDialogStore(); const { isOpen, id, trigger, setOpen } = useAssetDetailsDialogStore();
@ -24,6 +25,7 @@ const DialogsContainer = () => {
<WelcomeDialog /> <WelcomeDialog />
<Web3ConnectUncontrolledDialog /> <Web3ConnectUncontrolledDialog />
<WithdrawalApprovalDialogContainer /> <WithdrawalApprovalDialogContainer />
<ProfileDialog />
</> </>
); );
}; };

View File

@ -0,0 +1,19 @@
import { create } from 'zustand';
interface ProfileDialogStore {
open: boolean;
pubKey: string | undefined;
setOpen: (pubKey: string | undefined) => void;
}
export const useProfileDialogStore = create<ProfileDialogStore>((set) => ({
open: false,
pubKey: undefined,
setOpen: (pubKey) => {
if (pubKey) {
set({ open: true, pubKey });
} else {
set({ open: false, pubKey: undefined });
}
},
}));

View File

@ -86,6 +86,7 @@
"Docs": "Docs", "Docs": "Docs",
"Earn commission & stake rewards": "Earn commission & stake rewards", "Earn commission & stake rewards": "Earn commission & stake rewards",
"Earned by me": "Earned by me", "Earned by me": "Earned by me",
"Edit alias": "Edit alias",
"Eligible teams": "Eligible teams", "Eligible teams": "Eligible teams",
"Enactment date reached and usual auction exit checks pass": "Enactment date reached and usual auction exit checks pass", "Enactment date reached and usual auction exit checks pass": "Enactment date reached and usual auction exit checks pass",
"[empty]": "[empty]", "[empty]": "[empty]",
@ -162,6 +163,7 @@
"Joined": "Joined", "Joined": "Joined",
"Joined at": "Joined at", "Joined at": "Joined at",
"Joined epoch": "Joined epoch", "Joined epoch": "Joined epoch",
"Key name": "Key name",
"gameCount_one": "Last game result", "gameCount_one": "Last game result",
"gameCount_other": "Last {{count}} game results", "gameCount_other": "Last {{count}} game results",
"Learn about providing liquidity": "Learn about providing liquidity", "Learn about providing liquidity": "Learn about providing liquidity",
@ -194,6 +196,7 @@
"My liquidity provision": "My liquidity provision", "My liquidity provision": "My liquidity provision",
"My trading fees": "My trading fees", "My trading fees": "My trading fees",
"Name": "Name", "Name": "Name",
"No alias": "No alias",
"No closed orders": "No closed orders", "No closed orders": "No closed orders",
"No data": "No data", "No data": "No data",
"No deposits": "No deposits", "No deposits": "No deposits",
@ -227,6 +230,7 @@
"Not connected": "Not connected", "Not connected": "Not connected",
"Number of epochs after distribution to delay vesting of rewards by": "Number of epochs after distribution to delay vesting of rewards by", "Number of epochs after distribution to delay vesting of rewards by": "Number of epochs after distribution to delay vesting of rewards by",
"Number of traders": "Number of traders", "Number of traders": "Number of traders",
"On-change alias": "On-change alias",
"Open": "Open", "Open": "Open",
"Open a position": "Open a position", "Open a position": "Open a position",
"Open markets": "Open markets", "Open markets": "Open markets",
@ -249,6 +253,7 @@
"Portfolio": "Portfolio", "Portfolio": "Portfolio",
"Positions": "Positions", "Positions": "Positions",
"Price": "Price", "Price": "Price",
"Profile updated": "Profile updated",
"Program ends:": "Program ends:", "Program ends:": "Program ends:",
"Propose a new market": "Propose a new market", "Propose a new market": "Propose a new market",
"Proposed final price is {{price}} {{assetSymbol}}.": "Proposed final price is {{price}} {{assetSymbol}}.", "Proposed final price is {{price}} {{assetSymbol}}.": "Proposed final price is {{price}} {{assetSymbol}}.",
@ -289,6 +294,7 @@
"Search": "Search", "Search": "Search",
"See all markets": "See all markets", "See all markets": "See all markets",
"Select market": "Select market", "Select market": "Select market",
"Set party alias": "Set party alias",
"Settings": "Settings", "Settings": "Settings",
"Settlement asset": "Settlement asset", "Settlement asset": "Settlement asset",
"Settlement date": "Settlement date", "Settlement date": "Settlement date",
@ -311,6 +317,7 @@
"Stop": "Stop", "Stop": "Stop",
"Stop orders": "Stop orders", "Stop orders": "Stop orders",
"Streak reward multiplier": "Streak reward multiplier", "Streak reward multiplier": "Streak reward multiplier",
"Submit": "Submit",
"Successor of a market": "Successor of a market", "Successor of a market": "Successor of a market",
"Successors to this market have been proposed": "Successors to this market have been proposed", "Successors to this market have been proposed": "Successors to this market have been proposed",
"Supplied stake": "Supplied stake", "Supplied stake": "Supplied stake",
@ -371,6 +378,7 @@
"Staking rewards": "Staking rewards", "Staking rewards": "Staking rewards",
"Unknown": "Unknown", "Unknown": "Unknown",
"Unknown settlement date": "Unknown settlement date", "Unknown settlement date": "Unknown settlement date",
"Unnamed key": "Unnamed key",
"Update team": "Update team", "Update team": "Update team",
"URL": "URL", "URL": "URL",
"Use a comma separated list to allow only specific public keys to join the team": "Use a comma separated list to allow only specific public keys to join the team", "Use a comma separated list to allow only specific public keys to join the team": "Use a comma separated list to allow only specific public keys to join the team",
@ -407,8 +415,10 @@
"You will no longer be able to hold a position on this market when it closes in {{duration}}.": "You will no longer be able to hold a position on this market when it closes in {{duration}}.", "You will no longer be able to hold a position on this market when it closes in {{duration}}.": "You will no longer be able to hold a position on this market when it closes in {{duration}}.",
"Your code has been rejected": "Your code has been rejected", "Your code has been rejected": "Your code has been rejected",
"Your identity is always anonymous on Vega": "Your identity is always anonymous on Vega", "Your identity is always anonymous on Vega": "Your identity is always anonymous on Vega",
"Your key's private name, can be changed in your wallet": "Your key's private name, can be changed in your wallet",
"Your referral code": "Your referral code", "Your referral code": "Your referral code",
"Your tier": "Your tier", "Your tier": "Your tier",
"Your public alias, stored on chain": "Your public alias, stored on chain",
"checkOutProposalsAndVote": "Check out the terms of the proposals and vote:", "checkOutProposalsAndVote": "Check out the terms of the proposals and vote:",
"checkOutProposalsAndVote_one": "Check out the terms of the proposal and vote:", "checkOutProposalsAndVote_one": "Check out the terms of the proposal and vote:",
"checkOutProposalsAndVote_other": "Check out the terms of the proposals and vote:", "checkOutProposalsAndVote_other": "Check out the terms of the proposals and vote:",

View File

@ -33,6 +33,12 @@ export const useSimpleTransaction = (opts?: Options) => {
const [result, setResult] = useState<Result>(); const [result, setResult] = useState<Result>();
const [error, setError] = useState<string>(); const [error, setError] = useState<string>();
const reset = () => {
setStatus('idle');
setResult(undefined);
setError(undefined);
};
const send = async (tx: Transaction) => { const send = async (tx: Transaction) => {
if (!pubKey) { if (!pubKey) {
throw new Error('no pubKey'); throw new Error('no pubKey');
@ -114,5 +120,6 @@ export const useSimpleTransaction = (opts?: Options) => {
error, error,
status, status,
send, send,
reset,
}; };
}; };

View File

@ -492,6 +492,14 @@ export interface UpdateMarginMode {
export interface UpdateMarginModeBody { export interface UpdateMarginModeBody {
updateMarginMode: UpdateMarginMode; updateMarginMode: UpdateMarginMode;
} }
export interface UpdatePartyProfile {
updatePartyProfile: {
alias: string;
metadata: Array<{ key: string; value: string }>;
};
}
export type Transaction = export type Transaction =
| UpdateMarginModeBody | UpdateMarginModeBody
| StopOrdersSubmissionBody | StopOrdersSubmissionBody
@ -510,7 +518,8 @@ export type Transaction =
| ApplyReferralCode | ApplyReferralCode
| JoinTeam | JoinTeam
| CreateReferralSet | CreateReferralSet
| UpdateReferralSet; | UpdateReferralSet
| UpdatePartyProfile;
export interface TransactionResponse { export interface TransactionResponse {
transactionHash: string; transactionHash: string;