tapimo 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/ApiKey.d.ts +12 -11
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +10 -12
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +139 -0
- package/dist/ApiKeys.d.ts.map +1 -0
- package/dist/ApiKeys.js +166 -0
- package/dist/ApiKeys.js.map +1 -0
- package/dist/App.d.ts +920 -910
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +36 -12
- package/dist/App.js.map +1 -1
- package/dist/Handler.d.ts +86 -0
- package/dist/Handler.d.ts.map +1 -0
- package/dist/Handler.js +83 -0
- package/dist/Handler.js.map +1 -0
- package/dist/Scope.d.ts +4 -1
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +5 -1
- package/dist/Scope.js.map +1 -1
- package/dist/Webhooks.d.ts +3 -5
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +9 -2
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/Access.d.ts +1 -3
- package/dist/admin/Access.d.ts.map +1 -1
- package/dist/admin/Access.js +1 -3
- package/dist/admin/Access.js.map +1 -1
- package/dist/admin/App.d.ts +98 -209
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +2 -4
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +7 -5
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +8 -9
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/scopes.d.ts +3 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +101 -215
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +17 -38
- package/dist/admin/apps/verified-tokens.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +1 -1
- package/dist/apps/balances.d.ts +2 -2
- package/dist/apps/receipts.d.ts +43 -43
- package/dist/apps/tokens.d.ts +454 -454
- package/dist/apps/transactions.d.ts +43 -43
- package/dist/apps/transfers.d.ts +29 -29
- package/dist/apps/webhooks.d.ts +5 -2
- package/dist/apps/webhooks.d.ts.map +1 -1
- package/dist/apps/webhooks.js +38 -34
- package/dist/apps/webhooks.js.map +1 -1
- package/dist/{Cli.d.ts → cli/Cli.d.ts} +13 -2
- package/dist/cli/Cli.d.ts.map +1 -0
- package/dist/cli/Cli.js +19 -0
- package/dist/cli/Cli.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +4 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/admin.d.ts +15 -0
- package/dist/cli/commands/admin.d.ts.map +1 -0
- package/dist/cli/commands/admin.js +60 -0
- package/dist/cli/commands/admin.js.map +1 -0
- package/dist/cloudflare.d.ts +7 -0
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +9 -0
- package/dist/cloudflare.js.map +1 -1
- package/dist/db/Db.d.ts +103 -0
- package/dist/db/Db.d.ts.map +1 -0
- package/dist/db/Db.js +274 -0
- package/dist/db/Db.js.map +1 -0
- package/dist/db/Schema.d.ts +88 -0
- package/dist/db/Schema.d.ts.map +1 -0
- package/dist/db/Schema.js +130 -0
- package/dist/db/Schema.js.map +1 -0
- package/dist/db/tables/organizations.d.ts +79 -0
- package/dist/db/tables/organizations.d.ts.map +1 -0
- package/dist/db/tables/organizations.js +81 -0
- package/dist/db/tables/organizations.js.map +1 -0
- package/dist/db/tables/verifiedTokens.d.ts +53 -0
- package/dist/db/tables/verifiedTokens.d.ts.map +1 -0
- package/dist/db/tables/verifiedTokens.js +74 -0
- package/dist/db/tables/verifiedTokens.js.map +1 -0
- package/dist/db/tables/webhookDeliveries.d.ts +71 -0
- package/dist/db/tables/webhookDeliveries.d.ts.map +1 -0
- package/dist/db/tables/webhookDeliveries.js +119 -0
- package/dist/db/tables/webhookDeliveries.js.map +1 -0
- package/dist/db/tables/webhookSubscriptions.d.ts +158 -0
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -0
- package/dist/db/tables/webhookSubscriptions.js +272 -0
- package/dist/db/tables/webhookSubscriptions.js.map +1 -0
- package/dist/handlers/internal/executionError.d.ts +25 -0
- package/dist/handlers/internal/executionError.d.ts.map +1 -0
- package/dist/handlers/internal/executionError.js +184 -0
- package/dist/handlers/internal/executionError.js.map +1 -0
- package/dist/handlers/internal/multisig.d.ts +237 -0
- package/dist/handlers/internal/multisig.d.ts.map +1 -0
- package/dist/handlers/internal/multisig.js +567 -0
- package/dist/handlers/internal/multisig.js.map +1 -0
- package/dist/handlers/internal/requestListener.d.ts +124 -0
- package/dist/handlers/internal/requestListener.d.ts.map +1 -0
- package/dist/handlers/internal/requestListener.js +173 -0
- package/dist/handlers/internal/requestListener.js.map +1 -0
- package/dist/handlers/internal/sponsorship.d.ts +83 -0
- package/dist/handlers/internal/sponsorship.d.ts.map +1 -0
- package/dist/handlers/internal/sponsorship.js +103 -0
- package/dist/handlers/internal/sponsorship.js.map +1 -0
- package/dist/handlers/internal/types.d.ts +284 -0
- package/dist/handlers/internal/types.d.ts.map +1 -0
- package/dist/handlers/internal/types.js +2 -0
- package/dist/handlers/internal/types.js.map +1 -0
- package/dist/handlers/internal/utils.d.ts +34 -0
- package/dist/handlers/internal/utils.d.ts.map +1 -0
- package/dist/handlers/internal/utils.js +131 -0
- package/dist/handlers/internal/utils.js.map +1 -0
- package/dist/handlers/relay.d.ts +119 -0
- package/dist/handlers/relay.d.ts.map +1 -0
- package/dist/handlers/relay.js +1264 -0
- package/dist/handlers/relay.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/internal/Auth.d.ts +16 -10
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +72 -12
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/RpcSchema.d.ts +10 -0
- package/dist/internal/RpcSchema.d.ts.map +1 -0
- package/dist/internal/RpcSchema.js +2 -0
- package/dist/internal/RpcSchema.js.map +1 -0
- package/dist/internal/Store.d.ts +42 -3
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +65 -3
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/VerifiedTokens.d.ts +15 -34
- package/dist/internal/VerifiedTokens.d.ts.map +1 -1
- package/dist/internal/VerifiedTokens.js +115 -159
- package/dist/internal/VerifiedTokens.js.map +1 -1
- package/dist/internal/Webhooks.d.ts +49 -35
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +92 -205
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +7 -2
- package/src/ApiKey.test.ts +3 -3
- package/src/ApiKey.ts +10 -12
- package/src/ApiKeys.test.ts +175 -0
- package/src/ApiKeys.ts +205 -0
- package/src/App.test-d.ts +1 -1
- package/src/App.test.ts +10768 -351
- package/src/App.ts +79 -36
- package/src/Client.test-d.ts +1 -1
- package/src/Handler.ts +154 -0
- package/src/Scope.ts +5 -1
- package/src/Webhooks.test.ts +8 -7
- package/src/Webhooks.ts +11 -5
- package/src/admin/Access.ts +1 -3
- package/src/admin/App.ts +19 -24
- package/src/admin/apps/api-keys.test.ts +19 -18
- package/src/admin/apps/api-keys.ts +8 -9
- package/src/admin/apps/scopes.test.ts +4 -0
- package/src/admin/apps/verified-tokens.test.ts +3 -30
- package/src/admin/apps/verified-tokens.ts +17 -36
- package/src/admin/ui.generated.ts +1 -1
- package/src/apps/balances.test.ts +4 -3
- package/src/apps/blocks.test.ts +1 -1
- package/src/apps/exchanges.test.ts +1 -1
- package/src/apps/indexer.test.ts +1 -1
- package/src/apps/receipts.test.ts +1 -1
- package/src/apps/rpc.test.ts +100 -10
- package/src/apps/tokenlist.test.ts +5 -7
- package/src/apps/tokens.test.ts +4 -3
- package/src/apps/transactions.test.ts +1 -1
- package/src/apps/transfers.test.ts +1 -1
- package/src/apps/verified-tokens.test.ts +24 -12
- package/src/apps/webhooks.test.ts +130 -143
- package/src/apps/webhooks.ts +45 -47
- package/src/{Cli.test.ts → cli/Cli.test.ts} +10 -9
- package/src/{Cli.ts → cli/Cli.ts} +16 -9
- package/src/cli/bin.ts +4 -0
- package/src/cli/commands/admin.test.ts +71 -0
- package/src/cli/commands/admin.ts +60 -0
- package/src/cloudflare.ts +10 -0
- package/src/db/Db.test.ts +84 -0
- package/src/db/Db.ts +383 -0
- package/src/db/Schema.ts +164 -0
- package/src/db/tables/organizations.test.ts +124 -0
- package/src/db/tables/organizations.ts +127 -0
- package/src/db/tables/verifiedTokens.ts +111 -0
- package/src/db/tables/webhookDeliveries.ts +159 -0
- package/src/db/tables/webhookSubscriptions.ts +358 -0
- package/src/handlers/internal/executionError.ts +205 -0
- package/src/handlers/internal/multisig.localnet.test.ts +146 -0
- package/src/handlers/internal/multisig.test.ts +744 -0
- package/src/handlers/internal/multisig.ts +858 -0
- package/src/handlers/internal/relay.localnet.test.ts +2035 -0
- package/src/handlers/internal/requestListener.ts +273 -0
- package/src/handlers/internal/sponsorship.ts +186 -0
- package/src/handlers/internal/types.ts +378 -0
- package/src/handlers/internal/utils.ts +153 -0
- package/src/handlers/relay.ts +1605 -0
- package/src/index.ts +4 -2
- package/src/internal/Auth.test.ts +21 -21
- package/src/internal/Auth.ts +92 -19
- package/src/internal/RateLimit.test.ts +1 -1
- package/src/internal/RpcSchema.ts +10 -0
- package/src/internal/Store.test.ts +98 -10
- package/src/internal/Store.ts +112 -6
- package/src/internal/VerifiedTokens.test.ts +99 -115
- package/src/internal/VerifiedTokens.ts +123 -175
- package/src/internal/Webhooks.test.ts +107 -107
- package/src/internal/Webhooks.ts +102 -220
- package/dist/ApiKeySource.d.ts +0 -166
- package/dist/ApiKeySource.d.ts.map +0 -1
- package/dist/ApiKeySource.js +0 -234
- package/dist/ApiKeySource.js.map +0 -1
- package/dist/Cli.d.ts.map +0 -1
- package/dist/Cli.js +0 -13
- package/dist/Cli.js.map +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000000.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000001.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000002.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000003.svg +0 -1
- package/seeds/31318/verified-tokens.json +0 -30
- package/seeds/4217/icons/0x20c0000000000000000000000000000000000000.svg +0 -25
- package/seeds/4217/icons/0x20c000000000000000000000048c8f36df1c9a4a.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000000520792dcccccccc.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000000a6da882d075a4c3.svg +0 -4
- package/seeds/4217/icons/0x20c000000000000000000000111111111e910f0f.svg +0 -30
- package/seeds/4217/icons/0x20c00000000000000000000014f22ca97301eb73.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000001621e21f71cf12fb.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000002f52d5cc21a3207b.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000003158081efd85bfc2.svg +0 -11
- package/seeds/4217/icons/0x20c0000000000000000000003554d28269e0f3c2.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000383a23bacb546ab9.svg +0 -1
- package/seeds/4217/icons/0x20c00000000000000000000042109aef2f8b28e1.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000005c0bac7cef389a11.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000006fd9a167923ba194.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000007f7ba549dd0251b9.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000008191667423f70e67.svg +0 -5
- package/seeds/4217/icons/0x20c0000000000000000000008ee4fcff88888888.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000009a4a4b17e0dc6651.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000ab02d39df30bd17e.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000ae247a1130450f09.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000aeed2ec36a54d0e5.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000b9537d11c60e8b50.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000bd95bfb69fbe6ce3.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000c412ec89d0c08be5.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000f047dd7018e50367.png +0 -0
- package/seeds/4217/verified-tokens.json +0 -177
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000000.svg +0 -25
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000001.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000002.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000003.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000009e8d7eb59b783726.svg +0 -1
- package/seeds/42431/icons/0x20c000000000000000000000d72572838bbee59c.svg +0 -1
- package/seeds/42431/verified-tokens.json +0 -44
- package/seeds/openrpc/README.md +0 -27
- package/seeds/openrpc/execution-apis.json +0 -8989
- package/src/ApiKeySource.test-d.ts +0 -11
- package/src/ApiKeySource.test.ts +0 -295
- package/src/ApiKeySource.ts +0 -303
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
import { Address, Hex, P256 } from 'ox'
|
|
2
|
+
import { MultisigConfig, SignatureEnvelope, TxEnvelopeTempo } from 'ox/tempo'
|
|
3
|
+
import { createClient, custom } from 'viem'
|
|
4
|
+
import { Account, Transaction } from 'viem/tempo'
|
|
5
|
+
import { tempoDevnet } from 'viem/tempo/chains'
|
|
6
|
+
|
|
7
|
+
import * as Multisig from './multisig.js'
|
|
8
|
+
|
|
9
|
+
const privateKey_1 = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
|
|
10
|
+
const privateKey_2 = '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d'
|
|
11
|
+
const privateKey_3 = '0x7c852118294f7e395961158d39c448445a22a434adcd1d4e9048bcd3d85e9e4d'
|
|
12
|
+
const feeToken = '0x20c0000000000000000000000000000000000000' as const
|
|
13
|
+
|
|
14
|
+
describe('memoryStore', () => {
|
|
15
|
+
test('behavior: submission claims are exclusive until TTL expiry', async () => {
|
|
16
|
+
const store = Multisig.memoryStore()
|
|
17
|
+
const operation = createOperation()
|
|
18
|
+
|
|
19
|
+
await store.savePending(operation)
|
|
20
|
+
const first = await store.claimSubmission(operation, { ttl: 1_000 })
|
|
21
|
+
const second = await store.claimSubmission(operation, { ttl: 1_000 })
|
|
22
|
+
|
|
23
|
+
expect({ first: first.status, second: second.status }).toMatchInlineSnapshot(`
|
|
24
|
+
{
|
|
25
|
+
"first": "claimed",
|
|
26
|
+
"second": "submitting",
|
|
27
|
+
}
|
|
28
|
+
`)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('behavior: expired submission claims can be reclaimed', async () => {
|
|
32
|
+
const store = Multisig.memoryStore()
|
|
33
|
+
const operation = createOperation()
|
|
34
|
+
|
|
35
|
+
await store.savePending(operation)
|
|
36
|
+
await store.claimSubmission(operation, { ttl: -1 })
|
|
37
|
+
const reclaimed = await store.claimSubmission(operation, { ttl: 1_000 })
|
|
38
|
+
|
|
39
|
+
expect(reclaimed.status).toMatchInlineSnapshot(`"claimed"`)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('behavior: pending saves merge signatures and submitted operations are not listed as pending', async () => {
|
|
43
|
+
const store = Multisig.memoryStore()
|
|
44
|
+
const operation = createOperation()
|
|
45
|
+
|
|
46
|
+
await store.savePending({ ...operation, signatures: [`0x${'11'.repeat(65)}`] })
|
|
47
|
+
const merged = await store.savePending({ ...operation, signatures: [`0x${'22'.repeat(65)}`] })
|
|
48
|
+
await store.claimSubmission(merged, { ttl: 1_000 })
|
|
49
|
+
await store.setSubmitted(operation.id, `0x${'33'.repeat(32)}`)
|
|
50
|
+
|
|
51
|
+
expect({
|
|
52
|
+
listed: await store.listPendingByAddress(operation.account),
|
|
53
|
+
signatures: merged.signatures.length,
|
|
54
|
+
status: (await store.get(operation.id))?.status,
|
|
55
|
+
}).toMatchInlineSnapshot(`
|
|
56
|
+
{
|
|
57
|
+
"listed": [],
|
|
58
|
+
"signatures": 2,
|
|
59
|
+
"status": "submitted",
|
|
60
|
+
}
|
|
61
|
+
`)
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe('handleRawTransaction', () => {
|
|
66
|
+
test('behavior: rejects first approvals when no config can be resolved', async () => {
|
|
67
|
+
const owner = Account.fromSecp256k1(privateKey_1)
|
|
68
|
+
const account = Account.fromMultisig({
|
|
69
|
+
threshold: 1,
|
|
70
|
+
owners: [{ owner: owner.address, weight: 1 }],
|
|
71
|
+
})
|
|
72
|
+
const transaction = {
|
|
73
|
+
calls: [{ to: '0xcafebabecafebabecafebabecafebabecafebabe', value: 1n }],
|
|
74
|
+
chainId: tempoDevnet.id,
|
|
75
|
+
gas: 21_000n,
|
|
76
|
+
maxFeePerGas: 1n,
|
|
77
|
+
maxPriorityFeePerGas: 0n,
|
|
78
|
+
multisig: account.config,
|
|
79
|
+
nonce: 0n,
|
|
80
|
+
}
|
|
81
|
+
const signature = await owner.signTransaction(transaction as never)
|
|
82
|
+
const serialized = withoutInit(
|
|
83
|
+
await account.signTransaction({ ...transaction, signatures: [signature] } as never),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
await expect(
|
|
87
|
+
Multisig.handleRawTransaction({
|
|
88
|
+
getClient: (() => undefined) as never,
|
|
89
|
+
method: 'eth_sendRawTransaction',
|
|
90
|
+
request: { params: [serialized] },
|
|
91
|
+
store: Multisig.memoryStore(),
|
|
92
|
+
}),
|
|
93
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
94
|
+
`[RpcResponse.InvalidParamsError: Multisig config is required to collect approvals. Provide it in the bootstrap transaction or configure \`multisig.resolveConfig\`.]`,
|
|
95
|
+
)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('behavior: finalize submitted preserves async broadcast method', async () => {
|
|
99
|
+
const owner = Account.fromSecp256k1(privateKey_1)
|
|
100
|
+
const account = Account.fromMultisig({
|
|
101
|
+
threshold: 1,
|
|
102
|
+
owners: [{ owner: owner.address, weight: 1 }],
|
|
103
|
+
})
|
|
104
|
+
const transaction = {
|
|
105
|
+
calls: [{ to: '0xcafebabecafebabecafebabecafebabecafebabe', value: 1n }],
|
|
106
|
+
chainId: tempoDevnet.id,
|
|
107
|
+
gas: 21_000n,
|
|
108
|
+
maxFeePerGas: 1n,
|
|
109
|
+
maxPriorityFeePerGas: 0n,
|
|
110
|
+
multisig: account.config,
|
|
111
|
+
nonce: 0n,
|
|
112
|
+
}
|
|
113
|
+
const signature = await owner.signTransaction(transaction as never)
|
|
114
|
+
const serialized = await account.signTransaction({
|
|
115
|
+
...transaction,
|
|
116
|
+
signatures: [signature],
|
|
117
|
+
} as never)
|
|
118
|
+
let broadcastMethod: string | undefined
|
|
119
|
+
|
|
120
|
+
const hash = await Multisig.handleRawTransaction({
|
|
121
|
+
finalize: 'submitted',
|
|
122
|
+
getClient: (() => ({
|
|
123
|
+
request: async ({ method, params }: { method: string; params: unknown }) => {
|
|
124
|
+
broadcastMethod = method
|
|
125
|
+
return hashRawTransaction(params)
|
|
126
|
+
},
|
|
127
|
+
})) as never,
|
|
128
|
+
method: 'eth_sendRawTransaction',
|
|
129
|
+
request: { params: [serialized] },
|
|
130
|
+
store: Multisig.memoryStore(),
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
expect({ broadcastMethod, hash }).toMatchInlineSnapshot(`
|
|
134
|
+
{
|
|
135
|
+
"broadcastMethod": "eth_sendRawTransaction",
|
|
136
|
+
"hash": "${hash}",
|
|
137
|
+
}
|
|
138
|
+
`)
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
describe('config resolution', () => {
|
|
143
|
+
test('behavior: reuses pending bootstrap config when a later approval omits init', async () => {
|
|
144
|
+
const store = Multisig.memoryStore()
|
|
145
|
+
const owner_1 = Account.fromSecp256k1(privateKey_1)
|
|
146
|
+
const owner_2 = Account.fromSecp256k1(privateKey_2)
|
|
147
|
+
const account = Account.fromMultisig({
|
|
148
|
+
threshold: 2,
|
|
149
|
+
owners: [
|
|
150
|
+
{ owner: owner_1.address, weight: 1 },
|
|
151
|
+
{ owner: owner_2.address, weight: 1 },
|
|
152
|
+
],
|
|
153
|
+
})
|
|
154
|
+
const transaction = {
|
|
155
|
+
calls: [{ to: '0xcafebabecafebabecafebabecafebabecafebabe', value: 1n }],
|
|
156
|
+
chainId: tempoDevnet.id,
|
|
157
|
+
gas: 21_000n,
|
|
158
|
+
maxFeePerGas: 1n,
|
|
159
|
+
maxPriorityFeePerGas: 0n,
|
|
160
|
+
multisig: account.config,
|
|
161
|
+
nonce: 0n,
|
|
162
|
+
}
|
|
163
|
+
const signature_1 = await owner_1.signTransaction(transaction as never)
|
|
164
|
+
const signature_2 = await owner_2.signTransaction(transaction as never)
|
|
165
|
+
const first = await account.signTransaction({
|
|
166
|
+
...transaction,
|
|
167
|
+
signatures: [signature_1],
|
|
168
|
+
} as never)
|
|
169
|
+
const second = withoutInit(
|
|
170
|
+
await account.signTransaction({ ...transaction, signatures: [signature_2] } as never),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
await Multisig.handleRawTransaction({
|
|
174
|
+
getClient: (() => undefined) as never,
|
|
175
|
+
method: 'eth_sendRawTransaction',
|
|
176
|
+
request: { params: [first] },
|
|
177
|
+
store,
|
|
178
|
+
})
|
|
179
|
+
const hash = await Multisig.handleRawTransaction({
|
|
180
|
+
getClient: (() => ({
|
|
181
|
+
request: async ({ params }: { params: unknown }) => hashRawTransaction(params),
|
|
182
|
+
})) as never,
|
|
183
|
+
method: 'eth_sendRawTransaction',
|
|
184
|
+
request: { params: [second] },
|
|
185
|
+
store,
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
expect(hash).toMatchInlineSnapshot(`"${hash}"`)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('behavior: preserves bootstrap init when it arrives after a resolver-backed pending approval', async () => {
|
|
192
|
+
const store = Multisig.memoryStore()
|
|
193
|
+
const owner_1 = Account.fromSecp256k1(privateKey_1)
|
|
194
|
+
const owner_2 = Account.fromSecp256k1(privateKey_2)
|
|
195
|
+
const account = Account.fromMultisig({
|
|
196
|
+
threshold: 2,
|
|
197
|
+
owners: [
|
|
198
|
+
{ owner: owner_1.address, weight: 1 },
|
|
199
|
+
{ owner: owner_2.address, weight: 1 },
|
|
200
|
+
],
|
|
201
|
+
})
|
|
202
|
+
const transaction = {
|
|
203
|
+
calls: [{ to: '0xcafebabecafebabecafebabecafebabecafebabe', value: 1n }],
|
|
204
|
+
chainId: tempoDevnet.id,
|
|
205
|
+
gas: 21_000n,
|
|
206
|
+
maxFeePerGas: 1n,
|
|
207
|
+
maxPriorityFeePerGas: 0n,
|
|
208
|
+
multisig: account.config,
|
|
209
|
+
nonce: 0n,
|
|
210
|
+
}
|
|
211
|
+
const signature_1 = await owner_1.signTransaction(transaction as never)
|
|
212
|
+
const signature_2 = await owner_2.signTransaction(transaction as never)
|
|
213
|
+
const first = withoutInit(
|
|
214
|
+
await account.signTransaction({ ...transaction, signatures: [signature_1] } as never),
|
|
215
|
+
)
|
|
216
|
+
const second = await account.signTransaction({
|
|
217
|
+
...transaction,
|
|
218
|
+
signatures: [signature_2],
|
|
219
|
+
} as never)
|
|
220
|
+
let submitted: `0x${string}` | undefined
|
|
221
|
+
|
|
222
|
+
await Multisig.handleRawTransaction({
|
|
223
|
+
getClient: (() => undefined) as never,
|
|
224
|
+
method: 'eth_sendRawTransaction',
|
|
225
|
+
request: { params: [first] },
|
|
226
|
+
resolveConfig: () => account.config,
|
|
227
|
+
store,
|
|
228
|
+
})
|
|
229
|
+
const hash = await Multisig.handleRawTransaction({
|
|
230
|
+
getClient: (() => ({
|
|
231
|
+
request: async ({ params }: { params: unknown }) => {
|
|
232
|
+
submitted = Array.isArray(params) ? (params[0] as `0x${string}`) : undefined
|
|
233
|
+
return hashRawTransaction(params)
|
|
234
|
+
},
|
|
235
|
+
})) as never,
|
|
236
|
+
method: 'eth_sendRawTransaction',
|
|
237
|
+
request: { params: [second] },
|
|
238
|
+
resolveConfig: () => account.config,
|
|
239
|
+
store,
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
const transaction_submitted = Transaction.deserialize(submitted! as never) as {
|
|
243
|
+
signature: SignatureEnvelope.Multisig
|
|
244
|
+
}
|
|
245
|
+
expect({
|
|
246
|
+
hash,
|
|
247
|
+
init: transaction_submitted.signature.init
|
|
248
|
+
? MultisigConfig.toId(transaction_submitted.signature.init)
|
|
249
|
+
: undefined,
|
|
250
|
+
}).toMatchInlineSnapshot(`
|
|
251
|
+
{
|
|
252
|
+
"hash": "${hash}",
|
|
253
|
+
"init": "${MultisigConfig.toId(account.config)}",
|
|
254
|
+
}
|
|
255
|
+
`)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
test('behavior: attaches resolver-provided bootstrap config when approvals omit init', async () => {
|
|
259
|
+
const store = Multisig.memoryStore()
|
|
260
|
+
const owner_1 = Account.fromSecp256k1(privateKey_1)
|
|
261
|
+
const owner_2 = Account.fromSecp256k1(privateKey_2)
|
|
262
|
+
const account = Account.fromMultisig({
|
|
263
|
+
threshold: 2,
|
|
264
|
+
owners: [
|
|
265
|
+
{ owner: owner_1.address, weight: 1 },
|
|
266
|
+
{ owner: owner_2.address, weight: 1 },
|
|
267
|
+
],
|
|
268
|
+
})
|
|
269
|
+
const transaction = {
|
|
270
|
+
calls: [{ to: '0xcafebabecafebabecafebabecafebabecafebabe', value: 1n }],
|
|
271
|
+
chainId: tempoDevnet.id,
|
|
272
|
+
gas: 21_000n,
|
|
273
|
+
maxFeePerGas: 1n,
|
|
274
|
+
maxPriorityFeePerGas: 0n,
|
|
275
|
+
multisig: account.config,
|
|
276
|
+
nonce: 0n,
|
|
277
|
+
}
|
|
278
|
+
const signature_1 = await owner_1.signTransaction(transaction as never)
|
|
279
|
+
const signature_2 = await owner_2.signTransaction(transaction as never)
|
|
280
|
+
const first = withoutInit(
|
|
281
|
+
await account.signTransaction({ ...transaction, signatures: [signature_1] } as never),
|
|
282
|
+
)
|
|
283
|
+
const second = withoutInit(
|
|
284
|
+
await account.signTransaction({ ...transaction, signatures: [signature_2] } as never),
|
|
285
|
+
)
|
|
286
|
+
let submitted: `0x${string}` | undefined
|
|
287
|
+
|
|
288
|
+
await Multisig.handleRawTransaction({
|
|
289
|
+
getClient: (() => undefined) as never,
|
|
290
|
+
method: 'eth_sendRawTransaction',
|
|
291
|
+
request: { params: [first] },
|
|
292
|
+
resolveConfig: () => account.config,
|
|
293
|
+
store,
|
|
294
|
+
})
|
|
295
|
+
const hash = await Multisig.handleRawTransaction({
|
|
296
|
+
getClient: (() => ({
|
|
297
|
+
request: async ({ params }: { params: unknown }) => {
|
|
298
|
+
submitted = Array.isArray(params) ? (params[0] as `0x${string}`) : undefined
|
|
299
|
+
return hashRawTransaction(params)
|
|
300
|
+
},
|
|
301
|
+
})) as never,
|
|
302
|
+
method: 'eth_sendRawTransaction',
|
|
303
|
+
request: { params: [second] },
|
|
304
|
+
resolveConfig: () => account.config,
|
|
305
|
+
store,
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
const transaction_submitted = Transaction.deserialize(submitted! as never) as {
|
|
309
|
+
signature: SignatureEnvelope.Multisig
|
|
310
|
+
}
|
|
311
|
+
expect({
|
|
312
|
+
hash,
|
|
313
|
+
init: transaction_submitted.signature.init
|
|
314
|
+
? MultisigConfig.toId(transaction_submitted.signature.init)
|
|
315
|
+
: undefined,
|
|
316
|
+
}).toMatchInlineSnapshot(`
|
|
317
|
+
{
|
|
318
|
+
"hash": "${hash}",
|
|
319
|
+
"init": "${MultisigConfig.toId(account.config)}",
|
|
320
|
+
}
|
|
321
|
+
`)
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
test('behavior: preserves bootstrap init when claimed operation omits stored init config', async () => {
|
|
325
|
+
const base = Multisig.memoryStore()
|
|
326
|
+
const store: Multisig.Store = {
|
|
327
|
+
claimSubmission: async (operation, options) => {
|
|
328
|
+
const result = await base.claimSubmission(operation, options)
|
|
329
|
+
if (result.status !== 'claimed') return result
|
|
330
|
+
return {
|
|
331
|
+
operation: { ...result.operation, initConfig: undefined },
|
|
332
|
+
status: result.status,
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
get: base.get,
|
|
336
|
+
listPendingByAddress: base.listPendingByAddress,
|
|
337
|
+
savePending: base.savePending,
|
|
338
|
+
setSubmitted: base.setSubmitted,
|
|
339
|
+
}
|
|
340
|
+
const owner = Account.fromSecp256k1(privateKey_1)
|
|
341
|
+
const account = Account.fromMultisig({
|
|
342
|
+
threshold: 1,
|
|
343
|
+
owners: [{ owner: owner.address, weight: 1 }],
|
|
344
|
+
})
|
|
345
|
+
const transaction = {
|
|
346
|
+
calls: [{ to: '0xcafebabecafebabecafebabecafebabecafebabe', value: 1n }],
|
|
347
|
+
chainId: tempoDevnet.id,
|
|
348
|
+
gas: 21_000n,
|
|
349
|
+
maxFeePerGas: 1n,
|
|
350
|
+
maxPriorityFeePerGas: 0n,
|
|
351
|
+
multisig: account.config,
|
|
352
|
+
nonce: 0n,
|
|
353
|
+
}
|
|
354
|
+
const signature = await owner.signTransaction(transaction as never)
|
|
355
|
+
const serialized = await account.signTransaction({
|
|
356
|
+
...transaction,
|
|
357
|
+
signatures: [signature],
|
|
358
|
+
} as never)
|
|
359
|
+
let submitted: `0x${string}` | undefined
|
|
360
|
+
|
|
361
|
+
const hash = await Multisig.handleRawTransaction({
|
|
362
|
+
getClient: (() => ({
|
|
363
|
+
request: async ({ params }: { params: unknown }) => {
|
|
364
|
+
submitted = Array.isArray(params) ? (params[0] as `0x${string}`) : undefined
|
|
365
|
+
return hashRawTransaction(params)
|
|
366
|
+
},
|
|
367
|
+
})) as never,
|
|
368
|
+
method: 'eth_sendRawTransaction',
|
|
369
|
+
request: { params: [serialized] },
|
|
370
|
+
store,
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
const transaction_submitted = Transaction.deserialize(submitted! as never) as {
|
|
374
|
+
signature: SignatureEnvelope.Multisig
|
|
375
|
+
}
|
|
376
|
+
expect({
|
|
377
|
+
hash,
|
|
378
|
+
init: transaction_submitted.signature.init
|
|
379
|
+
? MultisigConfig.toId(transaction_submitted.signature.init)
|
|
380
|
+
: undefined,
|
|
381
|
+
}).toMatchInlineSnapshot(`
|
|
382
|
+
{
|
|
383
|
+
"hash": "${hash}",
|
|
384
|
+
"init": "${MultisigConfig.toId(account.config)}",
|
|
385
|
+
}
|
|
386
|
+
`)
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
test('behavior: rejects resolved configs that do not match the multisig account', async () => {
|
|
390
|
+
const owner_1 = Account.fromSecp256k1(privateKey_1)
|
|
391
|
+
const owner_2 = Account.fromSecp256k1(privateKey_2)
|
|
392
|
+
const account = Account.fromMultisig({
|
|
393
|
+
threshold: 1,
|
|
394
|
+
owners: [{ owner: owner_1.address, weight: 1 }],
|
|
395
|
+
})
|
|
396
|
+
const wrong = Account.fromMultisig({
|
|
397
|
+
threshold: 1,
|
|
398
|
+
owners: [{ owner: owner_2.address, weight: 1 }],
|
|
399
|
+
})
|
|
400
|
+
const transaction = {
|
|
401
|
+
calls: [{ to: '0xcafebabecafebabecafebabecafebabecafebabe', value: 1n }],
|
|
402
|
+
chainId: tempoDevnet.id,
|
|
403
|
+
gas: 21_000n,
|
|
404
|
+
maxFeePerGas: 1n,
|
|
405
|
+
maxPriorityFeePerGas: 0n,
|
|
406
|
+
multisig: account.config,
|
|
407
|
+
nonce: 0n,
|
|
408
|
+
}
|
|
409
|
+
const signature = await owner_1.signTransaction(transaction as never)
|
|
410
|
+
const serialized = await account.signTransaction({
|
|
411
|
+
...transaction,
|
|
412
|
+
signatures: [signature],
|
|
413
|
+
} as never)
|
|
414
|
+
|
|
415
|
+
await expect(
|
|
416
|
+
Multisig.handleRawTransaction({
|
|
417
|
+
getClient: (() => undefined) as never,
|
|
418
|
+
method: 'eth_sendRawTransaction',
|
|
419
|
+
request: { params: [serialized] },
|
|
420
|
+
resolveConfig: () => wrong.config,
|
|
421
|
+
store: Multisig.memoryStore(),
|
|
422
|
+
}),
|
|
423
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
424
|
+
`[RpcResponse.InvalidParamsError: Signature from non-owner 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266.]`,
|
|
425
|
+
)
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
test('behavior: accepts current configs for updated multisig signer sets', async () => {
|
|
429
|
+
const owner_1 = Account.fromSecp256k1(privateKey_1)
|
|
430
|
+
const owner_2 = Account.fromSecp256k1(privateKey_2)
|
|
431
|
+
const account = Account.fromMultisig({
|
|
432
|
+
threshold: 1,
|
|
433
|
+
owners: [{ owner: owner_1.address, weight: 1 }],
|
|
434
|
+
})
|
|
435
|
+
const current = MultisigConfig.from({
|
|
436
|
+
owners: [
|
|
437
|
+
{ owner: owner_1.address, weight: 1 },
|
|
438
|
+
{ owner: owner_2.address, weight: 1 },
|
|
439
|
+
],
|
|
440
|
+
threshold: 1,
|
|
441
|
+
})
|
|
442
|
+
const transaction = {
|
|
443
|
+
calls: [{ to: '0xcafebabecafebabecafebabecafebabecafebabe', value: 1n }],
|
|
444
|
+
chainId: tempoDevnet.id,
|
|
445
|
+
gas: 21_000n,
|
|
446
|
+
maxFeePerGas: 1n,
|
|
447
|
+
maxPriorityFeePerGas: 0n,
|
|
448
|
+
multisig: account.config,
|
|
449
|
+
nonce: 1n,
|
|
450
|
+
}
|
|
451
|
+
const signature = await owner_2.signTransaction(transaction as never)
|
|
452
|
+
const serialized = withoutInit(
|
|
453
|
+
await account.signTransaction({
|
|
454
|
+
...transaction,
|
|
455
|
+
signatures: [signature],
|
|
456
|
+
} as never),
|
|
457
|
+
)
|
|
458
|
+
let submitted: `0x${string}` | undefined
|
|
459
|
+
|
|
460
|
+
const hash = await Multisig.handleRawTransaction({
|
|
461
|
+
getClient: (() => ({
|
|
462
|
+
request: async ({ params }: { params: unknown }) => {
|
|
463
|
+
submitted = Array.isArray(params) ? (params[0] as `0x${string}`) : undefined
|
|
464
|
+
return hashRawTransaction(params)
|
|
465
|
+
},
|
|
466
|
+
})) as never,
|
|
467
|
+
method: 'eth_sendRawTransaction',
|
|
468
|
+
request: { params: [serialized] },
|
|
469
|
+
resolveConfig: () => current,
|
|
470
|
+
store: Multisig.memoryStore(),
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
const transaction_submitted = Transaction.deserialize(submitted! as never) as {
|
|
474
|
+
signature: SignatureEnvelope.Multisig
|
|
475
|
+
}
|
|
476
|
+
expect({
|
|
477
|
+
hash,
|
|
478
|
+
init: transaction_submitted.signature.init,
|
|
479
|
+
genesisConfigId: transaction_submitted.signature.genesisConfigId,
|
|
480
|
+
}).toMatchInlineSnapshot(`
|
|
481
|
+
{
|
|
482
|
+
"genesisConfigId": "${MultisigConfig.toId(account.config)}",
|
|
483
|
+
"hash": "${hash}",
|
|
484
|
+
"init": undefined,
|
|
485
|
+
}
|
|
486
|
+
`)
|
|
487
|
+
})
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
describe('approval validation', () => {
|
|
491
|
+
test('behavior: rejects P256 approvals whose embedded owner key did not sign the operation', async () => {
|
|
492
|
+
const privateKey = P256.randomPrivateKey()
|
|
493
|
+
const publicKey = P256.getPublicKey({ privateKey })
|
|
494
|
+
const owner = Address.fromPublicKey(publicKey)
|
|
495
|
+
const account = Account.fromMultisig({
|
|
496
|
+
threshold: 1,
|
|
497
|
+
owners: [{ owner, weight: 1 }],
|
|
498
|
+
})
|
|
499
|
+
const transaction = {
|
|
500
|
+
calls: [{ to: '0xcafebabecafebabecafebabecafebabecafebabe', value: 1n }],
|
|
501
|
+
chainId: tempoDevnet.id,
|
|
502
|
+
gas: 21_000n,
|
|
503
|
+
maxFeePerGas: 1n,
|
|
504
|
+
maxPriorityFeePerGas: 0n,
|
|
505
|
+
multisig: account.config,
|
|
506
|
+
nonce: 0n,
|
|
507
|
+
}
|
|
508
|
+
const signature = SignatureEnvelope.serialize(
|
|
509
|
+
SignatureEnvelope.from({
|
|
510
|
+
publicKey,
|
|
511
|
+
signature: P256.sign({ payload: `0x${'44'.repeat(32)}`, privateKey }),
|
|
512
|
+
type: 'p256',
|
|
513
|
+
}),
|
|
514
|
+
)
|
|
515
|
+
const serialized = await account.signTransaction({
|
|
516
|
+
...transaction,
|
|
517
|
+
signatures: [signature],
|
|
518
|
+
} as never)
|
|
519
|
+
|
|
520
|
+
await expect(
|
|
521
|
+
Multisig.handleRawTransaction({
|
|
522
|
+
getClient: (() => ({
|
|
523
|
+
request: async ({ params }: { params: unknown }) => hashRawTransaction(params),
|
|
524
|
+
})) as never,
|
|
525
|
+
method: 'eth_sendRawTransaction',
|
|
526
|
+
request: { params: [serialized] },
|
|
527
|
+
store: Multisig.memoryStore(),
|
|
528
|
+
}),
|
|
529
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
530
|
+
`[RpcResponse.InvalidParamsError: Invalid signature from owner ${owner}.]`,
|
|
531
|
+
)
|
|
532
|
+
})
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
describe('handleRawTransaction with sponsor', () => {
|
|
536
|
+
test('behavior: broadcasts finalized multisig transactions with feePayerSignature and default feeToken', async () => {
|
|
537
|
+
const store = Multisig.memoryStore()
|
|
538
|
+
const owner_1 = Account.fromSecp256k1(privateKey_1)
|
|
539
|
+
const owner_2 = Account.fromSecp256k1(privateKey_2)
|
|
540
|
+
const feePayer = Account.fromSecp256k1(privateKey_3)
|
|
541
|
+
const account = Account.fromMultisig({
|
|
542
|
+
threshold: 2,
|
|
543
|
+
owners: [
|
|
544
|
+
{ owner: owner_1.address, weight: 1 },
|
|
545
|
+
{ owner: owner_2.address, weight: 1 },
|
|
546
|
+
],
|
|
547
|
+
})
|
|
548
|
+
const transaction = {
|
|
549
|
+
calls: [{ to: '0xcafebabecafebabecafebabecafebabecafebabe', value: 1n }],
|
|
550
|
+
chainId: tempoDevnet.id,
|
|
551
|
+
feePayerSignature: null,
|
|
552
|
+
gas: 21_000n,
|
|
553
|
+
maxFeePerGas: 1n,
|
|
554
|
+
maxPriorityFeePerGas: 0n,
|
|
555
|
+
multisig: account.config,
|
|
556
|
+
nonce: 0n,
|
|
557
|
+
}
|
|
558
|
+
const signature_1 = await owner_1.signTransaction(transaction as never)
|
|
559
|
+
const signature_2 = await owner_2.signTransaction(transaction as never)
|
|
560
|
+
const first = await account.signTransaction({
|
|
561
|
+
...transaction,
|
|
562
|
+
signatures: [signature_1],
|
|
563
|
+
} as never)
|
|
564
|
+
const second = await account.signTransaction({
|
|
565
|
+
...transaction,
|
|
566
|
+
signatures: [signature_2],
|
|
567
|
+
} as never)
|
|
568
|
+
const broadcasted: string[] = []
|
|
569
|
+
|
|
570
|
+
await Multisig.handleRawTransaction({
|
|
571
|
+
getClient: (() => undefined) as never,
|
|
572
|
+
method: 'eth_sendRawTransaction',
|
|
573
|
+
request: { params: [first] },
|
|
574
|
+
sponsor: {
|
|
575
|
+
account: feePayer as never,
|
|
576
|
+
resolveFeeToken: () => feeToken,
|
|
577
|
+
},
|
|
578
|
+
store,
|
|
579
|
+
})
|
|
580
|
+
const hash = await Multisig.handleRawTransaction({
|
|
581
|
+
getClient: () =>
|
|
582
|
+
createClient({
|
|
583
|
+
chain: tempoDevnet,
|
|
584
|
+
transport: custom({
|
|
585
|
+
request: async ({ method, params }) => {
|
|
586
|
+
if (method === 'eth_chainId') return Hex.fromNumber(tempoDevnet.id)
|
|
587
|
+
broadcasted.push((params as readonly string[])[0]!)
|
|
588
|
+
return hashRawTransaction(params)
|
|
589
|
+
},
|
|
590
|
+
}),
|
|
591
|
+
}),
|
|
592
|
+
method: 'eth_sendRawTransaction',
|
|
593
|
+
request: { params: [second] },
|
|
594
|
+
sponsor: {
|
|
595
|
+
account: feePayer as never,
|
|
596
|
+
resolveFeeToken: () => feeToken,
|
|
597
|
+
},
|
|
598
|
+
store,
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
const transaction_broadcasted = Transaction.deserialize(broadcasted[0] as never) as Record<
|
|
602
|
+
string,
|
|
603
|
+
unknown
|
|
604
|
+
>
|
|
605
|
+
const signature = transaction_broadcasted['signature'] as { init?: unknown; type?: string }
|
|
606
|
+
expect({
|
|
607
|
+
feePayerSignature: Boolean(transaction_broadcasted['feePayerSignature']),
|
|
608
|
+
feeToken: (transaction_broadcasted['feeToken'] as string).toLowerCase(),
|
|
609
|
+
hash,
|
|
610
|
+
init: Boolean(signature.init),
|
|
611
|
+
signature: signature.type,
|
|
612
|
+
}).toMatchInlineSnapshot(`
|
|
613
|
+
{
|
|
614
|
+
"feePayerSignature": true,
|
|
615
|
+
"feeToken": "0x20c0000000000000000000000000000000000000",
|
|
616
|
+
"hash": "${hash}",
|
|
617
|
+
"init": true,
|
|
618
|
+
"signature": "multisig",
|
|
619
|
+
}
|
|
620
|
+
`)
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
test('behavior: broadcasts already sponsored multisig transactions without replacing fee payer', async () => {
|
|
624
|
+
const owner = Account.fromSecp256k1(privateKey_1)
|
|
625
|
+
const feePayer_original = Account.fromSecp256k1(privateKey_2)
|
|
626
|
+
const feePayer_relay = Account.fromSecp256k1(privateKey_3)
|
|
627
|
+
const account = Account.fromMultisig({
|
|
628
|
+
threshold: 1,
|
|
629
|
+
owners: [{ owner: owner.address, weight: 1 }],
|
|
630
|
+
})
|
|
631
|
+
const transaction = {
|
|
632
|
+
calls: [{ to: '0xcafebabecafebabecafebabecafebabecafebabe', value: 1n }],
|
|
633
|
+
chainId: tempoDevnet.id,
|
|
634
|
+
feePayerSignature: null,
|
|
635
|
+
gas: 21_000n,
|
|
636
|
+
maxFeePerGas: 1n,
|
|
637
|
+
maxPriorityFeePerGas: 0n,
|
|
638
|
+
multisig: account.config,
|
|
639
|
+
nonce: 0n,
|
|
640
|
+
}
|
|
641
|
+
const signature = await owner.signTransaction(transaction as never)
|
|
642
|
+
const serialized = await account.signTransaction({
|
|
643
|
+
...transaction,
|
|
644
|
+
signatures: [signature],
|
|
645
|
+
} as never)
|
|
646
|
+
const sponsored: `0x${string}`[] = []
|
|
647
|
+
|
|
648
|
+
await Multisig.handleRawTransaction({
|
|
649
|
+
getClient: () =>
|
|
650
|
+
createClient({
|
|
651
|
+
chain: tempoDevnet,
|
|
652
|
+
transport: custom({
|
|
653
|
+
request: async ({ method, params }) => {
|
|
654
|
+
if (method === 'eth_chainId') return Hex.fromNumber(tempoDevnet.id)
|
|
655
|
+
sponsored.push((params as readonly `0x${string}`[])[0]!)
|
|
656
|
+
return hashRawTransaction(params)
|
|
657
|
+
},
|
|
658
|
+
}),
|
|
659
|
+
}),
|
|
660
|
+
method: 'eth_sendRawTransaction',
|
|
661
|
+
request: { params: [serialized] },
|
|
662
|
+
sponsor: {
|
|
663
|
+
account: feePayer_original as never,
|
|
664
|
+
resolveFeeToken: () => feeToken,
|
|
665
|
+
},
|
|
666
|
+
store: Multisig.memoryStore(),
|
|
667
|
+
})
|
|
668
|
+
const presponsored = sponsored[0]
|
|
669
|
+
if (!presponsored) throw new Error('Expected pre-sponsored transaction.')
|
|
670
|
+
|
|
671
|
+
let broadcasted: `0x${string}` | undefined
|
|
672
|
+
const hash = await Multisig.handleRawTransaction({
|
|
673
|
+
getClient: (() => ({
|
|
674
|
+
request: async ({ params }: { params: unknown }) => {
|
|
675
|
+
broadcasted = Array.isArray(params) ? (params[0] as `0x${string}`) : undefined
|
|
676
|
+
return hashRawTransaction(params)
|
|
677
|
+
},
|
|
678
|
+
})) as never,
|
|
679
|
+
method: 'eth_sendRawTransaction',
|
|
680
|
+
request: { params: [presponsored] },
|
|
681
|
+
sponsor: {
|
|
682
|
+
account: feePayer_relay as never,
|
|
683
|
+
feeToken,
|
|
684
|
+
validate: () => false,
|
|
685
|
+
},
|
|
686
|
+
store: Multisig.memoryStore(),
|
|
687
|
+
})
|
|
688
|
+
|
|
689
|
+
const transaction_broadcasted = Transaction.deserialize(broadcasted! as never) as Record<
|
|
690
|
+
string,
|
|
691
|
+
unknown
|
|
692
|
+
>
|
|
693
|
+
expect({
|
|
694
|
+
feePayerSignature: Boolean(transaction_broadcasted['feePayerSignature']),
|
|
695
|
+
feeToken: (transaction_broadcasted['feeToken'] as string).toLowerCase(),
|
|
696
|
+
hash,
|
|
697
|
+
signature: (transaction_broadcasted['signature'] as { type?: string }).type,
|
|
698
|
+
}).toMatchInlineSnapshot(`
|
|
699
|
+
{
|
|
700
|
+
"feePayerSignature": true,
|
|
701
|
+
"feeToken": "0x20c0000000000000000000000000000000000000",
|
|
702
|
+
"hash": "${hash}",
|
|
703
|
+
"signature": "multisig",
|
|
704
|
+
}
|
|
705
|
+
`)
|
|
706
|
+
})
|
|
707
|
+
})
|
|
708
|
+
|
|
709
|
+
function createOperation(): Multisig.Operation {
|
|
710
|
+
const now = Date.now()
|
|
711
|
+
return {
|
|
712
|
+
account: '0x0000000000000000000000000000000000000001',
|
|
713
|
+
chainId: tempoDevnet.id,
|
|
714
|
+
createdAt: now,
|
|
715
|
+
genesisConfigId: `0x${'11'.repeat(32)}`,
|
|
716
|
+
id: `0x${'22'.repeat(32)}`,
|
|
717
|
+
payload: `0x${'33'.repeat(32)}`,
|
|
718
|
+
signatures: [],
|
|
719
|
+
status: 'pending',
|
|
720
|
+
transaction: `0x76${'44'.repeat(32)}`,
|
|
721
|
+
updatedAt: now,
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function hashRawTransaction(params: unknown) {
|
|
726
|
+
const serialized = Array.isArray(params) ? params[0] : undefined
|
|
727
|
+
if (typeof serialized !== 'string') throw new Error('Expected raw transaction.')
|
|
728
|
+
return TxEnvelopeTempo.hash(
|
|
729
|
+
TxEnvelopeTempo.deserialize(serialized as TxEnvelopeTempo.Serialized) as TxEnvelopeTempo.Signed,
|
|
730
|
+
)
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function withoutInit(serialized: `0x${string}`) {
|
|
734
|
+
const transaction = Transaction.deserialize(serialized as never) as Record<string, unknown>
|
|
735
|
+
const signature = transaction['signature'] as SignatureEnvelope.Multisig
|
|
736
|
+
const { signature: _, ...unsigned } = transaction
|
|
737
|
+
return TxEnvelopeTempo.serialize(TxEnvelopeTempo.from(unsigned as never), {
|
|
738
|
+
signature: SignatureEnvelope.from({
|
|
739
|
+
account: signature.account,
|
|
740
|
+
genesisConfigId: signature.genesisConfigId,
|
|
741
|
+
signatures: signature.signatures,
|
|
742
|
+
}),
|
|
743
|
+
})
|
|
744
|
+
}
|