UNPKG

viem

Version:

TypeScript Interface for Ethereum

1,000 lines • 41.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.handleRequest = handleRequest; const Address_ = require("ox/Address"); const Hash = require("ox/Hash"); const Hex = require("ox/Hex"); const RpcResponse = require("ox/RpcResponse"); const tempo_1 = require("ox/tempo"); const getBlockNumber_js_1 = require("../actions/public/getBlockNumber.js"); const createClient_js_1 = require("../clients/createClient.js"); const custom_js_1 = require("../clients/transports/custom.js"); const decodeFunctionData_js_1 = require("../utils/abi/decodeFunctionData.js"); const isAddressEqual_js_1 = require("../utils/address/isAddressEqual.js"); const Abis = require("./Abis.js"); const Addresses = require("./Addresses.js"); const multisig_js_1 = require("./actions/multisig.js"); const ConfigStore = require("./multisig/Config.js"); const OperationStore = require("./multisig/Operation.js"); const Transaction = require("./Transaction.js"); const submissionTtl = 30_000; const pollingInterval = 100; function handleRequest(next, parameters) { if (!parameters.store.compareAndSet) throw new RpcResponse.InvalidParamsError({ message: 'Multisig coordination requires a store with atomic `compareAndSet`.', }); return async (request, requestOptions_) => { const requestOptions = await resolveRequestOptions({ request, requestOptions: requestOptions_, store: parameters.store, }); const client = (0, createClient_js_1.createClient)({ transport: (0, custom_js_1.custom)({ request: ({ method, params }, options) => next({ method, params }, { ...requestOptions, ...options }), }), }); if (request.method === 'multisig_getConfig') { const value = request.params?.[0]; const address = value && typeof value === 'object' && 'address' in value ? value.address : undefined; if (typeof address !== 'string' || !Address_.validate(address) || Hex.toBigInt(address) === 0n) throw new RpcResponse.InvalidParamsError({ message: 'Expected a multisig account address.', }); const blockNumber = await (0, getBlockNumber_js_1.getBlockNumber)(client, { cacheTime: 0 }); const commitment = await (0, multisig_js_1.getConfigCommitment)(client, { account: address, blockNumber, }); const config = await ConfigStore.read(parameters.store, { address, commitment, }); if (!config) return null; return tempo_1.MultisigConfig.toRpc(config); } if (request.method === 'multisig_getOperation') { const hash = request.params?.[0]; if (typeof hash !== 'string' || !Hash.validate(hash)) throw new RpcResponse.InvalidParamsError({ message: 'Expected a multisig operation hash.', }); const operation = await OperationStore.read(parameters.store, hash); return operation ? tempo_1.MultisigOperation.toRpc(operation) : null; } if (request.method === 'multisig_approveKeyAuthorization') return await approveKeyAuthorization({ client, request, store: parameters.store, }); if (request.method === 'eth_getTransactionByHash' || request.method === 'eth_getTransactionReceipt') { const hash = request.params?.[0]; if (typeof hash !== 'string' || !Hash.validate(hash)) return await next(request, requestOptions); const operation = await OperationStore.read(parameters.store, hash); if (!operation || operation.type !== 'transaction') return await next(request, requestOptions); if (request.method === 'eth_getTransactionReceipt') { if (operation.status === 'pending') return null; const transactionHash = await getSubmittedTransactionHash(parameters.store, operation); if (!transactionHash) return null; const receipt = await next({ ...request, params: [transactionHash], }, requestOptions); if (!receipt || typeof receipt !== 'object') return receipt; const success = operation.status === 'submitting' ? await completeSubmission(parameters.store, operation, transactionHash) : operation; return { ...receipt, multisig: tempo_1.MultisigOperation.toRpc(success), }; } if (operation.status === 'pending') return await toTransaction(client, operation); const transactionHash = await getSubmittedTransactionHash(parameters.store, operation); if (!transactionHash) return await toTransaction(client, operation); const transaction = await next({ ...request, params: [transactionHash], }, requestOptions); if (!transaction || typeof transaction !== 'object') return await toTransaction(client, operation); const success = operation.status === 'submitting' ? await completeSubmission(parameters.store, operation, transactionHash) : operation; return { ...transaction, multisig: tempo_1.MultisigOperation.toRpc(success), }; } if (request.method !== 'eth_sendRawTransaction' && request.method !== 'eth_sendRawTransactionSync' && request.method !== 'multisig_approveRawTransaction' && request.method !== 'multisig_approveRawTransactionSync') return await next(request, requestOptions); const standard = request.method === 'eth_sendRawTransaction' || request.method === 'eth_sendRawTransactionSync'; const serialized = request.params?.[0]; if (!isSerializedTempoTransaction(serialized)) { if (standard) return await next(request, requestOptions); throw new RpcResponse.InvalidParamsError({ message: 'Expected a serialized Tempo multisig transaction.', }); } return await submit({ client, method: request.method, next, request, requestOptions, serialized, store: parameters.store, }); }; } async function submit(options) { const transaction = (() => { try { return deserialize(options.serialized); } catch (error) { if (options.method !== 'eth_sendRawTransaction' && options.method !== 'eth_sendRawTransactionSync') throw error; return undefined; } })(); const signature = transaction?.signature; if (!transaction || signature?.type !== 'multisig') { if (options.method === 'eth_sendRawTransaction' || options.method === 'eth_sendRawTransactionSync') return await options.next(options.request, options.requestOptions); throw new RpcResponse.InvalidParamsError({ message: 'Expected a multisig transaction signature.', }); } const { signature: _, ...unsigned } = transaction; const envelope = tempo_1.TxEnvelopeTempo.from(unsigned); const serializedUnsigned = serializeUnsigned(envelope, options.serialized.startsWith(tempo_1.TxEnvelopeTempo.feePayerMagic)); const blockNumber = await (0, getBlockNumber_js_1.getBlockNumber)(options.client, { cacheTime: 0 }); const config = tempo_1.MultisigConfig.from(signature.config); const configCommitment = await validateConfig({ account: signature.account, blockNumber, client: options.client, config, }); const operationHash = tempo_1.MultisigOperation.getHash({ account: signature.account, config, transaction: serializedUnsigned, type: 'transaction', }); const incoming = signature.signatures.map((signature) => tempo_1.SignatureEnvelope.serialize(signature)); if (incoming.length === 0) throw new RpcResponse.InvalidParamsError({ message: 'A multisig approval envelope must include a signature.', }); const now = Date.now(); const expiredSubmissionIds = new Set(); const operation = await OperationStore.update(options.store, operationHash, async (existing) => { if (existing && existing.type !== 'transaction') throw new OperationStore.InvalidStoreValueError(); if (existing?.status === 'success') return existing; if (existing?.status === 'submitting' && existing.expiresAt > now) return existing; if (existing?.status === 'submitting') expiredSubmissionIds.add(existing.submissionId); const existingApprovals = existing ? await selectApprovals({ account: signature.account, config, hash: operationHash, approvals: existing.approvals, }) : undefined; const approvals = await selectApprovals({ account: signature.account, config, hash: operationHash, approvals: [...(existingApprovals?.approvals ?? []), ...incoming], }); return tempo_1.MultisigOperation.from({ account: signature.account, approvals: approvals.approvals, config, createdAt: existing?.createdAt ?? now, hash: operationHash, signatureCount: approvals.signatureCount, status: 'pending', threshold: approvals.threshold, transaction: mergeTransaction(existing?.transaction, serializedUnsigned), type: 'transaction', updatedAt: now, weight: approvals.weight, }); }); for (const submissionId of expiredSubmissionIds) { if (operation.status === 'submitting' && operation.submissionId?.toLowerCase() === submissionId.toLowerCase()) continue; await removeSettledSubmission(options.store, operationHash, submissionId); } await ConfigStore.write(options.store, { address: signature.account, commitment: configCommitment, config, }); await cacheNextConfigs({ account: signature.account, config, store: options.store, transaction, }); if (operation.type !== 'transaction') throw new OperationStore.InvalidStoreValueError(); if (operation.status === 'success') return await submittedResult(options, operation); if (operation.status === 'submitting' && operation.expiresAt > Date.now()) return await submittingResult(options, operation); if (operation.weight < operation.threshold) return pendingResult(options.method, operation); const submissionId = Hex.random(32); const timeout = options.request.params?.[1]; const synchronous = options.method === 'eth_sendRawTransactionSync' || options.method === 'multisig_approveRawTransactionSync'; const leaseTtl = synchronous && typeof timeout === 'number' && Number.isSafeInteger(timeout) && timeout >= 0 ? timeout + submissionTtl : submissionTtl; const claim = await OperationStore.update(options.store, operationHash, (current) => { if (!current || current.type !== 'transaction') throw new OperationStore.InvalidStoreValueError(); if (current.status === 'success') return current; if (current.status === 'submitting' && current.expiresAt > Date.now()) return current; if (current.weight < current.threshold) return current; const now = Date.now(); return tempo_1.MultisigOperation.from({ ...current, expiresAt: Math.min(now + leaseTtl, Number.MAX_SAFE_INTEGER), status: 'submitting', submissionId, updatedAt: now, }); }); if (claim.type !== 'transaction') throw new OperationStore.InvalidStoreValueError(); if (claim.status === 'success') return await submittedResult(options, claim); if (claim.status === 'pending') return pendingResult(options.method, claim); if (claim.submissionId !== submissionId) return await submittingResult(options, claim); const { final, transactionHash: initialTransactionHash } = await (async () => { try { const blockNumber = await (0, getBlockNumber_js_1.getBlockNumber)(options.client, { cacheTime: 0, }); await validateConfig({ account: claim.account, blockNumber, client: options.client, config: claim.config, }); const finalApprovals = await selectApprovals({ account: claim.account, config: claim.config, hash: claim.hash, approvals: claim.approvals, }); const final = tempo_1.MultisigOperation.serializeTransaction(claim, { approvals: finalApprovals.selectedApprovals, }); const transactionHash = tempo_1.TxEnvelopeTempo.hash(tempo_1.TxEnvelopeTempo.deserialize(final)); await OperationStore.writeSubmission(options.store, operationHash, submissionId, final); return { final, transactionHash }; } catch (error) { await releaseSubmission(options.store, operationHash, submissionId); throw error; } })(); let transactionHash = initialTransactionHash; let result; const stopLease = maintainSubmissionLease(options.store, operationHash, submissionId, leaseTtl); let leaseStopped = false; const stopLeaseOnce = async () => { if (leaseStopped) return; leaseStopped = true; await stopLease(); }; try { try { result = await options.next({ method: options.method === 'eth_sendRawTransaction' || options.method === 'multisig_approveRawTransaction' ? 'eth_sendRawTransaction' : 'eth_sendRawTransactionSync', params: [final, ...(options.request.params?.slice(1) ?? [])], }, options.requestOptions); const returnedHash = getTransactionHash(result); if (returnedHash.toLowerCase() !== transactionHash.toLowerCase()) transactionHash = returnedHash; } catch (error) { const submittedHash = await OperationStore.readSubmission(options.store, claim, submissionId); const transaction = await (async () => { if (!submittedHash) return null; try { return await options.next({ method: 'eth_getTransactionByHash', params: [submittedHash], }); } catch { return null; } })(); await stopLeaseOnce(); if (!submittedHash || !transaction) { await releaseSubmission(options.store, operationHash, submissionId); throw error; } transactionHash = submittedHash; } } finally { await stopLeaseOnce(); } const success = await OperationStore.update(options.store, operationHash, (current) => { if (!current || current.type !== 'transaction') throw new OperationStore.InvalidStoreValueError(); if (current.status === 'success') { if (current.transactionHash?.toLowerCase() !== transactionHash.toLowerCase()) throw new OperationStore.InvalidStoreValueError(); return current; } if (current.status !== 'submitting' || current.submissionId !== submissionId) throw new OperationStore.InvalidStoreValueError(); const { expiresAt: _, submissionId: __, ...operation } = current; return tempo_1.MultisigOperation.from({ ...operation, status: 'success', transactionHash, updatedAt: Date.now(), }); }); if (success.type !== 'transaction' || success.status !== 'success') throw new OperationStore.InvalidStoreValueError(); await removeSettledSubmission(options.store, operationHash, submissionId); if (options.method === 'eth_sendRawTransactionSync' && result && typeof result === 'object') return { ...result, multisig: tempo_1.MultisigOperation.toRpc(success) }; return await submittedResult(options, success); } async function approveKeyAuthorization(options) { const value = options.request.params?.[0]; if (!value || typeof value !== 'object') throw new RpcResponse.InvalidParamsError({ message: 'Expected a multisig key authorization approval.', }); const approval = await (async () => { if ('keyAuthorization' in value) { const authorization = (() => { try { return tempo_1.KeyAuthorization.fromRpc(value.keyAuthorization); } catch { throw new RpcResponse.InvalidParamsError({ message: 'Invalid multisig key authorization.', }); } })(); const signature = authorization.signature; if (signature?.type !== 'multisig') throw new RpcResponse.InvalidParamsError({ message: 'Expected a multisig key authorization signature.', }); if (!authorization.account || !(0, isAddressEqual_js_1.isAddressEqual)(authorization.account, signature.account)) throw new RpcResponse.InvalidParamsError({ message: 'Multisig key authorization account does not match its signature.', }); const { signature: _, ...unsigned } = authorization; const keyAuthorization = tempo_1.KeyAuthorization.serialize(tempo_1.KeyAuthorization.from(unsigned)); const config = tempo_1.MultisigConfig.from(signature.config); const hash = tempo_1.MultisigOperation.getHash({ account: signature.account, config, keyAuthorization, type: 'keyAuthorization', }); return { account: signature.account, approvals: signature.signatures.map((signature) => tempo_1.SignatureEnvelope.serialize(signature)), config, hash, keyAuthorization, }; } if ('hash' in value && 'signature' in value) { if (typeof value.hash !== 'string' || !Hash.validate(value.hash)) throw new RpcResponse.InvalidParamsError({ message: 'Expected a multisig operation hash.', }); const operation = await OperationStore.read(options.store, value.hash); if (!operation || operation.type !== 'keyAuthorization') throw new RpcResponse.InvalidParamsError({ message: 'Multisig key authorization operation was not found.', }); if (operation.status === 'success') return { operation }; const signature = (() => { try { return tempo_1.SignatureEnvelope.serialize(tempo_1.SignatureEnvelope.from(value.signature)); } catch { throw new RpcResponse.InvalidParamsError({ message: 'Invalid multisig owner signature.', }); } })(); return { account: operation.account, approvals: [signature], config: operation.config, hash: operation.hash, keyAuthorization: operation.keyAuthorization, }; } throw new RpcResponse.InvalidParamsError({ message: 'Expected a multisig key authorization approval.', }); })(); if ('operation' in approval) return tempo_1.MultisigOperation.toRpc(approval.operation); if (approval.approvals.length === 0) throw new RpcResponse.InvalidParamsError({ message: 'A multisig approval envelope must include a signature.', }); const blockNumber = await (0, getBlockNumber_js_1.getBlockNumber)(options.client, { cacheTime: 0 }); const commitment = await validateConfig({ account: approval.account, blockNumber, client: options.client, config: approval.config, }); const now = Date.now(); const operation = await OperationStore.update(options.store, approval.hash, async (existing) => { if (existing && existing.type !== 'keyAuthorization') throw new OperationStore.InvalidStoreValueError(); if (existing?.status === 'success') return existing; if (existing && (existing.account.toLowerCase() !== approval.account.toLowerCase() || tempo_1.MultisigConfig.getCommitment(existing.config).toLowerCase() !== tempo_1.MultisigConfig.getCommitment(approval.config).toLowerCase() || existing.keyAuthorization.toLowerCase() !== approval.keyAuthorization.toLowerCase())) throw new OperationStore.InvalidStoreValueError(); const existingApprovals = existing ? await selectApprovals({ account: approval.account, approvals: existing.approvals, config: approval.config, hash: approval.hash, }) : undefined; const approvals = await selectApprovals({ account: approval.account, approvals: [ ...(existingApprovals?.approvals ?? []), ...approval.approvals, ], config: approval.config, hash: approval.hash, }); if (existing && approvals.approvals.length === existing.approvals.length && approvals.approvals.every((approval, index) => approval === existing.approvals[index])) return existing; const pending = { account: approval.account, approvals: approvals.approvals, config: approval.config, createdAt: existing?.createdAt ?? now, hash: approval.hash, keyAuthorization: approval.keyAuthorization, signatureCount: approvals.signatureCount, threshold: approvals.threshold, type: 'keyAuthorization', updatedAt: now, weight: approvals.weight, }; if (approvals.weight < approvals.threshold) return tempo_1.MultisigOperation.from({ ...pending, status: 'pending' }); return tempo_1.MultisigOperation.from({ ...pending, keyAuthorization: tempo_1.MultisigOperation.serializeKeyAuthorization(approval.keyAuthorization, { account: approval.account, approvals: approvals.selectedApprovals, config: approval.config, }), status: 'success', }); }); await ConfigStore.write(options.store, { address: approval.account, commitment, config: approval.config, }); if (operation.type !== 'keyAuthorization') throw new OperationStore.InvalidStoreValueError(); return tempo_1.MultisigOperation.toRpc(operation); } function deserialize(serialized) { try { return Transaction.deserialize(serialized); } catch { throw new RpcResponse.InvalidParamsError({ message: 'Invalid serialized Tempo transaction.', }); } } function assertConfig(options) { const { account, commitment, config } = options; if (config.version === 0n && tempo_1.MultisigConfig.getAddress(config, { factory: Addresses.nativeMultisigFactory, }).toLowerCase() !== account.toLowerCase()) throw new RpcResponse.InvalidParamsError({ message: 'Initial multisig config does not match the multisig account.', }); if (config.version === 0n && Hex.toBigInt(commitment) === 0n) return; if (commitment.toLowerCase() !== tempo_1.MultisigConfig.getCommitment(config).toLowerCase()) throw new RpcResponse.InvalidParamsError({ message: `Multisig config does not match account ${account}.`, }); } async function validateConfig(options) { const { account, blockNumber, client, config } = options; const commitment = await (0, multisig_js_1.getConfigCommitment)(client, { account, blockNumber, }); assertConfig({ account, commitment, config }); return commitment; } async function cacheNextConfigs(options) { let currentConfig = options.config; for (const call of options.transaction.calls) { if (!call.to || !(0, isAddressEqual_js_1.isAddressEqual)(call.to, Addresses.nativeMultisig) || !call.data) continue; const decoded = (() => { try { return (0, decodeFunctionData_js_1.decodeFunctionData)({ abi: Abis.nativeMultisig, data: call.data }); } catch { return undefined; } })(); if (decoded?.functionName !== 'updateConfig') continue; const [current, threshold, owners] = decoded.args; const suppliedConfig = (() => { try { return tempo_1.MultisigConfig.from(current); } catch { return undefined; } })(); if (!suppliedConfig || tempo_1.MultisigConfig.getCommitment(suppliedConfig).toLowerCase() !== tempo_1.MultisigConfig.getCommitment(currentConfig).toLowerCase()) continue; const config = (() => { try { return tempo_1.MultisigConfig.from({ owners, salt: suppliedConfig.salt, threshold, version: suppliedConfig.version + 1n, }); } catch { return undefined; } })(); if (!config) continue; await ConfigStore.write(options.store, { address: options.account, commitment: tempo_1.MultisigConfig.getCommitment(config), config, }); currentConfig = config; } } async function selectApprovals(options) { try { return await tempo_1.MultisigOperation.selectApprovals(options); } catch (error) { if (error instanceof tempo_1.MultisigOperation.InvalidApprovalError) throw new RpcResponse.InvalidParamsError({ message: error.shortMessage }); throw error; } } async function submittedResult(options, operation) { if (options.method === 'eth_sendRawTransaction' || options.method === 'multisig_approveRawTransaction') return operation.hash; if (options.method === 'multisig_approveRawTransactionSync') return tempo_1.MultisigOperation.toRpc(operation); const timeout = options.request.params?.[1]; const deadline = Date.now() + (typeof timeout === 'number' && timeout >= 0 ? timeout : submissionTtl); while (true) { const receipt = await options.next({ method: 'eth_getTransactionReceipt', params: [operation.transactionHash], }, options.requestOptions); if (receipt && typeof receipt === 'object') return { ...receipt, multisig: tempo_1.MultisigOperation.toRpc(operation) }; if (Date.now() >= deadline) throw new Error('Timed out while waiting for the multisig transaction.'); await new Promise((resolve) => setTimeout(resolve, pollingInterval)); } } async function submittingResult(options, operation) { if (options.method === 'eth_sendRawTransaction' || options.method === 'multisig_approveRawTransaction') return operation.hash; const timeout = options.request.params?.[1]; const deadline = Date.now() + (typeof timeout === 'number' && timeout >= 0 ? timeout : submissionTtl); while (true) { const current = await OperationStore.read(options.store, operation.hash); if (!current || current.type !== 'transaction') throw new OperationStore.InvalidStoreValueError(); if (current.status === 'success') return await submittedResult(options, current); if (current.status === 'pending' || current.expiresAt <= Date.now()) return await submit(options); if (Date.now() >= deadline) return pendingResult(options.method, current); await new Promise((resolve) => setTimeout(resolve, pollingInterval)); } } function pendingResult(method, operation) { if (method === 'eth_sendRawTransaction' || method === 'multisig_approveRawTransaction') return operation.hash; if (method === 'multisig_approveRawTransactionSync') return tempo_1.MultisigOperation.toRpc(operation); return { blockHash: null, blockNumber: null, contractAddress: null, cumulativeGasUsed: null, effectiveGasPrice: null, from: operation.account, gasUsed: null, logs: [], logsBloom: null, multisig: tempo_1.MultisigOperation.toRpc(operation), status: 'pending', to: null, transactionHash: operation.hash, transactionIndex: null, type: '0x76', }; } async function completeSubmission(store, operation, transactionHash) { if (operation.status !== 'submitting' || !operation.submissionId) throw new OperationStore.InvalidStoreValueError(); const success = await OperationStore.update(store, operation.hash, (current) => { if (!current || current.type !== 'transaction') throw new OperationStore.InvalidStoreValueError(); if (current.status === 'success') { if (current.transactionHash?.toLowerCase() !== transactionHash.toLowerCase()) throw new OperationStore.InvalidStoreValueError(); return current; } if (current.status !== 'submitting' || current.submissionId !== operation.submissionId) throw new OperationStore.InvalidStoreValueError(); const { expiresAt: _, submissionId: __, transactionHash: ___, ...value } = current; return tempo_1.MultisigOperation.from({ ...value, status: 'success', transactionHash, updatedAt: Date.now(), }); }); await removeSettledSubmission(store, operation.hash, operation.submissionId); return success; } async function releaseSubmission(store, hash, submissionId) { await OperationStore.update(store, hash, (current) => { if (!current || current.type !== 'transaction') throw new OperationStore.InvalidStoreValueError(); if (current.status !== 'submitting' || current.submissionId !== submissionId) return current; const { expiresAt: _, submissionId: __, ...operation } = current; return tempo_1.MultisigOperation.from({ ...operation, status: 'pending', updatedAt: Date.now(), }); }); await OperationStore.removeSubmission(store, hash, submissionId); } async function removeSettledSubmission(store, hash, submissionId) { try { await OperationStore.removeSubmission(store, hash, submissionId); } catch { } } function mergeTransaction(existing, incoming) { if (!existing) return incoming; const existingTransaction = tempo_1.TxEnvelopeTempo.deserialize(existing); const incomingTransaction = tempo_1.TxEnvelopeTempo.deserialize(incoming); if (!('feePayerSignature' in incomingTransaction)) return existing; if (incomingTransaction.feePayerSignature !== null) return incoming; if (!('feePayerSignature' in existingTransaction)) return incoming; return existing; } async function toTransaction(client, operation) { const approvals = await (async () => { if (operation.status !== 'pending') return await tempo_1.MultisigOperation.selectApprovals({ account: operation.account, approvals: operation.approvals, config: operation.config, hash: operation.hash, }); const blockNumber = await (0, getBlockNumber_js_1.getBlockNumber)(client, { cacheTime: 0 }); await validateConfig({ account: operation.account, blockNumber, client, config: operation.config, }); return await selectApprovals({ account: operation.account, approvals: operation.approvals, config: operation.config, hash: operation.hash, }); })(); const current = operation.status === 'pending' ? tempo_1.MultisigOperation.from({ ...operation, approvals: approvals.approvals, signatureCount: approvals.signatureCount, threshold: approvals.threshold, weight: approvals.weight, }) : operation; const serialized = tempo_1.MultisigOperation.serializeTransaction(current, { approvals: approvals.selectedApprovals, }); const transaction = deserialize(serialized); return { ...tempo_1.Transaction.toRpc({ ...transaction, blockHash: null, blockNumber: null, from: operation.account, hash: operation.hash, transactionIndex: null, }, { pending: true }), multisig: tempo_1.MultisigOperation.toRpc(current), }; } async function getSubmittedTransactionHash(store, operation) { if (operation.status === 'success') return operation.transactionHash; if (operation.status !== 'submitting' || !operation.submissionId) throw new OperationStore.InvalidStoreValueError(); return await OperationStore.readSubmission(store, operation, operation.submissionId); } function maintainSubmissionLease(store, hash, submissionId, leaseTtl) { let active = true; let error; let renewal = Promise.resolve(); const timer = setInterval(() => { renewal = renewal.then(async () => { if (!active || error) return; try { const operation = await OperationStore.update(store, hash, (current) => { if (!current || current.type !== 'transaction') throw new OperationStore.InvalidStoreValueError(); if (current.status !== 'submitting' || current.submissionId !== submissionId) return current; return tempo_1.MultisigOperation.from({ ...current, expiresAt: Math.min(Date.now() + leaseTtl, Number.MAX_SAFE_INTEGER), updatedAt: Date.now(), }); }); if (operation.type !== 'transaction' || operation.status !== 'submitting' || operation.submissionId !== submissionId) throw new OperationStore.InvalidStoreValueError(); } catch (cause) { error = cause; } }); }, submissionTtl / 2); return async () => { active = false; clearInterval(timer); await renewal; if (error) throw error; }; } function serializeUnsigned(envelope, feePayer) { return tempo_1.TxEnvelopeTempo.serialize(envelope, feePayer ? envelope.from ? { format: 'feePayer', sender: envelope.from } : { format: 'feePayer' } : {}); } function getTransactionHash(result) { if (typeof result === 'string' && Hash.validate(result)) return result; if (result && typeof result === 'object' && 'transactionHash' in result && typeof result.transactionHash === 'string' && Hash.validate(result.transactionHash)) return result.transactionHash; throw new Error('Expected transaction hash in multisig broadcast result.'); } async function resolveRequestOptions(options) { const { request, requestOptions, store } = options; const chainId_explicit = requestOptions?.chainId; if (chainId_explicit !== undefined && (!Number.isSafeInteger(chainId_explicit) || chainId_explicit <= 0)) throw new RpcResponse.InvalidParamsError({ message: 'Expected a valid chain ID.', }); const chainId_request = await resolveRequestChainId(request, store); if (chainId_explicit !== undefined && chainId_request !== undefined && chainId_explicit !== chainId_request) throw new RpcResponse.InvalidParamsError({ message: 'Conflicting chain ids.', }); const chainId = chainId_explicit ?? chainId_request; if (chainId === undefined) return requestOptions; return { ...requestOptions, chainId }; } async function resolveRequestChainId(request, store) { const value = request.params?.[0]; const chainId_body = value && typeof value === 'object' && 'chainId' in value ? parseChainId(value.chainId) : undefined; const chainId_multisig = await (async () => { if (request.method === 'eth_sendRawTransaction' || request.method === 'eth_sendRawTransactionSync' || request.method === 'multisig_approveRawTransaction' || request.method === 'multisig_approveRawTransactionSync') { if (!isSerializedTempoTransaction(value)) return undefined; try { return parseChainId(Transaction.deserialize(value).chainId); } catch { return undefined; } } if (request.method === 'multisig_approveKeyAuthorization' && value && typeof value === 'object' && 'keyAuthorization' in value) { try { return parseChainId(tempo_1.KeyAuthorization.fromRpc(value.keyAuthorization).chainId); } catch { return undefined; } } const hash = (() => { if (request.method === 'eth_getTransactionByHash' || request.method === 'eth_getTransactionReceipt') return value; if (request.method === 'multisig_approveKeyAuthorization' && value && typeof value === 'object' && 'hash' in value) return value.hash; return undefined; })(); if (typeof hash !== 'string' || !Hash.validate(hash)) return undefined; const operation = await OperationStore.read(store, hash); if (!operation) return undefined; if (operation.type === 'transaction') return parseChainId(Transaction.deserialize(operation.transaction).chainId); return parseChainId(tempo_1.KeyAuthorization.deserialize(operation.keyAuthorization).chainId); })(); if (chainId_body !== undefined && chainId_multisig !== undefined && chainId_body !== chainId_multisig) throw new RpcResponse.InvalidParamsError({ message: 'Conflicting chain ids.', }); return chainId_body ?? chainId_multisig; } function parseChainId(value) { const chainId = (() => { if (typeof value === 'number') return value; if (typeof value === 'bigint') return Number(value); if (typeof value === 'string' && Hex.validate(value)) { try { return Hex.toNumber(value); } catch { return undefined; } } return undefined; })(); if (chainId === undefined || !Number.isSafeInteger(chainId) || chainId <= 0) return undefined; return chainId; } function isSerializedTempoTransaction(value) { return (typeof value === 'string' && (value.startsWith('0x76') || value.startsWith('0x78'))); } //# sourceMappingURL=Multisig.js.map