ox
Version:
Ethereum Standard Library
422 lines (392 loc) • 15.3 kB
text/typescript
/* eslint-disable jsdoc-js/require-jsdoc, jsdoc-js/require-description, jsdoc-js/require-example */
import type * as core_TransactionRequest from '../../tempo/TransactionRequest.js'
import * as core_Hex from '../../core/Hex.js'
import * as z_AccessList from '../AccessList.js'
import * as z_Address from '../Address.js'
import * as z_Hex from '../Hex.js'
import * as z_TransactionRequest from '../TransactionRequest.js'
import {
encodeNumberish,
uintBigintNumberish,
uintNumberNumberish,
} from '../internal/Integer.js'
import * as z from 'zod/mini'
import * as z_AuthorizationTempo from './AuthorizationTempo.js'
import * as z_KeyAuthorization from './KeyAuthorization.js'
import * as z_SignatureEnvelope from './SignatureEnvelope.js'
const fromRpcType = { '0x76': 'tempo' } as const
const toRpcType = { tempo: '0x76' } as const
const KeyType = z.union([
z.literal('secp256k1'),
z.literal('p256'),
z.literal('webAuthn'),
])
const SignatureRpc = z.object({
r: z_Hex.Hex,
s: z_Hex.Hex,
v: z.optional(z_Hex.Hex),
yParity: z_Hex.Hex,
})
const SignatureDecoded = z.object({
r: z_Hex.Hex,
s: z_Hex.Hex,
v: z.optional(z.number()),
yParity: z.number(),
})
const CallRpc = z.object({
data: z.optional(z_Hex.Hex),
to: z.optional(z_Address.Address),
value: z.optional(z_Hex.Hex),
})
const Call = z.object({
data: z.optional(z_Hex.Hex),
to: z.optional(z_Address.Address),
value: z.optional(z.bigint()),
})
const CallToRpc = z.object({
data: z.optional(z_Hex.Hex),
to: z.optional(z_Address.Address),
value: z.optional(uintBigintNumberish()),
})
const Capabilities = z.record(z.string(), z.unknown())
const MultisigInit = z.object({
salt: z_Hex.Hex,
threshold: z.number(),
owners: z.readonly(
z.array(z.object({ owner: z_Address.Address, weight: z.number() })),
),
})
/** RPC tempo transaction request schema. */
export const Rpc = z.object({
accessList: z.optional(z_AccessList.AccessList),
authorizationList: z.optional(z_AuthorizationTempo.ListRpc),
blobVersionedHashes: z.optional(z.readonly(z.array(z_Hex.Hex))),
blobs: z.optional(z.readonly(z.array(z_Hex.Hex))),
calls: z.optional(z.readonly(z.array(CallRpc))),
capabilities: z.optional(Capabilities),
chainId: z.optional(z_Hex.Hex),
data: z.optional(z_Hex.Hex),
feePayer: z.optional(z.boolean()),
feePayerSignature: z.optional(z.union([SignatureRpc, z.null()])),
feeToken: z.optional(z_Hex.Hex),
from: z.optional(z_Address.Address),
gas: z.optional(z_Hex.Hex),
gasPrice: z.optional(z_Hex.Hex),
input: z.optional(z_Hex.Hex),
keyAuthorization: z.optional(z_KeyAuthorization.Rpc),
keyData: z.optional(z_Hex.Hex),
keyId: z.optional(z_Address.Address),
keyType: z.optional(KeyType),
maxFeePerBlobGas: z.optional(z_Hex.Hex),
maxFeePerGas: z.optional(z_Hex.Hex),
maxPriorityFeePerGas: z.optional(z_Hex.Hex),
multisigInit: z.optional(MultisigInit),
multisigSignatureCount: z.optional(z.number()),
nonce: z.optional(z_Hex.Hex),
nonceKey: z.optional(z_Hex.Hex),
r: z.optional(z_Hex.Hex),
s: z.optional(z_Hex.Hex),
signature: z.optional(z_SignatureEnvelope.Rpc),
to: z.optional(z.union([z_Address.Address, z.null()])),
type: z.optional(z.string()),
v: z.optional(z_Hex.Hex),
validAfter: z.optional(z_Hex.Hex),
validBefore: z.optional(z_Hex.Hex),
value: z.optional(z_Hex.Hex),
yParity: z.optional(z_Hex.Hex),
})
const AuthorizationSigned = z.object({
address: z_Address.Address,
chainId: z.number(),
nonce: z.bigint(),
signature: z_SignatureEnvelope.Domain,
})
const AuthorizationSignedToRpc = z.object({
address: z_Address.Address,
chainId: uintNumberNumberish(),
nonce: uintBigintNumberish(),
signature: z_SignatureEnvelope.Domain,
})
/** Decoded tempo transaction request schema. */
export const Domain = z.object({
accessList: z.optional(z_AccessList.AccessList),
authorizationList: z.optional(z.readonly(z.array(AuthorizationSigned))),
blobVersionedHashes: z.optional(z.readonly(z.array(z_Hex.Hex))),
blobs: z.optional(z.readonly(z.array(z_Hex.Hex))),
calls: z.optional(z.readonly(z.array(Call))),
capabilities: z.optional(Capabilities),
chainId: z.optional(z.number()),
data: z.optional(z_Hex.Hex),
feePayer: z.optional(z.boolean()),
feePayerSignature: z.optional(z.union([SignatureDecoded, z.null()])),
feeToken: z.optional(z_Address.Address),
from: z.optional(z_Address.Address),
gas: z.optional(z.bigint()),
gasPrice: z.optional(z.bigint()),
input: z.optional(z_Hex.Hex),
keyAuthorization: z.optional(z_KeyAuthorization.Domain),
keyData: z.optional(z_Hex.Hex),
keyId: z.optional(z_Address.Address),
keyType: z.optional(KeyType),
maxFeePerBlobGas: z.optional(z.bigint()),
maxFeePerGas: z.optional(z.bigint()),
maxPriorityFeePerGas: z.optional(z.bigint()),
multisigInit: z.optional(MultisigInit),
multisigSignatureCount: z.optional(z.number()),
nonce: z.optional(z.bigint()),
nonceKey: z.optional(z.union([z.bigint(), z.literal('random')])),
r: z.optional(z_Hex.Hex),
s: z.optional(z_Hex.Hex),
signature: z.optional(z_SignatureEnvelope.Domain),
to: z.optional(z.union([z_Address.Address, z.null()])),
type: z.optional(z.string()),
v: z.optional(z.number()),
validAfter: z.optional(z.number()),
validBefore: z.optional(z.number()),
value: z.optional(z.bigint()),
yParity: z.optional(z.number()),
})
/** Encode-only decoded tempo transaction request schema accepting numberish `toRpc` inputs. */
export const DomainToRpc = z.object({
accessList: z.optional(z_AccessList.AccessList),
authorizationList: z.optional(z.readonly(z.array(AuthorizationSignedToRpc))),
blobVersionedHashes: z.optional(z.readonly(z.array(z_Hex.Hex))),
blobs: z.optional(z.readonly(z.array(z_Hex.Hex))),
calls: z.optional(z.readonly(z.array(CallToRpc))),
capabilities: z.optional(Capabilities),
chainId: z.optional(uintNumberNumberish()),
data: z.optional(z_Hex.Hex),
feePayer: z.optional(z.boolean()),
feePayerSignature: z.optional(z.union([SignatureDecoded, z.null()])),
feeToken: z.optional(z_Address.Address),
from: z.optional(z_Address.Address),
gas: z.optional(uintBigintNumberish()),
gasPrice: z.optional(uintBigintNumberish()),
input: z.optional(z_Hex.Hex),
keyAuthorization: z.optional(z_KeyAuthorization.DomainToRpc),
keyData: z.optional(z_Hex.Hex),
keyId: z.optional(z_Address.Address),
keyType: z.optional(KeyType),
maxFeePerBlobGas: z.optional(uintBigintNumberish()),
maxFeePerGas: z.optional(uintBigintNumberish()),
maxPriorityFeePerGas: z.optional(uintBigintNumberish()),
multisigInit: z.optional(MultisigInit),
multisigSignatureCount: z.optional(z.number()),
nonce: z.optional(uintBigintNumberish()),
nonceKey: z.optional(z.union([uintBigintNumberish(), z.literal('random')])),
r: z.optional(z_Hex.Hex),
s: z.optional(z_Hex.Hex),
signature: z.optional(z_SignatureEnvelope.Domain),
to: z.optional(z.union([z_Address.Address, z.null()])),
type: z.optional(z.string()),
v: z.optional(z.number()),
validAfter: z.optional(uintNumberNumberish()),
validBefore: z.optional(uintNumberNumberish()),
value: z.optional(uintBigintNumberish()),
yParity: z.optional(z.number()),
})
/** Codec decoding an RPC tempo transaction request into a transaction request. */
export const TransactionRequest = z.codec(Rpc, Domain, {
decode: (value) => fromRpc(value as never) as never,
encode: (value) => toRpc(value as never) as never,
})
/** Encode-only tempo transaction request codec accepting numberish `toRpc` inputs. */
export const TransactionRequestToRpc = z.codec(Rpc, DomainToRpc, {
decode: (value) => fromRpc(value as never) as never,
encode: (value) => toRpc(value as never) as never,
})
/** Decodes an RPC tempo transaction request into a transaction request. */
function fromRpc(
request: core_TransactionRequest.Rpc,
): core_TransactionRequest.TransactionRequest {
const { authorizationList: _, ...rest } = request
const request_ = z.decode(
z_TransactionRequest.TransactionRequest,
rest as never,
) as core_TransactionRequest.TransactionRequest
if (typeof request.type !== 'undefined')
request_.type =
fromRpcType[request.type as keyof typeof fromRpcType] || request_.type
if (request.authorizationList)
request_.authorizationList = z.decode(
z_AuthorizationTempo.ListSigned,
request.authorizationList as never,
) as never
if (request.signature)
request_.signature = z.decode(
z_SignatureEnvelope.SignatureEnvelope,
request.signature as never,
) as never
if (request.feePayerSignature)
request_.feePayerSignature = signatureFromRpc(request.feePayerSignature)
if (request.calls)
request_.calls = request.calls.map((call) => ({
to: call.to,
data: call.data,
...(call.value && call.value !== '0x'
? { value: core_Hex.toBigInt(call.value) }
: {}),
}))
if (typeof request.feeToken !== 'undefined')
request_.feeToken = request.feeToken
if (request.keyAuthorization)
request_.keyAuthorization = z.decode(
z_KeyAuthorization.KeyAuthorization,
request.keyAuthorization as never,
) as never
if (typeof request.capabilities !== 'undefined')
request_.capabilities = request.capabilities
if (typeof request.feePayer !== 'undefined')
request_.feePayer = request.feePayer
if (typeof request.keyData !== 'undefined') request_.keyData = request.keyData
if (typeof request.keyId !== 'undefined') request_.keyId = request.keyId
if (typeof request.keyType !== 'undefined') request_.keyType = request.keyType
if (typeof request.multisigInit !== 'undefined')
request_.multisigInit = request.multisigInit
if (typeof request.multisigSignatureCount !== 'undefined')
request_.multisigSignatureCount = request.multisigSignatureCount
if (typeof request.validBefore !== 'undefined')
request_.validBefore = core_Hex.toNumber(request.validBefore)
if (typeof request.validAfter !== 'undefined')
request_.validAfter = core_Hex.toNumber(request.validAfter)
if (typeof request.nonceKey !== 'undefined')
request_.nonceKey = core_Hex.toBigInt(request.nonceKey)
return request_
}
/** Encodes a transaction request into an RPC tempo transaction request. */
function toRpc(
request: core_TransactionRequest.TransactionRequest,
): core_TransactionRequest.Rpc {
const request_rpc = z.encode(z_TransactionRequest.TransactionRequestToRpc, {
...request,
authorizationList: undefined,
} as never) as core_TransactionRequest.Rpc
const tempo =
typeof request.calls !== 'undefined' ||
typeof request.capabilities !== 'undefined' ||
typeof request.feePayer !== 'undefined' ||
typeof request.feeToken !== 'undefined' ||
typeof request.keyAuthorization !== 'undefined' ||
typeof request.keyData !== 'undefined' ||
typeof request.keyId !== 'undefined' ||
typeof request.keyType !== 'undefined' ||
typeof request.multisigInit !== 'undefined' ||
typeof request.multisigSignatureCount !== 'undefined' ||
typeof request.nonceKey !== 'undefined' ||
typeof request.validBefore !== 'undefined' ||
typeof request.validAfter !== 'undefined' ||
request.type === 'tempo'
if (request.authorizationList)
request_rpc.authorizationList = z.encode(
z_AuthorizationTempo.ListSignedToRpc,
request.authorizationList as never,
) as never
if (request.signature)
request_rpc.signature = z.encode(
z_SignatureEnvelope.SignatureEnvelope,
request.signature as never,
) as never
if (request.feePayerSignature)
request_rpc.feePayerSignature = signatureToRpc(request.feePayerSignature)
if (request.calls)
request_rpc.calls = request.calls.map((call) => ({
to: call.to,
value: call.value ? encodeNumberish(call.value) : '0x',
data: call.data ?? '0x',
}))
else if (request.to || request.data || request.value || tempo)
request_rpc.calls = [
{
to:
request.to ||
(tempo && (!request.data || request.data === '0x')
? '0x0000000000000000000000000000000000000000'
: undefined),
value: request.value ? encodeNumberish(request.value) : '0x',
data: request.data ?? '0x',
},
]
if (
typeof request.feeToken !== 'undefined' &&
!(request.feePayer === true && !request.feePayerSignature)
)
request_rpc.feeToken = request.feeToken
if (request.keyAuthorization)
request_rpc.keyAuthorization = z.encode(
z_KeyAuthorization.KeyAuthorizationToRpc,
request.keyAuthorization as never,
) as never
if (typeof request.capabilities !== 'undefined')
request_rpc.capabilities = request.capabilities
if (typeof request.feePayer !== 'undefined')
request_rpc.feePayer = request.feePayer
if (typeof request.keyData !== 'undefined')
request_rpc.keyData = shimKeyData(request.keyData)
if (typeof request.keyId !== 'undefined') request_rpc.keyId = request.keyId
if (typeof request.keyType !== 'undefined')
request_rpc.keyType = request.keyType
if (typeof request.multisigInit !== 'undefined')
request_rpc.multisigInit = request.multisigInit
if (typeof request.multisigSignatureCount !== 'undefined')
request_rpc.multisigSignatureCount = request.multisigSignatureCount
if (typeof request.validBefore !== 'undefined')
request_rpc.validBefore = encodeNumberish(request.validBefore)
if (typeof request.validAfter !== 'undefined')
request_rpc.validAfter = encodeNumberish(request.validAfter)
const nonceKey = (() => {
if (request.nonceKey === 'random') return core_Hex.random(24)
if (typeof request.nonceKey !== 'undefined')
return encodeNumberish(request.nonceKey)
return undefined
})()
if (nonceKey) request_rpc.nonceKey = nonceKey
if (tempo) {
request_rpc.type = toRpcType.tempo
delete request_rpc.data
delete request_rpc.input
delete request_rpc.to
delete request_rpc.value
}
return request_rpc
}
/**
* Shims key data longer than 4 bytes into a 2-byte big-endian length hint
* (the node's gas estimator only accepts 1, 2, or 4-byte key data).
*/
function shimKeyData(data: core_Hex.Hex): core_Hex.Hex {
const byteLength = core_Hex.size(data)
if (byteLength <= 4) return data
return core_Hex.fromNumber(byteLength, { size: 2 })
}
/** Decodes an RPC signature into a recovered signature. */
function signatureFromRpc(signature: {
r: core_Hex.Hex
s: core_Hex.Hex
v?: core_Hex.Hex | undefined
yParity: core_Hex.Hex
}) {
const v = signature.v ? Number(signature.v) : undefined
let yParity = signature.yParity ? Number(signature.yParity) : undefined
if (typeof v === 'number' && typeof yParity !== 'number') {
if (v === 0 || v === 27) yParity = 0
else if (v === 1 || v === 28) yParity = 1
else if (v >= 35) yParity = v % 2 === 0 ? 1 : 0
}
return {
r: core_Hex.padLeft(signature.r, 32),
s: core_Hex.padLeft(signature.s, 32),
yParity: yParity as number,
}
}
/** Encodes a recovered signature into an RPC signature. */
function signatureToRpc(signature: {
r: core_Hex.Hex
s: core_Hex.Hex
yParity: number
}) {
return {
r: signature.r,
s: signature.s,
yParity: signature.yParity === 0 ? '0x0' : '0x1',
} as const
}