Skip to content

Multisigs#3720

Merged
vkulinich-cl merged 3 commits intomasterfrom
feat/multisig-wallet
Apr 15, 2026
Merged

Multisigs#3720
vkulinich-cl merged 3 commits intomasterfrom
feat/multisig-wallet

Conversation

@jvonasek
Copy link
Copy Markdown
Collaborator

@jvonasek jvonasek commented Apr 8, 2026

implements #3689

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 8, 2026

Deploy Preview for testnet-hydra-app ready!

Name Link
🔨 Latest commit b8592d2
🔍 Latest deploy log https://app.netlify.com/projects/testnet-hydra-app/deploys/69de547df198bc000866259e
😎 Deploy Preview https://deploy-preview-3720--testnet-hydra-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 8, 2026

Deploy Preview for hydration-storybook ready!

Name Link
🔨 Latest commit b8592d2
🔍 Latest deploy log https://app.netlify.com/projects/hydration-storybook/deploys/69de547dc07efe000801ad39
😎 Deploy Preview https://deploy-preview-3720--hydration-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 8, 2026

Deploy Preview for edge-hydra-app ready!

Name Link
🔨 Latest commit b8592d2
🔍 Latest deploy log https://app.netlify.com/projects/edge-hydra-app/deploys/69de547d42636b0008a13412
😎 Deploy Preview https://deploy-preview-3720--edge-hydra-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 0 (no change from production)
Accessibility: 66 (no change from production)
Best Practices: 92 (no change from production)
SEO: 91 (no change from production)
PWA: 80 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@jvonasek jvonasek force-pushed the feat/multisig-wallet branch 4 times, most recently from 56e960f to f88f89b Compare April 8, 2026 23:43
@jvonasek jvonasek linked an issue Apr 9, 2026 that may be closed by this pull request
@jvonasek jvonasek force-pushed the feat/multisig-wallet branch 7 times, most recently from a2a4c83 to b1c3f82 Compare April 10, 2026 22:44
papi.constants.Multisig.DepositBase(),
papi.constants.Multisig.DepositFactor(),
])
const deposit = base + factor * BigInt(numSignatories)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use Big.js for calculations

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

base and factor are native BigInt, using Big.js seems redundant just to convert it back to primitive type.

Comment thread apps/main/src/api/multisig.ts Outdated
const deposit = base + factor * BigInt(numSignatories)
return {
deposit,
depositHuman: scaleHuman(deposit, native.decimals),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont' use scaleHuman anymore

queryFn: async () => {
const [metadata, blockHash] = await Promise.all([
hydration.getMetadata(),
papiClient._request<string>("chain_getBlockHash", [tx.when.height]),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do not use the classic papi call?

Copy link
Copy Markdown
Collaborator Author

@jvonasek jvonasek Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There doesn't seem to be a way to get block hash by block number

])
const decoder = getExtrinsicDecoder(metadata)

const [body, timestamp] = await Promise.all([
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timestamp can be taken from query cache

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is timestamp at specific block hash, not current timestamp

Comment thread apps/main/src/api/multisig.ts Outdated
enabled: isApiLoaded && !!signerAddress,
queryKey: ["multisig", "signerBalance", signerAddress],
queryFn: async () => {
const balanceData = await papi.query.System.Account.getValue(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use sdk balance client instead

@@ -0,0 +1,72 @@
/* import {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be removed?

(state) => state.pendingTransactions,
)

const isMultisig = !!(account as StoredAccount | null)?.isMultisig
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should type assertion be here?

const otherSignatories = config.signers
.filter((s: string) => {
try {
const signerBytes = AccountId().enc(signerAddress)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

signerBytes can be moved out from filter callback

}),
}
},
[account, activeMultisigConfig, papi],
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need the whole account object but only multisigSignerAddress. Can sort and filter be simplified without address encoding?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked, removed the custom sorting function and used helper from Papi, but it still has to be converted to raw Uint8Array

}

export const MultisigStatus: FC<MultisigStatusProps> = ({ approved }) => {
const { t } = useTranslation()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add translation file here

Comment on lines +39 to +44
stringEquals(
safeConvertSS58toPublicKey(account?.multisigSignerAddress ?? ""),
entry.signatory.pubKey,
)
const isConnected =
!!account && account.publicKey === entry.signatory.pubKey
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just compare sigAddress (ss85) with ss58 with ss58 address, no need to convert them to public key, since ss58 is shorter. Also these two variables can be simplified to one

{t("close")}
</Button>
</ModalCloseTrigger>
<Button
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be a link

return keys.reduce<unknown>(
(acc, key) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
acc === null || acc === undefined ? acc : prop(acc as any, key),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this or it was generated by claud?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need this to parse wrapped multisig call, but I moved this to utils package and removed the TS ignore

label={t("transaction.summary.cost.label")}
content={<ReviewTransactionFee />}
/>
{isMultisig && <MultisigDepositRow />}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just use one condition

Copy link
Copy Markdown
Collaborator Author

@jvonasek jvonasek Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has to be rendered separately because of Summary separator logic.

When using React.Fragment, it renders only one separator for both rows

const { isApiLoaded, papi } = useRpcProvider()
const queryClient = useQueryClient()

const multisigs = useMemo(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to memorize it?


return (
<SStepTrigger isInteractive={isDone} {...props}>
<Flex
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create styled component

const isSignerAddress = (address: string) =>
config.signers.some((signer) =>
stringEquals(
safeConvertSS58toPublicKey(signer),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why convert addresses?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public key is unique, ss58 is not, we cannot be sure what ss58 format will be returned by Multix Indexer

const indexedConfigs: MultisigConfig[] =
accounts
.filter((multisig) => isNumber(multisig.threshold))
.filter((multisig) => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be moved to the first filter callback

),
isCustom: false,
}
}) ?? []
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[] can be removed

{
name: "multisig-configs",
version: 2,
migrate: (persistedState: unknown, version: number) => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need I guess, since that's a new store and version can be set to 0 or 1

// explicit account → null transition, not on every unrelated state update
// (e.g. wallet connecting / accounts array loading).
/* const prevAccountRef = useRef(useWeb3Connect.getState().account)
useEffect(() => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be removed?

@jvonasek jvonasek marked this pull request as ready for review April 14, 2026 10:27
@jvonasek jvonasek force-pushed the feat/multisig-wallet branch from b1c3f82 to 43b9742 Compare April 14, 2026 13:06
@jvonasek jvonasek force-pushed the feat/multisig-wallet branch from 43b9742 to 9842133 Compare April 14, 2026 13:18
@vkulinich-cl vkulinich-cl self-requested a review April 15, 2026 09:35
@vkulinich-cl vkulinich-cl merged commit 78f9af5 into master Apr 15, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multisig implementation

2 participants