Skip to content

Commit c1139f3

Browse files
authored
Add multisig to Account Wizard (#527)
1 parent 8f2f0e9 commit c1139f3

File tree

9 files changed

+1395
-21
lines changed

9 files changed

+1395
-21
lines changed

packages/core/solidity/src/account.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ test('account API assert defaults', async t => {
5050
t.is(account.print(account.defaults), account.print());
5151
});
5252

53-
for (const signer of [false, 'ERC7702', 'ECDSA', 'P256', 'RSA'] as const) {
53+
for (const signer of [false, 'ERC7702', 'ECDSA', 'P256', 'RSA', 'Multisig', 'MultisigWeighted'] as const) {
5454
let title = 'Account';
5555
if (signer) {
5656
title += ` with Signer${signer}`;

packages/core/solidity/src/account.test.ts.md

Lines changed: 1279 additions & 0 deletions
Large diffs are not rendered by default.
1.12 KB
Binary file not shown.

packages/core/solidity/src/account.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ function addParents(c: ContractBuilder, opts: AccountOptions): void {
8686
addSignatureValidation(c, opts);
8787
addERC7579Modules(c, opts);
8888
addSigner(c, opts.signer ?? false);
89+
addMultisigFunctions(c, opts);
8990
addBatchedExecution(c, opts);
9091
addERC721Holder(c, opts);
9192
addERC1155Holder(c, opts);
@@ -175,6 +176,23 @@ function addERC7579Modules(c: ContractBuilder, opts: AccountOptions): void {
175176
);
176177
}
177178

179+
function addMultisigFunctions(c: ContractBuilder, opts: AccountOptions): void {
180+
switch (opts.signer) {
181+
case 'MultisigWeighted':
182+
c.addFunctionCode(
183+
`_setSignerWeights(${functions.setSignerWeights.args.map(({ name }) => name).join(', ')});`,
184+
functions.setSignerWeights,
185+
);
186+
// eslint-disable-next-line no-fallthrough
187+
case 'Multisig':
188+
c.addFunctionCode(`_addSigners(${functions.addSigners.args[0]!.name});`, functions.addSigners);
189+
c.addFunctionCode(`_removeSigners(${functions.removeSigners.args[0]!.name});`, functions.removeSigners);
190+
c.addFunctionCode(`_setThreshold(${functions.setThreshold.args[0]!.name});`, functions.setThreshold);
191+
break;
192+
default:
193+
}
194+
}
195+
178196
function addEIP712(c: ContractBuilder, opts: AccountOptions): void {
179197
if (opts.signatureValidation != 'ERC7739') return;
180198
c.addParent(
@@ -198,7 +216,7 @@ function overrideRawSignatureValidation(c: ContractBuilder, opts: AccountOptions
198216
c.setFunctionBody(['// Custom validation logic', 'return false;'], signerFunctions._rawSignatureValidation);
199217
}
200218

201-
// Disambiguate between Signer and AccountERC7579
219+
// Dissambiguate between Signer and AccountERC7579
202220
if (opts.signer && opts.ERC7579Modules) {
203221
c.addImportOnly({
204222
name: 'AbstractSigner',
@@ -247,5 +265,24 @@ const functions = {
247265
returns: ['bool'],
248266
mutability: 'view' as const,
249267
},
268+
addSigners: {
269+
kind: 'public' as const,
270+
args: [{ name: 'signers', type: 'bytes[] memory' }],
271+
},
272+
removeSigners: {
273+
kind: 'public' as const,
274+
args: [{ name: 'signers', type: 'bytes[] memory' }],
275+
},
276+
setThreshold: {
277+
kind: 'public' as const,
278+
args: [{ name: 'threshold', type: 'uint256' }],
279+
},
280+
setSignerWeights: {
281+
kind: 'public' as const,
282+
args: [
283+
{ name: 'signers', type: 'bytes[] memory' },
284+
{ name: 'weights', type: 'uint256[] memory' },
285+
],
286+
},
250287
}),
251288
};

packages/core/solidity/src/generate/account.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const account = {
77
signatureValidation: [false, 'ERC1271', 'ERC7739'] as const,
88
ERC721Holder: [false, true] as const,
99
ERC1155Holder: [false, true] as const,
10-
signer: ['ERC7702', 'ECDSA', 'P256', 'RSA'] as const,
10+
signer: ['ERC7702', 'ECDSA', 'P256', 'RSA', 'Multisig', 'MultisigWeighted'] as const,
1111
batchedExecution: [false, true] as const,
1212
ERC7579Modules: [false, 'AccountERC7579', 'AccountERC7579Hooked'] as const,
1313
access: [false] as const,

packages/core/solidity/src/signer.ts

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ContractBuilder } from './contract';
22
import { defineFunctions } from './utils/define-functions';
33

4-
export const SignerOptions = [false, 'ERC7702', 'ECDSA', 'P256', 'RSA'] as const;
4+
export const SignerOptions = [false, 'ERC7702', 'ECDSA', 'P256', 'RSA', 'Multisig', 'MultisigWeighted'] as const;
55
export type SignerOptions = (typeof SignerOptions)[number];
66

77
export function addSigner(c: ContractBuilder, signer: SignerOptions): void {
@@ -19,13 +19,30 @@ export function addSigner(c: ContractBuilder, signer: SignerOptions): void {
1919
});
2020
const fn = signerFunctions[`initialize${signer}`];
2121
c.addModifier('initializer', fn);
22-
c.addFunctionCode(
23-
`_setSigner(${fn.args
24-
.map(({ name }) => name)
25-
.join(', ')
26-
.trimEnd()});`,
27-
fn,
28-
);
22+
23+
const args = fn.args;
24+
25+
switch (signer) {
26+
case 'Multisig':
27+
c.addFunctionCode(`_addSigners(${args[0]!.name});`, fn);
28+
c.addFunctionCode(`_setThreshold(${args[1]!.name});`, fn);
29+
break;
30+
case 'MultisigWeighted':
31+
c.addFunctionCode(`_addSigners(${args[0]!.name});`, fn);
32+
c.addFunctionCode(`_setSignerWeights(${args[0]!.name}, ${args[1]!.name});`, fn);
33+
c.addFunctionCode(`_setThreshold(${args[2]!.name});`, fn);
34+
break;
35+
case 'ECDSA':
36+
case 'P256':
37+
case 'RSA':
38+
c.addFunctionCode(
39+
`_setSigner(${fn.args
40+
.map(({ name }) => name)
41+
.join(', ')
42+
.trimEnd()});`,
43+
fn,
44+
);
45+
}
2946
}
3047

3148
const parents = {
@@ -45,6 +62,14 @@ const parents = {
4562
name: 'SignerRSA',
4663
path: '@openzeppelin/community-contracts/contracts/utils/cryptography/SignerRSA.sol',
4764
},
65+
Multisig: {
66+
name: 'MultiSignerERC7913',
67+
path: '@openzeppelin/community-contracts/contracts/utils/cryptography/MultiSignerERC7913.sol',
68+
},
69+
MultisigWeighted: {
70+
name: 'MultiSignerERC7913Weighted',
71+
path: '@openzeppelin/community-contracts/contracts/utils/cryptography/MultiSignerERC7913Weighted.sol',
72+
},
4873
};
4974

5075
export const signerFunctions = {
@@ -67,6 +92,21 @@ export const signerFunctions = {
6792
{ name: 'n', type: 'bytes memory' },
6893
],
6994
},
95+
initializeMultisig: {
96+
kind: 'public' as const,
97+
args: [
98+
{ name: 'signers', type: 'bytes[] memory' },
99+
{ name: 'threshold', type: 'uint256' },
100+
],
101+
},
102+
initializeMultisigWeighted: {
103+
kind: 'public' as const,
104+
args: [
105+
{ name: 'signers', type: 'bytes[] memory' },
106+
{ name: 'weights', type: 'uint256[] memory' },
107+
{ name: 'threshold', type: 'uint256' },
108+
],
109+
},
70110
_rawSignatureValidation: {
71111
kind: 'internal' as const,
72112
args: [

packages/ui/api/ai-assistant/function-definitions/solidity.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,15 @@ export const solidityAccountAIFunctionDefinition = {
209209
signer: {
210210
anyOf: [
211211
{ type: 'boolean', enum: [false] },
212-
{ type: 'string', enum: ['ECDSA', 'ERC7702', 'P256', 'RSA'] },
212+
{ type: 'string', enum: ['ECDSA', 'ERC7702', 'P256', 'RSA', 'Multisig', 'MultisigWeighted'] },
213213
],
214-
description: 'Defines the signature verification algorithm used by the account to verify user operations.',
214+
description: `Defines the signature verification algorithm used by the account to verify user operations. Options:
215+
- ECDSA: Standard Ethereum signature validation using secp256k1, validates signatures against a specified owner address
216+
- ERC7702: Special ECDSA validation using account's own address as signer, enables EOAs to delegate execution rights
217+
- P256: NIST P-256 curve (secp256r1) validation for integration with Passkeys and HSMs
218+
- RSA: RSA PKCS#1 v1.5 signature validation (RFC8017) for PKI systems and HSMs
219+
- Multisig: ERC-7913 multisignature requiring minimum number of signatures from authorized signers
220+
- MultisigWeighted: ERC-7913 weighted multisignature where signers have different voting weights`,
215221
},
216222
batchedExecution: {
217223
type: 'boolean',

packages/ui/src/solidity/AccountControls.svelte

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,25 @@
176176
rights to the account while maintaining their native signature verification.
177177
</HelpTooltip>
178178
</label>
179+
<label class:checked={opts.signer === 'Multisig'}>
180+
<input type="radio" bind:group={opts.signer} value="Multisig" />
181+
Multisig*
182+
<HelpTooltip link="https://docs.openzeppelin.com/community-contracts/multisig-account#multisignererc7913">
183+
ERC-7913 multisignature validation requiring a minimum number of signatures to approve operations. The contract
184+
maintains a set of authorized signers and validates that the number of valid signatures meets the threshold
185+
requirement.
186+
</HelpTooltip>
187+
</label>
188+
<label class:checked={opts.signer === 'MultisigWeighted'}>
189+
<input type="radio" bind:group={opts.signer} value="MultisigWeighted" />
190+
Multisig Weighted*
191+
<HelpTooltip
192+
link="https://docs.openzeppelin.com/community-contracts/0.0.1/multisig-account#multisignererc7913weighted"
193+
>
194+
Weighted version of ERC-7913 multisignature validation. Signers have different voting weights, allowing for
195+
flexible governance. The total weight of valid signatures must meet the threshold requirement.
196+
</HelpTooltip>
197+
</label>
179198
<label class:checked={opts.signer === 'P256'}>
180199
<input type="radio" bind:group={opts.signer} value="P256" />
181200
P256

yarn.lock

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@
77
resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30"
88
integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
99

10-
"@axelar-network/axelar-gmp-sdk-solidity@^6.0.6":
11-
version "6.0.6"
12-
resolved "https://registry.yarnpkg.com/@axelar-network/axelar-gmp-sdk-solidity/-/axelar-gmp-sdk-solidity-6.0.6.tgz#6559394df9b551978ca8843a9cdf3dc89eacdcd4"
13-
integrity sha512-XIcDlr1HoYSqcxuvvusILmiqerh2bL9NJLwU4lFBAJK5E/st/q3Em9ropBBZML9iuUZ+hDsch8Ev9rMO+ulaSQ==
14-
1510
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4":
1611
version "7.26.2"
1712
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
@@ -570,9 +565,7 @@
570565

571566
"@openzeppelin/community-contracts@https://github.com/OpenZeppelin/openzeppelin-community-contracts":
572567
version "0.0.1"
573-
resolved "https://github.com/OpenZeppelin/openzeppelin-community-contracts#30ed769d7b65723ebe1194398f2b644b6679fb98"
574-
dependencies:
575-
"@axelar-network/axelar-gmp-sdk-solidity" "^6.0.6"
568+
resolved "https://github.com/OpenZeppelin/openzeppelin-community-contracts#de17c8ee4b0329867f7219fbc401707be9518ff1"
576569

577570
"@openzeppelin/contracts-upgradeable@^5.3.0":
578571
version "5.3.0"

0 commit comments

Comments
 (0)