Skip to content

Commit

Permalink
Support Joystream (#437)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tbaut authored Nov 22, 2023
1 parent 9b2b9bb commit 4a63d90
Show file tree
Hide file tree
Showing 13 changed files with 117 additions and 44 deletions.
10 changes: 5 additions & 5 deletions packages/ui/cypress/fixtures/landingData.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export const baseUrl = 'http://localhost:3333'
export const networkParams = 'network=rococo'
export const landingPageUrl = `${baseUrl}?${networkParams}`
export const settingsPageUrl = `${baseUrl}/settings?${networkParams}`
export const defaultNetwork = 'rococo'
const WATCH_ACCOUNT_ANCHOR = 'watched-accounts'
export const landingPageNetwork = (networkName: string) => `${baseUrl}?network=${networkName}`
export const landingPageUrl = landingPageNetwork('rococo')
export const settingsPageUrl = `${baseUrl}/settings?network=${defaultNetwork}`
export const settingsPageWatchAccountUrl = `${settingsPageUrl}#${WATCH_ACCOUNT_ANCHOR}`
export const landingPageAddressUrl = (address: string) =>
`${baseUrl}?${networkParams}&address=${address}`
export const landingPageAddressUrl = (address: string) => `${landingPageUrl}&address=${address}`
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,13 @@ export const setIdentitySignatories = [
name: 'no identity Signatory 2',
type: 'sr25519',
mnemonic: 'will hero hire retreat capital dash ordinary trap loop dragon dog parade'
},
{
// this is a joystream account, with multisig, but not one of our accounts, we don't actually know the mnemonic
// so we can't sign anything with it
address: 'j4WauZ6dVC6G8C1gDYKMbTZVVJoMAud6znTbdpNhKDpcZD4yX',
name: 'Real account with Multisig',
type: 'sr25519',
mnemonic: ''
}
] as InjectedAccountWitMnemonic[]
3 changes: 2 additions & 1 deletion packages/ui/cypress/support/Extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export class Extension {
await cryptoWaitReady()
this.keyring = new Keyring({ type: 'sr25519' })
accounts.forEach(({ mnemonic }) => {
this.keyring?.addFromMnemonic(mnemonic)
// we only add to the keyring the accounts with a known mnemonic
!!mnemonic && this.keyring?.addFromMnemonic(mnemonic)
})
}

Expand Down
19 changes: 16 additions & 3 deletions packages/ui/cypress/tests/setIdentity.cy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { InjectedAccountWitMnemonic } from '../fixtures/testAccounts'
import { landingPageUrl } from '../fixtures/landingData'
import { landingPageAddressUrl, landingPageNetwork, landingPageUrl } from '../fixtures/landingData'
import { setIdentityMultisigs } from '../fixtures/setIdentity/setIdentityMultisigs'
import { setIdentitySignatories } from '../fixtures/setIdentity/setIdentitySignatories'
import { multisigPage } from '../support/page-objects/multisigPage'
Expand All @@ -8,14 +8,27 @@ import { topMenuItems } from '../support/page-objects/topMenuItems'
import { clickOnConnect } from '../utils/clickOnConnect'
import { waitForTxRequest } from '../utils/waitForTxRequests'

