@rhinestone/sdk 2.5.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/accounts/adapters/kernel.d.ts +0 -1
- package/dist/src/accounts/adapters/kernel.d.ts.map +1 -1
- package/dist/src/accounts/adapters/kernel.js +1 -13
- package/dist/src/accounts/kernel-signing.d.ts +3 -0
- package/dist/src/accounts/kernel-signing.d.ts.map +1 -0
- package/dist/src/accounts/kernel-signing.js +13 -0
- package/dist/src/api/account.d.ts.map +1 -1
- package/dist/src/api/account.js +118 -0
- package/dist/src/api/compose-types.d.ts +7 -0
- package/dist/src/api/compose-types.d.ts.map +1 -1
- package/dist/src/api/direct-signing.d.ts.map +1 -1
- package/dist/src/api/direct-signing.js +11 -6
- package/dist/src/api/signer-selection.js +11 -0
- package/dist/src/clients/orchestrator/client.js +1 -1
- package/dist/src/clients/orchestrator/public.d.ts +8 -1
- package/dist/src/clients/orchestrator/public.d.ts.map +1 -1
- package/dist/src/clients/orchestrator/types.d.ts +6 -0
- package/dist/src/clients/orchestrator/types.d.ts.map +1 -1
- package/dist/src/config/account.d.ts +152 -7
- package/dist/src/config/account.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/modules/validators/capabilities.d.ts.map +1 -1
- package/dist/src/modules/validators/capabilities.js +33 -10
- package/dist/src/modules/validators/contribution.d.ts.map +1 -1
- package/dist/src/modules/validators/contribution.js +12 -0
- package/dist/src/modules/validators/definition.d.ts.map +1 -1
- package/dist/src/modules/validators/definition.js +18 -0
- package/dist/src/modules/validators/permissions.d.ts +13 -0
- package/dist/src/modules/validators/permissions.d.ts.map +1 -1
- package/dist/src/modules/validators/permissions.js +140 -2
- package/dist/src/modules/validators/quorum.d.ts +66 -0
- package/dist/src/modules/validators/quorum.d.ts.map +1 -0
- package/dist/src/modules/validators/quorum.js +231 -0
- package/dist/src/modules/validators/resolve.d.ts.map +1 -1
- package/dist/src/modules/validators/resolve.js +5 -0
- package/dist/src/modules/validators/smart-sessions/resolve.d.ts.map +1 -1
- package/dist/src/modules/validators/smart-sessions/resolve.js +112 -13
- package/dist/src/modules/validators/smart-sessions/swap/fynd.d.ts +92 -0
- package/dist/src/modules/validators/smart-sessions/swap/fynd.d.ts.map +1 -0
- package/dist/src/modules/validators/smart-sessions/swap/fynd.js +101 -0
- package/dist/src/modules/validators/smart-sessions/swap/rhinestone.d.ts +147 -0
- package/dist/src/modules/validators/smart-sessions/swap/rhinestone.d.ts.map +1 -0
- package/dist/src/modules/validators/smart-sessions/swap/rhinestone.js +305 -0
- package/dist/src/modules/validators/smart-sessions/swap/rules.d.ts +73 -0
- package/dist/src/modules/validators/smart-sessions/swap/rules.d.ts.map +1 -0
- package/dist/src/modules/validators/smart-sessions/swap/rules.js +105 -0
- package/dist/src/modules/validators/smart-sessions/swap/scope.d.ts +31 -0
- package/dist/src/modules/validators/smart-sessions/swap/scope.d.ts.map +1 -0
- package/dist/src/modules/validators/smart-sessions/swap/scope.js +168 -0
- package/dist/src/modules/validators/smart-sessions/swap/zero-ex.d.ts +143 -0
- package/dist/src/modules/validators/smart-sessions/swap/zero-ex.d.ts.map +1 -0
- package/dist/src/modules/validators/smart-sessions/swap/zero-ex.js +164 -0
- package/dist/src/modules/validators/smart-sessions/types.d.ts +78 -0
- package/dist/src/modules/validators/smart-sessions/types.d.ts.map +1 -1
- package/dist/src/modules/validators/types.d.ts +26 -3
- package/dist/src/modules/validators/types.d.ts.map +1 -1
- package/dist/src/signing/hash.d.ts +9 -0
- package/dist/src/signing/hash.d.ts.map +1 -0
- package/dist/src/signing/hash.js +15 -0
- package/dist/src/signing/intent-plans/assemble.d.ts.map +1 -1
- package/dist/src/signing/intent-plans/assemble.js +10 -0
- package/dist/src/signing/intent-plans/independent.d.ts.map +1 -1
- package/dist/src/signing/intent-plans/independent.js +5 -1
- package/dist/src/signing/plan.d.ts.map +1 -1
- package/dist/src/signing/plan.js +14 -1
- package/dist/src/signing/quorum.d.ts +77 -0
- package/dist/src/signing/quorum.d.ts.map +1 -0
- package/dist/src/signing/quorum.js +52 -0
- package/dist/src/signing/signers/ecdsa.d.ts +1 -1
- package/dist/src/signing/signers/ecdsa.d.ts.map +1 -1
- package/dist/src/signing/signers/ecdsa.js +8 -0
- package/dist/src/signing/typed-data.d.ts +12 -6
- package/dist/src/signing/typed-data.d.ts.map +1 -1
- package/dist/src/signing/typed-data.js +9 -4
- package/dist/src/signing/types.d.ts +8 -0
- package/dist/src/signing/types.d.ts.map +1 -1
- package/dist/src/smart-sessions/index.d.ts +10 -5
- package/dist/src/smart-sessions/index.d.ts.map +1 -1
- package/dist/src/smart-sessions/index.js +6 -1
- package/dist/src/transactions/intents/sign-transaction.d.ts.map +1 -1
- package/dist/src/transactions/intents/sign-transaction.js +161 -47
- package/dist/src/transactions/user-operations/prepare.d.ts.map +1 -1
- package/dist/src/transactions/user-operations/prepare.js +3 -1
- package/dist/src/transactions/user-operations/validator-account.d.ts.map +1 -1
- package/dist/src/transactions/user-operations/validator-account.js +4 -0
- package/package.json +5 -1
|
@@ -10,6 +10,88 @@ function isStaticAbiType(type) {
|
|
|
10
10
|
}
|
|
11
11
|
return false;
|
|
12
12
|
}
|
|
13
|
+
/** Split `T[k]` / `T[]` into element type and length (`''` when unbounded). */
|
|
14
|
+
const ARRAY_SUFFIX = /^(.*)\[(\d*)\]$/;
|
|
15
|
+
function withType(param, type) {
|
|
16
|
+
return { ...param, type };
|
|
17
|
+
}
|
|
18
|
+
function tupleComponents(param) {
|
|
19
|
+
return param.components ?? [];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* True when the param is encoded as a 32-byte offset pointer in the head rather
|
|
23
|
+
* than inline. Mirrors the ABI spec: `bytes`, `string`, unbounded arrays, and
|
|
24
|
+
* any tuple/fixed-array transitively containing one of those.
|
|
25
|
+
*/
|
|
26
|
+
function isDynamicAbiParam(param) {
|
|
27
|
+
const { type } = param;
|
|
28
|
+
if (type === 'bytes' || type === 'string')
|
|
29
|
+
return true;
|
|
30
|
+
const array = ARRAY_SUFFIX.exec(type);
|
|
31
|
+
if (array) {
|
|
32
|
+
const [, elementType, length] = array;
|
|
33
|
+
if (length === '')
|
|
34
|
+
return true;
|
|
35
|
+
return isDynamicAbiParam(withType(param, elementType));
|
|
36
|
+
}
|
|
37
|
+
if (type === 'tuple')
|
|
38
|
+
return tupleComponents(param).some(isDynamicAbiParam);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Bytes this param occupies in the calldata HEAD.
|
|
43
|
+
*
|
|
44
|
+
* Not always 32: a static tuple is flattened inline (one word per leaf) and a
|
|
45
|
+
* fixed-size static array occupies `length` words. Computing offsets as
|
|
46
|
+
* `paramIndex * 32` silently mis-addresses every param that follows one of
|
|
47
|
+
* those — e.g. `f(uint256[3] a, address b)` would point at word 1 instead of
|
|
48
|
+
* word 3 and compare against array data, producing a policy that passes or
|
|
49
|
+
* fails for reasons unrelated to `b`.
|
|
50
|
+
*/
|
|
51
|
+
function headSize(param) {
|
|
52
|
+
if (isDynamicAbiParam(param))
|
|
53
|
+
return 32;
|
|
54
|
+
const array = ARRAY_SUFFIX.exec(param.type);
|
|
55
|
+
if (array) {
|
|
56
|
+
const [, elementType, length] = array;
|
|
57
|
+
return Number.parseInt(length, 10) * headSize(withType(param, elementType));
|
|
58
|
+
}
|
|
59
|
+
if (param.type === 'tuple') {
|
|
60
|
+
return tupleComponents(param).reduce((sum, c) => sum + headSize(c), 0);
|
|
61
|
+
}
|
|
62
|
+
return 32;
|
|
63
|
+
}
|
|
64
|
+
/** Byte offset of `inputs[index]` within the calldata head (past the selector). */
|
|
65
|
+
function headOffset(inputs, index) {
|
|
66
|
+
let offset = 0;
|
|
67
|
+
for (let i = 0; i < index; i++)
|
|
68
|
+
offset += headSize(inputs[i]);
|
|
69
|
+
return BigInt(offset);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Calldata head offset of every named top-level parameter of `functionName`.
|
|
73
|
+
*
|
|
74
|
+
* Lets callers that build raw param rules (the swap venues) address arguments by
|
|
75
|
+
* name instead of hardcoding byte offsets — the offsets then follow the ABI
|
|
76
|
+
* automatically, and a signature change surfaces as a missing key rather than a
|
|
77
|
+
* rule silently pointing at the wrong word.
|
|
78
|
+
*
|
|
79
|
+
* Only single-word static params are included; anything dynamic or multi-word
|
|
80
|
+
* cannot be compared by a 32-byte reference value and is omitted.
|
|
81
|
+
*/
|
|
82
|
+
export function namedParamOffsets(abi, functionName) {
|
|
83
|
+
const entry = abi.find((item) => item.type === 'function' && item.name === functionName);
|
|
84
|
+
if (!entry) {
|
|
85
|
+
throw new Error(`Function "${functionName}" not found in ABI`);
|
|
86
|
+
}
|
|
87
|
+
const offsets = {};
|
|
88
|
+
entry.inputs.forEach((param, index) => {
|
|
89
|
+
if (param.name && isStaticAbiType(param.type)) {
|
|
90
|
+
offsets[param.name] = headOffset(entry.inputs, index);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
return offsets;
|
|
94
|
+
}
|
|
13
95
|
function toReferenceValue(value, abiType) {
|
|
14
96
|
if (abiType === 'address') {
|
|
15
97
|
if (typeof value === 'string' && isAddress(value))
|
|
@@ -161,7 +243,22 @@ function resolvePermission(permission) {
|
|
|
161
243
|
'Permission rules only support static types ' +
|
|
162
244
|
'(address, bool, uint*, int*, bytes1–bytes32).');
|
|
163
245
|
}
|
|
164
|
-
const calldataOffset =
|
|
246
|
+
const calldataOffset = headOffset(abiEntry.inputs, paramIndex);
|
|
247
|
+
if (rule.min !== undefined || rule.max !== undefined) {
|
|
248
|
+
if (rule.min === undefined || rule.max === undefined) {
|
|
249
|
+
throw new Error(`Parameter "${paramName}" needs both "min" and "max" — ` +
|
|
250
|
+
'provide the other bound, or use a single ' +
|
|
251
|
+
'{ condition, value } comparison.');
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
paramName,
|
|
255
|
+
calldataOffset,
|
|
256
|
+
abiType: param.type,
|
|
257
|
+
min: rule.min,
|
|
258
|
+
max: rule.max,
|
|
259
|
+
usageLimit: rule.usageLimit,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
165
262
|
if (rule.anyOf !== undefined) {
|
|
166
263
|
if (rule.anyOf.length === 0) {
|
|
167
264
|
throw new Error(`Parameter "${paramName}" has empty anyOf — provide at least one value.`);
|
|
@@ -182,7 +279,9 @@ function resolvePermission(permission) {
|
|
|
182
279
|
usageLimit: rule.usageLimit,
|
|
183
280
|
};
|
|
184
281
|
});
|
|
185
|
-
|
|
282
|
+
// Both `anyOf` (OR chain) and `min`/`max` (AND of two rules on ONE param)
|
|
283
|
+
// need more than the flat one-rule-per-param shape UniActionPolicy offers.
|
|
284
|
+
const usesArgPolicy = normalized.some((n) => n.anyOf !== undefined || n.min !== undefined);
|
|
186
285
|
// UniActionPolicy/ArgPolicy reject `msg.value > valueLimitPerUse`, so a
|
|
187
286
|
// default of 0 would block any non-zero msg.value before the standalone
|
|
188
287
|
// value-limit policy (sugar) could allow it. Inherit the sugar's cap so
|
|
@@ -203,6 +302,45 @@ function resolvePermission(permission) {
|
|
|
203
302
|
}));
|
|
204
303
|
return leaves.length === 1 ? leaves[0] : orChain(leaves);
|
|
205
304
|
}
|
|
305
|
+
if (n.min !== undefined) {
|
|
306
|
+
// Inclusive bounds as AND(x >= min, x <= max). Deliberately NOT the
|
|
307
|
+
// policy's own `inRange` condition: that packs two uint128 bounds
|
|
308
|
+
// into one bytes32 `ref`, which this SDK has no verified encoding
|
|
309
|
+
// for. Two comparisons are exactly equivalent and use primitives
|
|
310
|
+
// already covered by tests.
|
|
311
|
+
//
|
|
312
|
+
// `usageLimit` rides on the lower-bound leaf only — putting it on
|
|
313
|
+
// both would double-count every call against the cumulative cap.
|
|
314
|
+
const minRef = toReferenceValue(n.min, n.abiType);
|
|
315
|
+
const maxRef = toReferenceValue(n.max, n.abiType);
|
|
316
|
+
if (typeof minRef === 'bigint' &&
|
|
317
|
+
typeof maxRef === 'bigint' &&
|
|
318
|
+
minRef > maxRef) {
|
|
319
|
+
throw new Error(`Parameter "${n.paramName}" has min (${minRef}) greater than ` +
|
|
320
|
+
`max (${maxRef}) — no value can satisfy this rule.`);
|
|
321
|
+
}
|
|
322
|
+
return andChain([
|
|
323
|
+
{
|
|
324
|
+
type: 'rule',
|
|
325
|
+
rule: {
|
|
326
|
+
condition: 'greaterThanOrEqual',
|
|
327
|
+
calldataOffset: n.calldataOffset,
|
|
328
|
+
referenceValue: minRef,
|
|
329
|
+
...(n.usageLimit !== undefined
|
|
330
|
+
? { usageLimit: n.usageLimit }
|
|
331
|
+
: {}),
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
type: 'rule',
|
|
336
|
+
rule: {
|
|
337
|
+
condition: 'lessThanOrEqual',
|
|
338
|
+
calldataOffset: n.calldataOffset,
|
|
339
|
+
referenceValue: maxRef,
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
]);
|
|
343
|
+
}
|
|
206
344
|
return {
|
|
207
345
|
type: 'rule',
|
|
208
346
|
rule: {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type Address, type Hex } from 'viem';
|
|
2
|
+
import type { ResolvedModule } from '../types.js';
|
|
3
|
+
import type { AtomicValidatorDefinition, ValidatorContributionInput } from './types.js';
|
|
4
|
+
/** Bind an ERC-1271 digest to its Quorum module, chain, and smart account. */
|
|
5
|
+
export declare function getQuorumSignableHash(input: {
|
|
6
|
+
readonly validator: Address;
|
|
7
|
+
readonly chainId: number;
|
|
8
|
+
readonly account: Address;
|
|
9
|
+
readonly hash: Hex;
|
|
10
|
+
}): Hex;
|
|
11
|
+
export interface QuorumMerkleProof {
|
|
12
|
+
readonly root: Hex;
|
|
13
|
+
readonly proof: readonly Hex[];
|
|
14
|
+
}
|
|
15
|
+
export interface QuorumMerkleOperation extends QuorumMerkleProof {
|
|
16
|
+
readonly account: Address;
|
|
17
|
+
readonly digest: Hex;
|
|
18
|
+
readonly leaf: Hex;
|
|
19
|
+
}
|
|
20
|
+
/** Build account-bound leaves and Solady-compatible sorted-pair proofs. */
|
|
21
|
+
export declare function buildQuorumMerkleTree(operations: readonly {
|
|
22
|
+
readonly account: Address;
|
|
23
|
+
readonly digest: Hex;
|
|
24
|
+
}[]): {
|
|
25
|
+
readonly root: Hex;
|
|
26
|
+
readonly operations: readonly QuorumMerkleOperation[];
|
|
27
|
+
};
|
|
28
|
+
/** Hash the chain-agnostic EIP-712 root message signed by every owner. */
|
|
29
|
+
export declare function getQuorumMerkleRootSignableHash(input: {
|
|
30
|
+
readonly validator: Address;
|
|
31
|
+
readonly root: Hex;
|
|
32
|
+
}): Hex;
|
|
33
|
+
/** Resolve Quorum Signer installation data from a weighted owner definition. */
|
|
34
|
+
export declare function resolveQuorumValidator(definition: AtomicValidatorDefinition): ResolvedModule;
|
|
35
|
+
/** Encode a regular Quorum Signer envelope from owner signature contributions. */
|
|
36
|
+
export declare function encodeQuorumValidatorContribution(input: {
|
|
37
|
+
readonly owners: readonly {
|
|
38
|
+
readonly ownerId: string;
|
|
39
|
+
readonly signer: Address;
|
|
40
|
+
readonly weight: bigint;
|
|
41
|
+
}[];
|
|
42
|
+
readonly thresholdWeight: bigint;
|
|
43
|
+
readonly contributions: readonly Extract<ValidatorContributionInput, {
|
|
44
|
+
readonly kind: 'ecdsa';
|
|
45
|
+
}>[];
|
|
46
|
+
}): Hex;
|
|
47
|
+
/** Encode one Merkle proof with an already packed weighted quorum. */
|
|
48
|
+
export declare function encodeQuorumMerkleEnvelope(input: {
|
|
49
|
+
readonly proof: QuorumMerkleProof;
|
|
50
|
+
readonly signatures: Hex;
|
|
51
|
+
}): Hex;
|
|
52
|
+
/** Encode weighted entries without the regular or Merkle envelope prefix. */
|
|
53
|
+
export declare function encodeQuorumWeightedSignatures(input: {
|
|
54
|
+
readonly owners: readonly {
|
|
55
|
+
readonly ownerId: string;
|
|
56
|
+
readonly signer: Address;
|
|
57
|
+
readonly weight: bigint;
|
|
58
|
+
}[];
|
|
59
|
+
readonly thresholdWeight: bigint;
|
|
60
|
+
readonly contributions: readonly Extract<ValidatorContributionInput, {
|
|
61
|
+
readonly kind: 'ecdsa';
|
|
62
|
+
}>[];
|
|
63
|
+
}): Hex;
|
|
64
|
+
/** Build a structurally valid maximum-cost signature for bundler gas estimation. */
|
|
65
|
+
export declare function encodeQuorumMockSignature(definition: AtomicValidatorDefinition): Hex;
|
|
66
|
+
//# sourceMappingURL=quorum.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quorum.d.ts","sourceRoot":"","sources":["../../../../modules/validators/quorum.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { concat, encodeAbiParameters, hashTypedData, keccak256, size, toHex, } from 'viem';
|
|
2
|
+
import { compareHexValues } from './ordering.js';
|
|
3
|
+
const MAX_QUORUM_OWNERS = 32;
|
|
4
|
+
const MAX_UINT96 = (1n << 96n) - 1n;
|
|
5
|
+
const QUORUM_MESSAGE_TYPEHASH = keccak256(toHex('WeightedOwnableValidatorMessage(address validator,uint256 chainId,address account,bytes32 hash)'));
|
|
6
|
+
/** Bind an ERC-1271 digest to its Quorum module, chain, and smart account. */
|
|
7
|
+
export function getQuorumSignableHash(input) {
|
|
8
|
+
return keccak256(encodeAbiParameters([
|
|
9
|
+
{ type: 'bytes32' },
|
|
10
|
+
{ type: 'address' },
|
|
11
|
+
{ type: 'uint256' },
|
|
12
|
+
{ type: 'address' },
|
|
13
|
+
{ type: 'bytes32' },
|
|
14
|
+
], [
|
|
15
|
+
QUORUM_MESSAGE_TYPEHASH,
|
|
16
|
+
input.validator,
|
|
17
|
+
BigInt(input.chainId),
|
|
18
|
+
input.account,
|
|
19
|
+
input.hash,
|
|
20
|
+
]));
|
|
21
|
+
}
|
|
22
|
+
/** Build account-bound leaves and Solady-compatible sorted-pair proofs. */
|
|
23
|
+
export function buildQuorumMerkleTree(operations) {
|
|
24
|
+
if (operations.length < 2) {
|
|
25
|
+
throw new Error('Quorum Merkle signing requires at least two operations');
|
|
26
|
+
}
|
|
27
|
+
const leaves = operations.map(({ account, digest }) => keccak256(encodeAbiParameters([{ type: 'address' }, { type: 'bytes32' }], [account, digest])));
|
|
28
|
+
const layers = [leaves];
|
|
29
|
+
while (layers.at(-1).length > 1) {
|
|
30
|
+
const current = layers.at(-1);
|
|
31
|
+
const next = [];
|
|
32
|
+
for (let index = 0; index < current.length; index += 2) {
|
|
33
|
+
next.push(index + 1 < current.length
|
|
34
|
+
? hashQuorumMerklePair(current[index], current[index + 1])
|
|
35
|
+
: current[index]);
|
|
36
|
+
}
|
|
37
|
+
layers.push(next);
|
|
38
|
+
}
|
|
39
|
+
const root = layers.at(-1)[0];
|
|
40
|
+
return {
|
|
41
|
+
root,
|
|
42
|
+
operations: operations.map(({ account, digest }, leafIndex) => {
|
|
43
|
+
const proof = [];
|
|
44
|
+
let index = leafIndex;
|
|
45
|
+
for (const layer of layers.slice(0, -1)) {
|
|
46
|
+
const siblingIndex = index % 2 === 0 ? index + 1 : index - 1;
|
|
47
|
+
if (siblingIndex < layer.length)
|
|
48
|
+
proof.push(layer[siblingIndex]);
|
|
49
|
+
index = Math.floor(index / 2);
|
|
50
|
+
}
|
|
51
|
+
return { account, digest, leaf: leaves[leafIndex], root, proof };
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Hash the chain-agnostic EIP-712 root message signed by every owner. */
|
|
56
|
+
export function getQuorumMerkleRootSignableHash(input) {
|
|
57
|
+
return hashTypedData({
|
|
58
|
+
domain: {
|
|
59
|
+
name: 'Quorum Signer',
|
|
60
|
+
version: '1',
|
|
61
|
+
verifyingContract: input.validator,
|
|
62
|
+
},
|
|
63
|
+
types: {
|
|
64
|
+
WeightedMerkleRoot: [{ name: 'root', type: 'bytes32' }],
|
|
65
|
+
},
|
|
66
|
+
primaryType: 'WeightedMerkleRoot',
|
|
67
|
+
message: { root: input.root },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function hashQuorumMerklePair(left, right) {
|
|
71
|
+
return compareHexValues(left, right) < 0
|
|
72
|
+
? keccak256(concat([left, right]))
|
|
73
|
+
: keccak256(concat([right, left]));
|
|
74
|
+
}
|
|
75
|
+
const MAX_SIGNATURE_LENGTH = 65_535;
|
|
76
|
+
const QUORUM_MOCK_SIGNATURE = `0x${'00'.repeat(64)}1b`;
|
|
77
|
+
/** Resolve Quorum Signer installation data from a weighted owner definition. */
|
|
78
|
+
export function resolveQuorumValidator(definition) {
|
|
79
|
+
if (definition.kind !== 'quorum') {
|
|
80
|
+
throw new Error('Quorum resolver received a non-quorum validator');
|
|
81
|
+
}
|
|
82
|
+
const owners = resolveOwners(definition).sort((left, right) => compareHexValues(left.signer, right.signer));
|
|
83
|
+
const thresholdWeight = requireThresholdWeight(definition);
|
|
84
|
+
validateQuorumConfig(owners, thresholdWeight);
|
|
85
|
+
if (definition.module.source !== 'explicit') {
|
|
86
|
+
throw new Error('Quorum validator requires a deployed module address');
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
kind: 'validator',
|
|
90
|
+
address: definition.module.address,
|
|
91
|
+
initData: encodeAbiParameters([
|
|
92
|
+
{ name: 'thresholdWeight', type: 'uint256' },
|
|
93
|
+
{
|
|
94
|
+
name: 'owners',
|
|
95
|
+
type: 'tuple[]',
|
|
96
|
+
components: [
|
|
97
|
+
{ name: 'addr', type: 'address' },
|
|
98
|
+
{ name: 'weight', type: 'uint96' },
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
], [
|
|
102
|
+
thresholdWeight,
|
|
103
|
+
owners.map(({ signer, weight }) => ({ addr: signer, weight })),
|
|
104
|
+
]),
|
|
105
|
+
deInitData: '0x',
|
|
106
|
+
additionalContext: '0x',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** Encode a regular Quorum Signer envelope from owner signature contributions. */
|
|
110
|
+
export function encodeQuorumValidatorContribution(input) {
|
|
111
|
+
return concat(['0x00', encodeSelectedQuorumSignatures(input)]);
|
|
112
|
+
}
|
|
113
|
+
function encodeSelectedQuorumSignatures(input) {
|
|
114
|
+
validateQuorumConfig(input.owners, input.thresholdWeight);
|
|
115
|
+
const configured = new Map(input.owners.map((owner) => [owner.ownerId, owner]));
|
|
116
|
+
const contributions = new Map();
|
|
117
|
+
for (const contribution of input.contributions) {
|
|
118
|
+
if (!configured.has(contribution.ownerId)) {
|
|
119
|
+
throw new Error(`Unknown validator owner ${contribution.ownerId}`);
|
|
120
|
+
}
|
|
121
|
+
if (contributions.has(contribution.ownerId)) {
|
|
122
|
+
throw new Error(`Duplicate validator owner ${contribution.ownerId}`);
|
|
123
|
+
}
|
|
124
|
+
contributions.set(contribution.ownerId, contribution.signature);
|
|
125
|
+
}
|
|
126
|
+
const selected = input.owners
|
|
127
|
+
.filter((owner) => contributions.has(owner.ownerId))
|
|
128
|
+
.sort((left, right) => compareAddresses(left.signer, right.signer));
|
|
129
|
+
const signedWeight = selected.reduce((sum, owner) => sum + owner.weight, 0n);
|
|
130
|
+
if (signedWeight < input.thresholdWeight) {
|
|
131
|
+
throw new Error(`Insufficient validator contribution weight: required ${input.thresholdWeight}, received ${signedWeight}`);
|
|
132
|
+
}
|
|
133
|
+
return encodeQuorumEntries(selected.map((owner) => ({
|
|
134
|
+
signer: owner.signer,
|
|
135
|
+
signature: contributions.get(owner.ownerId),
|
|
136
|
+
})));
|
|
137
|
+
}
|
|
138
|
+
/** Encode one Merkle proof with an already packed weighted quorum. */
|
|
139
|
+
export function encodeQuorumMerkleEnvelope(input) {
|
|
140
|
+
if (input.proof.proof.length < 1 || input.proof.proof.length > 32) {
|
|
141
|
+
throw new Error('Quorum Merkle proof length must be between 1 and 32');
|
|
142
|
+
}
|
|
143
|
+
if (input.signatures === '0x') {
|
|
144
|
+
throw new Error('Quorum Merkle signatures must not be empty');
|
|
145
|
+
}
|
|
146
|
+
return concat([
|
|
147
|
+
toHex(input.proof.proof.length, { size: 1 }),
|
|
148
|
+
input.proof.root,
|
|
149
|
+
...input.proof.proof,
|
|
150
|
+
input.signatures,
|
|
151
|
+
]);
|
|
152
|
+
}
|
|
153
|
+
/** Encode weighted entries without the regular or Merkle envelope prefix. */
|
|
154
|
+
export function encodeQuorumWeightedSignatures(input) {
|
|
155
|
+
return encodeSelectedQuorumSignatures(input);
|
|
156
|
+
}
|
|
157
|
+
/** Build a structurally valid maximum-cost signature for bundler gas estimation. */
|
|
158
|
+
export function encodeQuorumMockSignature(definition) {
|
|
159
|
+
if (definition.kind !== 'quorum') {
|
|
160
|
+
throw new Error('Quorum mock signature requires a quorum validator');
|
|
161
|
+
}
|
|
162
|
+
const owners = resolveOwners(definition);
|
|
163
|
+
validateQuorumConfig(owners, requireThresholdWeight(definition));
|
|
164
|
+
return concat([
|
|
165
|
+
'0x00',
|
|
166
|
+
encodeQuorumEntries(owners
|
|
167
|
+
.map(({ signer }) => ({ signer, signature: QUORUM_MOCK_SIGNATURE }))
|
|
168
|
+
.sort((left, right) => compareAddresses(left.signer, right.signer))),
|
|
169
|
+
]);
|
|
170
|
+
}
|
|
171
|
+
function encodeQuorumEntries(entries) {
|
|
172
|
+
if (entries.length === 0)
|
|
173
|
+
throw new Error('Quorum signature must not be empty');
|
|
174
|
+
let previous;
|
|
175
|
+
const packed = entries.map(({ signer, signature }) => {
|
|
176
|
+
if (previous && compareAddresses(previous, signer) >= 0) {
|
|
177
|
+
throw new Error('Quorum signer addresses must be strictly sorted and unique');
|
|
178
|
+
}
|
|
179
|
+
previous = signer;
|
|
180
|
+
const signatureLength = size(signature);
|
|
181
|
+
if (signatureLength > MAX_SIGNATURE_LENGTH) {
|
|
182
|
+
throw new Error(`Quorum inner signature exceeds ${MAX_SIGNATURE_LENGTH} bytes`);
|
|
183
|
+
}
|
|
184
|
+
return concat([signer, toHex(signatureLength, { size: 2 }), signature]);
|
|
185
|
+
});
|
|
186
|
+
return concat(packed);
|
|
187
|
+
}
|
|
188
|
+
function resolveOwners(definition) {
|
|
189
|
+
return definition.owners.map((owner) => {
|
|
190
|
+
if (owner.kind === 'webauthn' || owner.weight === undefined) {
|
|
191
|
+
throw new Error('Quorum validator requires weighted ECDSA owners');
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
ownerId: owner.id,
|
|
195
|
+
signer: owner.account.address,
|
|
196
|
+
weight: owner.weight,
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function requireThresholdWeight(definition) {
|
|
201
|
+
if (definition.thresholdWeight === undefined) {
|
|
202
|
+
throw new Error('Quorum validator threshold weight is missing');
|
|
203
|
+
}
|
|
204
|
+
return definition.thresholdWeight;
|
|
205
|
+
}
|
|
206
|
+
function validateQuorumConfig(owners, thresholdWeight) {
|
|
207
|
+
if (owners.length < 1 || owners.length > MAX_QUORUM_OWNERS) {
|
|
208
|
+
throw new Error(`Quorum owners length must be between 1 and ${MAX_QUORUM_OWNERS}`);
|
|
209
|
+
}
|
|
210
|
+
const seen = new Set();
|
|
211
|
+
let totalWeight = 0n;
|
|
212
|
+
for (const [index, owner] of owners.entries()) {
|
|
213
|
+
const signer = owner.signer.toLowerCase();
|
|
214
|
+
if (signer === '0x0000000000000000000000000000000000000000') {
|
|
215
|
+
throw new Error(`Quorum owner ${index} cannot be the zero address`);
|
|
216
|
+
}
|
|
217
|
+
if (seen.has(signer))
|
|
218
|
+
throw new Error(`Duplicate quorum owner ${owner.signer}`);
|
|
219
|
+
seen.add(signer);
|
|
220
|
+
if (owner.weight <= 0n || owner.weight > MAX_UINT96) {
|
|
221
|
+
throw new Error(`Quorum owner ${owner.signer} weight must fit uint96 and be non-zero`);
|
|
222
|
+
}
|
|
223
|
+
totalWeight += owner.weight;
|
|
224
|
+
}
|
|
225
|
+
if (thresholdWeight <= 0n || thresholdWeight > totalWeight) {
|
|
226
|
+
throw new Error(`Quorum threshold weight must be between 1 and total owner weight ${totalWeight}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function compareAddresses(left, right) {
|
|
230
|
+
return compareHexValues(left, right);
|
|
231
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../../modules/validators/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../../modules/validators/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAK9C,OAAO,KAAK,EACV,yBAAyB,EACzB,2BAA2B,EAC5B,MAAM,SAAS,CAAA;AAChB,OAAO,EAGL,KAAK,0BAA0B,EAChC,MAAM,YAAY,CAAA;AAEnB,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,yBAAyB,EACrC,gBAAgB,CAAC,EAAE,0BAA0B,GAC5C,cAAc,CAchB;AAKD,wBAAgB,mCAAmC,CACjD,UAAU,EAAE,yBAAyB,GACpC,GAAG,CAgBL;AAED,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,yBAAyB;;;EAMtC;AAKD,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,2BAA2B,EACvC,gBAAgB,CAAC,EAAE,0BAA0B,GAC5C,cAAc,CAIhB"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { encodeEnsStatelessData, resolveEnsValidator } from './ens.js';
|
|
2
2
|
import { resolveMultiFactorValidator } from './multi-factor.js';
|
|
3
3
|
import { resolveOwnableStatelessData, resolveOwnableValidator } from './ownable.js';
|
|
4
|
+
import { resolveQuorumValidator } from './quorum.js';
|
|
4
5
|
import { resolveWebauthnStatelessData, resolveWebauthnValidator, } from './webauthn.js';
|
|
5
6
|
export function resolveAtomicValidator(definition, webauthnOrdering) {
|
|
6
7
|
switch (definition.kind) {
|
|
7
8
|
case 'ecdsa':
|
|
8
9
|
return resolveOwnableValidator(definition);
|
|
10
|
+
case 'quorum':
|
|
11
|
+
return resolveQuorumValidator(definition);
|
|
9
12
|
case 'ens':
|
|
10
13
|
return resolveEnsValidator(definition);
|
|
11
14
|
case 'passkey':
|
|
@@ -26,6 +29,8 @@ export function resolveAtomicValidatorStatelessData(definition) {
|
|
|
26
29
|
return encodeEnsStatelessData(definition);
|
|
27
30
|
case 'passkey':
|
|
28
31
|
return resolveWebauthnStatelessData(definition);
|
|
32
|
+
case 'quorum':
|
|
33
|
+
throw new Error('Validator quorum cannot be used as a multi-factor factor');
|
|
29
34
|
case 'k1':
|
|
30
35
|
case 'smart-session':
|
|
31
36
|
throw new Error(`Validator ${definition.kind} requires feature input`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../../../modules/validators/smart-sessions/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,GAAG,EAAgC,MAAM,MAAM,CAAA;AAU3E,OAAO,EACL,wBAAwB,EAEzB,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../../../modules/validators/smart-sessions/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,GAAG,EAAgC,MAAM,MAAM,CAAA;AAU3E,OAAO,EACL,wBAAwB,EAEzB,MAAM,sBAAsB,CAAA;AAQ7B,OAAO,KAAK,EAGV,OAAO,EAEP,WAAW,EACX,iBAAiB,EAClB,MAAM,SAAS,CAAA;AAEhB,eAAO,MAAM,mCAAmC,EAAE,OACJ,CAAA;AAC9C,eAAO,MAAM,4CAA4C,EAAE,GAAkB,CAAA;AAC7E,eAAO,MAAM,2EAA2E,EACtF,YAAqB,CAAA;AACvB,eAAO,MAAM,uBAAuB,EAClC,4CAAqD,CAAA;AACvD,eAAO,MAAM,yBAAyB,EAAG,YAAqB,CAAA;AAU9D,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,YAAY,GAAG,aAAa,CAAA;IAGnD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;CACtC;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,iBAAiB,EAC7B,OAAO,GAAE,qBAA0B,GAClC,WAAW,CAqMb;AAED,wBAAgB,SAAS,CACvB,UAAU,EAAE,iBAAiB,EAC7B,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAoCT;AAED,OAAO,EAAE,wBAAwB,EAAE,CAAA"}
|