@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
127 lines • 7.19 kB
JavaScript
import { BaseFee__factory, } from '@hyperlane-xyz/core';
import { assert, eqAddress } from '@hyperlane-xyz/utils';
import { HyperlaneDeployer, } from '../deploy/HyperlaneDeployer.js';
import { EvmTokenFeeReader } from './EvmTokenFeeReader.js';
import { evmTokenFeeFactories } from './contracts.js';
import { TokenFeeConfigSchema, TokenFeeType, } from './types.js';
export class EvmTokenFeeDeployer extends HyperlaneDeployer {
multiProvider;
chain;
tokenFeeReader;
constructor(multiProvider, chain, options = {}) {
super(multiProvider, evmTokenFeeFactories, options);
this.multiProvider = multiProvider;
this.chain = chain;
this.tokenFeeReader = new EvmTokenFeeReader(multiProvider, chain);
}
async deployContracts(chain, config) {
const deployedContract = {}; // This is a partial HyperlaneContracts<EvmTokenFeeFactories>
const parsedConfig = TokenFeeConfigSchema.parse(config);
switch (parsedConfig.type) {
case TokenFeeType.LinearFee:
case TokenFeeType.ProgressiveFee:
case TokenFeeType.RegressiveFee:
deployedContract[parsedConfig.type] = await this.deployFee(chain, parsedConfig);
break;
case TokenFeeType.OffchainQuotedLinearFee:
deployedContract[parsedConfig.type] =
await this.deployOffchainQuotedLinearFee(chain, parsedConfig);
break;
case TokenFeeType.RoutingFee: {
deployedContract[TokenFeeType.RoutingFee] = await this.deployRoutingFee(chain, parsedConfig);
break;
}
case TokenFeeType.CrossCollateralRoutingFee: {
deployedContract[TokenFeeType.CrossCollateralRoutingFee] =
await this.deployCrossCollateralRoutingFee(chain, parsedConfig);
break;
}
}
return deployedContract;
}
async deployFee(chain, config) {
let { maxFee, halfAmount } = config;
if (config.type === TokenFeeType.LinearFee &&
config.bps &&
(!maxFee || !halfAmount)) {
const { maxFee: calculatedMaxFee, halfAmount: calculatedHalfAmount } = this.tokenFeeReader.convertFromBps(config.bps);
maxFee = calculatedMaxFee;
halfAmount = calculatedHalfAmount;
}
return this.deployContract(chain, config.type, [
config.token,
maxFee,
halfAmount,
config.owner,
]);
}
async deployOffchainQuotedLinearFee(chain, config) {
let { maxFee, halfAmount } = config;
if (config.bps && (!maxFee || !halfAmount)) {
const derived = this.tokenFeeReader.convertFromBps(config.bps);
maxFee = derived.maxFee;
halfAmount = derived.halfAmount;
}
assert(config.quoteSigners?.length, 'At least one quote signer is required for OffchainQuotedLinearFee');
const signerAddress = await this.multiProvider.getSignerAddress(chain);
const [firstSigner, ...additionalSigners] = config.quoteSigners;
// addQuoteSigner is onlyOwner, so deploy with signer as temporary owner
const contract = await this.deployContract(chain, TokenFeeType.OffchainQuotedLinearFee, [firstSigner, config.token, maxFee, halfAmount, signerAddress]);
for (const signer of additionalSigners) {
await this.multiProvider.handleTx(chain, contract.addQuoteSigner(signer, this.multiProvider.getTransactionOverrides(chain)));
}
if (!eqAddress(signerAddress, config.owner)) {
await this.multiProvider.handleTx(chain, contract.transferOwnership(config.owner, this.multiProvider.getTransactionOverrides(chain)));
}
return contract;
}
async deployRoutingFee(chain, config) {
const signerAddress = await this.multiProvider.getSignerAddress(chain);
// RoutingFee.setFeeContract is onlyOwner, so we deploy with the signer as a
// temporary owner to allow setup, then transfer to the configured owner.
const routingFee = await this.deployContract(chain, TokenFeeType.RoutingFee, [config.token, signerAddress]);
// Deploy each fee contract & set each fee for the routing fee
for (const [destinationChain, feeConfig] of Object.entries(config.feeContracts)) {
// Sub-fee configs inherit the routing fee's token if not explicitly set
const resolvedFeeConfig = {
...feeConfig,
token: feeConfig.token ?? config.token,
};
const deployedFeeContract = resolvedFeeConfig.type === TokenFeeType.OffchainQuotedLinearFee
? BaseFee__factory.connect((await this.deployOffchainQuotedLinearFee(chain, resolvedFeeConfig)).address, this.multiProvider.getSigner(chain))
: await this.deployFee(chain, resolvedFeeConfig);
await this.multiProvider.handleTx(chain, routingFee.setFeeContract(this.multiProvider.getDomainId(destinationChain), deployedFeeContract.address, this.multiProvider.getTransactionOverrides(chain)));
}
if (!eqAddress(signerAddress, config.owner)) {
this.logger.debug(`Transferring ownership of RoutingFee to ${config.owner} on ${chain}`);
await this.multiProvider.handleTx(chain, routingFee.transferOwnership(config.owner, this.multiProvider.getTransactionOverrides(chain)));
}
return routingFee;
}
async deployCrossCollateralRoutingFee(chain, config) {
const signerAddress = await this.multiProvider.getSignerAddress(chain);
const routingFee = await this.deployContract(chain, TokenFeeType.CrossCollateralRoutingFee, [signerAddress]);
const destinationDomains = [];
const routerKeys = [];
const feeAddresses = [];
for (const [destinationChain, destinationConfig] of Object.entries(config.feeContracts)) {
for (const [routerKey, routerFeeConfig] of Object.entries(destinationConfig)) {
const { address } = routerFeeConfig.type === TokenFeeType.OffchainQuotedLinearFee
? await this.deployOffchainQuotedLinearFee(chain, routerFeeConfig)
: await this.deployFee(chain, routerFeeConfig);
destinationDomains.push(this.multiProvider.getDomainId(destinationChain));
routerKeys.push(routerKey);
feeAddresses.push(address);
}
}
if (destinationDomains.length > 0) {
await this.multiProvider.handleTx(chain, routingFee.setCrossCollateralRouterFeeContracts(destinationDomains, routerKeys, feeAddresses, this.multiProvider.getTransactionOverrides(chain)));
}
if (!eqAddress(signerAddress, config.owner)) {
this.logger.debug(`Transferring ownership of CrossCollateralRoutingFee to ${config.owner} on ${chain}`);
await this.multiProvider.handleTx(chain, routingFee.transferOwnership(config.owner, this.multiProvider.getTransactionOverrides(chain)));
}
return routingFee;
}
}
//# sourceMappingURL=EvmTokenFeeDeployer.js.map