accounts 0.5.5 → 0.5.7

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.
@@ -1,5 +1,6 @@
1
1
  import { Hono } from 'hono'
2
- import { Base64, Bytes, Hex, RpcRequest, RpcResponse } from 'ox'
2
+ import { Base64, Bytes, Hex, RpcRequest, RpcResponse, Signature } from 'ox'
3
+ import { Transaction as core_Transaction, TxEnvelopeTempo } from 'ox/tempo'
3
4
  import { Credential } from 'ox/webauthn'
4
5
  import { type Chain, type Client, createClient, http, type Transport } from 'viem'
5
6
  import type { LocalAccount } from 'viem/accounts'
@@ -15,7 +16,7 @@ import * as z from 'zod/mini'
15
16
 
16
17
  import * as CliAuth from './CliAuth.js'
17
18
  import * as RequestListener from './internal/requestListener.js'
18
- import type { Kv } from './Kv.js'
19
+ import * as Kv from './Kv.js'
19
20
 
20
21
  export type Handler = Hono & {
21
22
  listener: (req: any, res: any) => void
@@ -228,7 +229,7 @@ export declare namespace from {
228
229
  * chains: [tempo, tempoModerato],
229
230
  * transports: {
230
231
  * [tempo.id]: http('https://rpc.tempo.xyz'),
231
- * [tempoModerato.id]: http('https://rpc.moderato.tempo.xyz'),
232
+ * [tempoModerato.id]: http('https://rpc.testnet.tempo.xyz'),
232
233
  * },
233
234
  * })
