@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
210 lines • 9.27 kB
JavaScript
import { constants } from 'ethers';
import { BaseFee__factory, CrossCollateralRoutingFee__factory, LinearFee__factory, OffchainQuotedLinearFee__factory, RoutingFee__factory, } from '@hyperlane-xyz/core';
import { assert, concurrentMap, rootLogger, } from '@hyperlane-xyz/utils';
import { DEFAULT_CONTRACT_READ_CONCURRENCY } from '../consts/concurrency.js';
import { HyperlaneReader } from '../utils/HyperlaneReader.js';
import { OnchainTokenFeeType, TokenFeeType, } from './types.js';
import { getCrossCollateralRouterKeys, getEffectiveCrossCollateralDestinations, } from './crossCollateralUtils.js';
import { ASSUMED_MAX_AMOUNT_FOR_ZERO_SUPPLY, BPS_PRECISION, MAX_BPS, assertBpsPrecision, convertToBps, } from './utils.js';
export class EvmTokenFeeReader extends HyperlaneReader {
multiProvider;
chain;
concurrency;
logger = rootLogger.child({ module: 'EvmTokenFeeReader' });
constructor(multiProvider, chain, concurrency = multiProvider.tryGetRpcConcurrency(chain) ?? DEFAULT_CONTRACT_READ_CONCURRENCY) {
super(multiProvider, chain);
this.multiProvider = multiProvider;
this.chain = chain;
this.concurrency = concurrency;
}
async deriveTokenFeeConfig(params) {
const { address, routingDestinations, crossCollateralRouters } = params;
const tokenFee = BaseFee__factory.connect(address, this.provider);
let derivedConfig;
const onchainFeeType = await tokenFee.feeType();
switch (onchainFeeType) {
case OnchainTokenFeeType.LinearFee:
derivedConfig = await this.deriveLinearFeeConfig(address);
break;
case OnchainTokenFeeType.ProgressiveFee:
derivedConfig = await this.deriveProgressiveFeeConfig(address);
break;
case OnchainTokenFeeType.RegressiveFee:
derivedConfig = await this.deriveRegressiveFeeConfig(address);
break;
case OnchainTokenFeeType.RoutingFee:
derivedConfig = await this.deriveRoutingFeeConfig({
address,
routingDestinations,
});
break;
case OnchainTokenFeeType.OffchainQuotedLinearFee:
derivedConfig = await this.deriveOffchainQuotedLinearFeeConfig(address);
break;
case OnchainTokenFeeType.CrossCollateralRoutingFee:
derivedConfig = await this.deriveCrossCollateralRoutingFeeConfig({
address,
routingDestinations,
crossCollateralRouters,
});
break;
default:
throw new Error(`Unsupported token fee type: ${onchainFeeType}`);
}
return derivedConfig;
}
async deriveLinearFeeConfig(address) {
const tokenFee = LinearFee__factory.connect(address, this.provider);
const [token, owner, maxFee, halfAmount] = await Promise.all([
tokenFee.token(),
tokenFee.owner(),
tokenFee.maxFee(),
tokenFee.halfAmount(),
]);
const maxFeeBn = BigInt(maxFee.toString());
const halfAmountBn = BigInt(halfAmount.toString());
const bps = convertToBps(maxFeeBn, halfAmountBn);
return {
type: TokenFeeType.LinearFee,
maxFee: maxFeeBn,
halfAmount: halfAmountBn,
address,
bps,
token,
owner,
};
}
async deriveOffchainQuotedLinearFeeConfig(address) {
const tokenFee = OffchainQuotedLinearFee__factory.connect(address, this.provider);
const [token, owner, maxFee, halfAmount, quoteSigners] = await Promise.all([
tokenFee.token(),
tokenFee.owner(),
tokenFee.maxFee(),
tokenFee.halfAmount(),
tokenFee.quoteSigners(),
]);
const maxFeeBn = BigInt(maxFee.toString());
const halfAmountBn = BigInt(halfAmount.toString());
const bps = convertToBps(maxFeeBn, halfAmountBn);
return {
type: TokenFeeType.OffchainQuotedLinearFee,
maxFee: maxFeeBn,
halfAmount: halfAmountBn,
address,
bps,
token,
owner,
quoteSigners: [...quoteSigners],
};
}
async deriveProgressiveFeeConfig(_address) {
throw new Error('Not implemented');
}
async deriveRegressiveFeeConfig(_address) {
throw new Error('Not implemented');
}
async deriveRoutingFeeConfig(params) {
const { address, routingDestinations } = params;
const routingFee = RoutingFee__factory.connect(address, this.provider);
const [token, owner] = await Promise.all([
routingFee.token(),
routingFee.owner(),
]);
const feeContracts = {};
if (routingDestinations)
await Promise.all(routingDestinations.map(async (destination) => {
const subFeeAddress = await routingFee.feeContracts(destination);
if (subFeeAddress === constants.AddressZero)
return;
const chainName = this.multiProvider.tryGetChainName(destination);
if (!chainName)
return;
feeContracts[chainName] = await this.deriveTokenFeeConfig({
address: subFeeAddress,
});
}));
return {
type: TokenFeeType.RoutingFee,
address,
token,
owner,
feeContracts,
};
}
async deriveCrossCollateralRoutingFeeConfig(params) {
const { address, routingDestinations, crossCollateralRouters } = params;
const effectiveRoutingDestinations = getEffectiveCrossCollateralDestinations(routingDestinations, crossCollateralRouters);
assert(effectiveRoutingDestinations.length > 0, 'CrossCollateralRoutingFee requires routingDestinations or crossCollateralRouters to derive fee config');
const routingFee = CrossCollateralRoutingFee__factory.connect(address, this.provider);
const owner = await routingFee.owner();
const feeConfigCache = new Map();
const parseFeeConfig = this.createCachedFeeConfigReader(feeConfigCache);
const destinationEntries = await concurrentMap(this.concurrency, effectiveRoutingDestinations, async (destination) => this.deriveCrossCollateralDestinationFees({
routingFee,
destination,
crossCollateralRouters,
parseFeeConfig,
}));
const feeContracts = {};
for (const entry of destinationEntries) {
if (!entry)
continue;
feeContracts[entry.chainName] = entry.routerFeeConfigs;
}
return {
type: TokenFeeType.CrossCollateralRoutingFee,
address,
owner,
feeContracts,
};
}
createCachedFeeConfigReader(feeConfigCache) {
// Multiple router keys can point at the same child fee address.
return async (subFeeAddress) => {
const cacheKey = subFeeAddress.toLowerCase();
if (!feeConfigCache.has(cacheKey)) {
feeConfigCache.set(cacheKey, this.deriveTokenFeeConfig({
address: subFeeAddress,
}));
}
const cachedFeeConfig = feeConfigCache.get(cacheKey);
assert(cachedFeeConfig, `Missing cached fee config promise for ${subFeeAddress}`);
return cachedFeeConfig;
};
}
async deriveCrossCollateralDestinationFees({ routingFee, destination, crossCollateralRouters, parseFeeConfig, }) {
const chainName = this.multiProvider.tryGetChainName(destination);
if (!chainName)
return undefined;
const routerKeys = getCrossCollateralRouterKeys(destination, crossCollateralRouters);
const routerSubFees = await concurrentMap(this.concurrency, routerKeys, async (router) => ({
router,
subFeeAddress: await routingFee.feeContracts(destination, router),
}));
const routerFeeConfigs = {};
await concurrentMap(this.concurrency, routerSubFees, async ({ router, subFeeAddress }) => {
if (subFeeAddress === constants.AddressZero)
return;
routerFeeConfigs[router] = await parseFeeConfig(subFeeAddress);
});
if (Object.keys(routerFeeConfigs).length === 0) {
return undefined;
}
return { chainName, routerFeeConfigs };
}
convertFromBps(bps) {
if (!Number.isFinite(bps) || bps <= 0) {
throw new Error('bps must be > 0 to prevent division by zero');
}
assertBpsPrecision(bps);
const maxFee = BigInt(constants.MaxUint256.toString()) /
ASSUMED_MAX_AMOUNT_FOR_ZERO_SUPPLY;
const scaledBps = BigInt(Math.round(bps * Number(BPS_PRECISION)));
const halfAmount = ((maxFee / 2n) * MAX_BPS * BPS_PRECISION) / scaledBps;
return {
maxFee,
halfAmount,
};
}
}
//# sourceMappingURL=EvmTokenFeeReader.js.map