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