234
235
  * ```
@@ -240,9 +241,12 @@ export function feePayer(options: feePayer.Options) {
240
241
  const {
241
242
  account,
242
243
  chains = [tempo, tempoModerato],
244
+ name,
243
245
  onRequest,
244
246
  path = '/',
245
247
  transports = {},
248
+ url,
249
+ ...rest
246
250
  } = options
247
251
 
248
252
  const clients = new Map<number, Client>()
@@ -260,7 +264,13 @@ export function feePayer(options: feePayer.Options) {
260
264
  return clients.get(chains[0]!.id)!
261
265
  }
262
266
 
263
- const router = from(options)
267
+ const sponsor = {
268
+ address: account.address,
269
+ ...(name ? { name } : {}),
270
+ ...(url ? { url } : {}),
271
+ }
272
+
273
+ const router = from(rest)
264
274
 
265
275
  router.post(path, async (c) => {
266
276
  const request = RpcRequest.from((await c.req.raw.json()) as any)
@@ -270,6 +280,7 @@ export function feePayer(options: feePayer.Options) {
270
280
 
271
281
  const method = request.method as string
272
282
  if (
283
+ method !== 'eth_fillTransaction' &&
273
284
  method !== 'eth_signRawTransaction' &&
274
285
  method !== 'eth_sendRawTransaction' &&
275
286
  method !== 'eth_sendRawTransactionSync'
@@ -285,14 +296,65 @@ export function feePayer(options: feePayer.Options) {
285
296
  ),
286
297
  )
287
298
 
288
- const serialized = request.params?.[0] as `0x76${string}`
299
+ if (method === 'eth_fillTransaction') {
300
+ const [parameters] = z
301
+ .readonly(z.tuple([z.record(z.string(), z.unknown())]))
302
+ .parse(request.params) as [Record<string, unknown>]
303
+ const chainId = resolveChainId(parameters.chainId)
304
+ const client = getClient(chainId)
305
+ const transaction = await (async () => {
306
+ if (isPreparedFeePayerTransaction(parameters))
307
+ return normalizeTempoTransaction(parameters)
308
+
309
+ const fillRequest = formatFillTransactionRequest(client, {
310
+ ...normalizeFillTransactionRequest(parameters),
311
+ ...(typeof chainId !== 'undefined' ? { chainId } : {}),
312
+ feePayer: true,
313
+ })
314
+ const result = (await client.request({
315
+ method: 'eth_fillTransaction' as never,
316
+ params: [fillRequest],
317
+ })) as { tx?: Record<string, unknown> | undefined }
318
+ return normalizeTempoTransaction(result.tx)
319
+ })()
320
+
321
+ const from =
322
+ (transaction.from as `0x${string}` | undefined) ??
323
+ (typeof parameters.from === 'string' ? (parameters.from as `0x${string}`) : undefined)
324
+ const { signature: _, ...withoutSenderSig } = transaction as Record<string, unknown>
325
+ const prepared = { ...withoutSenderSig, from }
326
+
327
+ if (!prepared.from)
328
+ throw new RpcResponse.InvalidParamsError({
329
+ message: 'Transaction sender must be provided before fee payer signing.',
330
+ })
331
+ if (!account.sign) throw new Error('Fee payer account cannot sign transactions.')
332
+
333
+ const feePayerSignature = Signature.from(
334
+ await account.sign({
335
+ hash: TxEnvelopeTempo.getFeePayerSignPayload(TxEnvelopeTempo.from(prepared as never), {
336
+ sender: prepared.from,
337
+ }),
338
+ }),
339
+ )
340
+
341
+ return rpcResult(request, {
342
+ sponsor,
343
+ tx: core_Transaction.toRpc({
344
+ ...prepared,
345
+ feePayerSignature,
346
+ } as core_Transaction.Transaction),
347
+ })
348
+ }
349
+
350
+ const serialized = request.params?.[0] as `0x76${string}` | undefined
289
351
 
290
352
  if (!serialized?.startsWith('0x76') && !serialized?.startsWith('0x78'))
291
353
  throw new RpcResponse.InvalidParamsError({
292
354
  message: 'Only Tempo (0x76/0x78) transactions are supported.',
293
355
  })
294
356
 
295
- const transaction = Transaction.deserialize(serialized) as any
357
+ const transaction = Transaction.deserialize(serialized)
296
358
 
297
359
  if (!transaction.signature || !transaction.from)
298
360
  throw new RpcResponse.InvalidParamsError({
@@ -300,32 +362,25 @@ export function feePayer(options: feePayer.Options) {
300
362
  })
301
363
 
302
364
  const client = getClient(transaction.chainId)
303
- const serializedTransaction = await signTransaction(client, {
304
- ...transaction,
305
- account,
306
- feePayer: account,
307
- })
365
+ const serializedTransaction = toSerializedTransaction(
366
+ await signTransaction(client, {
367
+ ...transaction,
368
+ account,
369
+ feePayer: account,
370
+ } as never),
371
+ )
308
372
 
309
373
  if (method === 'eth_signRawTransaction')
310
374
  return Response.json(RpcResponse.from({ result: serializedTransaction }, { request }))
311
375
 
312
- const result = await (client as any).request({
313
- method,
376
+ const result = await client.request({
377
+ method: method as never,
314
378
  params: [serializedTransaction],
315
379
  })
316
380
 
317
381
  return Response.json(RpcResponse.from({ result }, { request }))
318
382
  } catch (error) {
319
- return Response.json(
320
- RpcResponse.from(
321
- {
322
- error: new RpcResponse.InternalError({
323
- message: (error as Error).message,
324
- }),
325
- },
326
- { request },
327
- ),
328
- )
383
+ return rpcError(request, error)
329
384
  }
330
385
  })
331
386
 
@@ -346,8 +401,12 @@ export declare namespace feePayer {
346
401
  onRequest?: (request: RpcRequest.RpcRequest) => Promise<void>
347
402
  /** Path to use for the handler. */
348
403
  path?: string | undefined
404
+ /** Sponsor display name returned from `eth_fillTransaction`. */
405
+ name?: string | undefined
349
406
  /** Transports keyed by chain ID. Defaults to `http()` for each chain. */
350
407
  transports?: Record<number, Transport> | undefined
408
+ /** Sponsor URL returned from `eth_fillTransaction`. */
409
+ url?: string | undefined
351
410
  }
352
411
  }
353
412
 
@@ -658,7 +717,7 @@ export declare namespace webAuthn {
658
717
  /** Maximum age of a challenge in seconds before it expires. @default 300 */
659
718
  challengeTtl?: number | undefined
660
719
  /** Key-value store for challenges and credentials. */
661
- kv: Kv
720
+ kv: Kv.Kv
662
721
  /** Called after a successful registration. The returned response is merged onto the default JSON response. */
663
722
  onRegister?: (parameters: {
664
723
  credentialId: string
@@ -681,6 +740,103 @@ export declare namespace webAuthn {
681
740
  }
682
741
  }
683
742
 
743
+ /** @internal */
744
+ function resolveChainId(value: unknown) {
745
+ if (typeof value === 'number') return value
746
+ if (typeof value === 'bigint') return Number(value)
747
+ if (typeof value === 'string' && Hex.validate(value)) return Hex.toNumber(value)
748
+ return undefined
749
+ }
750
+
751
+ /** @internal */
752
+ function isPreparedFeePayerTransaction(value: Record<string, unknown>) {
753
+ return (
754
+ typeof value.from === 'string' &&
755
+ typeof resolveChainId(value.chainId) === 'number' &&
756
+ typeof value.gas !== 'undefined' &&
757
+ typeof value.nonce !== 'undefined' &&
758
+ (typeof value.maxFeePerGas !== 'undefined' || typeof value.gasPrice !== 'undefined')
759
+ )
760
+ }
761
+
762
+ /** @internal */
763
+ function formatFillTransactionRequest(client: Client, value: Record<string, unknown>) {
764
+ const format = client.chain?.formatters?.transactionRequest?.format
765
+ if (!format) return value
766
+ return format({ ...value } as never, 'fillTransaction') as Record<string, unknown>
767
+ }
768
+
769
+ /** @internal */
770
+ function normalizeFillTransactionRequest(value: Record<string, unknown>) {
771
+ if (typeof value.to !== 'undefined' || typeof value.data !== 'undefined') return value
772
+ if (!Array.isArray(value.calls) || value.calls.length !== 1) return value
773
+ const [call] = value.calls as Array<Record<string, unknown>>
774
+ const { calls: _, ...rest } = value
775
+ return {
776
+ ...rest,
777
+ ...(typeof call?.data !== 'undefined' ? { data: call.data } : {}),
778
+ ...(typeof call?.to !== 'undefined' ? { to: call.to } : {}),
779
+ ...(typeof call?.value !== 'undefined' ? { value: normalizeFillValue(call.value) } : {}),
780
+ }
781
+ }
782
+
783
+ /** @internal */
784
+ function normalizeFillValue(value: unknown) {
785
+ if (typeof value !== 'string' || !value.startsWith('0x')) return value
786
+ return BigInt(value === '0x' ? '0x0' : value)
787
+ }
788
+
789
+ /** @internal */
790
+ function normalizeTempoTransaction(value: Record<string, unknown> | undefined) {
791
+ if (!value) throw new Error('Expected `tx` in eth_fillTransaction response.')
792
+ return core_Transaction.fromRpc({ type: '0x76', ...value } as core_Transaction.Rpc)!
793
+ }
794
+
795
+ /** @internal */
796
+ function rpcError(request: RpcRequest.RpcRequest, error: unknown) {
797
+ if (error instanceof RpcResponse.InvalidParamsError)
798
+ return Response.json(RpcResponse.from({ error }, { request }))
799
+
800
+ if (error instanceof RpcResponse.MethodNotSupportedError)
801
+ return Response.json(RpcResponse.from({ error }, { request }))
802
+
803
+ if ((error as { name?: string | undefined }).name === 'ZodError')
804
+ return Response.json(
805
+ RpcResponse.from(
806
+ {
807
+ error: new RpcResponse.InvalidParamsError({
808
+ message: (error as Error).message,
809
+ }),
810
+ },
811
+ { request },
812
+ ),
813
+ )
814
+
815
+ return Response.json(
816
+ RpcResponse.from(
817
+ {
818
+ error: new RpcResponse.InternalError({
819
+ message: (error as Error).message,
820
+ }),
821
+ },
822
+ { request },
823
+ ),
824
+ )
825
+ }
826
+
827
+ /** @internal */
828
+ function rpcResult(request: RpcRequest.RpcRequest, result: unknown) {
829
+ return Response.json(RpcResponse.from({ result }, { request }))
830
+ }
831
+
832
+ /** @internal */
833
+ function toSerializedTransaction(value: unknown) {
834
+ if (typeof value === 'string') return value
835
+ if (value && typeof value === 'object' && 'raw' in value && typeof value.raw === 'string')
836
+ return value.raw
837
+ throw new Error('Expected a serialized transaction result.')
838
+ }
839
+
684
840
  /** @internal */
685
841
  async function mergeResponse(
686
842
  json: Record<string, unknown>,
@@ -318,10 +318,10 @@ export declare namespace dialog {
318
318
  * ```
319
319
  */
320
320
  export function dangerous_secp256k1(options: dangerous_secp256k1.Options = {}) {
321
- const { icon, name, rdns, ...rest } = options
321
+ const { icon, name, privateKey, rdns, ...rest } = options
322
322
  return setup({
323
323
  ...rest,
324
- adapter: dangerous_secp256k1_adapter({ icon, name, rdns }),
324
+ adapter: dangerous_secp256k1_adapter({ icon, name, privateKey, rdns }),
325
325
  })
326
326
  }
327
327