Chore/list asset (#1964)
* chore: add whitelist asset function * feat: add whitelist button * test: fix tests for proposal form * test: add tests for new component * chore: revert incorectly comitted changes * Update apps/token/src/routes/governance/components/proposal/proposal.spec.tsx Co-authored-by: Sam Keen <samuel@vegaprotocol.io> Co-authored-by: Sam Keen <samuel@vegaprotocol.io>
This commit is contained in:
		
							parent
							
								
									e5432888aa
								
							
						
					
					
						commit
						113eb90469
					
				| @ -1,7 +1,7 @@ | ||||
| # App configuration variables | ||||
| NX_VEGA_ENV=TESTNET | ||||
| NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json | ||||
| NX_VEGA_URL=https://api.n06.testnet.vega.xyz/graphql | ||||
| NX_VEGA_URL=https://api.n11.testnet.vega.xyz/graphql | ||||
| NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 | ||||
| NX_ETHERSCAN_URL=https://sepolia.etherscan.io | ||||
| NX_FAIRGROUND=false | ||||
|  | ||||
| @ -0,0 +1,36 @@ | ||||
| import { Button } from '@vegaprotocol/ui-toolkit'; | ||||
| import { useWeb3React } from '@web3-react/core'; | ||||
| import type { ReactElement } from 'react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import { | ||||
|   AppStateActionType, | ||||
|   useAppState, | ||||
| } from '../../contexts/app-state/app-state-context'; | ||||
| 
 | ||||