const initAndConnect = (account: InjectedAccountWitMnemonic) => {
cy.visit(landingPageUrl)
const initAndConnect = (account: InjectedAccountWitMnemonic, landingPage = landingPageUrl) => {
cy.visit(landingPage)
cy.initExtension([account])
clickOnConnect()
cy.connectAccounts([account.address])
}

describe('Set an identity', () => {
it('Does not have the identity option if the pallet is not present', () => {
const multisigSignatoryWithoutIdentity = setIdentitySignatories[3]
initAndConnect(multisigSignatoryWithoutIdentity, landingPageNetwork('joystream'))
multisigPage.optionsMenuButton().click()
multisigPage.setIdentityMenuOption().should('not.exist')

//click outside to close the menu
cy.get('body').click(0, 0)
multisigPage.newTransactionButton().click()
sendTxModal.selectEasySetup().should('contain', 'Send tokens').click()
sendTxModal.selectionEasySetupSetIdentity().should('not.exist')
})

it('Can set an identity from the options menu', () => {
const multisigSignatoryWithoutIdentity = setIdentitySignatories[1]
initAndConnect(multisigSignatoryWithoutIdentity)
Expand Down
17 changes: 11 additions & 6 deletions packages/ui/src/components/modals/ChangeMultisig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { Button } from '../library'
import { ModalCloseButton } from '../library/ModalCloseButton'
import { useGetSortAddress } from '../../hooks/useGetSortAddress'
import { useGetMultisigAddress } from '../../contexts/useGetMultisigAddress'
import { getAsMultiTx } from '../../utils/getAsMultiTx'

interface Props {
onClose: () => void
Expand Down Expand Up @@ -121,9 +122,11 @@ const ChangeMultisig = ({ onClose, className }: Props) => {
const addProxyTx = api.tx.proxy.addProxy(newMultisigAddress, 'Any', 0)
const proxyTx = api.tx.proxy.proxy(selectedMultiProxy?.proxy, null, addProxyTx)
// call with the old multisig
return api.tx.multisig.asMulti(oldThreshold, otherOldSignatories, null, proxyTx, {
refTime: 0,
proofSize: 0
return getAsMultiTx({
api,
threshold: oldThreshold,
otherSignatories: otherOldSignatories,
tx: proxyTx
})
}, [
api,
Expand Down Expand Up @@ -168,9 +171,11 @@ const ChangeMultisig = ({ onClose, className }: Props) => {
)
const removeProxyTx = api.tx.proxy.removeProxy(selectedMultisig?.address, 'Any', 0)
const proxyTx = api.tx.proxy.proxy(selectedMultiProxy?.proxy, null, removeProxyTx)
return api.tx.multisig.asMulti(newThreshold, otherNewSignatories, null, proxyTx, {
refTime: 0,
proofSize: 0
return getAsMultiTx({
api,
otherSignatories: otherNewSignatories,
threshold: newThreshold,
tx: proxyTx
})
}, [
api,
Expand Down
14 changes: 8 additions & 6 deletions packages/ui/src/components/modals/ProposalSigning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ModalCloseButton } from '../library/ModalCloseButton'
import { useGetSortAddress } from '../../hooks/useGetSortAddress'
import { useCheckBalance } from '../../hooks/useCheckBalance'
import BN from 'bn.js'
import { getAsMultiTx } from '../../utils/getAsMultiTx'
import { CallDataInfoFromChain } from '../../hooks/usePendingTx'

export interface SigningModalProps {
Expand Down Expand Up @@ -192,13 +193,14 @@ const ProposalSigning = ({

// If we can submit the proposal and have the call data
} else if (shouldSubmit && callInfo?.call && callInfo?.weight) {
tx = api.tx.multisig.asMulti(
tx = getAsMultiTx({
api,
threshold,
otherSigners,
proposalData.info.when,
proposalData.callData || addedCallData,
callInfo.weight
)
otherSignatories: otherSigners,
weight: callInfo.weight,
when: proposalData.info.when,
tx: proposalData.callData || addedCallData
})

// if we can't submit yet (more signatures required), all we need is the call hash
} else if (!shouldSubmit && proposalData.hash) {
Expand Down
27 changes: 17 additions & 10 deletions packages/ui/src/components/modals/Send.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { formatBnBalance } from '../../utils/formatBnBalance'
import { useGetMultisigTx } from '../../hooks/useGetMultisigTx'
import SetIdentity from '../EasySetup/SetIdentity'
import { getErrorMessageReservedFunds } from '../../utils/getErrorMessageReservedFunds'
import { useHasIdentityPallet } from '../../hooks/useHasIdentityPallet'

export enum EasyTransferTitle {
SendTokens = 'Send tokens',
Expand Down Expand Up @@ -62,6 +63,7 @@ const Send = ({ onClose, className, onSuccess, onFinalized, preselected }: Props
(a) => !!a.address
) as AccountBaseInfo[]
}, [getMultisigAsAccountBaseInfo, selectedMultiProxy])
const hasIdentityPallet = useHasIdentityPallet()
const [selectedOrigin, setSelectedOrigin] = useState<AccountBaseInfo>(possibleOrigin[0])
const isProxySelected = useMemo(() => selectedOrigin.meta?.isProxy, [selectedOrigin])
const [selectedMultisig, setSelectedMultisig] = useState(selectedMultiProxy?.multisigs[0])
Expand Down Expand Up @@ -132,22 +134,15 @@ const Send = ({ onClose, className, onSuccess, onFinalized, preselected }: Props
[getMultisigByAddress, selectedMultiProxy]
)

const easySetupOptions: Record<EasyTransferTitle, ReactNode> = useMemo(() => {
return {
const easySetupOptions: Partial<Record<EasyTransferTitle, ReactNode>> = useMemo(() => {
const res = {
[EasyTransferTitle.SendTokens]: (
<BalancesTransfer
from={selectedOrigin.address}
onSetExtrinsic={setExtrinsicToCall}
onSetErrorMessage={setEasyOptionErrorMessage}
/>
),
[EasyTransferTitle.SetIdentity]: (
<SetIdentity
from={selectedOrigin.address}
onSetExtrinsic={setExtrinsicToCall}
onSetErrorMessage={setEasyOptionErrorMessage}
/>
),
[EasyTransferTitle.ManualExtrinsic]: (
<ManualExtrinsic
onSetExtrinsic={setExtrinsicToCall}
Expand All @@ -163,8 +158,20 @@ const Send = ({ onClose, className, onSuccess, onFinalized, preselected }: Props
isProxySelected={!!isProxySelected}
/>
)
} as Partial<Record<EasyTransferTitle, ReactNode>>

if (hasIdentityPallet) {
res[EasyTransferTitle.SetIdentity] = (
<SetIdentity
from={selectedOrigin.address}
onSetExtrinsic={setExtrinsicToCall}
onSetErrorMessage={setEasyOptionErrorMessage}
/>
)
}
}, [selectedOrigin, easyOptionErrorMessage, isProxySelected])

return res
}, [selectedOrigin, easyOptionErrorMessage, isProxySelected, hasIdentityPallet])

const signCallback = useSigningCallback({
onSuccess,
Expand Down
6 changes: 2 additions & 4 deletions packages/ui/src/hooks/useGetMultisigTx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { MultiProxy } from '../contexts/MultiProxyContext'
import { SubmittableExtrinsic } from '@polkadot/api/types'
import { ISubmittableResult } from '@polkadot/types/types'
import { useGetSortAddress } from './useGetSortAddress'
import { getAsMultiTx } from '../utils/getAsMultiTx'

interface Params {
selectedMultisig?: MultiProxy['multisigs'][0]
Expand Down Expand Up @@ -62,10 +63,7 @@ export const useGetMultisigTx = ({
tx = extrinsicToCall
}

return api.tx.multisig.asMulti(threshold, otherSigners, null, tx, {
refTime: 0,
proofSize: 0
})
return getAsMultiTx({ api, threshold, otherSignatories: otherSigners, tx })
} catch (e) {
console.error('Error in multisigTx')
console.error(e)
Expand Down
9 changes: 9 additions & 0 deletions packages/ui/src/hooks/useHasIdentityPallet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useMemo } from 'react'
import { useApi } from '../contexts/ApiContext'

export const useHasIdentityPallet = () => {
const { api } = useApi()
const hasIdentityPallet = useMemo(() => !!api && !!api.tx?.identity?.setIdentity, [api])

return hasIdentityPallet
}
12 changes: 4 additions & 8 deletions packages/ui/src/pages/Creation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import WithProxySelection from './WithProxySelection'
import { useGetSortAddress } from '../../hooks/useGetSortAddress'
import { useGetMultisigAddress } from '../../contexts/useGetMultisigAddress'
import { isEthereumAddress } from '@polkadot/util-crypto'
import { getAsMultiTx } from '../../utils/getAsMultiTx'

interface Props {
className?: string
Expand Down Expand Up @@ -100,10 +101,7 @@ const MultisigCreation = ({ className }: Props) => {
signatories.filter((sig) => sig !== selectedAccount.address)
)
const remarkTx = api.tx.system.remark(`Multix creation ${multiAddress}`)
return api.tx.multisig.asMulti(threshold, otherSignatories, null, remarkTx, {
refTime: 0,
proofSize: 0
})
return getAsMultiTx({ api, threshold, otherSignatories, tx: remarkTx })
}, [api, getSortAddress, multiAddress, selectedAccount, signatories, threshold, withProxy])

const batchCall = useMemo(() => {
Expand Down Expand Up @@ -139,10 +137,8 @@ const MultisigCreation = ({ className }: Props) => {
signatories.filter((sig) => sig !== selectedAccount.address)
)
const proxyTx = api.tx.proxy.createPure('Any', 0, 0)
const multiSigProxyCall = api.tx.multisig.asMulti(threshold, otherSignatories, null, proxyTx, {
refTime: 0,
proofSize: 0
})
const multiSigProxyCall = getAsMultiTx({ api, threshold, otherSignatories, tx: proxyTx })

// Some funds are needed on the multisig for the pure proxy creation
const transferTx = api.tx.balances.transferKeepAlive(
multiAddress,
Expand Down
3 changes: 3 additions & 0 deletions packages/ui/src/pages/Home/MultisigActionMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from 'react-icons/hi2'
import { useGetSubscanLinks } from '../../hooks/useSubscanLink'
import { EasyTransferTitle } from '../../components/modals/Send'
import { useHasIdentityPallet } from '../../hooks/useHasIdentityPallet'

interface MultisigActionMenuProps {
withNewTransactionButton?: boolean
Expand All @@ -24,6 +25,7 @@ const MultisigActionMenu = ({
const { selectedHasProxy, selectedIsWatched, selectedMultiProxy } = useMultiProxy()
const { setIsEditModalOpen, setIsChangeMultiModalOpen, onOpenSendModal } = useModals()
const { getSubscanAccountLink } = useGetSubscanLinks()
const hasIdentityPallet = useHasIdentityPallet()

const options: MenuOption[] = useMemo(() => {
const opts = [
Expand All @@ -45,6 +47,7 @@ const MultisigActionMenu = ({
]

!selectedIsWatched &&
hasIdentityPallet &&
opts.push({
text: 'Set identity',
icon: <IdentityIcon size={20} />,
Expand Down
31 changes: 31 additions & 0 deletions packages/ui/src/utils/getAsMultiTx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ApiPromise } from '@polkadot/api'
import { SubmittableExtrinsic } from '@polkadot/api/types'
import { ISubmittableResult } from '@polkadot/types/types'
import { Weight } from '@polkadot/types/interfaces'
import { HexString, MultisigStorageInfo } from '../types'

interface Params {
api: ApiPromise
threshold: number
otherSignatories: string[]
tx?: SubmittableExtrinsic<'promise', ISubmittableResult> | HexString
weight?: Weight
when?: MultisigStorageInfo['when']
}

const LEGACY_ASMULTI_PARAM_LENGTH = 6

export const getAsMultiTx = ({ api, threshold, otherSignatories, tx, weight, when }: Params) => {
return api.tx.multisig.asMulti.meta.args.length === LEGACY_ASMULTI_PARAM_LENGTH
? api.tx.multisig.asMulti(threshold, otherSignatories, when || null, tx, false, weight || 0)
: api.tx.multisig.asMulti(
threshold,
otherSignatories,
when || null,
tx,
weight || {
refTime: 0,
proofSize: 0
}
)
}
2 changes: 1 addition & 1 deletion packages/ui/src/utils/isTypeAccount.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export const isTypeAccount = (typeName?: string) =>
!!typeName && ['AccountIdLookupOf'].includes(typeName)
!!typeName && ['AccountIdLookupOf', 'LookupSource'].includes(typeName)

0 comments on commit 4a63d90

Please sign in to comment.