From c38955b1ec3f886fe4f005ddbb465507d4ac413d Mon Sep 17 00:00:00 2001 From: Thibaut Sardan <33178835+Tbaut@users.noreply.github.com> Date: Sun, 15 Dec 2024 19:35:09 +0100 Subject: [PATCH 1/4] Support calls with no params (#608) --- packages/ui/src/components/CallInfo.tsx | 2 +- packages/ui/src/utils/jsonPrint.ts | 28 +++++++++++++++---------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/components/CallInfo.tsx b/packages/ui/src/components/CallInfo.tsx index 88f28568..48397a2c 100644 --- a/packages/ui/src/components/CallInfo.tsx +++ b/packages/ui/src/components/CallInfo.tsx @@ -252,7 +252,7 @@ const CallInfo = ({ () => aggregatedData.callData && getDecodeUrl(aggregatedData.callData), [aggregatedData, getDecodeUrl] ) - const hasArgs = useMemo(() => decodedCall && Object.keys(decodedCall).length > 0, [decodedCall]) + const hasArgs = useMemo(() => decodedCall && decodedCall?.value?.value, [decodedCall]) return (
- json5 - .stringify(e, { - replacer: (_, v) => - typeof v === 'bigint' ? v.toString() : v instanceof Binary ? v.asText() : v, - space: 4 - }) - // remove { and } - .slice(1, -1) - // remove trailing comma if any - .replace(/,\s*$/, '') +export const JSONprint = (e: unknown) => { + if (e === null || e === undefined) { + return '' + } + return ( + json5 + .stringify(e, { + replacer: (_, v) => + typeof v === 'bigint' ? v.toString() : v instanceof Binary ? v.asText() : v, + space: 4 + }) + // remove { and } + .slice(1, -1) + // remove trailing comma if any + .replace(/,\s*$/, '') + ) +} From 3c4e0627c984793ad92b90306bd90a0253f2dc41 Mon Sep 17 00:00:00 2001 From: Thibaut Sardan <33178835+Tbaut@users.noreply.github.com> Date: Tue, 17 Dec 2024 12:09:47 +0100 Subject: [PATCH 2/4] Various enhancements (#610) --- .../ui/cypress/tests/multisig-creation.cy.ts | 3 +- packages/ui/package.json | 2 +- packages/ui/src/components/CallInfo.tsx | 56 +++++++++++++------ .../src/components/EasySetup/FromCallData.tsx | 16 +++++- .../components/library/TextFieldStyled.tsx | 3 +- packages/ui/src/hooks/useGetMultisigTx.tsx | 8 +-- .../src/pages/Creation/WithProxySelection.tsx | 21 +++++-- packages/ui/src/pages/Creation/index.tsx | 6 -- packages/ui/src/utils/getPapiHowLink.ts | 1 + yarn.lock | 12 ++-- 10 files changed, 84 insertions(+), 44 deletions(-) create mode 100644 packages/ui/src/utils/getPapiHowLink.ts diff --git a/packages/ui/cypress/tests/multisig-creation.cy.ts b/packages/ui/cypress/tests/multisig-creation.cy.ts index 345e7fa6..720a5822 100644 --- a/packages/ui/cypress/tests/multisig-creation.cy.ts +++ b/packages/ui/cypress/tests/multisig-creation.cy.ts @@ -99,6 +99,8 @@ describe('Multisig creation', () => { // Step 2 newMultisigPage.step2.thresholdInput().type('2') newMultisigPage.step2.nameInput().type(multisigName) + newMultisigPage.step2.checkboxUsePureProxy().click() + newMultisigPage.step2.checkboxUsePureProxy().should('be.checked') newMultisigPage.nextButton().should('contain', 'Next').click() // Step 3 @@ -136,7 +138,6 @@ describe('Multisig creation', () => { // Step 2 newMultisigPage.step2.thresholdInput().type('3') newMultisigPage.step2.nameInput().type(multisigName) - newMultisigPage.step2.checkboxUsePureProxy().click() newMultisigPage.step2.checkboxUsePureProxy().should('not.be.checked') newMultisigPage.nextButton().should('contain', 'Next').click() diff --git a/packages/ui/package.json b/packages/ui/package.json index 63b7f4fb..3aed2285 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -16,7 +16,7 @@ "@polkadot/react-identicon": "^3.11.3", "@polkadot/util-crypto": "^13.2.3", "@reactive-dot/core": "^0.27.1", - "@reactive-dot/react": "^0.27.1", + "@reactive-dot/react": "^0.28.0", "@tanstack/react-query": "^5.62.2", "@walletconnect/web3wallet": "^1.16.1", "dayjs": "^1.11.13", diff --git a/packages/ui/src/components/CallInfo.tsx b/packages/ui/src/components/CallInfo.tsx index 48397a2c..ff45a0e2 100644 --- a/packages/ui/src/components/CallInfo.tsx +++ b/packages/ui/src/components/CallInfo.tsx @@ -33,7 +33,7 @@ interface CreateTreeParams { chainInfo?: ChainInfoHuman } -const isWhiteListedCall = (type: string, value: string) => { +const isWhiteListedCall = (extrinsicName: string) => { return [ 'Balances.transfer', 'Balances.transfer_keep_alive', @@ -57,12 +57,18 @@ const isWhiteListedCall = (type: string, value: string) => { 'ConvictionVoting.vote', 'ConvictionVoting.remove_vote', 'ConvictionVoting.undelegate', - 'ConvictionVoting.unlock' - ].includes(`${type}.${value}`) + 'ConvictionVoting.unlock', + // Hydration + 'Tokens.transfer' + ].includes(extrinsicName) } -const isBatchedCall = (type: string, value: string) => { - return ['Utility.batch', 'Utility.batch_all', 'Utility.force_batch'].includes(`${type}.${value}`) +const isPreventBalanceFormat = (extrinsicName: string) => { + return ['Tokens.transfer'].includes(extrinsicName) +} + +const isBatchedCall = (extrinsicName: string) => { + return ['Utility.batch', 'Utility.batch_all', 'Utility.force_batch'].includes(extrinsicName) } const formatBalance = (amount: bigint, label: string, chainInfo: ChainInfoHuman, id: string) => ( @@ -74,11 +80,23 @@ const formatBalance = (amount: bigint, label: string, chainInfo: ChainInfoHuman, ) -const eachFieldRendered = (value: Record, chainInfo: ChainInfoHuman, id: string) => { +interface EachFieldRenderedParams { + value: Record + chainInfo: ChainInfoHuman + id: string + preventBalanceFormating?: boolean +} +const eachFieldRendered = ({ + value, + chainInfo, + id, + preventBalanceFormating = false +}: EachFieldRenderedParams) => { // for transfer, nomination, staking, bounties - const bigIntKey = ['value', 'fee', 'max_additional', 'balance'].find( - (key) => typeof value[key] === 'bigint' - ) + // We should make sure this is not done for hydration + const bigIntKey = + !preventBalanceFormating && + ['value', 'fee', 'max_additional', 'balance'].find((key) => typeof value[key] === 'bigint') if (bigIntKey) { return formatBalance(value[bigIntKey], bigIntKey, chainInfo, id) @@ -162,7 +180,10 @@ const preparedCall = ({ }: PreparedCallParams) => { if (!decodedCall) return - if (isBatchedCall(decodedCall.type, decodedCall.value.type)) { + const extrinsicName = getExtrinsicName(decodedCall.type, decodedCall.value.type) + const preventBalanceFormating = isPreventBalanceFormat(extrinsicName) + + if (isBatchedCall(extrinsicName)) { const lowerLevelCalls = decodedCall.value.value.calls as Array> return lowerLevelCalls.map((call, index) => { @@ -178,19 +199,20 @@ const preparedCall = ({ }) } - if (isWhiteListedCall(decodedCall.type, decodedCall.value.type)) { + if (isWhiteListedCall(extrinsicName)) { const lowerLevelCall = decodedCall.value.value if (typeof lowerLevelCall === 'object') { return ( <> - {isBatch && ( - - {getExtrinsicName(decodedCall.type, decodedCall.value.type)} - - )} + {isBatch && {extrinsicName}}
    {Object.entries(lowerLevelCall).map(([key, value], index) => - eachFieldRendered({ [key]: value }, chainInfo, `${decodedCall.type}-${index}`) + eachFieldRendered({ + value: { [key]: value }, + chainInfo, + id: `${decodedCall.type}-${index}`, + preventBalanceFormating + }) )}
diff --git a/packages/ui/src/components/EasySetup/FromCallData.tsx b/packages/ui/src/components/EasySetup/FromCallData.tsx index 13ea3d8b..fba3b3a6 100644 --- a/packages/ui/src/components/EasySetup/FromCallData.tsx +++ b/packages/ui/src/components/EasySetup/FromCallData.tsx @@ -8,6 +8,7 @@ import { useCallInfoFromCallData } from '../../hooks/useCallInfoFromCallData' import { getExtrinsicName } from '../../utils/getExtrinsicName' import { usePjsLinks } from '../../hooks/usePjsLinks' import { Binary, HexString, Transaction } from 'polkadot-api' +import { getPapiHowLink } from '../../utils/getPapiHowLink' interface Props { className?: string @@ -40,7 +41,8 @@ const FromCallData = ({ className, onSetExtrinsic }: Props) => { } return call - } catch { + } catch (e: unknown) { + !!e && setCallDataError(String(e)) return } }, @@ -82,13 +84,21 @@ const FromCallData = ({ className, onSetExtrinsic }: Props) => { return ( - Paste below the "encoded call data" from a{' '} + Paste below the "encoded call data" from{' '} + + papi console + {' '} + or{' '} - manual extrinsic + pjs manual extrinsic .
Multix will take care of wrapping it in a multisig/proxy call
diff --git a/packages/ui/src/components/library/TextFieldStyled.tsx b/packages/ui/src/components/library/TextFieldStyled.tsx index ac3cc220..188ad21a 100644 --- a/packages/ui/src/components/library/TextFieldStyled.tsx +++ b/packages/ui/src/components/library/TextFieldStyled.tsx @@ -50,13 +50,14 @@ const TextFieldStyled = styled(TextField)` &.Mui-error { outline: 3px solid ${({ theme }) => theme.custom.error}; + margin-bottom: 1rem; } } .MuiFormHelperText-root { &.Mui-error { position: absolute; - bottom: -24px; + bottom: -1rem; } } ` diff --git a/packages/ui/src/hooks/useGetMultisigTx.tsx b/packages/ui/src/hooks/useGetMultisigTx.tsx index da9c3619..58aa7d20 100644 --- a/packages/ui/src/hooks/useGetMultisigTx.tsx +++ b/packages/ui/src/hooks/useGetMultisigTx.tsx @@ -61,10 +61,10 @@ export const useGetMultisigTx = ({ } if (forceAsMulti && !extrinsicToCall) { - console.warn( - 'The extrinsic call is required when multisig.asMulti is called', - extrinsicToCall - ) + // console.warn( + // 'The extrinsic call is required when multisig.asMulti is called', + // extrinsicToCall + // ) return } diff --git a/packages/ui/src/pages/Creation/WithProxySelection.tsx b/packages/ui/src/pages/Creation/WithProxySelection.tsx index b9f047c5..4fc74b74 100644 --- a/packages/ui/src/pages/Creation/WithProxySelection.tsx +++ b/packages/ui/src/pages/Creation/WithProxySelection.tsx @@ -13,7 +13,19 @@ const WithProxySelection = ({ setWithProxy, withProxy, className }: Props) => { Pure proxy + Use a pure proxy (not cross-chain{' '} + + see here + + ) + + } control={ ( className={className} title={ - Using a proxy makes the multisig more flexible. You can then change the signatories without - changing the proxy address. More info{' '} + Using a pure proxy has advantages and drawbacks see when it makes sense to use it{' '} - in this video + in the docs . diff --git a/packages/ui/src/pages/Creation/index.tsx b/packages/ui/src/pages/Creation/index.tsx index e1207cc2..1aa048eb 100644 --- a/packages/ui/src/pages/Creation/index.tsx +++ b/packages/ui/src/pages/Creation/index.tsx @@ -276,12 +276,6 @@ const MultisigCreation = ({ className }: Props) => { threshold ]) - useEffect(() => { - // default to using a proxy - if (supportsProxy) { - setWithProxy(true) - } - }, [supportsProxy]) useEffect(() => { setErrorMessage('') diff --git a/packages/ui/src/utils/getPapiHowLink.ts b/packages/ui/src/utils/getPapiHowLink.ts new file mode 100644 index 00000000..14cd9bd8 --- /dev/null +++ b/packages/ui/src/utils/getPapiHowLink.ts @@ -0,0 +1 @@ +export const getPapiHowLink = () => 'https://dev.papi.how' diff --git a/yarn.lock b/yarn.lock index c1e7ffa4..c1ac0b08 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3689,15 +3689,15 @@ __metadata: languageName: node linkType: hard -"@reactive-dot/react@npm:^0.27.1": - version: 0.27.1 - resolution: "@reactive-dot/react@npm:0.27.1" +"@reactive-dot/react@npm:^0.28.0": + version: 0.28.0 + resolution: "@reactive-dot/react@npm:0.28.0" dependencies: "@reactive-dot/core": ^0.27.1 jotai: ^2.10.3 peerDependencies: - react: 18.x - checksum: 5e932f259c0c9ec99e0481f05cbba2f1920805e8df11f13722cd6ba999d588b1552ecf479b6cfe1c2aded27fa7876217b066731355dad4233bf8bb325d6b96e8 + react: 18.x || 19.x + checksum: c23a4f2a400864773c1408770d1bb38454fb02ffb9cb94372e8c2d6cc8d1a0aeb8490816454a6b51bc55a2e9f303bfbfaade76fa16d9e86459f22fe9aee1298f languageName: node linkType: hard @@ -10166,7 +10166,7 @@ __metadata: "@polkadot/react-identicon": ^3.11.3 "@polkadot/util-crypto": ^13.2.3 "@reactive-dot/core": ^0.27.1 - "@reactive-dot/react": ^0.27.1 + "@reactive-dot/react": ^0.28.0 "@tanstack/react-query": ^5.62.2 "@types/node": ^22.10.1 "@types/react-dom": ^18.3.1 From 23f67415d83ce0bb161a13900e681e4ad158ab66 Mon Sep 17 00:00:00 2001 From: Thibaut Sardan <33178835+Tbaut@users.noreply.github.com> Date: Tue, 17 Dec 2024 23:13:40 +0100 Subject: [PATCH 3/4] Support hydra specific calls (#611) --- .../components/EasySetup/BalancesTransfer.tsx | 26 +++++++---- .../src/components/modals/ChangeMultisig.tsx | 45 +++++++++++++------ packages/ui/src/hooks/useGetMultisigTx.tsx | 32 ++++++++----- packages/ui/src/pages/Creation/index.tsx | 21 ++++++--- 4 files changed, 84 insertions(+), 40 deletions(-) diff --git a/packages/ui/src/components/EasySetup/BalancesTransfer.tsx b/packages/ui/src/components/EasySetup/BalancesTransfer.tsx index 73d4bde4..2d77660f 100644 --- a/packages/ui/src/components/EasySetup/BalancesTransfer.tsx +++ b/packages/ui/src/components/EasySetup/BalancesTransfer.tsx @@ -8,8 +8,9 @@ import { inputToBigInt, getGlobalMaxValue } from '../../utils/bnUtils' import { TextField } from '../library' import { getOptionLabel } from '../../utils/getOptionLabel' import { useAccountBaseFromAccountList } from '../../hooks/useAccountBaseFromAccountList' -import { dot, MultiAddress } from '@polkadot-api/descriptors' +import { dot, hydration, MultiAddress } from '@polkadot-api/descriptors' import { Transaction, TypedApi } from 'polkadot-api' +import { useNetwork } from '../../contexts/NetworkContext' interface Props { className?: string @@ -31,6 +32,7 @@ const BalancesTransfer = ({ className, onSetExtrinsic, onSetErrorMessage, from } }) const maxValue = useMemo(() => getGlobalMaxValue(128), []) const toAddress = useMemo(() => selected?.address || '', [selected?.address]) + const { selectedNetwork } = useNetwork() useEffect(() => { if (!!amount && !hasEnoughFreeBalance) { @@ -41,7 +43,7 @@ const BalancesTransfer = ({ className, onSetExtrinsic, onSetErrorMessage, from } }, [amount, amountError, hasEnoughFreeBalance, onSetErrorMessage]) useEffect(() => { - if (!api) { + if (!api || !selectedNetwork) { onSetExtrinsic(undefined) return } @@ -51,13 +53,19 @@ const BalancesTransfer = ({ className, onSetExtrinsic, onSetErrorMessage, from } return } - onSetExtrinsic( - (api as TypedApi).tx.Balances.transfer_keep_alive({ - dest: MultiAddress.Id(toAddress), - value: amount - }) - ) - }, [amount, api, chainInfo, onSetExtrinsic, toAddress]) + const extrinsic = + selectedNetwork === 'hydration' + ? (api as TypedApi).tx.Balances.transfer_keep_alive({ + dest: toAddress, + value: amount + }) + : (api as TypedApi).tx.Balances.transfer_keep_alive({ + dest: MultiAddress.Id(toAddress), + value: amount + }) + + onSetExtrinsic(extrinsic) + }, [amount, api, chainInfo, onSetExtrinsic, selectedNetwork, toAddress]) const onAddressDestChange = useCallback((account: AccountBaseInfo) => { setSelected(account) diff --git a/packages/ui/src/components/modals/ChangeMultisig.tsx b/packages/ui/src/components/modals/ChangeMultisig.tsx index 6d539935..2be161cf 100644 --- a/packages/ui/src/components/modals/ChangeMultisig.tsx +++ b/packages/ui/src/components/modals/ChangeMultisig.tsx @@ -31,7 +31,8 @@ import { useGetSortAddress } from '../../hooks/useGetSortAddress' import { useGetMultisigAddress } from '../../contexts/useGetMultisigAddress' import { getAsMultiTx } from '../../utils/getAsMultiTx' import { Enum, TypedApi } from 'polkadot-api' -import { dot, MultiAddress } from '@polkadot-api/descriptors' +import { dot, hydration, MultiAddress } from '@polkadot-api/descriptors' +import { useNetwork } from '../../contexts/NetworkContext' interface Props { onClose: () => void @@ -41,6 +42,7 @@ interface Props { type Step = 'selection' | 'summary' | 'call1' | 'call2' const ChangeMultisig = ({ onClose, className }: Props) => { + const { selectedNetwork } = useNetwork() const modalRef = useRef(null) const { api, chainInfo, compatibilityToken } = useApi() const { selectedMultiProxy, getMultisigAsAccountBaseInfo, getMultisigByAddress } = useMultiProxy() @@ -87,7 +89,7 @@ const ChangeMultisig = ({ onClose, className }: Props) => { const [callError, setCallError] = useState('') const secondCall = useMemo(() => { - if (!api || !compatibilityToken) { + if (!api || !compatibilityToken || !selectedNetwork) { // console.error('api is not ready') return } @@ -120,17 +122,31 @@ const ChangeMultisig = ({ onClose, className }: Props) => { selectedMultisig.signatories.filter((sig) => sig !== selectedAccount.address) ) - const addProxyTx = (api as TypedApi).tx.Proxy.add_proxy({ - delegate: MultiAddress.Id(newMultisigAddress), - proxy_type: Enum('Any'), - delay: 0 - }) - - const proxyTx = (api as TypedApi).tx.Proxy.proxy({ - real: MultiAddress.Id(selectedMultiProxy?.proxy), - force_proxy_type: undefined, - call: addProxyTx.decodedCall - }) + const addProxyTx = + selectedNetwork === 'hydration' + ? (api as TypedApi).tx.Proxy.add_proxy({ + delegate: newMultisigAddress, + proxy_type: Enum('Any'), + delay: 0 + }) + : (api as TypedApi).tx.Proxy.add_proxy({ + delegate: MultiAddress.Id(newMultisigAddress), + proxy_type: Enum('Any'), + delay: 0 + }) + + const proxyTx = + selectedNetwork === 'hydration' + ? (api as TypedApi).tx.Proxy.proxy({ + real: selectedMultiProxy?.proxy, + force_proxy_type: undefined, + call: addProxyTx.decodedCall + }) + : (api as TypedApi).tx.Proxy.proxy({ + real: MultiAddress.Id(selectedMultiProxy?.proxy), + force_proxy_type: undefined, + call: addProxyTx.decodedCall + }) // call with the old multisig to delete the new one return getAsMultiTx({ api, @@ -149,7 +165,8 @@ const ChangeMultisig = ({ onClose, className }: Props) => { oldThreshold, selectedAccount, selectedMultiProxy?.proxy, - selectedMultisig + selectedMultisig, + selectedNetwork ]) const firstCall = useMemo(() => { diff --git a/packages/ui/src/hooks/useGetMultisigTx.tsx b/packages/ui/src/hooks/useGetMultisigTx.tsx index 58aa7d20..891caaa3 100644 --- a/packages/ui/src/hooks/useGetMultisigTx.tsx +++ b/packages/ui/src/hooks/useGetMultisigTx.tsx @@ -6,7 +6,8 @@ import { getAsMultiTx } from '../utils/getAsMultiTx' import { MultisigStorageInfo, Weight } from '../types' import { getApproveAsMultiTx } from '../utils/getApproveAsMultiTx' import { HexString, Transaction, TypedApi } from 'polkadot-api' -import { dot, MultiAddress } from '@polkadot-api/descriptors' +import { dot, hydration, MultiAddress } from '@polkadot-api/descriptors' +import { useNetwork } from '../contexts/NetworkContext' interface Params { selectedMultisig?: MultiProxy['multisigs'][0] @@ -37,6 +38,7 @@ export const useGetMultisigTx = ({ }: Params) => { const { api, compatibilityToken } = useApi() const { getSortAddress } = useGetSortAddress() + const { selectedNetwork } = useNetwork() const multisigTx = useMemo(() => { if (!selectedMultisig?.signatories) { @@ -52,7 +54,7 @@ export const useGetMultisigTx = ({ return } - if (!api) { + if (!api || !selectedNetwork) { return } @@ -85,11 +87,18 @@ export const useGetMultisigTx = ({ try { // the proxy is selected if (isProxy && !!extrinsicToCall) { - tx = (api as TypedApi).tx.Proxy.proxy({ - real: MultiAddress.Id(fromAddress), - force_proxy_type: undefined, - call: extrinsicToCall.decodedCall - }) + tx = + selectedNetwork === 'hydration' + ? (api as TypedApi).tx.Proxy.proxy({ + real: fromAddress, + force_proxy_type: undefined, + call: extrinsicToCall.decodedCall + }) + : (api as TypedApi).tx.Proxy.proxy({ + real: MultiAddress.Id(fromAddress), + force_proxy_type: undefined, + call: extrinsicToCall.decodedCall + }) // a multisig is selected } else { tx = extrinsicToCall @@ -109,19 +118,20 @@ export const useGetMultisigTx = ({ console.error(e) } }, [ - selectedMultisig?.signatories, + selectedMultisig, getSortAddress, threshold, api, + selectedNetwork, senderAddress, fromAddress, - extrinsicToCall, - isProxy, forceAsMulti, + extrinsicToCall, approvalLength, + approveAsMultiHash, + isProxy, weight, when, - approveAsMultiHash, compatibilityToken ]) diff --git a/packages/ui/src/pages/Creation/index.tsx b/packages/ui/src/pages/Creation/index.tsx index 1aa048eb..e8477452 100644 --- a/packages/ui/src/pages/Creation/index.tsx +++ b/packages/ui/src/pages/Creation/index.tsx @@ -29,7 +29,8 @@ import { isEthereumAddress } from '@polkadot/util-crypto' import { getAsMultiTx } from '../../utils/getAsMultiTx' import { useMultiProxy } from '../../contexts/MultiProxyContext' import { Binary, Enum, Transaction, TypedApi } from 'polkadot-api' -import { dot, MultiAddress } from '@polkadot-api/descriptors' +import { dot, hydration, MultiAddress } from '@polkadot-api/descriptors' +import { useNetwork } from '../../contexts/NetworkContext' interface Props { className?: string @@ -37,6 +38,7 @@ interface Props { const steps = ['Signatories', 'Threshold & Name', 'Review'] const MultisigCreation = ({ className }: Props) => { + const { selectedNetwork } = useNetwork() const [signatories, setSignatories] = useState([]) const [currentStep, setCurrentStep] = useState(0) const isLastStep = useMemo(() => currentStep === steps.length - 1, [currentStep]) @@ -146,7 +148,7 @@ const MultisigCreation = ({ className }: Props) => { // this batchCall is only useful if the user wants a proxy. return } - if (!api || !compatibilityToken) { + if (!api || !compatibilityToken || !selectedNetwork) { // console.error('api is not ready') return } @@ -187,10 +189,16 @@ const MultisigCreation = ({ className }: Props) => { }) // Some funds are needed on the multisig for the pure proxy creation - const transferTx = (api as TypedApi).tx.Balances.transfer_keep_alive({ - dest: MultiAddress.Id(multiAddress), - value: pureProxyCreationNeededFunds - }) + const transferTx = + selectedNetwork === 'hydration' + ? (api as TypedApi).tx.Balances.transfer_keep_alive({ + dest: multiAddress, + value: pureProxyCreationNeededFunds + }) + : (api as TypedApi).tx.Balances.transfer_keep_alive({ + dest: MultiAddress.Id(multiAddress), + value: pureProxyCreationNeededFunds + }) if (!multiSigProxyCall) { console.error('multiSigProxyCall is undefined in Creation index.tsx') @@ -209,6 +217,7 @@ const MultisigCreation = ({ className }: Props) => { multiAddress, pureProxyCreationNeededFunds, selectedAccount, + selectedNetwork, signatories, threshold, withProxy From ec1613164464e3c96c64a651507e8498e5738e3a Mon Sep 17 00:00:00 2001 From: Thibaut Sardan <33178835+Tbaut@users.noreply.github.com> Date: Fri, 20 Dec 2024 11:41:00 +0100 Subject: [PATCH 4/4] Support chopsticks with archive unstable body (#602) --- package.json | 2 +- .../support/page-objects/landingPage.ts | 1 + packages/ui/cypress/tests/address-bar.cy.ts | 8 +++- .../ui/cypress/tests/default-multisigs.cy.ts | 35 ++++++-------- .../ui/cypress/tests/multisig-creation.cy.ts | 34 ++++++-------- .../ui/cypress/tests/watched-accounts.cy.ts | 38 +++++++++------ packages/ui/cypress/utils/getShortAddress.ts | 1 + .../Transactions/TransactionList.tsx | 2 +- .../ui/src/hooks/useCallInfoFromCallData.tsx | 2 +- packages/ui/src/hooks/usePendingTx.tsx | 9 ++-- squid/.env.example | 46 +++---------------- 11 files changed, 75 insertions(+), 103 deletions(-) create mode 100644 packages/ui/cypress/utils/getShortAddress.ts diff --git a/package.json b/package.json index 7d3924ad..4ea0c871 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "lint:fix": "yarn workspaces foreach run lint:fix", "formatAll": "prettier --write .", "start:chopsticks-test-build-and-launch-all": "concurrently --kill-others 'npm run start:chopsticks' 'npm run ui:start-with-chopsticks' 'npm run docker:down && npm run docker:db && npm run build:indexer && npm run indexer:start:chopsticks-local' 'npm run start:graphql-server'", - "start:chopsticks": "npx --yes @acala-network/chopsticks@1.0.1 --config chopsticks-config.yml", + "start:chopsticks": "npx --yes @acala-network/chopsticks@1.0.2-1 --config chopsticks-config.yml", "start:graphql-server": "cd squid && npm run start:graphql-server", "indexer:start:chopsticks-ci": "cd squid && npm run start:chopsticks-ci", "indexer:start:chopsticks-local": "cd squid && npm run start:chopsticks-local", diff --git a/packages/ui/cypress/support/page-objects/landingPage.ts b/packages/ui/cypress/support/page-objects/landingPage.ts index 3d482fed..452c01a4 100644 --- a/packages/ui/cypress/support/page-objects/landingPage.ts +++ b/packages/ui/cypress/support/page-objects/landingPage.ts @@ -20,6 +20,7 @@ export const landingPage = { multisigCreationInfoBanner: (timeout = 4000) => cy.get('[data-cy=banner-multisig-creation-info]', { timeout }), creationInfoBannerCloseButton: () => cy.get('[data-cy=button-close-multisig-creation-info]'), + transactionListLoader: () => cy.get('[data-cy=loader-transaction-list]'), // page specific assertion shouldHaveNoAccountErrorAndWikiLink() { diff --git a/packages/ui/cypress/tests/address-bar.cy.ts b/packages/ui/cypress/tests/address-bar.cy.ts index 986075d3..cfc69ad3 100644 --- a/packages/ui/cypress/tests/address-bar.cy.ts +++ b/packages/ui/cypress/tests/address-bar.cy.ts @@ -277,11 +277,15 @@ describe('Account address in the address bar', () => { watchedAccounts: [publicKey] }) + multisigPage.accountHeader().within(() => { + accountDisplay.addressLabel().should('contain.text', address.slice(0, 6)) + }) + // check that there is an address in the address bar cy.url({ timeout: 3000 }).should('include', address) - // react-router takes some time to get the search params inside the links - cy.wait(1000) + // wait for the loader to be done otherwise the test fails + landingPage.transactionListLoader().should('not.exist') topMenuItems.homeButton().click() cy.url().should('include', address) diff --git a/packages/ui/cypress/tests/default-multisigs.cy.ts b/packages/ui/cypress/tests/default-multisigs.cy.ts index f36f8d0b..25530d36 100644 --- a/packages/ui/cypress/tests/default-multisigs.cy.ts +++ b/packages/ui/cypress/tests/default-multisigs.cy.ts @@ -15,24 +15,17 @@ describe('default Multisigs', () => { watchedAccounts: [lolmcshizPubKey] }) - multisigPage.accountHeader().within(() => { - accountDisplay - .addressLabel() - .invoke('text') - .as('defaultPolkadotAddress') - .should('not.contain', polkadotSelectedMultiproxy.slice(0, 6)) - }) - - cy.log('@defaultPolkadotAddress', cy.get('@defaultPolkadotAddress')) - // select another one - topMenuItems.desktopMenu().within(() => + topMenuItems.desktopMenu().within(() => { topMenuItems - .multiproxySelectorDesktop() - .wait(1000) + .multiproxySelectorInputDesktop() + .should('not.have.value', '') .click() - .type(`${polkadotSelectedMultiproxy.slice(0, 6)}{downArrow}{enter}`) - ) + .type(`${polkadotSelectedMultiproxy.slice(0, 6)}{downArrow}{enter}`, { + delay: 100, + timeout: 6000 + }) + }) // verify that it's displayed multisigPage.accountHeader().within(() => { @@ -47,19 +40,19 @@ describe('default Multisigs', () => { accountDisplay .addressLabel() .invoke('text') - .as('defaultKusamaAddress') .should('not.contain', kusamaSelectedMultiproxy.slice(0, 6)) }) - cy.log('@defaultKusamaAddress', cy.get('@defaultKusamaAddress')) - // select another one topMenuItems.desktopMenu().within(() => topMenuItems - .multiproxySelectorDesktop() - .wait(1000) + .multiproxySelectorInputDesktop() + .should('not.have.value', '') .click() - .type(`${kusamaSelectedMultiproxy.slice(0, 6)}{downArrow}{enter}`) + .type(`${kusamaSelectedMultiproxy.slice(0, 6)}{downArrow}{enter}`, { + delay: 100, + timeout: 6000 + }) ) // verify that it's displayed diff --git a/packages/ui/cypress/tests/multisig-creation.cy.ts b/packages/ui/cypress/tests/multisig-creation.cy.ts index 720a5822..40e65710 100644 --- a/packages/ui/cypress/tests/multisig-creation.cy.ts +++ b/packages/ui/cypress/tests/multisig-creation.cy.ts @@ -117,17 +117,14 @@ describe('Multisig creation', () => { verifySignatories() - // this is commented because chopsticks doesnot support archive_unstable_hashByHeight - // see https://github.com/AcalaNetwork/chopsticks/issues/852 - // there should be a pending pure proxy creation - // multisigPage - // .transactionList() - // .should('be.visible') - // .within(() => { - // multisigPage.pendingTransactionItem().should('have.length', 1) - // multisigPage.pendingTransactionCallName().should('contain.text', 'proxy.createPure') - // }) + multisigPage + .transactionList() + .should('be.visible') + .within(() => { + multisigPage.pendingTransactionItem().should('have.length', 1) + multisigPage.pendingTransactionCallName().should('contain.text', 'Proxy.create_pure') + }) }) it('Create a multisig without a pure proxy', () => { @@ -174,15 +171,14 @@ describe('Multisig creation', () => { verifySignatories() - // this is commented because chopsticks doesnot support archive_unstable_hashByHeight - // see https://github.com/AcalaNetwork/chopsticks/issues/852 - // multisigPage - // .transactionList() - // .should('be.visible') - // .within(() => { - // multisigPage.pendingTransactionItem().should('have.length', 1) - // multisigPage.pendingTransactionCallName().should('contain.text', 'remark:') - // }) + // there should be a pending remark + multisigPage + .transactionList() + .should('be.visible') + .within(() => { + multisigPage.pendingTransactionItem().should('have.length', 1) + multisigPage.pendingTransactionCallName().should('contain.text', 'System.remark') + }) }) }) diff --git a/packages/ui/cypress/tests/watched-accounts.cy.ts b/packages/ui/cypress/tests/watched-accounts.cy.ts index a100cf7f..1d12e52b 100644 --- a/packages/ui/cypress/tests/watched-accounts.cy.ts +++ b/packages/ui/cypress/tests/watched-accounts.cy.ts @@ -11,9 +11,10 @@ import { multisigPage } from '../support/page-objects/multisigPage' import { editNamesModal } from '../support/page-objects/modals/editNamesModal' import { testAccounts } from '../fixtures/testAccounts' import { knownMultisigs } from '../fixtures/knownMultisigs' +import { getShortAddress } from '../utils/getShortAddress' const addWatchAccount = (address: string, name?: string) => { - settingsPage.accountAddressInput().type(`${address}{enter}`, { delay: 20 }) + settingsPage.accountAddressInput().type(`${address}{enter}`, { delay: 20, timeout: 6000 }) if (name) { settingsPage.accountNameInput().type(name) @@ -304,7 +305,7 @@ describe('Watched Accounts', () => { it('can see all multisigs that a watched signatory is a member of', () => { const { publicKey: signatoryPublicKey } = testAccounts['Multisig Member Account 1'] - const expectedAddresses = [ + const expectedMultiproxies = [ { address: knownMultisigs['test-simple-multisig-1'].address, expectedBadge: 'multi' @@ -323,24 +324,33 @@ describe('Watched Accounts', () => { // ensure all multisigs are displayed in the multiproxy selector topMenuItems .multiproxySelectorOptionDesktop() - .should('have.length', expectedAddresses.length) - .each(($el, index) => { + .should('have.length', expectedMultiproxies.length) + .each(($el) => { cy.wrap($el).within(() => { accountDisplay .addressLabel() - .should('contain.text', expectedAddresses[index].address.slice(0, 6)) - accountDisplay.watchedIcon().should('be.visible') - if (expectedAddresses[index].expectedBadge === 'pure') { - accountDisplay.pureBadge().should('be.visible') - } else { - accountDisplay.multisigBadge().should('be.visible') - } + .invoke('text') + .then((address) => { + const account = expectedMultiproxies.find((a) => { + return getShortAddress(a.address) === (address as unknown as string) + }) + cy.wrap(account).should('not.be.undefined') + accountDisplay.watchedIcon().should('be.visible') + if (account?.expectedBadge === 'pure') { + accountDisplay.pureBadge().should('be.visible') + } else { + accountDisplay.multisigBadge().should('be.visible') + } + }) }) }) // ensure each multisig that the signatory is a member of can be viewed - expectedAddresses.forEach(({ address }, index) => { - topMenuItems.multiproxySelectorDesktop().click() - topMenuItems.multiproxySelectorOptionDesktop().eq(index).click() + expectedMultiproxies.forEach(({ address }) => { + topMenuItems + .multiproxySelectorDesktop() + .click() + .type(`${address.slice(0, 6)}{downArrow}{enter}`) + multisigPage .accountHeader() .should('be.visible') diff --git a/packages/ui/cypress/utils/getShortAddress.ts b/packages/ui/cypress/utils/getShortAddress.ts new file mode 100644 index 00000000..a9dce5db --- /dev/null +++ b/packages/ui/cypress/utils/getShortAddress.ts @@ -0,0 +1 @@ +export const getShortAddress = (address: string) => `${address.slice(0, 6)}..${address.slice(-6)}` diff --git a/packages/ui/src/components/Transactions/TransactionList.tsx b/packages/ui/src/components/Transactions/TransactionList.tsx index 3f94e839..89944406 100644 --- a/packages/ui/src/components/Transactions/TransactionList.tsx +++ b/packages/ui/src/components/Transactions/TransactionList.tsx @@ -29,7 +29,7 @@ const TransactionList = ({ className }: Props) => {

Transactions

{isLoadingPendingTxs && ( - + )} diff --git a/packages/ui/src/hooks/useCallInfoFromCallData.tsx b/packages/ui/src/hooks/useCallInfoFromCallData.tsx index 441278b1..b406ec6a 100644 --- a/packages/ui/src/hooks/useCallInfoFromCallData.tsx +++ b/packages/ui/src/hooks/useCallInfoFromCallData.tsx @@ -34,7 +34,7 @@ export const useCallInfoFromCallData = (callData?: HexString) => { decodedCall: tx?.decodedCall, call: tx, hash: hashFromTx(callData), - weight: { proof_size: weight.proof_size, ref_time: weight.ref_time }, + weight, section: tx?.decodedCall.type, method: tx?.decodedCall.value.type }) diff --git a/packages/ui/src/hooks/usePendingTx.tsx b/packages/ui/src/hooks/usePendingTx.tsx index 72d48fb8..d366829a 100644 --- a/packages/ui/src/hooks/usePendingTx.tsx +++ b/packages/ui/src/hooks/usePendingTx.tsx @@ -38,7 +38,7 @@ type AggGroupedByDate = { [index: string]: CallDataInfoFromChain[] } const opaqueMetadata = Tuple(compact, Bin(Infinity)).dec const getExtDecoderAt = async (api: ApiType, client: PolkadotClient, blockHash?: string) => { - const rawMetadata = await (blockHash + const rawMetadata = await (blockHash && !import.meta.env.DEV ? client ._request<{ result: HexString @@ -108,9 +108,10 @@ const getCallDataFromChainPromise = ( ) => pendingTxData.map(async (pendingTx) => { const blockNumber = pendingTx.info.when.height - const blockHash = ( - await client._request('archive_unstable_hashByHeight', [blockNumber]) - )?.[0] as HexString | undefined + const blockHashes = await client._request('archive_unstable_hashByHeight', [blockNumber]) + const blockHash = (Array.isArray(blockHashes) ? blockHashes?.[0] : blockHashes) as + | HexString + | undefined if (!blockHash) { console.log('no hash found for height', blockNumber) diff --git a/squid/.env.example b/squid/.env.example index 73b3e62c..580d2fc3 100644 --- a/squid/.env.example +++ b/squid/.env.example @@ -2,44 +2,10 @@ DB_PORT=5432 GQL_PORT=4350 SQD_DEBUG=sqd:processor:mapping -#rococo -# BLOCK_START=3510000 -# PREFIX=42 -# RPC_WS="wss://rococo-rpc.polkadot.io" -# CHAIN_ID='rococo' -#kusama -# BLOCK_START=15000000 -# PREFIX=2 -# RPC_WS="wss://rpc.ibp.network/kusama" -# CHAIN_ID='kusama' - -#polkadot -# BLOCK_START=12000000 -# PREFIX=0 -# RPC_WS="wss://rpc.ibp.network/polkadot" -# CHAIN_ID='polkadot' - -#rhala -# BLOCK_START=0 -# PREFIX=30 -# RPC_WS="wss://rhala-node.phala.network/w -# CHAIN_ID='rhala' - -#phala -# BLOCK_START=2400000 -# PREFIX=30 -# RPC_WS="wss://priv-api.phala.network/phala/ws" -# CHAIN_ID='phala' - -#khala -# BLOCK_START=0 -# PREFIX=30 -# RPC_WS="wss://khala-api.phala.network/ws" -# CHAIN_ID='khala' - -# hydra rococo -RPC_WS="wss://hydradx-rococo-rpc.play.hydration.cloud" -CHAIN_ID='hydra-rococo' -BLOCK_START=1560491 -PREFIX=30 \ No newline at end of file +#paseo +BLOCK_START=0 +PREFIX=0 +RPC_WS="wss://rpc.ibp.network/paseo" +CHAIN_ID='paseo' +GATEWAY_URL="https://v2.archive.subsquid.io/network/paseo" \ No newline at end of file