Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix 56 spawn 3rd party accounts using MultiSig accounts #57

Merged
merged 2 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@noble/ed25519": "^2.1.0",
"@scure/bip39": "^1.2.2",
"@spacemesh/ed25519-bip32": "^0.2.1",
"@spacemesh/sm-codec": "^0.7.2",
"@spacemesh/sm-codec": "^0.8.0",
"@tabler/icons-react": "^3.1.0",
"@tanstack/react-virtual": "^3.3.0",
"@uidotdev/usehooks": "^2.4.1",
Expand Down
46 changes: 30 additions & 16 deletions src/components/sendTx/ConfirmationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,14 @@ export type ConfirmationData = {

type ConfirmationModalProps = ConfirmationData & {
onClose: () => void;
onSubmit: (signWith: HexString, externalSignature?: HexString) => void;
onExport: (signWith: HexString | null, externalSignature?: HexString) => void;
onSubmit: (
signWith: HexString,
externalSignature?: HexString
) => Promise<boolean>;
onExport: (
signWith: HexString | null,
externalSignature?: HexString
) => Promise<boolean>;
isOpen: boolean;
estimatedGas: bigint | null;
isLedgerRejected?: boolean;
Expand Down Expand Up @@ -241,11 +247,13 @@ function ConfirmationModal({
// If SingleSig with attached signature,
// then put it into signature input
setValue('signWith', EXTERNAL);
return;
} else {
// Otherwise select eligible keys as usually
setValue('signWith', eligibleKeys[0]?.publicKey || EXTERNAL);
}
// Otherwise select eligible keys as usually
setValue('signWith', eligibleKeys[0]?.publicKey || EXTERNAL);
}, [eligibleKeys, hasSingleSig, setValue, signatures]);

return () => reset();
}, [eligibleKeys, hasSingleSig, setValue, signatures, reset]);

useEffect(() => {
if (signWith !== EXTERNAL) {
Expand All @@ -257,20 +265,26 @@ function ConfirmationModal({
}
}, [unregister, signWith, setValue, hasSingleSig, signatures]);

const submit = handleSubmit((data) => {
reset();
onSubmit(data.signWith, data.externalSignature);
const submit = handleSubmit(async (data) => {
const res = await onSubmit(data.signWith, data.externalSignature);
if (res) {
reset();
}
});

const exportSigned = handleSubmit((data) => {
reset();
onExport(data.signWith, data.externalSignature);
const exportSigned = handleSubmit(async (data) => {
const res = await onExport(data.signWith, data.externalSignature);
if (res) {
reset();
}
});

const exportUnsigned = () => {
reset();
onExport(null);
};
const exportUnsigned = handleSubmit(async () => {
const res = await onExport(null);
if (res) {
reset();
}
});

const renderSignatures = () => {
if (!signatures || signatures.length === 0) return null;
Expand Down
135 changes: 68 additions & 67 deletions src/components/sendTx/SendTxModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,15 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
currerntAccount.spawnArguments
);
const pinripalBytes = getWords(principal);
const commonProps = {
eligibleKeys: extractEligibleKeys(
currerntAccount,
accountsList,
wallet?.keychain ?? []
),
isMultiSig: isAnyMultiSig(currerntAccount),
required: extractRequiredSignatures(currerntAccount),
};

switch (data.payload.methodSelector) {
case StdMethods.Spawn: {
Expand Down Expand Up @@ -313,16 +322,11 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
principal,
form: data,
encoded,
eligibleKeys: extractEligibleKeys(
currerntAccount,
accountsList,
wallet?.keychain ?? []
),
description: `Spawn ${getTemplateNameByKey(
data.templateAddress
)} account using ${spawnAccount}`,
Arguments,
isMultiSig: isAnyMultiSig(currerntAccount),
...commonProps,
});
updateEstimatedGas(encoded, 0);
}
Expand Down Expand Up @@ -352,17 +356,11 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
principal,
form: data,
encoded,
eligibleKeys: extractEligibleKeys(
currerntAccount,
accountsList,
wallet?.keychain ?? []
),
description: `Spawn ${getTemplateNameByKey(
data.templateAddress
)} account: ${currerntAccount.displayName}`,
Arguments,
isMultiSig: isAnyMultiSig(currerntAccount),
required: args.Required,
...commonProps,
});
updateEstimatedGas(encoded, args.Required);
}
Expand All @@ -388,16 +386,11 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
principal,
form: data,
encoded,
eligibleKeys: extractEligibleKeys(
currerntAccount,
accountsList,
wallet?.keychain ?? []
),
description: `Spawn ${getTemplateNameByKey(
data.templateAddress
)} account: ${currerntAccount.displayName}`,
Arguments,
isMultiSig: isAnyMultiSig(currerntAccount),
...commonProps,
});
updateEstimatedGas(encoded, 0);
}
Expand All @@ -423,16 +416,11 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
principal,
form: data,
encoded,
eligibleKeys: extractEligibleKeys(
currerntAccount,
accountsList,
wallet?.keychain ?? []
),
description: `Send ${formatSmidge(args.Amount)} to ${
args.Destination
}`,
Arguments,
isMultiSig: isAnyMultiSig(currerntAccount),
...commonProps,
});
updateEstimatedGas(encoded, 0);
}
Expand All @@ -454,24 +442,17 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
}
);

