UNPKG

ox

Version:

Ethereum Standard Library

315 lines 12.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.InvalidValidityWindowError = exports.CallsEmptyError = exports.type = exports.serializedType = exports.feePayerMagic = void 0; exports.assert = assert; exports.deserialize = deserialize; exports.from = from; exports.serialize = serialize; exports.encodeForSigning = encodeForSigning; exports.getSignPayload = getSignPayload; exports.hash = hash; exports.getFeePayerSignPayload = getFeePayerSignPayload; exports.validate = validate; const AccessList = require("../core/AccessList.js"); const Address = require("../core/Address.js"); const Errors = require("../core/Errors.js"); const Hash = require("../core/Hash.js"); const Hex = require("../core/Hex.js"); const Rlp = require("../core/Rlp.js"); const Secp256k1 = require("../core/Secp256k1.js"); const Signature = require("../core/Signature.js"); const TransactionEnvelope = require("../core/TxEnvelope.js"); const AuthorizationTempo = require("./AuthorizationTempo.js"); const KeyAuthorization = require("./KeyAuthorization.js"); const SignatureEnvelope = require("./SignatureEnvelope.js"); const TempoAddress = require("./TempoAddress.js"); const TokenId = require("./TokenId.js"); exports.feePayerMagic = '0x78'; exports.serializedType = '0x76'; exports.type = 'tempo'; function assert(envelope) { const { calls, chainId, maxFeePerGas, maxPriorityFeePerGas, validBefore, validAfter, } = envelope; if (!calls || calls.length === 0) throw new CallsEmptyError(); if (typeof validBefore === 'number' && typeof validAfter === 'number' && validBefore <= validAfter) { throw new InvalidValidityWindowError({ validBefore: validBefore, validAfter: validAfter, }); } if (calls) for (const call of calls) if (call.to) Address.assert(call.to, { strict: false }); if (chainId <= 0) throw new TransactionEnvelope.InvalidChainIdError({ chainId }); if (maxFeePerGas && BigInt(maxFeePerGas) > 2n ** 256n - 1n) throw new TransactionEnvelope.FeeCapTooHighError({ feeCap: maxFeePerGas, }); if (maxPriorityFeePerGas && maxFeePerGas && maxPriorityFeePerGas > maxFeePerGas) throw new TransactionEnvelope.TipAboveFeeCapError({ maxFeePerGas, maxPriorityFeePerGas, }); } function deserialize(serialized) { const transactionArray = Rlp.toHex(Hex.slice(serialized, 1)); const [chainId, maxPriorityFeePerGas, maxFeePerGas, gas, calls, accessList, nonceKey, nonce, validBefore, validAfter, feeToken, feePayerSignatureOrSender, authorizationList, keyAuthorizationOrSignature, maybeSignature,] = transactionArray; const keyAuthorization = Array.isArray(keyAuthorizationOrSignature) ? keyAuthorizationOrSignature : undefined; const signature = keyAuthorization ? maybeSignature : keyAuthorizationOrSignature; if (!(transactionArray.length === 13 || transactionArray.length === 14 || transactionArray.length === 15)) throw new TransactionEnvelope.InvalidSerializedError({ attributes: { authorizationList, chainId, maxPriorityFeePerGas, maxFeePerGas, gas, calls, accessList, keyAuthorization, nonceKey, nonce, validBefore, validAfter, feeToken, feePayerSignatureOrSender, ...(transactionArray.length > 12 ? { signature, } : {}), }, serialized, type: exports.type, }); let transaction = { chainId: Number(chainId), type: exports.type, }; if (Hex.validate(gas) && gas !== '0x') transaction.gas = BigInt(gas); if (Hex.validate(nonce)) transaction.nonce = nonce === '0x' ? 0n : BigInt(nonce); if (Hex.validate(maxFeePerGas) && maxFeePerGas !== '0x') transaction.maxFeePerGas = BigInt(maxFeePerGas); if (Hex.validate(maxPriorityFeePerGas) && maxPriorityFeePerGas !== '0x') transaction.maxPriorityFeePerGas = BigInt(maxPriorityFeePerGas); if (Hex.validate(nonceKey)) transaction.nonceKey = nonceKey === '0x' ? 0n : BigInt(nonceKey); if (Hex.validate(validBefore) && validBefore !== '0x') transaction.validBefore = Number(validBefore); if (Hex.validate(validAfter) && validAfter !== '0x') transaction.validAfter = Number(validAfter); if (Hex.validate(feeToken) && feeToken !== '0x') transaction.feeToken = feeToken; if (calls && calls !== '0x') { const callsArray = calls; transaction.calls = callsArray.map((callTuple) => { const [to, value, data] = callTuple; const call = {}; if (to && to !== '0x') call.to = to; if (value && value !== '0x') call.value = BigInt(value); if (data && data !== '0x') call.data = data; return call; }); } if (accessList?.length !== 0 && accessList !== '0x') transaction.accessList = AccessList.fromTupleList(accessList); if (authorizationList?.length !== 0 && authorizationList !== '0x') transaction.authorizationList = AuthorizationTempo.fromTupleList(authorizationList); if (feePayerSignatureOrSender !== '0x' && feePayerSignatureOrSender !== undefined) { if (feePayerSignatureOrSender === '0x00' || Address.validate(feePayerSignatureOrSender)) { transaction.feePayerSignature = null; if (Address.validate(feePayerSignatureOrSender)) transaction.from = feePayerSignatureOrSender; } else transaction.feePayerSignature = Signature.fromTuple(feePayerSignatureOrSender); } if (keyAuthorization) transaction.keyAuthorization = KeyAuthorization.fromTuple(keyAuthorization); const signatureEnvelope = signature ? SignatureEnvelope.deserialize(signature) : undefined; if (signatureEnvelope) transaction = { ...transaction, signature: signatureEnvelope, }; if (!transaction.from && signatureEnvelope) { try { transaction.from = SignatureEnvelope.extractAddress({ payload: getSignPayload(from(transaction)), signature: signatureEnvelope, root: true, }); } catch { } } assert(transaction); return transaction; } function from(envelope, options = {}) { const { feePayerSignature, signature } = options; const envelope_ = (typeof envelope === 'string' ? deserialize(envelope) : envelope); if (envelope_.from) envelope_.from = TempoAddress.resolve(envelope_.from); if (envelope_.calls) envelope_.calls = envelope_.calls.map((call) => ({ ...call, ...(call.to ? { to: TempoAddress.resolve(call.to) } : {}), })); assert(envelope_); return { ...envelope_, ...(signature ? { signature: SignatureEnvelope.from(signature) } : {}), ...(feePayerSignature ? { feePayerSignature: Signature.from(feePayerSignature) } : {}), type: 'tempo', }; } function serialize(envelope, options = {}) { const { accessList, authorizationList, calls, chainId, feeToken, gas, keyAuthorization, nonce, nonceKey, maxFeePerGas, maxPriorityFeePerGas, validBefore, validAfter, } = envelope; assert(envelope); const accessTupleList = AccessList.toTupleList(accessList); const signature = options.signature || envelope.signature; const authorizationTupleList = AuthorizationTempo.toTupleList(authorizationList); const callsTupleList = calls.map((call) => [ call.to ? TempoAddress.resolve(call.to) : '0x', call.value ? Hex.fromNumber(call.value) : '0x', call.data ?? '0x', ]); let skipFeeToken = false; const feePayerSignatureOrSender = (() => { if (options.sender) return options.sender; if (options.format === 'feePayer' && signature) { const sig = SignatureEnvelope.from(signature); if (sig.type === 'keychain') return sig.userAddress; if (sig.type === 'p256' || sig.type === 'webAuthn') return Address.fromPublicKey(sig.publicKey); if (sig.type === 'secp256k1') return Secp256k1.recoverAddress({ payload: getSignPayload(from(envelope)), signature: sig.signature, }); } const feePayerSignature = typeof options.feePayerSignature !== 'undefined' ? options.feePayerSignature : envelope.feePayerSignature; if (feePayerSignature === null) { skipFeeToken = true; return '0x00'; } if (!feePayerSignature) return '0x'; return Signature.toTuple(feePayerSignature); })(); const serialized = [ Hex.fromNumber(chainId), maxPriorityFeePerGas ? Hex.fromNumber(maxPriorityFeePerGas) : '0x', maxFeePerGas ? Hex.fromNumber(maxFeePerGas) : '0x', gas ? Hex.fromNumber(gas) : '0x', callsTupleList, accessTupleList, nonceKey ? Hex.fromNumber(nonceKey) : '0x', nonce ? Hex.fromNumber(nonce) : '0x', typeof validBefore === 'number' ? Hex.fromNumber(validBefore) : '0x', typeof validAfter === 'number' ? Hex.fromNumber(validAfter) : '0x', !skipFeeToken && (typeof feeToken === 'bigint' || typeof feeToken === 'string') ? TokenId.toAddress(feeToken) : '0x', feePayerSignatureOrSender, authorizationTupleList, ...(keyAuthorization ? [KeyAuthorization.toTuple(keyAuthorization)] : []), ...(signature ? [SignatureEnvelope.serialize(SignatureEnvelope.from(signature))] : []), ]; return Hex.concat(options.format === 'feePayer' ? exports.feePayerMagic : exports.serializedType, Rlp.fromHex(serialized)); } function encodeForSigning(envelope) { return serialize({ ...envelope, signature: undefined, ...(envelope.feePayerSignature !== undefined ? { feePayerSignature: null } : {}), }); } function getSignPayload(envelope, options = {}) { const sigHash = hash(envelope, { presign: true }); if (options.from) return Hash.keccak256(Hex.concat('0x04', sigHash, TempoAddress.resolve(options.from))); return sigHash; } function hash(envelope, options = {}) { const serialized = options.presign ? encodeForSigning(envelope) : serialize(envelope); return Hash.keccak256(serialized); } function getFeePayerSignPayload(envelope, options) { const sender = TempoAddress.resolve(options.sender); const serialized = serialize({ ...envelope, signature: undefined }, { sender, format: 'feePayer', }); return Hash.keccak256(serialized); } function validate(envelope) { try { assert(envelope); return true; } catch { return false; } } class CallsEmptyError extends Errors.BaseError { constructor() { super('Calls list cannot be empty.'); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'TxEnvelopeTempo.CallsEmptyError' }); } } exports.CallsEmptyError = CallsEmptyError; class InvalidValidityWindowError extends Errors.BaseError { constructor({ validBefore, validAfter, }) { super(`validBefore (${validBefore}) must be greater than validAfter (${validAfter}).`); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'TxEnvelopeTempo.InvalidValidityWindowError' }); } } exports.InvalidValidityWindowError = InvalidValidityWindowError; //# sourceMappingURL=TxEnvelopeTempo.js.map