viem 3.0.0-next.13 → 3.0.0-next.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chains/definitions/arcTestnet.d.ts +5 -5
- package/dist/chains/definitions/arcTestnet.d.ts.map +1 -1
- package/dist/chains/definitions/arcTestnet.js +11 -8
- package/dist/chains/definitions/arcTestnet.js.map +1 -1
- package/dist/chains/definitions/qmsTestnet.d.ts +27 -0
- package/dist/chains/definitions/qmsTestnet.d.ts.map +1 -0
- package/dist/chains/definitions/qmsTestnet.js +23 -0
- package/dist/chains/definitions/qmsTestnet.js.map +1 -0
- package/dist/chains/definitions/tempo.d.ts +1 -1
- package/dist/chains/definitions/tempo.js +1 -1
- package/dist/chains/definitions/tempoModerato.d.ts +2 -2
- package/dist/chains/definitions/tempoModerato.js +2 -2
- package/dist/chains/index.d.ts +1 -0
- package/dist/chains/index.d.ts.map +1 -1
- package/dist/chains/index.js +1 -0
- package/dist/chains/index.js.map +1 -1
- package/dist/core/actions/transaction/waitForReceipt.d.ts +1 -1
- package/dist/core/actions/transaction/waitForReceipt.d.ts.map +1 -1
- package/dist/core/actions/transaction/waitForReceipt.js +5 -2
- package/dist/core/actions/transaction/waitForReceipt.js.map +1 -1
- package/dist/core/actions/wallet/waitForCallsStatus.d.ts.map +1 -1
- package/dist/core/actions/wallet/waitForCallsStatus.js +24 -1
- package/dist/core/actions/wallet/waitForCallsStatus.js.map +1 -1
- package/dist/internal/inference.d.ts +3 -3
- package/dist/internal/inference.d.ts.map +1 -1
- package/dist/tempo/Abis.d.ts +1935 -899
- package/dist/tempo/Abis.d.ts.map +1 -1
- package/dist/tempo/Abis.js +215 -0
- package/dist/tempo/Abis.js.map +1 -1
- package/dist/tempo/Account.d.ts +30 -30
- package/dist/tempo/Account.d.ts.map +1 -1
- package/dist/tempo/Account.js +43 -43
- package/dist/tempo/Account.js.map +1 -1
- package/dist/tempo/Addresses.d.ts +2 -0
- package/dist/tempo/Addresses.d.ts.map +1 -1
- package/dist/tempo/Addresses.js +2 -0
- package/dist/tempo/Addresses.js.map +1 -1
- package/dist/tempo/Decorator.d.ts +33 -10
- package/dist/tempo/Decorator.d.ts.map +1 -1
- package/dist/tempo/Decorator.js +9 -8
- package/dist/tempo/Decorator.js.map +1 -1
- package/dist/tempo/ExecutionError.js +1 -1
- package/dist/tempo/ExecutionError.js.map +1 -1
- package/dist/tempo/KeyAuthorizationManager.d.ts +24 -13
- package/dist/tempo/KeyAuthorizationManager.d.ts.map +1 -1
- package/dist/tempo/KeyAuthorizationManager.js +44 -25
- package/dist/tempo/KeyAuthorizationManager.js.map +1 -1
- package/dist/tempo/Relay.d.ts +61 -12
- package/dist/tempo/Relay.d.ts.map +1 -1
- package/dist/tempo/Relay.js +48 -8
- package/dist/tempo/Relay.js.map +1 -1
- package/dist/tempo/Selectors.d.ts +12 -0
- package/dist/tempo/Selectors.d.ts.map +1 -1
- package/dist/tempo/Selectors.js +12 -0
- package/dist/tempo/Selectors.js.map +1 -1
- package/dist/tempo/Transport.d.ts +19 -6
- package/dist/tempo/Transport.d.ts.map +1 -1
- package/dist/tempo/Transport.js +18 -12
- package/dist/tempo/Transport.js.map +1 -1
- package/dist/tempo/{multisig → accounts}/Config.d.ts +8 -8
- package/dist/tempo/accounts/Config.d.ts.map +1 -0
- package/dist/tempo/{multisig → accounts}/Config.js +14 -14
- package/dist/tempo/{multisig → accounts}/Config.js.map +1 -1
- package/dist/tempo/{multisig → accounts}/Operation.d.ts +8 -8
- package/dist/tempo/{multisig → accounts}/Operation.d.ts.map +1 -1
- package/dist/tempo/{multisig → accounts}/Operation.js +17 -17
- package/dist/tempo/{multisig → accounts}/Operation.js.map +1 -1
- package/dist/tempo/{multisig → accounts}/Signature.d.ts +1 -1
- package/dist/tempo/accounts/Signature.d.ts.map +1 -0
- package/dist/tempo/accounts/Signature.js +9 -0
- package/dist/tempo/accounts/Signature.js.map +1 -0
- package/dist/tempo/actions/accessKey/index.d.ts +1 -0
- package/dist/tempo/actions/accessKey/index.d.ts.map +1 -1
- package/dist/tempo/actions/accessKey/index.js +1 -0
- package/dist/tempo/actions/accessKey/index.js.map +1 -1
- package/dist/tempo/actions/accessKey/prepareAuthorization.d.ts +4 -4
- package/dist/tempo/actions/accessKey/prepareAuthorization.d.ts.map +1 -1
- package/dist/tempo/actions/accessKey/prepareAuthorization.js +10 -10
- package/dist/tempo/actions/accessKey/prepareAuthorization.js.map +1 -1
- package/dist/tempo/actions/accessKey/saveAuthorization.d.ts +40 -0
- package/dist/tempo/actions/accessKey/saveAuthorization.d.ts.map +1 -0
- package/dist/tempo/actions/accessKey/saveAuthorization.js +32 -0
- package/dist/tempo/actions/accessKey/saveAuthorization.js.map +1 -0
- package/dist/tempo/actions/accessKey/signAuthorization.d.ts +17 -13
- package/dist/tempo/actions/accessKey/signAuthorization.d.ts.map +1 -1
- package/dist/tempo/actions/accessKey/signAuthorization.js +43 -30
- package/dist/tempo/actions/accessKey/signAuthorization.js.map +1 -1
- package/dist/tempo/actions/{multisig → accounts}/getConfig.d.ts +5 -5
- package/dist/tempo/actions/accounts/getConfig.d.ts.map +1 -0
- package/dist/tempo/actions/{multisig → accounts}/getConfig.js +5 -5
- package/dist/tempo/actions/accounts/getConfig.js.map +1 -0
- package/dist/tempo/actions/{multisig → accounts}/getConfigCommitment.d.ts +3 -3
- package/dist/tempo/actions/{multisig → accounts}/getConfigCommitment.d.ts.map +1 -1
- package/dist/tempo/actions/{multisig → accounts}/getConfigCommitment.js +2 -2
- package/dist/tempo/actions/{multisig → accounts}/getConfigCommitment.js.map +1 -1
- package/dist/tempo/actions/{multisig → accounts}/getOperation.d.ts +4 -4
- package/dist/tempo/actions/{multisig → accounts}/getOperation.d.ts.map +1 -1
- package/dist/tempo/actions/{multisig → accounts}/getOperation.js +4 -4
- package/dist/tempo/actions/accounts/getOperation.js.map +1 -0
- package/dist/tempo/actions/{multisig → accounts}/index.d.ts.map +1 -1
- package/dist/tempo/actions/{multisig → accounts}/index.js.map +1 -1
- package/dist/tempo/actions/{multisig → accounts}/updateConfig.d.ts +12 -12
- package/dist/tempo/actions/{multisig → accounts}/updateConfig.d.ts.map +1 -1
- package/dist/tempo/actions/{multisig → accounts}/updateConfig.js +16 -21
- package/dist/tempo/actions/{multisig → accounts}/updateConfig.js.map +1 -1
- package/dist/tempo/actions/{multisig → accounts}/updateConfigSync.d.ts +6 -6
- package/dist/tempo/actions/{multisig → accounts}/updateConfigSync.d.ts.map +1 -1
- package/dist/tempo/actions/{multisig → accounts}/updateConfigSync.js +6 -6
- package/dist/tempo/actions/{multisig → accounts}/updateConfigSync.js.map +1 -1
- package/dist/tempo/actions/index.d.ts +2 -2
- package/dist/tempo/actions/index.d.ts.map +1 -1
- package/dist/tempo/actions/index.js +2 -2
- package/dist/tempo/actions/index.js.map +1 -1
- package/dist/tempo/chainConfig.d.ts +16 -16
- package/dist/tempo/chainConfig.d.ts.map +1 -1
- package/dist/tempo/chainConfig.js +52 -52
- package/dist/tempo/chainConfig.js.map +1 -1
- package/dist/tempo/index.d.ts +2 -2
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +2 -2
- package/dist/tempo/index.js.map +1 -1
- package/dist/tempo/internal/relay/accounts.d.ts +15 -0
- package/dist/tempo/internal/relay/accounts.d.ts.map +1 -0
- package/dist/tempo/internal/relay/{multisig.js → accounts.js} +96 -101
- package/dist/tempo/internal/relay/accounts.js.map +1 -0
- package/dist/tempo/internal/relay/feePayer.js +5 -5
- package/dist/tempo/internal/relay/feePayer.js.map +1 -1
- package/dist/tempo/internal/relay/keyAuthorization.d.ts +25 -0
- package/dist/tempo/internal/relay/keyAuthorization.d.ts.map +1 -0
- package/dist/tempo/internal/relay/keyAuthorization.js +339 -0
- package/dist/tempo/internal/relay/keyAuthorization.js.map +1 -0
- package/dist/tempo/internal/relay/plugin.d.ts +6 -3
- package/dist/tempo/internal/relay/plugin.d.ts.map +1 -1
- package/dist/tempo/internal/relay/plugin.js +15 -6
- package/dist/tempo/internal/relay/plugin.js.map +1 -1
- package/dist/tempo/internal/relay/utils.d.ts +8 -0
- package/dist/tempo/internal/relay/utils.d.ts.map +1 -1
- package/dist/utils/RpcClient.d.ts.map +1 -1
- package/dist/utils/RpcClient.js +36 -19
- package/dist/utils/RpcClient.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/src/chains/definitions/arcTestnet.ts +11 -8
- package/src/chains/definitions/qmsTestnet.ts +23 -0
- package/src/chains/definitions/tempo.ts +1 -1
- package/src/chains/definitions/tempoModerato.ts +2 -2
- package/src/chains/index.ts +1 -0
- package/src/core/actions/transaction/waitForReceipt.ts +5 -3
- package/src/core/actions/wallet/waitForCallsStatus.ts +25 -1
- package/src/internal/inference.ts +3 -3
- package/src/tempo/Abis.ts +216 -0
- package/src/tempo/Account.ts +97 -87
- package/src/tempo/Addresses.ts +2 -0
- package/src/tempo/Decorator.ts +55 -28
- package/src/tempo/ExecutionError.ts +1 -1
- package/src/tempo/KeyAuthorizationManager.ts +53 -31
- package/src/tempo/Relay.ts +69 -14
- package/src/tempo/Selectors.ts +13 -0
- package/src/tempo/Transport.ts +46 -20
- package/src/tempo/{multisig → accounts}/Config.ts +21 -21
- package/src/tempo/{multisig → accounts}/Operation.ts +26 -26
- package/src/tempo/{multisig → accounts}/Signature.ts +5 -3
- package/src/tempo/actions/accessKey/index.ts +1 -0
- package/src/tempo/actions/accessKey/prepareAuthorization.ts +16 -15
- package/src/tempo/actions/accessKey/saveAuthorization.ts +51 -0
- package/src/tempo/actions/accessKey/signAuthorization.ts +73 -51
- package/src/tempo/actions/{multisig → accounts}/getConfig.ts +7 -7
- package/src/tempo/actions/{multisig → accounts}/getConfigCommitment.ts +3 -3
- package/src/tempo/actions/{multisig → accounts}/getOperation.ts +6 -6
- package/src/tempo/actions/{multisig → accounts}/updateConfig.ts +21 -26
- package/src/tempo/actions/{multisig → accounts}/updateConfigSync.ts +7 -7
- package/src/tempo/actions/index.ts +2 -3
- package/src/tempo/chainConfig.ts +87 -85
- package/src/tempo/index.ts +4 -4
- package/src/tempo/internal/relay/{multisig.ts → accounts.ts} +121 -121
- package/src/tempo/internal/relay/feePayer.ts +5 -5
- package/src/tempo/internal/relay/keyAuthorization.ts +419 -0
- package/src/tempo/internal/relay/plugin.ts +20 -7
- package/src/utils/RpcClient.ts +38 -21
- package/src/version.ts +1 -1
- package/dist/tempo/actions/multisig/getConfig.d.ts.map +0 -1
- package/dist/tempo/actions/multisig/getConfig.js.map +0 -1
- package/dist/tempo/actions/multisig/getOperation.js.map +0 -1
- package/dist/tempo/internal/relay/multisig.d.ts +0 -4
- package/dist/tempo/internal/relay/multisig.d.ts.map +0 -1
- package/dist/tempo/internal/relay/multisig.js.map +0 -1
- package/dist/tempo/multisig/Config.d.ts.map +0 -1
- package/dist/tempo/multisig/Signature.d.ts.map +0 -1
- package/dist/tempo/multisig/Signature.js +0 -9
- package/dist/tempo/multisig/Signature.js.map +0 -1
- /package/dist/tempo/actions/{multisig → accounts}/index.d.ts +0 -0
- /package/dist/tempo/actions/{multisig → accounts}/index.js +0 -0
- /package/src/tempo/actions/{multisig → accounts}/index.ts +0 -0
|
@@ -150,8 +150,8 @@ export function create(options: Relay.feePayer.Options): Relay.Plugin {
|
|
|
150
150
|
if (!sponsored) return Request.fill(client, { ...base, feePayer: false })
|
|
151
151
|
|
|
152
152
|
const defer =
|
|
153
|
-
typeof normalized.
|
|
154
|
-
normalized.
|
|
153
|
+
typeof normalized.accountSimulation === 'object' &&
|
|
154
|
+
normalized.accountSimulation !== null
|
|
155
155
|
|
|
156
156
|
const sponsor = sponsored
|
|
157
157
|
? external
|
|
@@ -197,8 +197,8 @@ export function create(options: Relay.feePayer.Options): Relay.Plugin {
|
|
|
197
197
|
typeof parameters.feePayer === 'string' ||
|
|
198
198
|
parameters.feePayer === false ||
|
|
199
199
|
!result.capabilities?.sponsored ||
|
|
200
|
-
(typeof parameters.
|
|
201
|
-
parameters.
|
|
200
|
+
(typeof parameters.accountSimulation === 'object' &&
|
|
201
|
+
parameters.accountSimulation !== null)
|
|
202
202
|
)
|
|
203
203
|
return undefined
|
|
204
204
|
const transaction = Utils.normalizeTempoTransaction(result.tx)
|
|
@@ -420,7 +420,7 @@ async function handleRawTransaction(options: handleRawTransaction.Options) {
|
|
|
420
420
|
})
|
|
421
421
|
|
|
422
422
|
const transaction = Transaction.deserialize(serialized)
|
|
423
|
-
// Prefer sender recovered from raw envelope;
|
|
423
|
+
// Prefer sender recovered from raw envelope; configurable account finalize path supplies fallback sender.
|
|
424
424
|
const sender = transaction.from ?? options.sender
|
|
425
425
|
|
|
426
426
|
// Sponsorship only applies after sender has signed original transaction.
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { Address, type Hex, RpcResponse } from 'ox'
|
|
2
|
+
import {
|
|
3
|
+
AccountConfig,
|
|
4
|
+
AccountOperation,
|
|
5
|
+
KeyAuthorization,
|
|
6
|
+
SignatureEnvelope,
|
|
7
|
+
} from 'ox/tempo'
|
|
8
|
+
import { getNumber as getBlockNumber } from '../../../core/actions/block/getNumber.js'
|
|
9
|
+
import type { Client } from '../../../core/Client.js'
|
|
10
|
+
import { BaseError } from '../../../core/Errors.js'
|
|
11
|
+
import { getMetadata } from '../../actions/accessKey/getMetadata.js'
|
|
12
|
+
import { isWitnessBurned } from '../../actions/accessKey/isWitnessBurned.js'
|
|
13
|
+
import type * as Relay from '../../Relay.js'
|
|
14
|
+
import * as Store from '../../Store.js'
|
|
15
|
+
import { selectApprovals, validateConfig } from './accounts.js'
|
|
16
|
+
import * as Plugin from './plugin.js'
|
|
17
|
+
|
|
18
|
+
/** Maximum compare-and-set attempts before a write is abandoned. */
|
|
19
|
+
const maxWriteAttempts = 32
|
|
20
|
+
|
|
21
|
+
/** Maximum time a pending key authorization is kept. */
|
|
22
|
+
const maxTtl = 30 * 24 * 60 * 60 * 1_000
|
|
23
|
+
|
|
24
|
+
/** Creates middleware that stores pending key authorizations and attaches them to fills. @internal */
|
|
25
|
+
export function create(
|
|
26
|
+
options: Relay.keyAuthorization.Options = {},
|
|
27
|
+
): Relay.keyAuthorization.ReturnType {
|
|
28
|
+
const { store = Store.memory() } = options
|
|
29
|
+
if (!store.compareAndSet)
|
|
30
|
+
throw new RpcResponse.InvalidParamsError({
|
|
31
|
+
message:
|
|
32
|
+
'Key authorization storage requires a store with atomic `compareAndSet`.',
|
|
33
|
+
})
|
|
34
|
+
return Plugin.keyAuthorization({
|
|
35
|
+
async handleRequest(context, next) {
|
|
36
|
+
const { request } = context
|
|
37
|
+
|
|
38
|
+
if (request.method === 'relay_saveKeyAuthorization') {
|
|
39
|
+
const keyAuthorization = parse(request.params?.[0])
|
|
40
|
+
const chainId = Number(keyAuthorization.chainId)
|
|
41
|
+
if (
|
|
42
|
+
context.options.chainId !== undefined &&
|
|
43
|
+
context.options.chainId !== chainId
|
|
44
|
+
)
|
|
45
|
+
throw new RpcResponse.InvalidParamsError({
|
|
46
|
+
message: 'Conflicting chain ids.',
|
|
47
|
+
})
|
|
48
|
+
const client = context.getClient(chainId)
|
|
49
|
+
const account = await check(client, keyAuthorization)
|
|
50
|
+
if (!account) return null
|
|
51
|
+
// Drop an unusable pending authorization so it cannot block this one.
|
|
52
|
+
await read(client, store, {
|
|
53
|
+
account,
|
|
54
|
+
accessKey: keyAuthorization.address,
|
|
55
|
+
chainId,
|
|
56
|
+
})
|
|
57
|
+
if (!(await write(store, { account, keyAuthorization })))
|
|
58
|
+
throw new RpcResponse.InvalidParamsError({
|
|
59
|
+
message:
|
|
60
|
+
'A pending key authorization for this access key expires later.',
|
|
61
|
+
})
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (request.method === 'account_approveKeyAuthorization') {
|
|
66
|
+
await next()
|
|
67
|
+
const operation = AccountOperation.fromRpc(
|
|
68
|
+
context.result as AccountOperation.Rpc,
|
|
69
|
+
)
|
|
70
|
+
if (
|
|
71
|
+
operation.type !== 'keyAuthorization' ||
|
|
72
|
+
operation.status !== 'success'
|
|
73
|
+
)
|
|
74
|
+
return
|
|
75
|
+
const keyAuthorization = deserialize(operation.keyAuthorization)
|
|
76
|
+
if (!keyAuthorization) return
|
|
77
|
+
// Quorum may land after expiry, and a stored success may predate a config
|
|
78
|
+
// rotation. The approval succeeded, so failed checks skip storage instead
|
|
79
|
+
// of failing it.
|
|
80
|
+
const chainId = Number(keyAuthorization.chainId)
|
|
81
|
+
const client = context.getClient(chainId)
|
|
82
|
+
const account = await check(client, keyAuthorization).catch((error) => {
|
|
83
|
+
if (error instanceof RpcResponse.InvalidParamsError) return undefined
|
|
84
|
+
throw error
|
|
85
|
+
})
|
|
86
|
+
if (!account) return
|
|
87
|
+
await read(client, store, {
|
|
88
|
+
account,
|
|
89
|
+
accessKey: keyAuthorization.address,
|
|
90
|
+
chainId,
|
|
91
|
+
})
|
|
92
|
+
await write(store, { account, keyAuthorization })
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (request.method !== 'eth_fillTransaction') return next()
|
|
97
|
+
|
|
98
|
+
const { chainId } = context.options
|
|
99
|
+
const parameters = request.params?.[0] as Record<string, unknown>
|
|
100
|
+
const { from: account, keyId: accessKey } = parameters
|
|
101
|
+
if (
|
|
102
|
+
chainId === undefined ||
|
|
103
|
+
parameters.keyAuthorization ||
|
|
104
|
+
typeof account !== 'string' ||
|
|
105
|
+
typeof accessKey !== 'string' ||
|
|
106
|
+
!Address.validate(account, { strict: false }) ||
|
|
107
|
+
!Address.validate(accessKey, { strict: false })
|
|
108
|
+
)
|
|
109
|
+
return next()
|
|
110
|
+
|
|
111
|
+
const pending = await read(context.client, store, {
|
|
112
|
+
account,
|
|
113
|
+
accessKey,
|
|
114
|
+
chainId,
|
|
115
|
+
})
|
|
116
|
+
if (!pending) return next()
|
|
117
|
+
const metadata = await getMetadata(context.client, {
|
|
118
|
+
account,
|
|
119
|
+
accessKey,
|
|
120
|
+
})
|
|
121
|
+
// The key already landed onchain, so the authorization is spent.
|
|
122
|
+
if (isActive(metadata, accessKey)) {
|
|
123
|
+
await remove(store, pending)
|
|
124
|
+
return next()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Attach before forwarding so gas estimation and later plugins see the authorization.
|
|
128
|
+
context.request = {
|
|
129
|
+
...request,
|
|
130
|
+
params: [
|
|
131
|
+
{
|
|
132
|
+
...parameters,
|
|
133
|
+
keyAuthorization: KeyAuthorization.toRpc(pending.keyAuthorization),
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
}
|
|
137
|
+
return next()
|
|
138
|
+
},
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Removes a stored authorization only while it still holds the value that was read,
|
|
144
|
+
* so a concurrently saved replacement survives. The empty value expires immediately.
|
|
145
|
+
* @internal
|
|
146
|
+
*/
|
|
147
|
+
export async function remove(
|
|
148
|
+
store: Store.Atomic,
|
|
149
|
+
options: { key: string; value: string },
|
|
150
|
+
) {
|
|
151
|
+
const { key, value } = options
|
|
152
|
+
await store.compareAndSet(key, value, '', { expiresAt: Date.now() })
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Stores an authorization unless a pending authorization for the same key expires
|
|
157
|
+
* later, so a replayed older authorization cannot replace a newer one.
|
|
158
|
+
* @internal
|
|
159
|
+
*/
|
|
160
|
+
export async function write(
|
|
161
|
+
store: Store.Atomic,
|
|
162
|
+
options: {
|
|
163
|
+
account: Address.Address
|
|
164
|
+
keyAuthorization: KeyAuthorization.Signed
|
|
165
|
+
},
|
|
166
|
+
): Promise<boolean> {
|
|
167
|
+
const { account, keyAuthorization } = options
|
|
168
|
+
const key = getKey({
|
|
169
|
+
account,
|
|
170
|
+
accessKey: keyAuthorization.address,
|
|
171
|
+
chainId: Number(keyAuthorization.chainId),
|
|
172
|
+
})
|
|
173
|
+
const value = KeyAuthorization.serialize(keyAuthorization)
|
|
174
|
+
const expiresAt = Math.min(
|
|
175
|
+
Date.now() + maxTtl,
|
|
176
|
+
getExpiry(keyAuthorization) * 1_000,
|
|
177
|
+
)
|
|
178
|
+
for (let attempt = 0; attempt < maxWriteAttempts; attempt++) {
|
|
179
|
+
const current = (await store.getItem(key)) ?? null
|
|
180
|
+
const existing = current ? deserialize(current) : undefined
|
|
181
|
+
if (existing && current?.toLowerCase() === value.toLowerCase()) return true
|
|
182
|
+
if (existing && getExpiry(existing) >= getExpiry(keyAuthorization))
|
|
183
|
+
return false
|
|
184
|
+
// A lost compare-and-set means another writer replaced the value, so compare again.
|
|
185
|
+
if (await store.compareAndSet(key, current, value, { expiresAt }))
|
|
186
|
+
return true
|
|
187
|
+
}
|
|
188
|
+
throw new StoreConflictError()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Parses a signed key authorization from RPC parameters. */
|
|
192
|
+
function parse(value: unknown): KeyAuthorization.Signed {
|
|
193
|
+
const keyAuthorization = (() => {
|
|
194
|
+
try {
|
|
195
|
+
if (typeof value === 'string') return deserialize(value)
|
|
196
|
+
return KeyAuthorization.fromRpc(value as KeyAuthorization.Rpc)
|
|
197
|
+
} catch {
|
|
198
|
+
return undefined
|
|
199
|
+
}
|
|
200
|
+
})()
|
|
201
|
+
if (!keyAuthorization?.signature)
|
|
202
|
+
throw new RpcResponse.InvalidParamsError({
|
|
203
|
+
message: 'Expected a signed key authorization.',
|
|
204
|
+
})
|
|
205
|
+
return keyAuthorization
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Deserializes a signed key authorization, or returns `undefined` when malformed or unsigned. */
|
|
209
|
+
function deserialize(value: string): KeyAuthorization.Signed | undefined {
|
|
210
|
+
try {
|
|
211
|
+
const { signature, ...rest } = KeyAuthorization.deserialize(
|
|
212
|
+
value as Hex.Hex,
|
|
213
|
+
)
|
|
214
|
+
if (!signature) return undefined
|
|
215
|
+
return { ...rest, signature }
|
|
216
|
+
} catch {
|
|
217
|
+
return undefined
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Validates an authorization and returns the account it authorizes, or `undefined`
|
|
223
|
+
* when the access key is already active onchain.
|
|
224
|
+
*/
|
|
225
|
+
async function check(
|
|
226
|
+
client: Client,
|
|
227
|
+
keyAuthorization: KeyAuthorization.Signed,
|
|
228
|
+
): Promise<Address.Address | undefined> {
|
|
229
|
+
if (isExpired(keyAuthorization))
|
|
230
|
+
throw new RpcResponse.InvalidParamsError({
|
|
231
|
+
message: 'Key authorization has expired.',
|
|
232
|
+
})
|
|
233
|
+
const account = await verify(client, keyAuthorization)
|
|
234
|
+
if (await isBurned(client, { account, keyAuthorization }))
|
|
235
|
+
throw new RpcResponse.InvalidParamsError({
|
|
236
|
+
message: 'Key authorization witness has been burned.',
|
|
237
|
+
})
|
|
238
|
+
// Authorizations for keys that are already active would only be removed on the next fill.
|
|
239
|
+
const metadata = await getMetadata(client, {
|
|
240
|
+
account,
|
|
241
|
+
accessKey: keyAuthorization.address,
|
|
242
|
+
})
|
|
243
|
+
if (isActive(metadata, keyAuthorization.address)) return undefined
|
|
244
|
+
return account
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Verifies the authorization signature and returns the account it authorizes. */
|
|
248
|
+
async function verify(
|
|
249
|
+
client: Client,
|
|
250
|
+
keyAuthorization: KeyAuthorization.Signed,
|
|
251
|
+
): Promise<Address.Address> {
|
|
252
|
+
const { signature, ...unsigned } = keyAuthorization
|
|
253
|
+
const payload = KeyAuthorization.getSignPayload(unsigned)
|
|
254
|
+
|
|
255
|
+
if (signature.type === 'configurable') {
|
|
256
|
+
if (
|
|
257
|
+
!keyAuthorization.account ||
|
|
258
|
+
!Address.isEqual(keyAuthorization.account, signature.account)
|
|
259
|
+
)
|
|
260
|
+
throw new RpcResponse.InvalidParamsError({
|
|
261
|
+
message:
|
|
262
|
+
'Configurable account key authorization account does not match its signature.',
|
|
263
|
+
})
|
|
264
|
+
const config = AccountConfig.from(signature.config)
|
|
265
|
+
await validateConfig({
|
|
266
|
+
account: signature.account,
|
|
267
|
+
blockNumber: await getBlockNumber(client, { cacheTime: 0 }),
|
|
268
|
+
client,
|
|
269
|
+
config,
|
|
270
|
+
})
|
|
271
|
+
const approvals = await selectApprovals({
|
|
272
|
+
account: signature.account,
|
|
273
|
+
approvals: signature.signatures.map((signature) =>
|
|
274
|
+
SignatureEnvelope.serialize(signature),
|
|
275
|
+
),
|
|
276
|
+
config,
|
|
277
|
+
hash: AccountConfig.getSignPayload({
|
|
278
|
+
account: signature.account,
|
|
279
|
+
config,
|
|
280
|
+
payload,
|
|
281
|
+
}),
|
|
282
|
+
})
|
|
283
|
+
if (approvals.weight < approvals.threshold)
|
|
284
|
+
throw new RpcResponse.InvalidParamsError({
|
|
285
|
+
message:
|
|
286
|
+
'Configurable account key authorization has not reached quorum.',
|
|
287
|
+
})
|
|
288
|
+
return signature.account
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const signer = (() => {
|
|
292
|
+
try {
|
|
293
|
+
const signer = SignatureEnvelope.extractAddress({ payload, signature })
|
|
294
|
+
if (SignatureEnvelope.verify(signature, { address: signer, payload }))
|
|
295
|
+
return signer
|
|
296
|
+
} catch {}
|
|
297
|
+
throw new RpcResponse.InvalidParamsError({
|
|
298
|
+
message: 'Invalid key authorization signature.',
|
|
299
|
+
})
|
|
300
|
+
})()
|
|
301
|
+
|
|
302
|
+
// Admin-signed authorizations can only be submitted by the admin key itself,
|
|
303
|
+
// so only authorizations signed by the account can be attached for the new key.
|
|
304
|
+
if (
|
|
305
|
+
keyAuthorization.account &&
|
|
306
|
+
!Address.isEqual(keyAuthorization.account, signer)
|
|
307
|
+
)
|
|
308
|
+
throw new RpcResponse.InvalidParamsError({
|
|
309
|
+
message: 'Key authorization must be signed by its account.',
|
|
310
|
+
})
|
|
311
|
+
return signer
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Reads the pending authorization for an access key. Entries that are malformed,
|
|
316
|
+
* expired, no longer verify, or whose witness has been burned are removed.
|
|
317
|
+
*/
|
|
318
|
+
async function read(
|
|
319
|
+
client: Client,
|
|
320
|
+
store: Store.Atomic,
|
|
321
|
+
options: {
|
|
322
|
+
account: Address.Address
|
|
323
|
+
accessKey: Address.Address
|
|
324
|
+
chainId: number
|
|
325
|
+
},
|
|
326
|
+
) {
|
|
327
|
+
const { account } = options
|
|
328
|
+
const key = getKey(options)
|
|
329
|
+
const value = await store.getItem(key)
|
|
330
|
+
if (!value) return undefined
|
|
331
|
+
const keyAuthorization = deserialize(value)
|
|
332
|
+
if (
|
|
333
|
+
keyAuthorization &&
|
|
334
|
+
!isExpired(keyAuthorization) &&
|
|
335
|
+
(await isVerified(client, keyAuthorization)) &&
|
|
336
|
+
!(await isBurned(client, { account, keyAuthorization }))
|
|
337
|
+
)
|
|
338
|
+
return { key, keyAuthorization, value }
|
|
339
|
+
await remove(store, { key, value })
|
|
340
|
+
return undefined
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Returns whether the authorization still verifies. Operational failures propagate. */
|
|
344
|
+
async function isVerified(
|
|
345
|
+
client: Client,
|
|
346
|
+
keyAuthorization: KeyAuthorization.Signed,
|
|
347
|
+
) {
|
|
348
|
+
try {
|
|
349
|
+
await verify(client, keyAuthorization)
|
|
350
|
+
return true
|
|
351
|
+
} catch (error) {
|
|
352
|
+
if (error instanceof RpcResponse.InvalidParamsError) return false
|
|
353
|
+
throw error
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** Returns whether the authorization's witness has been burned onchain. */
|
|
358
|
+
async function isBurned(
|
|
359
|
+
client: Client,
|
|
360
|
+
options: {
|
|
361
|
+
account: Address.Address
|
|
362
|
+
keyAuthorization: KeyAuthorization.Signed
|
|
363
|
+
},
|
|
364
|
+
) {
|
|
365
|
+
const { account, keyAuthorization } = options
|
|
366
|
+
if (!keyAuthorization.witness) return false
|
|
367
|
+
return isWitnessBurned(client, {
|
|
368
|
+
account,
|
|
369
|
+
witness: keyAuthorization.witness,
|
|
370
|
+
})
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** Returns the authorization expiry in seconds, treating no expiry as unbounded. */
|
|
374
|
+
function getExpiry(keyAuthorization: KeyAuthorization.KeyAuthorization) {
|
|
375
|
+
const { expiry } = keyAuthorization
|
|
376
|
+
return expiry === undefined || expiry === null
|
|
377
|
+
? Number.POSITIVE_INFINITY
|
|
378
|
+
: Number(expiry)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Returns whether the access key is active onchain. */
|
|
382
|
+
function isActive(
|
|
383
|
+
metadata: getMetadata.ReturnType,
|
|
384
|
+
accessKey: Address.Address,
|
|
385
|
+
) {
|
|
386
|
+
return (
|
|
387
|
+
Address.isEqual(metadata.address, accessKey) &&
|
|
388
|
+
!metadata.isRevoked &&
|
|
389
|
+
metadata.expiry > BigInt(Math.floor(Date.now() / 1000))
|
|
390
|
+
)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function isExpired(keyAuthorization: KeyAuthorization.KeyAuthorization) {
|
|
394
|
+
return getExpiry(keyAuthorization) <= Math.floor(Date.now() / 1000)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function getKey(options: {
|
|
398
|
+
account: Address.Address
|
|
399
|
+
accessKey: Address.Address
|
|
400
|
+
chainId: number
|
|
401
|
+
}) {
|
|
402
|
+
const { account, accessKey, chainId } = options
|
|
403
|
+
return [
|
|
404
|
+
'keyAuthorization',
|
|
405
|
+
chainId,
|
|
406
|
+
account.toLowerCase(),
|
|
407
|
+
accessKey.toLowerCase(),
|
|
408
|
+
].join(':')
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Thrown when a pending key authorization cannot be written after repeated conflicts. */
|
|
412
|
+
class StoreConflictError extends BaseError {
|
|
413
|
+
override name = 'Relay.KeyAuthorization.StoreConflictError'
|
|
414
|
+
constructor() {
|
|
415
|
+
super(
|
|
416
|
+
'Pending key authorization could not be written after repeated conflicts.',
|
|
417
|
+
)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
import type * as Relay from '../../Relay.js'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const accountsPlugins = new WeakSet<Relay.Plugin>()
|
|
4
|
+
const keyAuthorizationPlugins = new WeakSet<Relay.Plugin>()
|
|
4
5
|
|
|
5
|
-
/** Registers the built-in
|
|
6
|
-
export function
|
|
7
|
-
|
|
8
|
-
return plugin as Relay.
|
|
6
|
+
/** Registers the built-in accounts plugin for transport capability detection. */
|
|
7
|
+
export function accounts(plugin: Relay.Plugin): Relay.accounts.ReturnType {
|
|
8
|
+
accountsPlugins.add(plugin)
|
|
9
|
+
return plugin as Relay.accounts.ReturnType
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
export function
|
|
12
|
-
return
|
|
12
|
+
export function isAccounts(plugin: Relay.Plugin): boolean {
|
|
13
|
+
return accountsPlugins.has(plugin)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Registers the built-in key authorization plugin for transport capability detection. */
|
|
17
|
+
export function keyAuthorization(
|
|
18
|
+
plugin: Relay.Plugin,
|
|
19
|
+
): Relay.keyAuthorization.ReturnType {
|
|
20
|
+
keyAuthorizationPlugins.add(plugin)
|
|
21
|
+
return plugin as Relay.keyAuthorization.ReturnType
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isKeyAuthorization(plugin: Relay.Plugin): boolean {
|
|
25
|
+
return keyAuthorizationPlugins.has(plugin)
|
|
13
26
|
}
|
package/src/utils/RpcClient.ts
CHANGED
|
@@ -140,8 +140,9 @@ export function http<schema extends RpcSchema.Schema = RpcSchema.Default>(
|
|
|
140
140
|
const { headers, method, signal: signal_ } = fetchOptions
|
|
141
141
|
|
|
142
142
|
try {
|
|
143
|
-
const response = await promise.withTimeout(
|
|
143
|
+
const { response, responseBody } = await promise.withTimeout(
|
|
144
144
|
async ({ signal }) => {
|
|
145
|
+
const timeoutSignal = timeout > 0 ? signal : null
|
|
145
146
|
const init: RequestInit = {
|
|
146
147
|
...fetchOptions,
|
|
147
148
|
body: stringify(body),
|
|
@@ -151,14 +152,24 @@ export function http<schema extends RpcSchema.Schema = RpcSchema.Default>(
|
|
|
151
152
|
...headers,
|
|
152
153
|
},
|
|
153
154
|
method: method || 'POST',
|
|
154
|
-
|
|
155
|
+
// A caller `signal` must not disable the timeout.
|
|
156
|
+
signal:
|
|
157
|
+
signal_ && timeoutSignal
|
|
158
|
+
? AbortSignal.any([signal_, timeoutSignal])
|
|
159
|
+
: (signal_ ?? timeoutSignal),
|
|
155
160
|
}
|
|
156
161
|
const request = new Request(url, init)
|
|
157
162
|
const overrides = (await onRequest?.(request, init)) ?? {
|
|
158
163
|
...init,
|
|
159
164
|
url,
|
|
160
165
|
}
|
|
161
|
-
|
|
166
|
+
const response = await fetchFn(overrides.url ?? url, overrides)
|
|
167
|
+
if (onResponse) await onResponse(response)
|
|
168
|
+
// Read the body within the timeout so a stalled body cannot hang.
|
|
169
|
+
const responseBody = await readResponseBody(response, {
|
|
170
|
+
maxResponseBodySize,
|
|
171
|
+
})
|
|
172
|
+
return { response, responseBody }
|
|
162
173
|
},
|
|
163
174
|
{
|
|
164
175
|
errorInstance: new TimeoutError({ body: errorBody, url }),
|
|
@@ -167,12 +178,7 @@ export function http<schema extends RpcSchema.Schema = RpcSchema.Default>(
|
|
|
167
178
|
},
|
|
168
179
|
)
|
|
169
180
|
|
|
170
|
-
if (onResponse) await onResponse(response)
|
|
171
|
-
|
|
172
181
|
let data: any
|
|
173
|
-
const responseBody = await readResponseBody(response, {
|
|
174
|
-
maxResponseBodySize,
|
|
175
|
-
})
|
|
176
182
|
if (
|
|
177
183
|
response.headers.get('Content-Type')?.startsWith('application/json')
|
|
178
184
|
)
|
|
@@ -556,6 +562,19 @@ export async function fromSocket<
|
|
|
556
562
|
else errorQueue.push(error)
|
|
557
563
|
}
|
|
558
564
|
|
|
565
|
+
// Reassigned when a reconnect re-subscribes with a new id.
|
|
566
|
+
let subscriptionId: Hex.Hex | undefined
|
|
567
|
+
let unsubscribed = false
|
|
568
|
+
function unsubscribe() {
|
|
569
|
+
unsubscribed = true
|
|
570
|
+
return new Promise<Socket.Response>((resolve) =>
|
|
571
|
+
requestInternal({
|
|
572
|
+
body: { method: 'eth_unsubscribe', params: [subscriptionId] },
|
|
573
|
+
onResponse: resolve,
|
|
574
|
+
}),
|
|
575
|
+
) as Promise<RpcResponse.RpcResponse<boolean>>
|
|
576
|
+
}
|
|
577
|
+
|
|
559
578
|
return new Promise<Socket.subscribe.ReturnType>((resolve, reject) => {
|
|
560
579
|
requestInternal({
|
|
561
580
|
body: { method: 'eth_subscribe', params },
|
|
@@ -571,9 +590,17 @@ export async function fromSocket<
|
|
|
571
590
|
return
|
|
572
591
|
}
|
|
573
592
|
if (typeof response.id === 'number') {
|
|
574
|
-
const
|
|
593
|
+
const resubscribed = subscriptionId !== undefined
|
|
594
|
+
subscriptionId = response.result as Hex.Hex
|
|
595
|
+
if (resubscribed) {
|
|
596
|
+
// Re-subscribed while an unsubscribe was in flight.
|
|
597
|
+
if (unsubscribed) unsubscribe()
|
|
598
|
+
return
|
|
599
|
+
}
|
|
575
600
|
resolve({
|
|
576
|
-
subscriptionId
|
|
601
|
+
get subscriptionId() {
|
|
602
|
+
return subscriptionId!
|
|
603
|
+
},
|
|
577
604
|
onData(listener) {
|
|
578
605
|
onDataFn = listener
|
|
579
606
|
while (dataQueue.length) listener(dataQueue.shift()!)
|
|
@@ -582,17 +609,7 @@ export async function fromSocket<
|
|
|
582
609
|
onErrorFn = listener
|
|
583
610
|
while (errorQueue.length) listener(errorQueue.shift()!)
|
|
584
611
|
},
|
|
585
|
-
unsubscribe
|
|
586
|
-
return new Promise<Socket.Response>((resolve) =>
|
|
587
|
-
requestInternal({
|
|
588
|
-
body: {
|
|
589
|
-
method: 'eth_unsubscribe',
|
|
590
|
-
params: [subscriptionId],
|
|
591
|
-
},
|
|
592
|
-
onResponse: resolve,
|
|
593
|
-
}),
|
|
594
|
-
) as Promise<RpcResponse.RpcResponse<boolean>>
|
|
595
|
-
},
|
|
612
|
+
unsubscribe,
|
|
596
613
|
})
|
|
597
614
|
return
|
|
598
615
|
}
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** @internal */
|
|
2
|
-
export const version = '3.0.0-next.
|
|
2
|
+
export const version = '3.0.0-next.14'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"getConfig.d.ts","sourceRoot":"","sources":["../../../../src/tempo/actions/multisig/getConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAA;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,KAAK,KAAK,MAAM,MAAM,yBAAyB,CAAA;AAEtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EACrB,UAAU,EAAE,SAAS,CAAC,OAAO,GAC5B,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC,CAM/B;AAED,MAAM,CAAC,OAAO,WAAW,SAAS,CAAC;IACjC,wCAAwC;IACxC,KAAY,OAAO,GAAG;QACpB,gCAAgC;QAChC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAA;KACzB,CAAA;IAED,0CAA0C;IAC1C,KAAY,UAAU,GAAG,cAAc,CAAC,MAAM,GAAG,IAAI,CAAA;IAErD,wCAAwC;IACxC,KAAY,SAAS,GAAG,MAAM,CAAC,eAAe,CAAA;CAC/C"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"getConfig.js","sourceRoot":"","sources":["../../../../src/tempo/actions/multisig/getConfig.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAGzC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,MAAqB,EACrB,UAA6B;IAE7B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QAClC,MAAM,EAAE,oBAAoB;QAC5B,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC;KAC1C,CAAC,CAAA;IACF,OAAO,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,MAA4B,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AAC7E,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"getOperation.js","sourceRoot":"","sources":["../../../../src/tempo/actions/multisig/getOperation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAG5C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAqB,EACrB,UAAgC;IAEhC,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QACrC,MAAM,EAAE,uBAAuB;QAC/B,MAAM,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;KAC1B,CAAC,CAAA;IACF,OAAO,SAAS;QACd,CAAC,CAAC,iBAAiB,CAAC,OAAO,CAAC,SAAkC,CAAC;QAC/D,CAAC,CAAC,IAAI,CAAA;AACV,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"multisig.d.ts","sourceRoot":"","sources":["../../../../src/tempo/internal/relay/multisig.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,KAAK,KAAK,MAAM,gBAAgB,CAAA;AAO5C,uEAAuE;AACvE,wBAAgB,MAAM,CACpB,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,OAAO,GAC9B,KAAK,CAAC,QAAQ,CAAC,UAAU,CAiL3B"}
|