const required = extractRequiredSignatures(currerntAccount);
setTxData({
principal,
form: data,
encoded,
eligibleKeys: extractEligibleKeys(
currerntAccount,
accountsList,
wallet?.keychain ?? []
),
description: `Send ${formatSmidge(args.Amount)} to ${
args.Destination
}`,
Arguments,
isMultiSig: isAnyMultiSig(currerntAccount),
required,
...commonProps,
});
updateEstimatedGas(encoded, required);
updateEstimatedGas(encoded, commonProps.required);
}

if (data.templateAddress === StdPublicKeys.Vault) {
Expand All @@ -493,14 +474,9 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
principal,
form: data,
encoded,
eligibleKeys: extractEligibleKeys(
currerntAccount,
accountsList,
wallet?.keychain ?? []
),
description: `Spawn ${formatSmidge(args.Amount)}`,
Arguments,
isMultiSig: isAnyMultiSig(currerntAccount),
...commonProps,
});
updateEstimatedGas(encoded, 0);
}
Expand All @@ -523,24 +499,17 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
}
);

const required = extractRequiredSignatures(currerntAccount);
setTxData({
principal,
form: data,
encoded,
eligibleKeys: extractEligibleKeys(
currerntAccount,
accountsList,
wallet?.keychain ?? []
),
description: `Drain ${formatSmidge(args.Amount)} from ${
args.Vault
} to ${args.Destination}`,
Arguments,
isMultiSig: isAnyMultiSig(currerntAccount),
required,
...commonProps,
});
updateEstimatedGas(encoded, required);
updateEstimatedGas(encoded, commonProps.required);
}
break;
}
Expand Down Expand Up @@ -634,6 +603,23 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
return SingleSigTemplate.methods[0].sign(txData.encoded, signature);
};

const signWithExistingSignatures = (
tx: Uint8Array,
signatures: SingleSig | MultiSigPart[]
) => {
if (!currerntAccount) {
throw new Error('Cannot sign transaction by unknown account');
}

if (currerntAccount.templateAddress === StdPublicKeys.SingleSig) {
return SingleSigTemplate.methods[0].sign(tx, signatures as SingleSig);
}
return MultiSigTemplate.methods[0].sign(
tx,
(signatures as MultiSigPart[]).sort((a, b) => Number(a.Ref - b.Ref))
);
};

const signAndPublish = async (
signWith: HexString,
externalSignature?: HexString
Expand All @@ -647,7 +633,13 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
'Cannot sign and publish transaction: No Genesis ID, please connect to the network first'
);
}
const signedTx = await sign(signWith, externalSignature);
const shouldSign =
!txData.isMultiSig || txData.signatures?.length !== txData.required;

