ox 1.7.2 → 1.7.5
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/CHANGELOG.md +43 -0
- package/dist/core/AbiEvent.d.ts.map +1 -1
- package/dist/core/AbiEvent.js +10 -9
- package/dist/core/AbiEvent.js.map +1 -1
- package/dist/core/Blobs.d.ts.map +1 -1
- package/dist/core/Blobs.js +6 -2
- package/dist/core/Blobs.js.map +1 -1
- package/dist/core/Signature.d.ts +6 -5
- package/dist/core/Signature.d.ts.map +1 -1
- package/dist/core/Signature.js +17 -7
- package/dist/core/Signature.js.map +1 -1
- package/dist/core/Siwe.d.ts.map +1 -1
- package/dist/core/Siwe.js +21 -1
- package/dist/core/Siwe.js.map +1 -1
- package/dist/tempo/KeyAuthorization.d.ts +43 -39
- package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/KeyAuthorization.js +24 -4
- package/dist/tempo/KeyAuthorization.js.map +1 -1
- package/dist/tempo/MultisigConfig.d.ts +18 -15
- package/dist/tempo/MultisigConfig.d.ts.map +1 -1
- package/dist/tempo/MultisigConfig.js +49 -20
- package/dist/tempo/MultisigConfig.js.map +1 -1
- package/dist/tempo/MultisigOperation.d.ts +45 -28
- package/dist/tempo/MultisigOperation.d.ts.map +1 -1
- package/dist/tempo/MultisigOperation.js +81 -131
- package/dist/tempo/MultisigOperation.js.map +1 -1
- package/dist/tempo/MultisigSimulation.d.ts +85 -0
- package/dist/tempo/MultisigSimulation.d.ts.map +1 -0
- package/dist/tempo/MultisigSimulation.js +112 -0
- package/dist/tempo/MultisigSimulation.js.map +1 -0
- package/dist/tempo/RpcSchemaTempo.d.ts +20 -10
- package/dist/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/tempo/SignatureEnvelope.d.ts +74 -153
- package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/tempo/SignatureEnvelope.js +130 -187
- package/dist/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/tempo/TransactionRequest.d.ts +9 -7
- package/dist/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/tempo/TransactionRequest.js +12 -7
- package/dist/tempo/TransactionRequest.js.map +1 -1
- package/dist/tempo/index.d.ts +7 -5
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +7 -5
- package/dist/tempo/index.js.map +1 -1
- package/dist/zod/tempo/AuthorizationTempo.d.ts +360 -140
- package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.d.ts +247 -487
- package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.js +29 -31
- package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
- package/dist/zod/tempo/MultisigOperation.d.ts +0 -10
- package/dist/zod/tempo/MultisigOperation.d.ts.map +1 -1
- package/dist/zod/tempo/MultisigOperation.js +0 -3
- package/dist/zod/tempo/MultisigOperation.js.map +1 -1
- package/dist/zod/tempo/MultisigSimulation.d.ts +52 -0
- package/dist/zod/tempo/MultisigSimulation.d.ts.map +1 -0
- package/dist/zod/tempo/MultisigSimulation.js +44 -0
- package/dist/zod/tempo/MultisigSimulation.js.map +1 -0
- package/dist/zod/tempo/RpcSchemaTempo.d.ts +357 -422
- package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/zod/tempo/RpcSchemaTempo.js +19 -10
- package/dist/zod/tempo/RpcSchemaTempo.js.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.d.ts +108 -42
- package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.js +21 -19
- package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/zod/tempo/Transaction.d.ts +678 -666
- package/dist/zod/tempo/Transaction.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.d.ts +580 -619
- package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.js +18 -10
- package/dist/zod/tempo/TransactionRequest.js.map +1 -1
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts +327 -213
- package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
- package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +105 -3
- package/dist/zod/tempo/ZoneRpcAuthentication.d.ts.map +1 -1
- package/dist/zod/tempo/z.d.ts +1 -1
- package/dist/zod/tempo/z.d.ts.map +1 -1
- package/dist/zod/tempo/z.js +1 -1
- package/dist/zod/tempo/z.js.map +1 -1
- package/package.json +9 -9
- package/src/core/AbiEvent.ts +11 -9
- package/src/core/Blobs.ts +6 -2
- package/src/core/Signature.ts +41 -11
- package/src/core/Siwe.ts +20 -1
- package/src/core/_test/AbiEvent.test.ts +45 -0
- package/src/core/_test/Blobs.test.ts +22 -0
- package/src/core/_test/Signature.test.ts +166 -0
- package/src/core/_test/Siwe.test.ts +100 -1
- package/src/tempo/AuthorizationTempo.test.ts +8 -0
- package/src/tempo/KeyAuthorization.test-d.ts +16 -74
- package/src/tempo/KeyAuthorization.test.ts +113 -14
- package/src/tempo/KeyAuthorization.ts +110 -66
- package/src/tempo/MultisigConfig.test.ts +44 -20
- package/src/tempo/MultisigConfig.ts +65 -23
- package/src/tempo/MultisigOperation.test-d.ts +8 -6
- package/src/tempo/MultisigOperation.test.ts +187 -591
- package/src/tempo/MultisigOperation.ts +129 -205
- package/src/tempo/MultisigSimulation.test-d.ts +33 -0
- package/src/tempo/MultisigSimulation.test.ts +194 -0
- package/src/tempo/MultisigSimulation.ts +192 -0
- package/src/tempo/RpcSchemaTempo.test-d.ts +45 -10
- package/src/tempo/RpcSchemaTempo.ts +15 -6
- package/src/tempo/SignatureEnvelope.test-d.ts +27 -101
- package/src/tempo/SignatureEnvelope.test.ts +330 -533
- package/src/tempo/SignatureEnvelope.ts +198 -310
- package/src/tempo/Transaction.test.ts +61 -0
- package/src/tempo/TransactionRequest.test-d.ts +7 -7
- package/src/tempo/TransactionRequest.test.ts +81 -223
- package/src/tempo/TransactionRequest.ts +34 -13
- package/src/tempo/index.ts +7 -5
- package/src/tempo/multisig.e2e.test.ts +329 -818
- package/src/version.ts +1 -1
- package/src/zod/tempo/KeyAuthorization.ts +50 -39
- package/src/zod/tempo/MultisigOperation.ts +0 -3
- package/src/zod/tempo/MultisigSimulation.ts +53 -0
- package/src/zod/tempo/RpcSchemaTempo.ts +20 -10
- package/src/zod/tempo/SignatureEnvelope.ts +46 -59
- package/src/zod/tempo/TransactionRequest.ts +28 -14
- package/src/zod/tempo/_test/KeyAuthorization.test.ts +66 -30
- package/src/zod/tempo/_test/MultisigOperation.test.ts +0 -2
- package/src/zod/tempo/_test/MultisigSimulation.test-d.ts +13 -0
- package/src/zod/tempo/_test/MultisigSimulation.test.ts +53 -0
- package/src/zod/tempo/_test/RpcSchemaTempo.test-d.ts +19 -2
- package/src/zod/tempo/_test/RpcSchemaTempo.test.ts +46 -4
- package/src/zod/tempo/_test/SignatureEnvelope.test.ts +108 -129
- package/src/zod/tempo/_test/TransactionRequest.test.ts +17 -168
- package/src/zod/tempo/z.ts +1 -1
- package/dist/tempo/MultisigWitness.d.ts +0 -106
- package/dist/tempo/MultisigWitness.d.ts.map +0 -1
- package/dist/tempo/MultisigWitness.js +0 -118
- package/dist/tempo/MultisigWitness.js.map +0 -1
- package/dist/zod/tempo/MultisigWitness.d.ts +0 -152
- package/dist/zod/tempo/MultisigWitness.d.ts.map +0 -1
- package/dist/zod/tempo/MultisigWitness.js +0 -76
- package/dist/zod/tempo/MultisigWitness.js.map +0 -1
- package/src/tempo/MultisigWitness.test-d.ts +0 -42
- package/src/tempo/MultisigWitness.test.ts +0 -288
- package/src/tempo/MultisigWitness.ts +0 -210
- package/src/zod/tempo/MultisigWitness.ts +0 -101
- package/src/zod/tempo/_test/MultisigWitness.test-d.ts +0 -13
- package/src/zod/tempo/_test/MultisigWitness.test.ts +0 -149
|
@@ -1,866 +1,377 @@
|
|
|
1
1
|
import { AbiFunction, Address, Hex, Secp256k1, Value } from 'ox'
|
|
2
|
-
import { getTransactionCount } from 'viem/actions'
|
|
3
2
|
import { describe, expect, test } from 'vp/test'
|
|
4
3
|
import { chain, client, fundAddress } from '../../test/tempo/config.js'
|
|
5
|
-
import {
|
|
4
|
+
import { factory } from '../../test/tempo/multisig.js'
|
|
5
|
+
import {
|
|
6
|
+
KeyAuthorization,
|
|
7
|
+
MultisigConfig,
|
|
8
|
+
MultisigOperation,
|
|
9
|
+
SignatureEnvelope,
|
|
10
|
+
} from './index.js'
|
|
6
11
|
import * as Transaction from './Transaction.js'
|
|
7
12
|
import * as TransactionReceipt from './TransactionReceipt.js'
|
|
13
|
+
import * as TransactionRequest from './TransactionRequest.js'
|
|
8
14
|
import * as TxEnvelopeTempo from './TxEnvelopeTempo.js'
|
|
9
15
|
|
|
10
|
-
const
|
|
16
|
+
const precompile = '0xaacc000000000000000000000000000000000000'
|
|
11
17
|
const updateConfig = AbiFunction.from(
|
|
12
18
|
'function updateConfig((bytes32 salt, uint64 version, uint8 threshold, (address owner, uint8 weight)[] owners) current, uint8 threshold, (address owner, uint8 weight)[] owners)',
|
|
13
19
|
)
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
async function setup() {
|
|
22
|
+
const keys = Array.from({ length: 3 }, () => {
|
|
17
23
|
const privateKey = Secp256k1.randomPrivateKey()
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function approve(parameters: {
|
|
52
|
-
address?: Address.Address | undefined
|
|
53
|
-
config: MultisigConfig.Config
|
|
54
|
-
payload: Hex.Hex
|
|
55
|
-
signers: readonly { privateKey: Hex.Hex }[]
|
|
56
|
-
}) {
|
|
57
|
-
const {
|
|
58
|
-
address = MultisigConfig.getAddress(parameters.config),
|
|
59
|
-
config,
|
|
60
|
-
payload,
|
|
61
|
-
signers,
|
|
62
|
-
} = parameters
|
|
63
|
-
const digest = MultisigConfig.getSignPayload({
|
|
64
|
-
account: address,
|
|
65
|
-
config,
|
|
66
|
-
payload,
|
|
67
|
-
})
|
|
68
|
-
const signatures = signers.map((signer) =>
|
|
69
|
-
SignatureEnvelope.from(
|
|
70
|
-
Secp256k1.sign({ payload: digest, privateKey: signer.privateKey }),
|
|
71
|
-
),
|
|
72
|
-
)
|
|
73
|
-
// The node requires approvals ordered by recovered owner address.
|
|
74
|
-
return SignatureEnvelope.sortMultisigApprovals({
|
|
75
|
-
account: address,
|
|
76
|
-
config,
|
|
77
|
-
payload,
|
|
78
|
-
signatures,
|
|
79
|
-
})
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function send(serialized: Hex.Hex) {
|
|
83
|
-
return (await client
|
|
84
|
-
.request({
|
|
85
|
-
method: 'eth_sendRawTransactionSync',
|
|
86
|
-
params: [serialized],
|
|
87
|
-
})
|
|
88
|
-
.then((transaction) => TransactionReceipt.fromRpc(transaction as any)))!
|
|
89
|
-
}
|
|
24
|
+
return {
|
|
25
|
+
privateKey,
|
|
26
|
+
address: Address.fromPublicKey(Secp256k1.getPublicKey({ privateKey })),
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
const config: MultisigConfig.Config = MultisigConfig.from({
|
|
30
|
+
salt: Hex.random(32),
|
|
31
|
+
threshold: 2,
|
|
32
|
+
owners: keys.map((key) => ({ owner: key.address, weight: 1 })),
|
|
33
|
+
})
|
|
34
|
+
const account = MultisigConfig.getAddress(config, { factory })
|
|
35
|
+
await fundAddress(client, { address: account })
|
|
36
|
+
return { account, config, keys }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function transaction(
|
|
40
|
+
nonce: bigint,
|
|
41
|
+
calls: TxEnvelopeTempo.TxEnvelopeTempo['calls'] = [
|
|
42
|
+
{ to: '0x0000000000000000000000000000000000000000' },
|
|
43
|
+
],
|
|
44
|
+
) {
|
|
45
|
+
return TxEnvelopeTempo.from({
|
|
46
|
+
calls,
|
|
47
|
+
chainId: chain.id,
|
|
48
|
+
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
49
|
+
nonce,
|
|
50
|
+
gas: 5_000_000n,
|
|
51
|
+
maxFeePerGas: Value.fromGwei('20'),
|
|
52
|
+
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
53
|
+
})
|
|
54
|
+
}
|
|
90
55
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
97
|
-
nonce: 0n,
|
|
98
|
-
gas: 5_000_000n,
|
|
99
|
-
maxFeePerGas: Value.fromGwei('20'),
|
|
100
|
-
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
101
|
-
})
|
|
102
|
-
const receipt = await send(
|
|
103
|
-
TxEnvelopeTempo.serialize(transaction, {
|
|
104
|
-
signature: SignatureEnvelope.from({
|
|
105
|
-
account: multisig.account,
|
|
106
|
-
config: multisig.initialConfig,
|
|
107
|
-
signatures: approve({
|
|
108
|
-
config: multisig.initialConfig,
|
|
109
|
-
payload: TxEnvelopeTempo.getSignPayload(transaction),
|
|
110
|
-
signers: multisig.ownerKeys,
|
|
111
|
-
}),
|
|
112
|
-
}),
|
|
113
|
-
}),
|
|
114
|
-
)
|
|
115
|
-
expect(receipt.status).toBe('success')
|
|
56
|
+
function sign(account: Awaited<ReturnType<typeof setup>>, payload: Hex.Hex) {
|
|
57
|
+
const value = {
|
|
58
|
+
account: account.account,
|
|
59
|
+
config: account.config,
|
|
60
|
+
payload,
|
|
116
61
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
nonce: 0n,
|
|
158
|
-
gas: 5_000_000n,
|
|
159
|
-
maxFeePerGas: Value.fromGwei('20'),
|
|
160
|
-
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
const bootstrap_signed = TxEnvelopeTempo.serialize(bootstrap, {
|
|
164
|
-
signature: SignatureEnvelope.from({
|
|
165
|
-
account,
|
|
166
|
-
config: initialConfig,
|
|
167
|
-
signatures: approve({
|
|
168
|
-
config: initialConfig,
|
|
169
|
-
payload: TxEnvelopeTempo.getSignPayload(bootstrap),
|
|
170
|
-
signers: [ownerKeys[0]!, ownerKeys[1]!],
|
|
171
|
-
}),
|
|
172
|
-
}),
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
const bootstrap_receipt = (await client
|
|
176
|
-
.request({
|
|
177
|
-
method: 'eth_sendRawTransactionSync',
|
|
178
|
-
params: [bootstrap_signed],
|
|
62
|
+
const digest = MultisigConfig.getSignPayload(value)
|
|
63
|
+
return SignatureEnvelope.from({
|
|
64
|
+
account: account.account,
|
|
65
|
+
config: account.config,
|
|
66
|
+
signatures: SignatureEnvelope.sortMultisigApprovals({
|
|
67
|
+
...value,
|
|
68
|
+
signatures: account.keys
|
|
69
|
+
.slice(0, 2)
|
|
70
|
+
.map((key) =>
|
|
71
|
+
SignatureEnvelope.from(
|
|
72
|
+
Secp256k1.sign({ payload: digest, privateKey: key.privateKey }),
|
|
73
|
+
),
|
|
74
|
+
),
|
|
75
|
+
}),
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function submit(
|
|
80
|
+
tx: TxEnvelopeTempo.TxEnvelopeTempo,
|
|
81
|
+
signature: SignatureEnvelope.SignatureEnvelope,
|
|
82
|
+
) {
|
|
83
|
+
const result = await client.request({
|
|
84
|
+
method: 'eth_sendRawTransactionSync',
|
|
85
|
+
params: [TxEnvelopeTempo.serialize(tx, { signature })],
|
|
86
|
+
})
|
|
87
|
+
return TransactionReceipt.fromRpc(result as unknown as TransactionReceipt.Rpc)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
describe('behavior: configurable accounts', () => {
|
|
91
|
+
test('registers, spends, and round-trips the RPC signature', async () => {
|
|
92
|
+
const account = await setup()
|
|
93
|
+
for (const nonce of [0n, 1n]) {
|
|
94
|
+
const tx = transaction(nonce)
|
|
95
|
+
const signature = sign(account, TxEnvelopeTempo.getSignPayload(tx))
|
|
96
|
+
const receipt = await submit(tx, signature)
|
|
97
|
+
expect(receipt.status).toMatchInlineSnapshot('"success"')
|
|
98
|
+
expect(receipt.from).toBe(account.account)
|
|
99
|
+
const rpc = await client.request({
|
|
100
|
+
method: 'eth_getTransactionByHash',
|
|
101
|
+
params: [receipt.transactionHash],
|
|
179
102
|
})
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
expect(bootstrap_receipt.status).toBe('success')
|
|
183
|
-
expect(bootstrap_receipt.from).toBe(account)
|
|
184
|
-
|
|
185
|
-
{
|
|
186
|
-
const response = await client
|
|
187
|
-
.request({
|
|
188
|
-
method: 'eth_getTransactionByHash',
|
|
189
|
-
params: [bootstrap_receipt.transactionHash],
|
|
190
|
-
})
|
|
191
|
-
.then((tx) => Transaction.fromRpc(tx as any))
|
|
192
|
-
if (!response) throw new Error()
|
|
193
|
-
expect(response.from).toBe(account)
|
|
194
|
-
expect(response.signature?.type).toBe('multisig')
|
|
195
|
-
expect(
|
|
196
|
-
(response.signature as SignatureEnvelope.Multisig | undefined)?.config,
|
|
197
|
-
).toEqual(initialConfig)
|
|
103
|
+
const returned = Transaction.fromRpc(rpc as unknown as Transaction.Rpc)
|
|
104
|
+
expect(returned.signature).toEqual(signature)
|
|
198
105
|
}
|
|
199
|
-
|
|
200
|
-
const updateNonce = await getTransactionCount(client, {
|
|
201
|
-
address: account,
|
|
202
|
-
blockTag: 'pending',
|
|
203
|
-
})
|
|
204
|
-
const update = TxEnvelopeTempo.from({
|
|
205
|
-
calls: [
|
|
206
|
-
{
|
|
207
|
-
to: '0xaacc000000000000000000000000000000000000',
|
|
208
|
-
data: AbiFunction.encodeData(updateConfig, [
|
|
209
|
-
initialConfig,
|
|
210
|
-
rotatedConfig.threshold,
|
|
211
|
-
rotatedConfig.owners,
|
|
212
|
-
]),
|
|
213
|
-
},
|
|
214
|
-
],
|
|
215
|
-
chainId,
|
|
216
|
-
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
217
|
-
nonce: BigInt(updateNonce),
|
|
218
|
-
gas: 5_000_000n,
|
|
219
|
-
maxFeePerGas: Value.fromGwei('20'),
|
|
220
|
-
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
221
|
-
})
|
|
222
|
-
const updateSigned = TxEnvelopeTempo.serialize(update, {
|
|
223
|
-
signature: SignatureEnvelope.from({
|
|
224
|
-
account,
|
|
225
|
-
config: initialConfig,
|
|
226
|
-
signatures: approve({
|
|
227
|
-
config: initialConfig,
|
|
228
|
-
payload: TxEnvelopeTempo.getSignPayload(update),
|
|
229
|
-
signers: [ownerKeys[0]!, ownerKeys[1]!],
|
|
230
|
-
}),
|
|
231
|
-
}),
|
|
232
|
-
})
|
|
233
|
-
const updateReceipt = (await client
|
|
234
|
-
.request({
|
|
235
|
-
method: 'eth_sendRawTransactionSync',
|
|
236
|
-
params: [updateSigned],
|
|
237
|
-
})
|
|
238
|
-
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
239
|
-
expect(updateReceipt.status).toBe('success')
|
|
240
|
-
|
|
241
|
-
const nonce = await getTransactionCount(client, {
|
|
242
|
-
address: account,
|
|
243
|
-
blockTag: 'pending',
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
const spend = TxEnvelopeTempo.from({
|
|
247
|
-
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
248
|
-
chainId,
|
|
249
|
-
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
250
|
-
nonce: BigInt(nonce),
|
|
251
|
-
gas: 5_000_000n,
|
|
252
|
-
maxFeePerGas: Value.fromGwei('20'),
|
|
253
|
-
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
const spend_signed = TxEnvelopeTempo.serialize(spend, {
|
|
257
|
-
signature: SignatureEnvelope.from({
|
|
258
|
-
account,
|
|
259
|
-
config: rotatedConfig,
|
|
260
|
-
signatures: approve({
|
|
261
|
-
address: account,
|
|
262
|
-
config: rotatedConfig,
|
|
263
|
-
payload: TxEnvelopeTempo.getSignPayload(spend),
|
|
264
|
-
signers: [replacement],
|
|
265
|
-
}),
|
|
266
|
-
}),
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
const spend_receipt = (await client
|
|
270
|
-
.request({
|
|
271
|
-
method: 'eth_sendRawTransactionSync',
|
|
272
|
-
params: [spend_signed],
|
|
273
|
-
})
|
|
274
|
-
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
275
|
-
expect(spend_receipt).toBeDefined()
|
|
276
|
-
expect(spend_receipt.status).toBe('success')
|
|
277
|
-
expect(spend_receipt.from).toBe(account)
|
|
278
106
|
})
|
|
279
107
|
|
|
280
|
-
test('
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
})
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
maxFeePerGas: Value.fromGwei('20'),
|
|
327
|
-
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
328
|
-
})
|
|
329
|
-
const digest = MultisigConfig.getSignPayload({
|
|
330
|
-
account,
|
|
331
|
-
config: initialConfig,
|
|
332
|
-
payload: TxEnvelopeTempo.getSignPayload(bootstrap),
|
|
333
|
-
})
|
|
334
|
-
const nested = SignatureEnvelope.from({
|
|
335
|
-
account: child.account,
|
|
336
|
-
config: child.initialConfig,
|
|
337
|
-
signatures: approve({
|
|
338
|
-
config: child.initialConfig,
|
|
339
|
-
payload: digest,
|
|
340
|
-
signers: child.ownerKeys,
|
|
341
|
-
}),
|
|
342
|
-
})
|
|
343
|
-
const bootstrapSigned = TxEnvelopeTempo.serialize(bootstrap, {
|
|
344
|
-
signature: SignatureEnvelope.from({
|
|
345
|
-
account,
|
|
346
|
-
config: initialConfig,
|
|
347
|
-
signatures: [nested],
|
|
348
|
-
}),
|
|
349
|
-
})
|
|
350
|
-
|
|
351
|
-
const receipt = (await client
|
|
352
|
-
.request({
|
|
353
|
-
method: 'eth_sendRawTransactionSync',
|
|
354
|
-
params: [bootstrapSigned],
|
|
355
|
-
})
|
|
356
|
-
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
357
|
-
expect(receipt.status).toBe('success')
|
|
358
|
-
expect(receipt.from).toBe(account)
|
|
359
|
-
|
|
360
|
-
const transaction = TxEnvelopeTempo.from({
|
|
361
|
-
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
362
|
-
chainId,
|
|
363
|
-
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
364
|
-
nonce: 1n,
|
|
365
|
-
gas: 5_000_000n,
|
|
366
|
-
maxFeePerGas: Value.fromGwei('20'),
|
|
367
|
-
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
368
|
-
})
|
|
369
|
-
const parentDigest = MultisigConfig.getSignPayload({
|
|
370
|
-
account,
|
|
371
|
-
config: initialConfig,
|
|
372
|
-
payload: TxEnvelopeTempo.getSignPayload(transaction),
|
|
373
|
-
})
|
|
374
|
-
const childSignature = SignatureEnvelope.from({
|
|
375
|
-
account: child.account,
|
|
376
|
-
config: child.initialConfig,
|
|
377
|
-
signatures: approve({
|
|
378
|
-
config: child.initialConfig,
|
|
379
|
-
payload: parentDigest,
|
|
380
|
-
signers: child.ownerKeys,
|
|
381
|
-
}),
|
|
382
|
-
})
|
|
383
|
-
const serialized = TxEnvelopeTempo.serialize(transaction, {
|
|
384
|
-
signature: SignatureEnvelope.from({
|
|
385
|
-
account,
|
|
386
|
-
config: initialConfig,
|
|
387
|
-
signatures: [childSignature],
|
|
388
|
-
}),
|
|
389
|
-
})
|
|
390
|
-
const nestedReceipt = (await client
|
|
391
|
-
.request({
|
|
392
|
-
method: 'eth_sendRawTransactionSync',
|
|
393
|
-
params: [serialized],
|
|
394
|
-
})
|
|
395
|
-
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
396
|
-
expect(nestedReceipt.status).toBe('success')
|
|
397
|
-
expect(nestedReceipt.from).toBe(account)
|
|
108
|
+
test('selects operation approvals and submits the serialized transaction', async () => {
|
|
109
|
+
const account = await setup()
|
|
110
|
+
const tx = transaction(0n)
|
|
111
|
+
const serialized = TxEnvelopeTempo.serialize(tx)
|
|
112
|
+
const hash = MultisigOperation.getHash({
|
|
113
|
+
account: account.account,
|
|
114
|
+
config: account.config,
|
|
115
|
+
transaction: serialized,
|
|
116
|
+
type: 'transaction',
|
|
117
|
+
})
|
|
118
|
+
const selected = await MultisigOperation.selectApprovals({
|
|
119
|
+
account: account.account,
|
|
120
|
+
approvals: account.keys.map(({ privateKey }) =>
|
|
121
|
+
SignatureEnvelope.serialize(
|
|
122
|
+
SignatureEnvelope.from(Secp256k1.sign({ payload: hash, privateKey })),
|
|
123
|
+
),
|
|
124
|
+
),
|
|
125
|
+
config: account.config,
|
|
126
|
+
hash,
|
|
127
|
+
})
|
|
128
|
+
const operation = MultisigOperation.from({
|
|
129
|
+
account: account.account,
|
|
130
|
+
approvals: selected.approvals,
|
|
131
|
+
config: account.config,
|
|
132
|
+
createdAt: 1,
|
|
133
|
+
hash,
|
|
134
|
+
signatureCount: selected.signatureCount,
|
|
135
|
+
status: 'pending',
|
|
136
|
+
threshold: selected.threshold,
|
|
137
|
+
transaction: serialized,
|
|
138
|
+
type: 'transaction',
|
|
139
|
+
updatedAt: 1,
|
|
140
|
+
weight: selected.weight,
|
|
141
|
+
})
|
|
142
|
+
const signed = MultisigOperation.serializeTransaction(operation, {
|
|
143
|
+
approvals: selected.selectedApprovals,
|
|
144
|
+
})
|
|
145
|
+
const result = await client.request({
|
|
146
|
+
method: 'eth_sendRawTransactionSync',
|
|
147
|
+
params: [signed],
|
|
148
|
+
})
|
|
149
|
+
const receipt = TransactionReceipt.fromRpc(
|
|
150
|
+
result as unknown as TransactionReceipt.Rpc,
|
|
151
|
+
)
|
|
152
|
+
expect(receipt.status).toMatchInlineSnapshot('"success"')
|
|
153
|
+
expect(receipt.from).toBe(account.account)
|
|
398
154
|
})
|
|
399
155
|
|
|
400
|
-
test('
|
|
401
|
-
const
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
nonce: 1n,
|
|
156
|
+
test('simulates registration with the full config without persisting it', async () => {
|
|
157
|
+
const account = await setup()
|
|
158
|
+
const data = AbiFunction.encodeData(
|
|
159
|
+
AbiFunction.from(
|
|
160
|
+
'function getConfigCommitment(address account) view returns (bytes32)',
|
|
161
|
+
),
|
|
162
|
+
[account.account],
|
|
163
|
+
)
|
|
164
|
+
const request = TransactionRequest.toRpc({
|
|
165
|
+
from: account.account,
|
|
166
|
+
to: precompile,
|
|
167
|
+
data,
|
|
413
168
|
gas: 5_000_000n,
|
|
414
|
-
maxFeePerGas: Value.fromGwei('20'),
|
|
415
|
-
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
416
|
-
})
|
|
417
|
-
const signedByOwners = TxEnvelopeTempo.from(transaction, {
|
|
418
|
-
signature: SignatureEnvelope.from({
|
|
419
|
-
account: multisig.account,
|
|
420
|
-
config: multisig.initialConfig,
|
|
421
|
-
signatures: approve({
|
|
422
|
-
config: multisig.initialConfig,
|
|
423
|
-
payload: TxEnvelopeTempo.getSignPayload(transaction),
|
|
424
|
-
signers: multisig.ownerKeys,
|
|
425
|
-
}),
|
|
426
|
-
}),
|
|
427
|
-
})
|
|
428
|
-
const sponsored = TxEnvelopeTempo.from({
|
|
429
|
-
...signedByOwners,
|
|
430
169
|
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
170
|
+
multisigSimulation: {
|
|
171
|
+
config: account.config,
|
|
172
|
+
approvals: account.config.owners
|
|
173
|
+
.slice(0, 2)
|
|
174
|
+
.map(({ owner }) => ({ owner, keyType: 'secp256k1' })),
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
const simulated = await client.request({
|
|
178
|
+
method: 'eth_call',
|
|
179
|
+
params: [request as never, 'latest'],
|
|
180
|
+
})
|
|
181
|
+
expect(simulated).not.toBe(MultisigConfig.zeroSalt)
|
|
182
|
+
const estimate = await client.request({
|
|
183
|
+
method: 'eth_estimateGas',
|
|
184
|
+
params: [request as never],
|
|
185
|
+
})
|
|
186
|
+
expect(Hex.toBigInt(estimate)).toBeGreaterThan(0n)
|
|
187
|
+
const persisted = await client.request({
|
|
188
|
+
method: 'eth_call',
|
|
189
|
+
params: [{ to: precompile, data }, 'latest'],
|
|
190
|
+
})
|
|
191
|
+
expect(persisted).toBe(MultisigConfig.zeroSalt)
|
|
192
|
+
const tx = transaction(0n)
|
|
193
|
+
await submit(tx, sign(account, TxEnvelopeTempo.getSignPayload(tx)))
|
|
194
|
+
expect(
|
|
195
|
+
await client.request({
|
|
196
|
+
method: 'eth_call',
|
|
197
|
+
params: [{ to: precompile, data }, 'latest'],
|
|
435
198
|
}),
|
|
436
|
-
|
|
437
|
-
})
|
|
438
|
-
const receipt = await send(
|
|
439
|
-
TxEnvelopeTempo.serialize(sponsored, { feePayerSignature }),
|
|
440
|
-
)
|
|
441
|
-
expect(receipt.status).toBe('success')
|
|
442
|
-
expect(receipt.from).toBe(multisig.account)
|
|
443
|
-
expect(receipt.feePayer).toBe(feePayer.address)
|
|
199
|
+
).toBe(simulated)
|
|
444
200
|
})
|
|
445
201
|
|
|
446
|
-
test('
|
|
447
|
-
const
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
202
|
+
test('rotates config and rejects stale witnesses and signatures', async () => {
|
|
203
|
+
const account = await setup()
|
|
204
|
+
const replacement = account
|
|
205
|
+
const tx = transaction(0n, [
|
|
206
|
+
{
|
|
207
|
+
to: precompile,
|
|
208
|
+
data: AbiFunction.encodeData(updateConfig, [
|
|
209
|
+
{ ...account.config, salt: account.config.salt!, version: 0n },
|
|
210
|
+
2,
|
|
211
|
+
replacement.config.owners,
|
|
212
|
+
]),
|
|
213
|
+
},
|
|
452
214
|
])
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
payload: TxEnvelopeTempo.getFeePayerSignPayload(transaction, {
|
|
466
|
-
sender: multisig.account,
|
|
467
|
-
}),
|
|
468
|
-
privateKey: feePayer.privateKey,
|
|
469
|
-
})
|
|
470
|
-
const sponsored = TxEnvelopeTempo.from(transaction, { feePayerSignature })
|
|
471
|
-
const receipt = await send(
|
|
472
|
-
TxEnvelopeTempo.serialize(sponsored, {
|
|
473
|
-
signature: SignatureEnvelope.from({
|
|
474
|
-
account: multisig.account,
|
|
475
|
-
config: multisig.initialConfig,
|
|
476
|
-
signatures: approve({
|
|
477
|
-
config: multisig.initialConfig,
|
|
478
|
-
payload: TxEnvelopeTempo.getSignPayload(sponsored),
|
|
479
|
-
signers: multisig.ownerKeys,
|
|
480
|
-
}),
|
|
481
|
-
}),
|
|
482
|
-
}),
|
|
483
|
-
)
|
|
484
|
-
expect(receipt.status).toBe('success')
|
|
485
|
-
expect(receipt.from).toBe(multisig.account)
|
|
486
|
-
expect(receipt.feePayer).toBe(feePayer.address)
|
|
487
|
-
})
|
|
488
|
-
|
|
489
|
-
test('example: weighted quorum', async () => {
|
|
490
|
-
const { account, initialConfig, ownerKeys } = setup({
|
|
491
|
-
count: 3,
|
|
492
|
-
threshold: 3,
|
|
493
|
-
weights: [2, 1, 1],
|
|
494
|
-
})
|
|
495
|
-
|
|
496
|
-
await fundAddress(client, { address: account })
|
|
497
|
-
|
|
498
|
-
const transaction = (nonce: bigint) =>
|
|
499
|
-
TxEnvelopeTempo.from({
|
|
500
|
-
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
501
|
-
chainId,
|
|
502
|
-
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
503
|
-
nonce,
|
|
504
|
-
gas: 5_000_000n,
|
|
505
|
-
maxFeePerGas: Value.fromGwei('20'),
|
|
506
|
-
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
507
|
-
})
|
|
508
|
-
const serialize = (
|
|
509
|
-
value: ReturnType<typeof transaction>,
|
|
510
|
-
signers: readonly { privateKey: Hex.Hex }[],
|
|
511
|
-
) =>
|
|
512
|
-
TxEnvelopeTempo.serialize(value, {
|
|
513
|
-
signature: SignatureEnvelope.from({
|
|
514
|
-
account,
|
|
515
|
-
config: initialConfig,
|
|
516
|
-
signatures: approve({
|
|
517
|
-
config: initialConfig,
|
|
518
|
-
payload: TxEnvelopeTempo.getSignPayload(value),
|
|
519
|
-
signers,
|
|
520
|
-
}),
|
|
521
|
-
}),
|
|
522
|
-
})
|
|
523
|
-
|
|
524
|
-
const bootstrap = transaction(0n)
|
|
525
|
-
const bootstrapReceipt = (await client
|
|
526
|
-
.request({
|
|
527
|
-
method: 'eth_sendRawTransactionSync',
|
|
528
|
-
params: [serialize(bootstrap, [ownerKeys[0]!, ownerKeys[1]!])],
|
|
529
|
-
})
|
|
530
|
-
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
531
|
-
expect(bootstrapReceipt.status).toBe('success')
|
|
532
|
-
|
|
533
|
-
const valid = transaction(1n)
|
|
534
|
-
const validReceipt = (await client
|
|
535
|
-
.request({
|
|
536
|
-
method: 'eth_sendRawTransactionSync',
|
|
537
|
-
params: [serialize(valid, [ownerKeys[0]!, ownerKeys[2]!])],
|
|
538
|
-
})
|
|
539
|
-
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
540
|
-
expect(validReceipt.status).toBe('success')
|
|
541
|
-
|
|
542
|
-
const belowThreshold = transaction(2n)
|
|
215
|
+
expect(
|
|
216
|
+
(await submit(tx, sign(account, TxEnvelopeTempo.getSignPayload(tx))))
|
|
217
|
+
.status,
|
|
218
|
+
).toMatchInlineSnapshot('"success"')
|
|
219
|
+
const spend = transaction(1n)
|
|
220
|
+
const old = sign(account, TxEnvelopeTempo.getSignPayload(spend))
|
|
221
|
+
await expect(submit(spend, old)).rejects.toThrow(/commitment mismatch/)
|
|
222
|
+
const rotated = {
|
|
223
|
+
...replacement,
|
|
224
|
+
account: account.account,
|
|
225
|
+
config: { ...replacement.config, salt: account.config.salt, version: 1n },
|
|
226
|
+
}
|
|
543
227
|
await expect(
|
|
544
|
-
|
|
545
|
-
method: 'eth_sendRawTransactionSync',
|
|
546
|
-
params: [serialize(belowThreshold, [ownerKeys[1]!, ownerKeys[2]!])],
|
|
547
|
-
}),
|
|
228
|
+
submit(spend, { ...old, config: rotated.config }),
|
|
548
229
|
).rejects.toThrow()
|
|
230
|
+
expect(
|
|
231
|
+
(
|
|
232
|
+
await submit(
|
|
233
|
+
spend,
|
|
234
|
+
sign(rotated, TxEnvelopeTempo.getSignPayload(spend)),
|
|
235
|
+
)
|
|
236
|
+
).status,
|
|
237
|
+
).toMatchInlineSnapshot('"success"')
|
|
238
|
+
})
|
|
549
239
|
|
|
240
|
+
test('rejects insufficient approvals without registering the account', async () => {
|
|
241
|
+
const account = await setup()
|
|
242
|
+
const tx = transaction(0n)
|
|
243
|
+
const signature = sign(account, TxEnvelopeTempo.getSignPayload(tx))
|
|
550
244
|
await expect(
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
245
|
+
submit(tx, {
|
|
246
|
+
...signature,
|
|
247
|
+
signatures: signature.signatures.slice(0, 1),
|
|
554
248
|
}),
|
|
555
|
-
).rejects.toThrow()
|
|
249
|
+
).rejects.toThrow(/threshold|weight|quorum/)
|
|
250
|
+
expect((await submit(tx, signature)).status).toMatchInlineSnapshot(
|
|
251
|
+
'"success"',
|
|
252
|
+
)
|
|
556
253
|
})
|
|
557
254
|
|
|
558
|
-
test('
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
nonce: 0n,
|
|
579
|
-
gas: 5_000_000n,
|
|
580
|
-
maxFeePerGas: Value.fromGwei('20'),
|
|
581
|
-
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
582
|
-
})
|
|
583
|
-
|
|
584
|
-
const bootstrap_signed = TxEnvelopeTempo.serialize(bootstrap, {
|
|
585
|
-
signature: SignatureEnvelope.from({
|
|
586
|
-
account,
|
|
587
|
-
config: initialConfig,
|
|
588
|
-
signatures: approve({
|
|
589
|
-
config: initialConfig,
|
|
590
|
-
payload: TxEnvelopeTempo.getSignPayload(bootstrap),
|
|
591
|
-
signers: ownerKeys,
|
|
592
|
-
}),
|
|
593
|
-
}),
|
|
255
|
+
test('authorizes a multisig delegate and preserves the grant after delegate rotation', async () => {
|
|
256
|
+
const parent = await setup()
|
|
257
|
+
const delegate = await setup()
|
|
258
|
+
const grant = KeyAuthorization.from({
|
|
259
|
+
address: delegate.account,
|
|
260
|
+
account: parent.account,
|
|
261
|
+
chainId: BigInt(chain.id),
|
|
262
|
+
type: 'multisig',
|
|
263
|
+
})
|
|
264
|
+
const signed = KeyAuthorization.from(grant, {
|
|
265
|
+
signature: sign(parent, KeyAuthorization.getSignPayload(grant)),
|
|
266
|
+
})
|
|
267
|
+
const tx = { ...transaction(0n), keyAuthorization: signed }
|
|
268
|
+
const signature = SignatureEnvelope.from({
|
|
269
|
+
userAddress: parent.account,
|
|
270
|
+
version: 'v2',
|
|
271
|
+
inner: sign(
|
|
272
|
+
delegate,
|
|
273
|
+
TxEnvelopeTempo.getSignPayload(tx, { from: parent.account }),
|
|
274
|
+
),
|
|
594
275
|
})
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
.request({
|
|
598
|
-
method: 'eth_sendRawTransactionSync',
|
|
599
|
-
params: [bootstrap_signed],
|
|
600
|
-
})
|
|
601
|
-
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
602
|
-
expect(bootstrap_receipt.status).toBe('success')
|
|
603
|
-
|
|
604
|
-
// Multisig accounts authorize access keys through AccountKeychain.
|
|
605
|
-
const authorizeKey = AbiFunction.from(
|
|
606
|
-
'function authorizeKey(address keyId, uint8 signatureType, (uint64 expiry, bool enforceLimits, (address token, uint256 amount, uint64 period)[] limits, bool allowAnyCalls, (address target, (bytes4 selector, address[] recipients)[] selectorRules)[] allowedCalls) config)',
|
|
276
|
+
expect((await submit(tx, signature)).status).toMatchInlineSnapshot(
|
|
277
|
+
'"success"',
|
|
607
278
|
)
|
|
608
279
|
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
config: initialConfig,
|
|
638
|
-
signatures: approve({
|
|
639
|
-
config: initialConfig,
|
|
640
|
-
payload: TxEnvelopeTempo.getSignPayload(authorize),
|
|
641
|
-
signers: ownerKeys,
|
|
642
|
-
}),
|
|
643
|
-
}),
|
|
644
|
-
})
|
|
645
|
-
|
|
646
|
-
const authorize_receipt = (await client
|
|
647
|
-
.request({
|
|
648
|
-
method: 'eth_sendRawTransactionSync',
|
|
649
|
-
params: [authorize_signed],
|
|
650
|
-
})
|
|
651
|
-
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
652
|
-
expect(authorize_receipt.status).toBe('success')
|
|
653
|
-
expect(authorize_receipt.from).toBe(account)
|
|
654
|
-
|
|
655
|
-
const nonce = await getTransactionCount(client, {
|
|
656
|
-
address: account,
|
|
657
|
-
blockTag: 'pending',
|
|
658
|
-
})
|
|
659
|
-
|
|
660
|
-
const spend = TxEnvelopeTempo.from({
|
|
661
|
-
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
662
|
-
chainId,
|
|
663
|
-
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
664
|
-
nonce: BigInt(nonce),
|
|
665
|
-
gas: 5_000_000n,
|
|
666
|
-
maxFeePerGas: Value.fromGwei('20'),
|
|
667
|
-
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
668
|
-
})
|
|
669
|
-
|
|
670
|
-
const spend_signature = Secp256k1.sign({
|
|
671
|
-
payload: TxEnvelopeTempo.getSignPayload(spend, { from: account }),
|
|
672
|
-
privateKey: access.privateKey,
|
|
673
|
-
})
|
|
674
|
-
|
|
675
|
-
const spend_signed = TxEnvelopeTempo.serialize(spend, {
|
|
676
|
-
signature: SignatureEnvelope.from({
|
|
677
|
-
userAddress: account,
|
|
678
|
-
inner: SignatureEnvelope.from(spend_signature),
|
|
679
|
-
type: 'keychain',
|
|
680
|
-
}),
|
|
681
|
-
})
|
|
682
|
-
|
|
683
|
-
const spend_receipt = (await client
|
|
684
|
-
.request({
|
|
685
|
-
method: 'eth_sendRawTransactionSync',
|
|
686
|
-
params: [spend_signed],
|
|
687
|
-
})
|
|
688
|
-
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
689
|
-
expect(spend_receipt.status).toBe('success')
|
|
690
|
-
expect(spend_receipt.from).toBe(account)
|
|
691
|
-
|
|
692
|
-
{
|
|
693
|
-
const response = await client
|
|
694
|
-
.request({
|
|
695
|
-
method: 'eth_getTransactionByHash',
|
|
696
|
-
params: [spend_receipt.transactionHash],
|
|
697
|
-
})
|
|
698
|
-
.then((tx) => Transaction.fromRpc(tx as any))
|
|
699
|
-
if (!response) throw new Error()
|
|
700
|
-
expect(response.from).toBe(account)
|
|
701
|
-
expect(response.signature?.type).toBe('keychain')
|
|
702
|
-
expect(
|
|
703
|
-
(response.signature as SignatureEnvelope.Keychain | undefined)
|
|
704
|
-
?.userAddress,
|
|
705
|
-
).toBe(account)
|
|
280
|
+
const replacement = await setup()
|
|
281
|
+
const rotation = transaction(0n, [
|
|
282
|
+
{
|
|
283
|
+
to: precompile,
|
|
284
|
+
data: AbiFunction.encodeData(updateConfig, [
|
|
285
|
+
{ ...delegate.config, salt: delegate.config.salt!, version: 0n },
|
|
286
|
+
2,
|
|
287
|
+
replacement.config.owners,
|
|
288
|
+
]),
|
|
289
|
+
},
|
|
290
|
+
])
|
|
291
|
+
expect(
|
|
292
|
+
(
|
|
293
|
+
await submit(
|
|
294
|
+
rotation,
|
|
295
|
+
sign(delegate, TxEnvelopeTempo.getSignPayload(rotation)),
|
|
296
|
+
)
|
|
297
|
+
).status,
|
|
298
|
+
).toMatchInlineSnapshot('"success"')
|
|
299
|
+
const spend = transaction(1n)
|
|
300
|
+
const rotated = {
|
|
301
|
+
...replacement,
|
|
302
|
+
account: delegate.account,
|
|
303
|
+
config: {
|
|
304
|
+
...replacement.config,
|
|
305
|
+
salt: delegate.config.salt,
|
|
306
|
+
version: 1n,
|
|
307
|
+
},
|
|
706
308
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
address: account,
|
|
710
|
-
blockTag: 'pending',
|
|
711
|
-
})
|
|
712
|
-
const update = TxEnvelopeTempo.from({
|
|
713
|
-
calls: [
|
|
714
|
-
{
|
|
715
|
-
to: '0xaacc000000000000000000000000000000000000',
|
|
716
|
-
data: AbiFunction.encodeData(updateConfig, [
|
|
717
|
-
initialConfig,
|
|
718
|
-
initialConfig.threshold,
|
|
719
|
-
initialConfig.owners,
|
|
720
|
-
]),
|
|
721
|
-
},
|
|
722
|
-
],
|
|
723
|
-
chainId,
|
|
724
|
-
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
725
|
-
nonce: BigInt(updateNonce),
|
|
726
|
-
gas: 5_000_000n,
|
|
727
|
-
maxFeePerGas: Value.fromGwei('20'),
|
|
728
|
-
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
729
|
-
})
|
|
730
|
-
const updateSignature = Secp256k1.sign({
|
|
731
|
-
payload: TxEnvelopeTempo.getSignPayload(update, { from: account }),
|
|
732
|
-
privateKey: access.privateKey,
|
|
733
|
-
})
|
|
734
|
-
const updateSigned = TxEnvelopeTempo.serialize(update, {
|
|
735
|
-
signature: SignatureEnvelope.from({
|
|
736
|
-
userAddress: account,
|
|
737
|
-
inner: SignatureEnvelope.from(updateSignature),
|
|
738
|
-
type: 'keychain',
|
|
739
|
-
}),
|
|
740
|
-
})
|
|
741
|
-
|
|
742
|
-
const updateReceipt = (await client
|
|
743
|
-
.request({
|
|
744
|
-
method: 'eth_sendRawTransactionSync',
|
|
745
|
-
params: [updateSigned],
|
|
746
|
-
})
|
|
747
|
-
.then((tx) => TransactionReceipt.fromRpc(tx as any)))!
|
|
748
|
-
expect(updateReceipt.status).toBe('reverted')
|
|
749
|
-
})
|
|
750
|
-
|
|
751
|
-
test('example: bootstrap and immediate access key use', async () => {
|
|
752
|
-
const multisig = setup({ count: 2, threshold: 2 })
|
|
753
|
-
const accessKey = createKey()
|
|
754
|
-
await fundAddress(client, { address: multisig.account })
|
|
755
|
-
|
|
756
|
-
const keyAuthorization = KeyAuthorization.from({
|
|
757
|
-
account: multisig.account,
|
|
758
|
-
address: accessKey.address,
|
|
759
|
-
chainId: BigInt(chainId),
|
|
760
|
-
isAdmin: false,
|
|
761
|
-
type: 'secp256k1',
|
|
309
|
+
const payload = TxEnvelopeTempo.getSignPayload(spend, {
|
|
310
|
+
from: parent.account,
|
|
762
311
|
})
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
signers: multisig.ownerKeys,
|
|
312
|
+
await expect(
|
|
313
|
+
submit(
|
|
314
|
+
spend,
|
|
315
|
+
SignatureEnvelope.from({
|
|
316
|
+
userAddress: parent.account,
|
|
317
|
+
version: 'v2',
|
|
318
|
+
inner: sign(delegate, payload),
|
|
771
319
|
}),
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
320
|
+
),
|
|
321
|
+
).rejects.toThrow(/commitment mismatch/)
|
|
322
|
+
expect(
|
|
323
|
+
(
|
|
324
|
+
await submit(
|
|
325
|
+
spend,
|
|
326
|
+
SignatureEnvelope.from({
|
|
327
|
+
userAddress: parent.account,
|
|
328
|
+
version: 'v2',
|
|
329
|
+
inner: sign(rotated, payload),
|
|
330
|
+
}),
|
|
331
|
+
)
|
|
332
|
+
).status,
|
|
333
|
+
).toMatchInlineSnapshot('"success"')
|
|
334
|
+
const unauthorized = transaction(2n, [
|
|
335
|
+
{
|
|
336
|
+
to: precompile,
|
|
337
|
+
data: AbiFunction.encodeData(updateConfig, [
|
|
338
|
+
{ ...parent.config, salt: parent.config.salt!, version: 0n },
|
|
339
|
+
1,
|
|
340
|
+
parent.config.owners,
|
|
341
|
+
]),
|
|
342
|
+
},
|
|
343
|
+
])
|
|
344
|
+
const unauthorizedSignature = SignatureEnvelope.from({
|
|
345
|
+
userAddress: parent.account,
|
|
346
|
+
version: 'v2',
|
|
347
|
+
inner: sign(
|
|
348
|
+
rotated,
|
|
349
|
+
TxEnvelopeTempo.getSignPayload(unauthorized, { from: parent.account }),
|
|
350
|
+
),
|
|
783
351
|
})
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
accessKey,
|
|
788
|
-
account: multisig.account,
|
|
789
|
-
transaction,
|
|
790
|
-
}),
|
|
791
|
-
}),
|
|
792
|
-
)
|
|
793
|
-
expect(receipt.status).toBe('success')
|
|
794
|
-
expect(receipt.from).toBe(multisig.account)
|
|
352
|
+
expect(
|
|
353
|
+
(await submit(unauthorized, unauthorizedSignature)).status,
|
|
354
|
+
).toMatchInlineSnapshot('"reverted"')
|
|
795
355
|
})
|
|
796
356
|
|
|
797
|
-
test('
|
|
798
|
-
const
|
|
799
|
-
const
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
address: accessKey.address,
|
|
805
|
-
chainId: BigInt(chainId),
|
|
806
|
-
isAdmin: false,
|
|
357
|
+
test('rejects a grant signed by an individual owner instead of the account quorum', async () => {
|
|
358
|
+
const account = await setup()
|
|
359
|
+
const key = account.keys[0]!
|
|
360
|
+
const grant = KeyAuthorization.from({
|
|
361
|
+
address: key.address,
|
|
362
|
+
account: account.account,
|
|
363
|
+
chainId: BigInt(chain.id),
|
|
807
364
|
type: 'secp256k1',
|
|
808
365
|
})
|
|
809
|
-
const
|
|
810
|
-
signature:
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
signatures: approve({
|
|
814
|
-
config: multisig.initialConfig,
|
|
815
|
-
payload: KeyAuthorization.getSignPayload(keyAuthorization),
|
|
816
|
-
signers: multisig.ownerKeys,
|
|
817
|
-
}),
|
|
818
|
-
}),
|
|
819
|
-
})
|
|
820
|
-
const bootstrapTransaction = TxEnvelopeTempo.from({
|
|
821
|
-
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
822
|
-
chainId,
|
|
823
|
-
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
824
|
-
keyAuthorization: keyAuthorizationSigned,
|
|
825
|
-
nonce: 0n,
|
|
826
|
-
gas: 5_000_000n,
|
|
827
|
-
maxFeePerGas: Value.fromGwei('20'),
|
|
828
|
-
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
829
|
-
})
|
|
830
|
-
const bootstrapReceipt = await send(
|
|
831
|
-
TxEnvelopeTempo.serialize(bootstrapTransaction, {
|
|
832
|
-
signature: SignatureEnvelope.from({
|
|
833
|
-
account: multisig.account,
|
|
834
|
-
config: multisig.initialConfig,
|
|
835
|
-
signatures: approve({
|
|
836
|
-
config: multisig.initialConfig,
|
|
837
|
-
payload: TxEnvelopeTempo.getSignPayload(bootstrapTransaction),
|
|
838
|
-
signers: multisig.ownerKeys,
|
|
839
|
-
}),
|
|
840
|
-
}),
|
|
366
|
+
const signed = KeyAuthorization.from(grant, {
|
|
367
|
+
signature: Secp256k1.sign({
|
|
368
|
+
payload: KeyAuthorization.getSignPayload(grant),
|
|
369
|
+
privateKey: key.privateKey,
|
|
841
370
|
}),
|
|
842
|
-
)
|
|
843
|
-
expect(bootstrapReceipt.status).toBe('success')
|
|
844
|
-
|
|
845
|
-
const transaction = TxEnvelopeTempo.from({
|
|
846
|
-
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
847
|
-
chainId,
|
|
848
|
-
feeToken: '0x20c0000000000000000000000000000000000001',
|
|
849
|
-
nonce: 1n,
|
|
850
|
-
gas: 5_000_000n,
|
|
851
|
-
maxFeePerGas: Value.fromGwei('20'),
|
|
852
|
-
maxPriorityFeePerGas: Value.fromGwei('10'),
|
|
853
371
|
})
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
account: multisig.account,
|
|
859
|
-
transaction,
|
|
860
|
-
}),
|
|
861
|
-
}),
|
|
862
|
-
)
|
|
863
|
-
expect(receipt.status).toBe('success')
|
|
864
|
-
expect(receipt.from).toBe(multisig.account)
|
|
372
|
+
const tx = { ...transaction(0n), keyAuthorization: signed }
|
|
373
|
+
await expect(
|
|
374
|
+
submit(tx, sign(account, TxEnvelopeTempo.getSignPayload(tx))),
|
|
375
|
+
).rejects.toThrow()
|
|
865
376
|
})
|
|
866
377
|
})
|