| export const EthWalletContainer = ({ | ||||
|   children, | ||||
| }: { | ||||
|   children: ReactElement; | ||||
| }) => { | ||||
|   const { account } = useWeb3React(); | ||||
|   const { t } = useTranslation(); | ||||
|   const { appDispatch } = useAppState(); | ||||
| 
 | ||||
|   if (!account) { | ||||
|     return ( | ||||
|       <div className="w-full text-center"> | ||||
|         <Button | ||||
|           onClick={() => | ||||
|             appDispatch({ | ||||
|               type: AppStateActionType.SET_ETH_WALLET_OVERLAY, | ||||
|               isOpen: true, | ||||
|             }) | ||||
|           } | ||||
|         > | ||||
|           {t('connectEthWallet')} | ||||
|         </Button> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
|   return children; | ||||
| }; | ||||
							
								
								
									
										1
									
								
								apps/token/src/components/eth-wallet-container/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								apps/token/src/components/eth-wallet-container/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| export * from './eth-wallet-container'; | ||||
| @ -704,5 +704,8 @@ | ||||
|   "votingEnded": "Voting has ended.", | ||||
|   "STATUS": "STATUS", | ||||
|   "ProposalMinimumAmounts": "Different proposal types can have different minimum token requirements. You must have the greater of the proposal minimum or spam protection minimum from the table below", | ||||
|   "SpamProtectionMin": "Spam protection minimum" | ||||
|   "SpamProtectionMin": "Spam protection minimum", | ||||
|   "ListAsset": "List Asset", | ||||
|   "ListAssetDescription": "This asset needs to be listed on the collateral bridge before it can be used.", | ||||
|   "ListAssetAction": "List asset" | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,19 @@ | ||||
| query ProposalAsset($assetId: ID!) { | ||||
|   asset(id: $assetId) { | ||||
|     status | ||||
|     source { | ||||
|       ... on ERC20 { | ||||
|         contractAddress | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| query AssetListBundle($assetId: ID!) { | ||||
|   erc20ListAssetBundle(assetId: $assetId) { | ||||
|     assetSource | ||||
|     vegaAssetId | ||||
|     nonce | ||||
|     signatures | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,98 @@ | ||||
| import { Schema as Types } from '@vegaprotocol/types'; | ||||
| 
 | ||||
| import { gql } from '@apollo/client'; | ||||
| import * as Apollo from '@apollo/client'; | ||||
| const defaultOptions = {} as const; | ||||
| export type ProposalAssetQueryVariables = Types.Exact<{ | ||||
|   assetId: Types.Scalars['ID']; | ||||
| }>; | ||||
| 
 | ||||
| 
 | ||||
| export type ProposalAssetQuery = { __typename?: 'Query', asset?: { __typename?: 'Asset', status: Types.AssetStatus, source: { __typename?: 'BuiltinAsset' } | { __typename?: 'ERC20', contractAddress: string } } | null }; | ||||
| 
 | ||||
| export type AssetListBundleQueryVariables = Types.Exact<{ | ||||
|   assetId: Types.Scalars['ID']; | ||||
| }>; | ||||
| 
 | ||||
| 
 | ||||
| export type AssetListBundleQuery = { __typename?: 'Query', erc20ListAssetBundle?: { __typename?: 'Erc20ListAssetBundle', assetSource: string, vegaAssetId: string, nonce: string, signatures: string } | null }; | ||||
| 
 | ||||
| 
 | ||||
| export const ProposalAssetDocument = gql` | ||||
|     query ProposalAsset($assetId: ID!) { | ||||
|   asset(id: $assetId) { | ||||
|     status | ||||
|     source { | ||||
|       ... on ERC20 { | ||||
|         contractAddress | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|     `;
 | ||||
| 
 | ||||
| /** | ||||
|  * __useProposalAssetQuery__ | ||||
|  * | ||||
|  * To run a query within a React component, call `useProposalAssetQuery` and pass it any options that fit your needs. | ||||
|  * When your component renders, `useProposalAssetQuery` 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 } = useProposalAssetQuery({ | ||||
|  *   variables: { | ||||
|  *      assetId: // value for 'assetId'
 | ||||
|  *   }, | ||||
|  * }); | ||||
|  */ | ||||
| export function useProposalAssetQuery(baseOptions: Apollo.QueryHookOptions<ProposalAssetQuery, ProposalAssetQueryVariables>) { | ||||
|         const options = {...defaultOptions, ...baseOptions} | ||||
|         return Apollo.useQuery<ProposalAssetQuery, ProposalAssetQueryVariables>(ProposalAssetDocument, options); | ||||
|       } | ||||
| export function useProposalAssetLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ProposalAssetQuery, ProposalAssetQueryVariables>) { | ||||
|           const options = {...defaultOptions, ...baseOptions} | ||||
|           return Apollo.useLazyQuery<ProposalAssetQuery, ProposalAssetQueryVariables>(ProposalAssetDocument, options); | ||||
|         } | ||||
| export type ProposalAssetQueryHookResult = ReturnType<typeof useProposalAssetQuery>; | ||||
| export type ProposalAssetLazyQueryHookResult = ReturnType<typeof useProposalAssetLazyQuery>; | ||||
| export type ProposalAssetQueryResult = Apollo.QueryResult<ProposalAssetQuery, ProposalAssetQueryVariables>; | ||||
| export const AssetListBundleDocument = gql` | ||||
|     query AssetListBundle($assetId: ID!) { | ||||
|   erc20ListAssetBundle(assetId: $assetId) { | ||||
|     assetSource | ||||
|     vegaAssetId | ||||
|     nonce | ||||
|     signatures | ||||
|   } | ||||
| } | ||||
|     `;
 | ||||
| 
 | ||||
| /** | ||||
|  * __useAssetListBundleQuery__ | ||||
|  * | ||||
|  * To run a query within a React component, call `useAssetListBundleQuery` and pass it any options that fit your needs. | ||||
|  * When your component renders, `useAssetListBundleQuery` 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 } = useAssetListBundleQuery({ | ||||
|  *   variables: { | ||||
|  *      assetId: // value for 'assetId'
 | ||||
|  *   }, | ||||
|  * }); | ||||
|  */ | ||||
| export function useAssetListBundleQuery(baseOptions: Apollo.QueryHookOptions<AssetListBundleQuery, AssetListBundleQueryVariables>) { | ||||
|         const options = {...defaultOptions, ...baseOptions} | ||||
|         return Apollo.useQuery<AssetListBundleQuery, AssetListBundleQueryVariables>(AssetListBundleDocument, options); | ||||
|       } | ||||
| export function useAssetListBundleLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<AssetListBundleQuery, AssetListBundleQueryVariables>) { | ||||
|           const options = {...defaultOptions, ...baseOptions} | ||||
|           return Apollo.useLazyQuery<AssetListBundleQuery, AssetListBundleQueryVariables>(AssetListBundleDocument, options); | ||||
|         } | ||||
| export type AssetListBundleQueryHookResult = ReturnType<typeof useAssetListBundleQuery>; | ||||
| export type AssetListBundleLazyQueryHookResult = ReturnType<typeof useAssetListBundleLazyQuery>; | ||||
| export type AssetListBundleQueryResult = Apollo.QueryResult<AssetListBundleQuery, AssetListBundleQueryVariables>; | ||||
| @ -0,0 +1 @@ | ||||
| export { ListAsset } from './list-asset'; | ||||
| @ -0,0 +1,186 @@ | ||||
| import { fireEvent, render, screen } from '@testing-library/react'; | ||||
| import { ListAsset } from './list-asset'; | ||||
| import type { MockedResponse } from '@apollo/client/testing'; | ||||
| import { MockedProvider } from '@apollo/client/testing'; | ||||
| import type { | ||||
|   AssetListBundleQuery, | ||||
|   ProposalAssetQuery, | ||||
| } from './__generated___/Asset'; | ||||
| import { AssetListBundleDocument } from './__generated___/Asset'; | ||||
| import { ProposalAssetDocument } from './__generated___/Asset'; | ||||
| import { AssetStatus } from '@vegaprotocol/types'; | ||||
| import type { useWeb3React } from '@web3-react/core'; | ||||
| import BigNumber from 'bignumber.js'; | ||||
| import type { AppState } from '../../../../contexts/app-state/app-state-context'; | ||||
| 
 | ||||
| const mockUseEthTx = { | ||||
|   perform: jest.fn(), | ||||
|   Dialog: () => null, | ||||
| }; | ||||
| 
 | ||||
| jest.mock('@vegaprotocol/web3', () => { | ||||
|   const orig = jest.requireActual('@vegaprotocol/web3'); | ||||
|   return { | ||||
|     ...orig, | ||||
|     useBridgeContract: jest.fn().mockReturnValue({ | ||||
|       list_asset: jest.fn(), | ||||
|       isNewContract: true, | ||||
|     }), | ||||
|     useEthereumTransaction: jest.fn(() => mockUseEthTx), | ||||
|   }; | ||||
| }); | ||||
| 
 | ||||
| const defaultHookValue = { | ||||
|   isActive: false, | ||||
|   error: undefined, | ||||
|   connector: null, | ||||
|   chainId: 3, | ||||
|   account: null, | ||||
| } as unknown as ReturnType<typeof useWeb3React>; | ||||
| let mockHookValue: ReturnType<typeof useWeb3React>; | ||||
| 
 | ||||
| jest.mock('@web3-react/core', () => { | ||||
|   const original = jest.requireActual('@web3-react/core'); | ||||
|   return { | ||||
|     ...original, | ||||
|     useWeb3React: jest.fn(() => mockHookValue), | ||||
|   }; | ||||
| }); | ||||
| 
 | ||||
| const mockAppState: AppState = { | ||||
|   totalAssociated: new BigNumber('50063005'), | ||||
|   decimals: 18, | ||||
|   totalSupply: new BigNumber(65000000), | ||||
|   balanceFormatted: new BigNumber(0), | ||||
|   walletBalance: new BigNumber(0), | ||||
|   lien: new BigNumber(0), | ||||
|   allowance: new BigNumber(0), | ||||
|   tranches: null, | ||||
|   vegaWalletOverlay: false, | ||||
|   vegaWalletManageOverlay: false, | ||||
|   ethConnectOverlay: false, | ||||
|   walletAssociatedBalance: null, | ||||
|   vestingAssociatedBalance: null, | ||||
|   trancheBalances: [], | ||||
|   totalLockedBalance: new BigNumber(0), | ||||
|   totalVestedBalance: new BigNumber(0), | ||||
|   trancheError: null, | ||||
|   drawerOpen: false, | ||||
|   associationBreakdown: { | ||||
|     vestingAssociations: {}, | ||||
|     stakingAssociations: {}, | ||||
|   }, | ||||
|   transactionOverlay: false, | ||||
|   bannerMessage: '', | ||||
| }; | ||||
| 
 | ||||
| jest.mock('../../../contexts/app-state/app-state-context', () => ({ | ||||
|   useAppState: () => ({ | ||||
|     appState: mockAppState, | ||||
|   }), | ||||
| })); | ||||
| 
 | ||||
| const ASSET_ID = 'foo'; | ||||
| 
 | ||||
| const DEFAULT__ASSET: ProposalAssetQuery = { | ||||
|   __typename: 'Query', | ||||
|   asset: { | ||||
|     __typename: 'Asset', | ||||
|     status: AssetStatus.STATUS_PENDING_LISTING, | ||||
|     source: { | ||||
|       __typename: 'ERC20', | ||||
|       contractAddress: '0x0', | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| const proposalAssetMock: MockedResponse<ProposalAssetQuery> = { | ||||
|   request: { | ||||
|     query: ProposalAssetDocument, | ||||
|     variables: { | ||||
|       assetId: ASSET_ID, | ||||
|     }, | ||||
|   }, | ||||
|   result: { | ||||
|     data: { | ||||
|       ...DEFAULT__ASSET, | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| const assetBundleMock: MockedResponse<AssetListBundleQuery> = { | ||||
|   request: { | ||||
|     query: AssetListBundleDocument, | ||||
|     variables: { | ||||
|       assetId: ASSET_ID, | ||||
|     }, | ||||
|   }, | ||||
|   result: { | ||||
|     data: { | ||||
|       erc20ListAssetBundle: { | ||||
|         assetSource: '0xf00', | ||||
|         vegaAssetId: ASSET_ID, | ||||
|         nonce: '1', | ||||
|         signatures: '0x0', | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| const renderComponent = (assetId: string) => { | ||||
|   return render( | ||||
|     <MockedProvider mocks={[proposalAssetMock, assetBundleMock]}> | ||||
|       <ListAsset | ||||
|         assetId={assetId} | ||||
|         withdrawalThreshold={'1'} | ||||
|         lifetimeLimit={'100'} | ||||
|       /> | ||||
|     </MockedProvider> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| it('Renders connect state if not connected', async () => { | ||||
|   mockHookValue = defaultHookValue; | ||||
|   renderComponent(ASSET_ID); | ||||
| 
 | ||||
|   expect( | ||||
|     await screen.findByText('Connect Ethereum wallet') | ||||
|   ).toBeInTheDocument(); | ||||
| }); | ||||
| 
 | ||||
| it('Renders title, description and button when connected', async () => { | ||||
|   mockHookValue = { | ||||
|     ...defaultHookValue, | ||||
|     account: 'foo', | ||||
|   }; | ||||
|   renderComponent(ASSET_ID); | ||||
|   expect(await screen.findByText('List asset')).toBeInTheDocument(); | ||||
|   expect( | ||||
|     await screen.findByText( | ||||
|       'This asset needs to be listed on the collateral bridge before it can be used.' | ||||
|     ) | ||||
|   ).toBeInTheDocument(); | ||||
|   expect(await screen.findByTestId('list-asset')).toHaveTextContent( | ||||
|     'List asset' | ||||
|   ); | ||||
| }); | ||||
| 
 | ||||
| it('Sends transaction correctly when button is pressed', async () => { | ||||
|   mockHookValue = { | ||||
|     ...defaultHookValue, | ||||
|     account: 'foo', | ||||
|   }; | ||||
|   renderComponent(ASSET_ID); | ||||
|   expect(await screen.findByTestId('list-asset')).toHaveTextContent( | ||||
|     'List asset' | ||||
|   ); | ||||
|   fireEvent.click(screen.getByTestId('list-asset')); | ||||
|   expect(mockUseEthTx.perform).toBeCalledWith( | ||||
|     '0xf00', | ||||
|     '0xfoo', | ||||
|     '100', | ||||
|     '1', | ||||
|     '1', | ||||
|     '0x0' | ||||
|   ); | ||||
| }); | ||||
| @ -0,0 +1,111 @@ | ||||
| import type { CollateralBridge } from '@vegaprotocol/smart-contracts'; | ||||
| import { AssetStatus } from '@vegaprotocol/types'; | ||||
| import { Button } from '@vegaprotocol/ui-toolkit'; | ||||
| import { useBridgeContract, useEthereumTransaction } from '@vegaprotocol/web3'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import { | ||||
|   useAssetListBundleQuery, | ||||
|   useProposalAssetQuery, | ||||
| } from './__generated___/Asset'; | ||||
| import { EthWalletContainer } from '../../../../components/eth-wallet-container'; | ||||
| 
 | ||||
| const useListAsset = (assetId: string) => { | ||||
|   const bridgeContract = useBridgeContract(); | ||||
| 
 | ||||
|   const transaction = useEthereumTransaction<CollateralBridge, 'list_asset'>( | ||||
|     bridgeContract, | ||||
|     'list_asset' | ||||
|   ); | ||||
|   const { | ||||
|     data, | ||||
|     loading: loadingAsset, | ||||
|     error: errorAsset, | ||||
|   } = useProposalAssetQuery({ | ||||
|     variables: { | ||||
|       assetId, | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   const { | ||||
|     data: assetData, | ||||
|     loading: loadingBundle, | ||||
|     error: errorBundle, | ||||
|   } = useAssetListBundleQuery({ | ||||
|     variables: { | ||||
|       assetId, | ||||
|     }, | ||||
|     skip: !data, | ||||
|   }); | ||||
|   return { | ||||
|     transaction, | ||||
|     data, | ||||
|     loadingAsset, | ||||
|     errorAsset, | ||||
|     assetData, | ||||
|     loadingBundle, | ||||
|     errorBundle, | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| export interface ListAssetProps { | ||||
|   lifetimeLimit: string; | ||||
|   withdrawalThreshold: string; | ||||
|   assetId: string; | ||||
| } | ||||
| 
 | ||||
| export const ListAsset = ({ | ||||
|   assetId, | ||||
|   lifetimeLimit, | ||||
|   withdrawalThreshold, | ||||
| }: ListAssetProps) => { | ||||
|   const { t } = useTranslation(); | ||||
|   const { | ||||
|     transaction, | ||||
|     data, | ||||
|     loadingAsset, | ||||
|     errorAsset, | ||||
|     assetData, | ||||
|     loadingBundle, | ||||
|     errorBundle, | ||||
|   } = useListAsset(assetId); | ||||
|   const { perform, Dialog } = transaction; | ||||
| 
 | ||||
|   if ( | ||||
|     !assetData?.erc20ListAssetBundle || | ||||
|     !assetData.erc20ListAssetBundle || | ||||
|     !data?.asset || | ||||
|     loadingAsset || | ||||
|     loadingBundle | ||||
|   ) { | ||||
|     return null; | ||||
|   } | ||||
|   if (data.asset.source.__typename !== 'ERC20') return null; | ||||
|   if (data.asset.status !== AssetStatus.STATUS_PENDING_LISTING) return null; | ||||
|   if (errorAsset || errorBundle) return null; | ||||
|   const { assetSource, signatures, vegaAssetId, nonce } = | ||||
|     assetData.erc20ListAssetBundle; | ||||
|   return ( | ||||
|     <div className="mb-8"> | ||||
|       <h3 className="text-xl mb-2">{t('ListAsset')}</h3> | ||||
|       <p className="pr-8">{t('ListAssetDescription')}</p> | ||||
|       <EthWalletContainer> | ||||
|         <Button | ||||
|           data-testid="list-asset" | ||||
|           onClick={() => | ||||
|             perform( | ||||
|               assetSource, | ||||
|               `0x${vegaAssetId}`, | ||||
|               lifetimeLimit, | ||||
|               withdrawalThreshold, | ||||
|               nonce, | ||||
|               signatures | ||||
|             ) | ||||
|           } | ||||
|         > | ||||
|           {t('ListAssetAction')} | ||||
|         </Button> | ||||
|       </EthWalletContainer> | ||||
|       <Dialog /> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
| @ -34,6 +34,9 @@ jest.mock('../proposal-votes-table', () => ({ | ||||
| jest.mock('../vote-details', () => ({ | ||||
|   VoteDetails: () => <div data-testid="proposal-vote-details"></div>, | ||||
| })); | ||||
| jest.mock('../list-asset', () => ({ | ||||
|   ListAsset: () => <div data-testid="proposal-list-asset"></div>, | ||||
| })); | ||||
| 
 | ||||
| it('Renders with data-testid', async () => { | ||||
|   const proposal = generateProposal(); | ||||
| @ -49,4 +52,26 @@ it('renders each section', async () => { | ||||
|   expect(screen.getByTestId('proposal-terms-json')).toBeInTheDocument(); | ||||
|   expect(screen.getByTestId('proposal-votes-table')).toBeInTheDocument(); | ||||
|   expect(screen.getByTestId('proposal-vote-details')).toBeInTheDocument(); | ||||
|   expect(screen.queryByTestId('proposal-list-asset')).not.toBeInTheDocument(); | ||||
| }); | ||||
| 
 | ||||
| it('renders whitelist section if proposal is new asset and source is erc20', async () => { | ||||
|   const proposal = generateProposal({ | ||||
|     terms: { | ||||
|       change: { | ||||
|         __typename: 'NewAsset', | ||||
|         name: 'foo', | ||||
|         symbol: 'FOO', | ||||
|         decimals: 18, | ||||
|         quantum: '1', | ||||
|         source: { | ||||
|           __typename: 'ERC20', | ||||
|           lifetimeLimit: '1', | ||||
|           withdrawThreshold: '100', | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|   }); | ||||
|   render(<Proposal proposal={proposal as Proposal_proposal} />); | ||||
|   expect(screen.getByTestId('proposal-list-asset')).toBeInTheDocument(); | ||||
| }); | ||||
|  | ||||
| @ -6,6 +6,7 @@ import { ProposalChangeTable } from '../proposal-change-table'; | ||||
| import { ProposalTermsJson } from '../proposal-terms-json'; | ||||
| import { ProposalVotesTable } from '../proposal-votes-table'; | ||||
| import { VoteDetails } from '../vote-details'; | ||||
| import { ListAsset } from '../list-asset'; | ||||
| 
 | ||||
| export enum ProposalType { | ||||
|   PROPOSAL_NEW_MARKET = 'PROPOSAL_NEW_MARKET', | ||||
| @ -15,7 +16,6 @@ export enum ProposalType { | ||||
|   PROPOSAL_NETWORK_PARAMETER = 'PROPOSAL_NETWORK_PARAMETER', | ||||
|   PROPOSAL_FREEFORM = 'PROPOSAL_FREEFORM', | ||||
| } | ||||
| 
 | ||||
| interface ProposalProps { | ||||
|   proposal: Proposal_proposal; | ||||
| } | ||||
| @ -77,6 +77,15 @@ export const Proposal = ({ proposal }: ProposalProps) => { | ||||
|         <div className="mb-8"> | ||||
|           <ProposalChangeTable proposal={proposal} /> | ||||
|         </div> | ||||
|         {proposal.terms.change.__typename === 'NewAsset' && | ||||
|         proposal.terms.change.source.__typename === 'ERC20' && | ||||
|         proposal.id ? ( | ||||
|           <ListAsset | ||||
|             assetId={proposal.id} | ||||
|             withdrawalThreshold={proposal.terms.change.source.withdrawThreshold} | ||||
|             lifetimeLimit={proposal.terms.change.source.lifetimeLimit} | ||||
|           /> | ||||
|         ) : null} | ||||
|         <div className="mb-8"> | ||||
|           <VoteDetails | ||||
|             proposal={proposal} | ||||
|  | ||||
| @ -37,6 +37,23 @@ export class CollateralBridge { | ||||
|   default_withdraw_delay() { | ||||
|     return this.contract.default_withdraw_delay(); | ||||
|   } | ||||
|   list_asset( | ||||
|     address: string, | ||||
|     vegaAssetId: string, | ||||
|     lifetimeLimit: string, | ||||
|     withdraw_threshold: string, | ||||
|     nonce: string, | ||||
|     signatures: string | ||||
|   ) { | ||||
|     return this.contract.list_asset( | ||||
|       address, | ||||
|       vegaAssetId, | ||||
|       lifetimeLimit, | ||||
|       withdraw_threshold, | ||||
|       nonce, | ||||
|       signatures | ||||
|     ); | ||||
|   } | ||||
|   withdraw_asset( | ||||
|     assetSource: string, | ||||
|     amount: string, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user