const signedTx = shouldSign
? await sign(signWith, externalSignature)
: signWithExistingSignatures(txData.encoded, txData.signatures ?? []);

if (signedTx) {
try {
const txId = await publishTx(signedTx);
Expand Down Expand Up @@ -689,7 +681,9 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
setTxData(null);
confirmationModal.onClose();
}
return true;
}
return false;
};

const exportTx = async (
Expand Down Expand Up @@ -717,7 +711,9 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
`tx-${signWith ? 'signed' : 'unsigned'}-${hex.slice(-6)}.hex`,
'text/plain'
);
return true;
}
return false;
};

const importTx = async (txs: HexString[]) => {
Expand Down Expand Up @@ -748,9 +744,17 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
}

const method = Number(headers[0].MethodSelector) as MethodSelectors;
const tpl = getTemplateMethod(currerntAccount.templateAddress, method);
const parsed = txs.map((x) => tpl.decode(fromHexString(x)));
const rawTx = parsed[0];
const templateAddress =
method === MethodSelectors.Spawn
? toHexString(
Codecs.SpawnTxHeader.dec(txs[0] as HexString).TemplateAddress
)
: currerntAccount.templateAddress;
const tpl = getTemplateMethod(templateAddress, method);
const parsed = txs.map((x) =>
tpl.decodeWithoutSignatures(fromHexString(x))
);
const rawTx = parsed[0]?.tx;

if (!rawTx) {
throw new Error('Cannot parse imported transaction');
Expand All @@ -763,7 +767,7 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
// TODO: TS cannot infer the type properly
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
x.Payload
x.tx.Payload
)
);
if (
Expand All @@ -783,8 +787,6 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
);

const convertToFormValues = (tx: typeof rawTx): FormValues => {
const templateAddress =
currerntAccount.templateAddress as StdTemplateKeys;
switch (method) {
case MethodSelectors.Spawn: {
if (templateAddress === StdPublicKeys.SingleSig) {
Expand Down Expand Up @@ -885,20 +887,18 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {

const extractSignatures = (): undefined | SingleSig | MultiSigPart[] => {
// Extract SingleSig
if (
currerntAccount.templateAddress === StdPublicKeys.SingleSig ||
currerntAccount.templateAddress === StdPublicKeys.Vault
) {
if (currerntAccount.templateAddress === StdPublicKeys.SingleSig) {
return parsed.reduce(
(acc, next) => next.Signature || acc,
(acc, next) => (next.rest ? Codecs.SingleSig.dec(next.rest) : acc),
undefined as undefined | SingleSig
);
}
// Extract MultiSig parts
const sigs = new Set<MultiSigPart>();
parsed.forEach((next) => {
if (next.Signatures) {
next.Signatures.forEach((nextSig) => {
if (next.rest) {
const parsedSigs = Codecs.MultiSig.dec(next.rest);
parsedSigs.forEach((nextSig) => {
sigs.add(nextSig);
});
}
Expand All @@ -909,10 +909,7 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
const extractRefs = (
signatures: undefined | SingleSig | MultiSigPart[]
) => {
if (
currerntAccount.templateAddress === StdPublicKeys.SingleSig ||
currerntAccount.templateAddress === StdPublicKeys.Vault
) {
if (currerntAccount.templateAddress === StdPublicKeys.SingleSig) {
return undefined;
}
return (signatures as MultiSigPart[]).map(({ Ref }) => Number(Ref));
Expand Down Expand Up @@ -970,6 +967,8 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
};

const showImportError = (err: Error) => {
// eslint-disable-next-line no-console
console.error('Cannot import transaction:', err);
setImportErrors(err.message);
};

Expand Down Expand Up @@ -1271,6 +1270,8 @@ function SendTxModal({ isOpen, onClose }: SendTxModalProps): JSX.Element {
onClose={onConfirmationModalClose}
onSubmit={signAndPublish}
onExport={exportTx}
isMultiSig={txData.isMultiSig ?? false}
required={txData.required}
isLedgerRejected={isLedgerRejected}
/>
)}
Expand Down
Loading