accounts 0.6.7 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/core/ExecutionError.d.ts +25 -0
- package/dist/core/ExecutionError.d.ts.map +1 -0
- package/dist/core/ExecutionError.js +170 -0
- package/dist/core/ExecutionError.js.map +1 -0
- package/dist/core/Schema.d.ts +33 -7
- package/dist/core/Schema.d.ts.map +1 -1
- package/dist/core/zod/rpc.d.ts +14 -1
- package/dist/core/zod/rpc.d.ts.map +1 -1
- package/dist/core/zod/rpc.js +14 -1
- package/dist/core/zod/rpc.js.map +1 -1
- package/dist/server/CliAuth.d.ts +110 -43
- package/dist/server/CliAuth.d.ts.map +1 -1
- package/dist/server/CliAuth.js +243 -155
- package/dist/server/CliAuth.js.map +1 -1
- package/dist/server/Handler.d.ts +0 -1
- package/dist/server/Handler.d.ts.map +1 -1
- package/dist/server/Handler.js +0 -1
- package/dist/server/Handler.js.map +1 -1
- package/dist/server/internal/handlers/relay.d.ts +29 -12
- package/dist/server/internal/handlers/relay.d.ts.map +1 -1
- package/dist/server/internal/handlers/relay.js +180 -125
- package/dist/server/internal/handlers/relay.js.map +1 -1
- package/dist/server/internal/handlers/sponsorship.d.ts +77 -0
- package/dist/server/internal/handlers/sponsorship.d.ts.map +1 -0
- package/dist/server/internal/handlers/sponsorship.js +96 -0
- package/dist/server/internal/handlers/sponsorship.js.map +1 -0
- package/dist/server/internal/handlers/utils.d.ts +3 -1
- package/dist/server/internal/handlers/utils.d.ts.map +1 -1
- package/dist/server/internal/handlers/utils.js +15 -12
- package/dist/server/internal/handlers/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ExecutionError.test.ts +205 -0
- package/src/core/ExecutionError.ts +189 -0
- package/src/core/Provider.test.ts +4 -2
- package/src/core/zod/rpc.ts +18 -1
- package/src/server/CliAuth.test-d.ts +6 -0
- package/src/server/CliAuth.test.ts +83 -0
- package/src/server/CliAuth.ts +331 -208
- package/src/server/Handler.ts +0 -1
- package/src/server/internal/handlers/relay.test.ts +318 -108
- package/src/server/internal/handlers/relay.ts +243 -138
- package/src/server/internal/handlers/sponsorship.ts +172 -0
- package/src/server/internal/handlers/utils.ts +15 -10
- package/dist/server/internal/handlers/feePayer.d.ts +0 -73
- package/dist/server/internal/handlers/feePayer.d.ts.map +0 -1
- package/dist/server/internal/handlers/feePayer.js +0 -184
- package/dist/server/internal/handlers/feePayer.js.map +0 -1
- package/src/server/internal/handlers/feePayer.test.ts +0 -336
- package/src/server/internal/handlers/feePayer.ts +0 -271
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { type DecodeErrorResultReturnType, type Hex, decodeErrorResult } from 'viem'
|
|
2
|
+
import { Abis } from 'viem/tempo'
|
|
3
|
+
|
|
4
|
+
import type { OneOf, UnionOmit } from '../internal/types.js'
|
|
5
|
+
|
|
6
|
+
type AllAbis = typeof Abis.abis
|
|
7
|
+
type AbiErrorName = Extract<AllAbis[number], { type: 'error' }>['name']
|
|
8
|
+
|
|
9
|
+
/** Decoded execution error from a Tempo precompile revert. */
|
|
10
|
+
export type ExecutionError = OneOf<
|
|
11
|
+
| (DecodeErrorResultReturnType<AllAbis> & {
|
|
12
|
+
data: Hex
|
|
13
|
+
message: string
|
|
14
|
+
})
|
|
15
|
+
| { errorName: 'unknown'; message: string }
|
|
16
|
+
>
|
|
17
|
+
|
|
18
|
+
/** RPC-serialized execution error (bigints and numbers as hex). */
|
|
19
|
+
export type Rpc = UnionOmit<ExecutionError, 'args'>
|
|
20
|
+
|
|
21
|
+
/** Human-readable messages keyed by ABI error name. */
|
|
22
|
+
export const messages: Record<AbiErrorName, string> = {
|
|
23
|
+
AddressAlreadyHasValidator: 'This address already has a validator.',
|
|
24
|
+
AddressNotReserved: 'Address is not reserved.',
|
|
25
|
+
AddressReserved: 'Address is reserved.',
|
|
26
|
+
AlreadyInitialized: 'Already initialized.',
|
|
27
|
+
BelowMinimumOrderSize: 'Below minimum order size: {0}.',
|
|
28
|
+
CallNotAllowed: 'This call is not allowed.',
|
|
29
|
+
CannotChangeWithPendingFees: 'Cannot change while fees are pending.',
|
|
30
|
+
CannotChangeWithinBlock: 'Cannot change within the same block.',
|
|
31
|
+
ContractPaused: 'Contract is paused.',
|
|
32
|
+
DivisionByZero: 'Division by zero.',
|
|
33
|
+
EmptyV1ValidatorSet: 'Validator set is empty.',
|
|
34
|
+
ExpiringNonceReplay: 'Expiring nonce has already been used.',
|
|
35
|
+
ExpiringNonceSetFull: 'Expiring nonce set is full.',
|
|
36
|
+
ExpiryInPast: 'Expiry is in the past.',
|
|
37
|
+
IdenticalAddresses: 'Addresses must be different.',
|
|
38
|
+
IdenticalTokens: 'Tokens must be different.',
|
|
39
|
+
IncompatiblePolicyType: 'Incompatible policy type.',
|
|
40
|
+
IngressAlreadyExists: 'Ingress "{0}" already exists.',
|
|
41
|
+
InsufficientAllowance: 'Insufficient allowance.',
|
|
42
|
+
InsufficientBalance: 'Insufficient balance. Required: {1}, available: {0}.',
|
|
43
|
+
InsufficientFeeTokenBalance: 'Insufficient fee token balance.',
|
|
44
|
+
InsufficientLiquidity: 'Insufficient liquidity.',
|
|
45
|
+
InsufficientOutput: 'Insufficient output amount.',
|
|
46
|
+
InsufficientReserves: 'Insufficient reserves.',
|
|
47
|
+
InternalError: 'Internal error.',
|
|
48
|
+
InvalidAmount: 'Invalid amount.',
|
|
49
|
+
InvalidBaseToken: 'Invalid base token.',
|
|
50
|
+
InvalidCallScope: 'Invalid call scope.',
|
|
51
|
+
InvalidCurrency: 'Invalid currency.',
|
|
52
|
+
InvalidExpiringNonceExpiry: 'Invalid expiring nonce expiry.',
|
|
53
|
+
InvalidFlipTick: 'Invalid flip tick.',
|
|
54
|
+
InvalidFormat: 'Invalid format.',
|
|
55
|
+
InvalidMasterAddress: 'Invalid master address.',
|
|
56
|
+
InvalidMigrationIndex: 'Invalid migration index.',
|
|
57
|
+
InvalidNonceKey: 'Invalid nonce key.',
|
|
58
|
+
InvalidOwner: 'Invalid owner.',
|
|
59
|
+
InvalidPayload: 'Invalid payload.',
|
|
60
|
+
InvalidPolicyType: 'Invalid policy type.',
|
|
61
|
+
InvalidPublicKey: 'Invalid public key.',
|
|
62
|
+
InvalidQuoteToken: 'Invalid quote token.',
|
|
63
|
+
InvalidRecipient: 'Invalid recipient.',
|
|
64
|
+
InvalidSignature: 'Invalid signature.',
|
|
65
|
+
InvalidSignatureFormat: 'Invalid signature format.',
|
|
66
|
+
InvalidSignatureType: 'Invalid signature type.',
|
|
67
|
+
InvalidSpendingLimit: 'Invalid spending limit.',
|
|
68
|
+
InvalidSupplyCap: 'Invalid supply cap.',
|
|
69
|
+
InvalidSwapCalculation: 'Invalid swap calculation.',
|
|
70
|
+
InvalidTick: 'Invalid tick.',
|
|
71
|
+
InvalidToken: 'Invalid token.',
|
|
72
|
+
InvalidTransferPolicyId: 'Invalid transfer policy.',
|
|
73
|
+
InvalidValidatorAddress: 'Invalid validator address.',
|
|
74
|
+
KeyAlreadyExists: 'Key already exists.',
|
|
75
|
+
KeyAlreadyRevoked: 'Key has already been revoked.',
|
|
76
|
+
KeyExpired: 'Key has expired.',
|
|
77
|
+
KeyNotFound: 'Key not found.',
|
|
78
|
+
LegacyAuthorizeKeySelectorChanged: 'Legacy authorize key selector changed to {0}.',
|
|
79
|
+
MasterIdCollision: 'Master ID collision with {0}.',
|
|
80
|
+
MaxInputExceeded: 'Maximum input exceeded.',
|
|
81
|
+
MigrationNotComplete: 'Migration is not complete.',
|
|
82
|
+
NoOptedInSupply: 'No opted-in supply.',
|
|
83
|
+
NonceOverflow: 'Nonce overflow.',
|
|
84
|
+
NotHostPort: '"{1}" is not a valid host:port for {0}.',
|
|
85
|
+
NotInitialized: 'Not initialized.',
|
|
86
|
+
NotIp: '"{0}" is not a valid IP address.',
|
|
87
|
+
NotIpPort: '"{1}" is not a valid IP:port for {0}.',
|
|
88
|
+
OnlySystemContract: 'Only callable by system contract.',
|
|
89
|
+
OnlyValidator: 'Only callable by a validator.',
|
|
90
|
+
OrderDoesNotExist: 'Order does not exist.',
|
|
91
|
+
OrderNotStale: 'Order is not stale.',
|
|
92
|
+
PairAlreadyExists: 'Pair already exists.',
|
|
93
|
+
PairDoesNotExist: 'Pair does not exist.',
|
|
94
|
+
PermitExpired: 'Permit has expired.',
|
|
95
|
+
PolicyForbids: 'Forbidden by policy.',
|
|
96
|
+
PolicyNotFound: 'Policy not found.',
|
|
97
|
+
PolicyNotSimple: 'Policy is not a simple policy.',
|
|
98
|
+
PoolDoesNotExist: 'Pool does not exist.',
|
|
99
|
+
ProofOfWorkFailed: 'Proof of work failed.',
|
|
100
|
+
ProtectedAddress: 'Address is protected.',
|
|
101
|
+
ProtocolNonceNotSupported: 'Protocol nonce is not supported.',
|
|
102
|
+
PublicKeyAlreadyExists: 'Public key already exists.',
|
|
103
|
+
SignatureTypeMismatch: 'Signature type mismatch. Expected {0}, got {1}.',
|
|
104
|
+
SpendingLimitExceeded: 'Spending limit exceeded.',
|
|
105
|
+
StringTooLong: 'String is too long.',
|
|
106
|
+
SupplyCapExceeded: 'Supply cap exceeded.',
|
|
107
|
+
TickOutOfBounds: 'Tick {0} is out of bounds.',
|
|
108
|
+
TokenAlreadyExists: 'Token {0} already exists.',
|
|
109
|
+
TokenPolicyForbids: 'Forbidden by token policy.',
|
|
110
|
+
TransfersDisabled: 'Transfers are disabled.',
|
|
111
|
+
Unauthorized: 'Unauthorized.',
|
|
112
|
+
UnauthorizedCaller: 'Unauthorized caller.',
|
|
113
|
+
Uninitialized: 'Uninitialized.',
|
|
114
|
+
ValidatorAlreadyDeactivated: 'Validator is already deactivated.',
|
|
115
|
+
ValidatorAlreadyExists: 'Validator already exists.',
|
|
116
|
+
ValidatorNotFound: 'Validator not found.',
|
|
117
|
+
VirtualAddressNotAllowed: 'Virtual address is not allowed.',
|
|
118
|
+
VirtualAddressUnregistered: 'Virtual address is not registered.',
|
|
119
|
+
ZeroPublicKey: 'Public key cannot be zero.',
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Interpolate `{0}`, `{1}`, … placeholders with args. */
|
|
123
|
+
function interpolate(template: string, args?: readonly unknown[]): string {
|
|
124
|
+
if (!args) return template
|
|
125
|
+
return template.replace(/\{(\d+)\}/g, (_, i) => {
|
|
126
|
+
const v = args[Number(i)]
|
|
127
|
+
return v === undefined ? `{${i}}` : String(v)
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Parse a viem error into a structured execution error. */
|
|
132
|
+
export function parse(error: Error): ExecutionError {
|
|
133
|
+
const raw =
|
|
134
|
+
(error as { details?: string }).details ??
|
|
135
|
+
(error as { shortMessage?: string }).shortMessage ??
|
|
136
|
+
error.message
|
|
137
|
+
|
|
138
|
+
const data = extractRevertData(error)
|
|
139
|
+
if (data) {
|
|
140
|
+
try {
|
|
141
|
+
const decoded = decodeErrorResult({ abi: Abis.abis, data })
|
|
142
|
+
const template = messages[decoded.errorName as AbiErrorName]
|
|
143
|
+
return {
|
|
144
|
+
...decoded,
|
|
145
|
+
data,
|
|
146
|
+
message: template
|
|
147
|
+
? interpolate(template, decoded.args as readonly unknown[])
|
|
148
|
+
: raw.replace(/^execution reverted:\s*/i, ''),
|
|
149
|
+
} as never
|
|
150
|
+
} catch {}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Fallback: extract error name from human-readable revert message.
|
|
154
|
+
const nameMatch = /:\s*(\w+)\(\w+/.exec(raw)
|
|
155
|
+
const errorName = nameMatch?.[1]
|
|
156
|
+
if (errorName && errorName in messages)
|
|
157
|
+
return { errorName: 'unknown', message: messages[errorName as AbiErrorName]! }
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
errorName: 'unknown',
|
|
161
|
+
message: raw.replace(/^execution reverted:\s*/i, ''),
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Serializes an ExecutionError for RPC transport (bigints/numbers → hex). */
|
|
166
|
+
export function serialize(preimage: ExecutionError): Rpc {
|
|
167
|
+
if (preimage.errorName === 'unknown') return { errorName: 'unknown', message: preimage.message }
|
|
168
|
+
return {
|
|
169
|
+
errorName: preimage.errorName,
|
|
170
|
+
abiItem: preimage.abiItem,
|
|
171
|
+
message: preimage.message,
|
|
172
|
+
data: preimage.data,
|
|
173
|
+
} as never
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function extractRevertData(error: unknown): Hex | null {
|
|
177
|
+
if (!error || typeof error !== 'object') return null
|
|
178
|
+
const e = error as Record<string, unknown>
|
|
179
|
+
if (typeof e.data === 'string' && e.data.startsWith('0x')) return e.data as Hex
|
|
180
|
+
if (e.cause) return extractRevertData(e.cause)
|
|
181
|
+
if (e.error) return extractRevertData(e.error)
|
|
182
|
+
if (typeof e.walk === 'function') {
|
|
183
|
+
const inner = (e as { walk: (fn: (e: unknown) => boolean) => unknown }).walk(
|
|
184
|
+
(e) => typeof (e as Record<string, unknown>).data === 'string',
|
|
185
|
+
)
|
|
186
|
+
if (inner) return extractRevertData(inner)
|
|
187
|
+
}
|
|
188
|
+
return null
|
|
189
|
+
}
|
|
@@ -1600,9 +1600,11 @@ describe.each(adapters)('$name', ({ adapter }: (typeof adapters)[number]) => {
|
|
|
1600
1600
|
|
|
1601
1601
|
beforeAll(async () => {
|
|
1602
1602
|
server = await createServer(
|
|
1603
|
-
Handler.
|
|
1604
|
-
account: feePayerAccount,
|
|
1603
|
+
Handler.relay({
|
|
1605
1604
|
chains: [chain],
|
|
1605
|
+
feePayer: {
|
|
1606
|
+
account: feePayerAccount,
|
|
1607
|
+
},
|
|
1606
1608
|
transports: { [chain.id]: http() },
|
|
1607
1609
|
}).listener,
|
|
1608
1610
|
)
|
package/src/core/zod/rpc.ts
CHANGED
|
@@ -150,11 +150,28 @@ export namespace eth_fillTransaction {
|
|
|
150
150
|
returns: z.object({
|
|
151
151
|
capabilities: z.object({
|
|
152
152
|
balanceDiffs: z.optional(z.record(u.address(), z.readonly(z.array(balanceDiff)))),
|
|
153
|
-
|
|
153
|
+
error: z.optional(
|
|
154
|
+
z.object({
|
|
155
|
+
abiItem: z.optional(z.record(z.string(), z.unknown())),
|
|
156
|
+
data: z.optional(u.hex()),
|
|
157
|
+
errorName: z.string(),
|
|
158
|
+
message: z.string(),
|
|
159
|
+
}),
|
|
160
|
+
),
|
|
161
|
+
fee: z.optional(
|
|
162
|
+
z.object({
|
|
163
|
+
amount: u.hex(),
|
|
164
|
+
decimals: z.number(),
|
|
165
|
+
formatted: z.string(),
|
|
166
|
+
symbol: z.string(),
|
|
167
|
+
}),
|
|
168
|
+
),
|
|
169
|
+
requireFunds: z.optional(
|
|
154
170
|
z.object({
|
|
155
171
|
amount: u.hex(),
|
|
156
172
|
decimals: z.number(),
|
|
157
173
|
formatted: z.string(),
|
|
174
|
+
token: u.address(),
|
|
158
175
|
symbol: z.string(),
|
|
159
176
|
}),
|
|
160
177
|
),
|
|
@@ -54,3 +54,9 @@ describe('Store', () => {
|
|
|
54
54
|
expectTypeOf(CliAuth.Store.memory).returns.toMatchTypeOf<CliAuth.Store>()
|
|
55
55
|
})
|
|
56
56
|
})
|
|
57
|
+
|
|
58
|
+
describe('from', () => {
|
|
59
|
+
test('returns the shared CLI auth helper contract', () => {
|
|
60
|
+
expectTypeOf(CliAuth.from).returns.toMatchTypeOf<CliAuth.CliAuth>()
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -170,6 +170,64 @@ async function get<response extends z.ZodMiniType>(
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
describe('from', () => {
|
|
174
|
+
test('default: shares defaults across the device-code flow', async () => {
|
|
175
|
+
const store = CliAuth.Store.memory()
|
|
176
|
+
const now = () => 1_000
|
|
177
|
+
const { codeVerifier, request } = await createRequest()
|
|
178
|
+
const cli = CliAuth.from({
|
|
179
|
+
chains: [chain],
|
|
180
|
+
now,
|
|
181
|
+
random: () => new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]),
|
|
182
|
+
store,
|
|
183
|
+
ttlMs: 30_000,
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
const created = await cli.createDeviceCode({ request })
|
|
187
|
+
const entry = await store.get(created.code)
|
|
188
|
+
const authorized = await cli.authorize({
|
|
189
|
+
request: await authorize(created.code),
|
|
190
|
+
})
|
|
191
|
+
const polled = await cli.poll({
|
|
192
|
+
code: created.code,
|
|
193
|
+
request: {
|
|
194
|
+
codeVerifier,
|
|
195
|
+
},
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
expect(created).toMatchInlineSnapshot(`
|
|
199
|
+
{
|
|
200
|
+
"code": "ABCDEFGH",
|
|
201
|
+
}
|
|
202
|
+
`)
|
|
203
|
+
expect(entry).toMatchInlineSnapshot(`
|
|
204
|
+
{
|
|
205
|
+
"chainId": 1337n,
|
|
206
|
+
"code": "ABCDEFGH",
|
|
207
|
+
"codeChallenge": "NUwjc1h8PuXcsvSOG44Rp4bMayBXnOkriHEJ19CaSQM",
|
|
208
|
+
"createdAt": 1000,
|
|
209
|
+
"expiresAt": 31000,
|
|
210
|
+
"expiry": ${expiry},
|
|
211
|
+
"keyType": "p256",
|
|
212
|
+
"limits": [
|
|
213
|
+
{
|
|
214
|
+
"limit": 1000n,
|
|
215
|
+
"token": "0x20c0000000000000000000000000000000000001",
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
"pubKey": "${accessKey.publicKey}",
|
|
219
|
+
"status": "pending",
|
|
220
|
+
}
|
|
221
|
+
`)
|
|
222
|
+
expect(authorized).toMatchInlineSnapshot(`
|
|
223
|
+
{
|
|
224
|
+
"status": "authorized",
|
|
225
|
+
}
|
|
226
|
+
`)
|
|
227
|
+
expect(polled.status).toMatchInlineSnapshot(`"authorized"`)
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
173
231
|
describe('createDeviceCode', () => {
|
|
174
232
|
test('default: creates a pending device code', async () => {
|
|
175
233
|
const store = CliAuth.Store.memory()
|
|
@@ -256,6 +314,31 @@ describe('createDeviceCode', () => {
|
|
|
256
314
|
expect(response.status).toMatchInlineSnapshot(`400`)
|
|
257
315
|
})
|
|
258
316
|
|
|
317
|
+
test('behavior: handler rejects requests for unconfigured chains', async () => {
|
|
318
|
+
const handler = Handler.codeAuth({
|
|
319
|
+
chains: [chain],
|
|
320
|
+
})
|
|
321
|
+
const { request } = await createRequest()
|
|
322
|
+
|
|
323
|
+
const result = await post(handler, {
|
|
324
|
+
body: {
|
|
325
|
+
...request,
|
|
326
|
+
chainId: BigInt(chain.id + 1),
|
|
327
|
+
},
|
|
328
|
+
request: CliAuth.createRequest,
|
|
329
|
+
url: 'http://localhost/auth/pkce/code',
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
expect(result).toMatchInlineSnapshot(`
|
|
333
|
+
{
|
|
334
|
+
"body": {
|
|
335
|
+
"error": "Chain 1338 not configured",
|
|
336
|
+
},
|
|
337
|
+
"status": 400,
|
|
338
|
+
}
|
|
339
|
+
`)
|
|
340
|
+
})
|
|
341
|
+
|
|
259
342
|
test('behavior: supports pubkey-only requests with server defaults', async () => {
|
|
260
343
|
const store = CliAuth.Store.memory()
|
|
261
344
|
const now = () => 1_000
|