viem
Version:
330 lines • 16.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.chainConfig = void 0;
const Hex = require("ox/Hex");
const tempo_1 = require("ox/tempo");
const getCode_js_1 = require("../actions/public/getCode.js");
const getTransaction_js_1 = require("../actions/public/getTransaction.js");
const verifyHash_js_1 = require("../actions/public/verifyHash.js");
const number_js_1 = require("../constants/number.js");
const isAddressEqual_js_1 = require("../utils/address/isAddressEqual.js");
const defineChain_js_1 = require("../utils/chain/defineChain.js");
const transaction_js_1 = require("../utils/formatters/transaction.js");
const transactionReceipt_js_1 = require("../utils/formatters/transactionReceipt.js");
const transactionRequest_js_1 = require("../utils/formatters/transactionRequest.js");
const getAction_js_1 = require("../utils/getAction.js");
const keccak256_js_1 = require("../utils/hash/keccak256.js");
const accessKey_js_1 = require("./actions/accessKey.js");
const multisig_js_1 = require("./actions/multisig.js");
const Formatters = require("./Formatters.js");
const Concurrent = require("./internal/concurrent.js");
const Transaction = require("./Transaction.js");
const maxExpirySecs = 25;
const serializeTransaction = ((transaction, signature) => Transaction.serialize(transaction, signature));
exports.chainConfig = {
blockTime: 1_000,
extendSchema: (0, defineChain_js_1.extendSchema)(),
formatters: {
transaction: (0, transaction_js_1.defineTransaction)({
exclude: ['aaAuthorizationList'],
format: Formatters.formatTransaction,
}),
transactionReceipt: (0, transactionReceipt_js_1.defineTransactionReceipt)({
format: Formatters.formatTransactionReceipt,
}),
transactionRequest: (0, transactionRequest_js_1.defineTransactionRequest)({
format: Formatters.formatTransactionRequest,
}),
},
prepareTransactionRequest: [
async (r, { client, phase }) => {
const request = r;
if (request.hash) {
if (!request.account ||
typeof request.account === 'string' ||
request.account.source !== 'multisig')
throw new Error('A local multisig account is required to approve a stored multisig transaction.');
if (!request.owner || typeof request.owner === 'string')
throw new Error('A local owner account is required to approve a stored multisig transaction.');
if (request.owner.source !== 'root')
throw new Error('A Tempo owner account is required to approve a stored multisig transaction.');
const transaction = await (0, getAction_js_1.getAction)(client, getTransaction_js_1.getTransaction, 'getTransaction')({ hash: request.hash });
const operation = 'multisig' in transaction
? transaction.multisig
: undefined;
if (!operation)
throw new Error('Expected a multisig operation transaction.');
if (!(0, isAddressEqual_js_1.isAddressEqual)(operation.account, request.account.address))
throw new Error('Multisig operation account does not match the requested account.');
const storedTransaction = Transaction.deserialize(operation.transaction);
const hash = tempo_1.MultisigOperation.getHash({
account: operation.account,
config: operation.config,
transaction: operation.transaction,
type: 'transaction',
});
if (hash.toLowerCase() !== request.hash.toLowerCase())
throw new Error('Multisig operation hash does not match transaction.');
return {
...storedTransaction,
account: request.account,
from: operation.account,
multisigSimulation: getMultisigSimulation({
config: operation.config,
local: request.account,
}),
owner: request.owner,
};
}
if (phase === 'afterFillParameters') {
if (typeof request.gas !== 'undefined' &&
request.feePayer &&
!request.feePayerSignature) {
if (request.keyAuthorization?.signature.type === 'webAuthn')
request.gas = (request.gas ?? 0n) + 20000n;
else if (request.account?.source === 'accessKey')
request.gas = (request.gas ?? 0n) + 10000n;
}
return request;
}
const multisigIdentity = (() => {
if (request.account?.source === 'multisig')
return {
account: request.account.address,
config: request.account.config,
local: request.account,
};
return undefined;
})();
if (request.owner && !multisigIdentity)
throw new Error('A multisig account is required when an owner is provided.');
if (request.owner && typeof request.owner === 'string')
throw new Error('A local owner account is required to approve a multisig transaction.');
if (request.owner && request.owner.source !== 'root')
throw new Error('A Tempo owner account is required to approve a multisig transaction.');
const coordinatedMultisig = !!multisigIdentity &&
!!request.owner &&
client.transport.multisig === true;
if (multisigIdentity) {
const { account, local } = multisigIdentity;
if (!account)
throw new Error('A multisig account address is required with a current config.');
const config = await (async () => {
if (multisigIdentity.config)
return multisigIdentity.config;
if (!coordinatedMultisig)
return undefined;
const cachedConfig = await (0, getAction_js_1.getAction)(client, multisig_js_1.getConfig, 'getConfig')({ address: account });
if (!cachedConfig)
throw new Error(`No current multisig config is cached for account ${account}. Provide the current config.`);
return cachedConfig;
})();
if (!config)
throw new Error('A multisig config is required to prepare a transaction.');
request.from = account;
request.multisigSimulation = getMultisigSimulation({
config,
local,
});
}
const useExpiringNonce = await (async () => {
if (request.nonceKey === 'expiring' || request.nonceKey === number_js_1.maxUint256)
return true;
if (typeof request.nonceKey !== 'undefined')
return false;
if (multisigIdentity)
return false;
if (request.feePayer && typeof request.nonceKey === 'undefined')
return true;
const account = request.account;
const address = typeof account === 'string' ? account : account?.address;
if (address && typeof request.nonceKey === 'undefined')
return await Concurrent.detect(address.toLowerCase());
return false;
})();
if (coordinatedMultisig && typeof request.nonceKey === 'undefined') {
request.nonceKey = Hex.toBigInt(Hex.random(31)) + 1n;
request.nonce = 0;
}
else if (useExpiringNonce) {
request.nonceKey = number_js_1.maxUint256;
request.nonce = 0;
if (typeof request.validAfter === 'undefined')
request.validAfter = randomValidAfter();
if (typeof request.validBefore === 'undefined')
request.validBefore = Math.floor(Date.now() / 1000) + maxExpirySecs;
}
else if (typeof request.nonceKey !== 'undefined') {
request.nonce = typeof request.nonce === 'number' ? request.nonce : 0;
}
if (!request.keyAuthorization &&
request.account?.source === 'accessKey') {
const keyAuthorizationManager = request.account.keyAuthorizationManager;
if (keyAuthorizationManager) {
const chainId = request.chainId ?? request.chain?.id;
if (typeof chainId !== 'undefined') {
const address = request.account.address;
const accessKey = request.account.accessKeyAddress;
const key = { address, accessKey, chainId };
const keyAuthorization = await keyAuthorizationManager.get(key);
if (keyAuthorization) {
const now = BigInt(Math.floor(Date.now() / 1000));
if (keyAuthorization.expiry != null &&
BigInt(keyAuthorization.expiry) <= now) {
await keyAuthorizationManager.remove(key);
}
else {
const metadata = await (0, getAction_js_1.getAction)(client, accessKey_js_1.getMetadata, 'getMetadata')({ account: address, accessKey });
if ((0, isAddressEqual_js_1.isAddressEqual)(metadata.address, accessKey) &&
!metadata.isRevoked &&
metadata.expiry > now)
await keyAuthorizationManager.remove(key);
else
request.keyAuthorization = keyAuthorization;
}
}
}
}
}
if (!request.feeToken && request.chain?.feeToken)
request.feeToken = request.chain.feeToken;
return request;
},
{ runAt: ['beforeFillTransaction', 'afterFillParameters'] },
],
serializers: {
transaction: serializeTransaction,
async transactionEnvelope({ serializedTransaction, transaction }) {
const request = transaction;
if (!request.multisigSimulation)
return serializedTransaction;
try {
tempo_1.SignatureEnvelope.deserialize(serializedTransaction);
}
catch {
const transaction = Transaction.deserialize(serializedTransaction);
if (transaction.signature?.type === 'multisig')
return serializedTransaction;
throw new Error('A Tempo owner account is required to approve a multisig transaction.');
}
return await serializeTransaction({
...request,
signatures: [...(request.signatures ?? []), serializedTransaction],
});
},
},
async verifyHash(client, parameters) {
const { address, hash, signature, mode } = parameters;
const envelope = (() => {
if (typeof signature !== 'string')
return;
try {
return tempo_1.SignatureEnvelope.deserialize(signature);
}
catch {
return undefined;
}
})();
if (envelope) {
if (envelope?.type === 'keychain' && mode === 'allowAccessKey') {
const innerPayload = envelope.version === 'v2'
? (0, keccak256_js_1.keccak256)(Hex.concat('0x04', hash, address))
: hash;
const accessKeyAddress = (() => {
try {
return tempo_1.SignatureEnvelope.extractAddress({
payload: innerPayload,
signature: envelope.inner,
});
}
catch {
return undefined;
}
})();
if (!accessKeyAddress)
return false;
const keyInfo = await (0, accessKey_js_1.getMetadata)(client, {
account: address,
accessKey: accessKeyAddress,
blockHash: parameters.blockHash,
blockNumber: parameters.blockNumber,
blockTag: parameters.blockTag,
requireCanonical: parameters.requireCanonical,
});
if (keyInfo.isRevoked)
return false;
if (keyInfo.expiry <= BigInt(Math.floor(Date.now() / 1000)))
return false;
return tempo_1.SignatureEnvelope.verify(envelope.inner, {
address: accessKeyAddress,
payload: innerPayload,
});
}
if (envelope.type === 'p256' || envelope.type === 'webAuthn') {
const code = await (0, getCode_js_1.getCode)(client, {
address,
blockHash: parameters.blockHash,
blockNumber: parameters.blockNumber,
blockTag: parameters.blockTag,
requireCanonical: parameters.requireCanonical,
});
if (!code ||
code === '0xef01007702c00000000000000000000000000000000000')
return tempo_1.SignatureEnvelope.verify(envelope, {
address,
payload: hash,
});
}
}
return await (0, getAction_js_1.getAction)(client, verifyHash_js_1.verifyHash, 'verifyHash')({ ...parameters, chain: null });
},
};
function getMultisigSimulation(options) {
const { config, local } = options;
return {
approvals: selectOwners(config).map((owner) => {
const localOwner = local?.owners.find((account) => (0, isAddressEqual_js_1.isAddressEqual)(account.address, owner.owner));
return {
...getSimulationKey(localOwner),
owner: owner.owner,
};
}),
config,
};
}
function getSimulationKey(account) {
const keyType = (() => {
if (account &&
typeof account === 'object' &&
'keyType' in account &&
typeof account.keyType === 'string')
return account.keyType;
return undefined;
})();
if (!keyType || keyType === 'webAuthn')
return { keyData: '0x0578', keyType: 'webAuthn' };
if (keyType === 'p256' || keyType === 'secp256k1')
return { keyType };
return { keyData: '0x0578', keyType: 'webAuthn' };
}
function randomValidAfter() {
const now = BigInt(Math.floor(Date.now() / 1_000));
const latest = now - 60n;
if (latest <= 0n)
return 0;
return Number(BigInt(Hex.random(8)) % latest);
}
function selectOwners(config) {
const owners = [...config.owners].sort((a, b) => Number(b.weight) - Number(a.weight) ||
a.owner.toLowerCase().localeCompare(b.owner.toLowerCase()));
const selected = [];
let weight = 0;
for (const owner of owners.slice(0, tempo_1.MultisigConfig.maxSignatures)) {
if (weight >= Number(config.threshold))
break;
selected.push(owner);
weight += Number(owner.weight);
}
return selected.sort((a, b) => a.owner.toLowerCase().localeCompare(b.owner.toLowerCase()));
}
//# sourceMappingURL=chainConfig.js.map