cosmic-interchain-cli
Version:
A command-line utility for Cosmic Wire's interchain messaging protocol
197 lines • 8.15 kB
JavaScript
import { confirm, input, select } from '@inquirer/prompts';
import { BigNumber as BigNumberJs } from 'bignumber.js';
import { ethers } from 'ethers';
import { z } from 'zod';
import { HookConfigSchema, HookType, } from '@hyperlane-xyz/sdk';
import { normalizeAddressEvm, objMap, toWei, } from '@hyperlane-xyz/utils';
import { errorRed, logBlue, logGreen, logRed } from '../logger.js';
import { runMultiChainSelectionStep } from '../utils/chains.js';
import { readYamlOrJson } from '../utils/files.js';
import { detectAndConfirmOrPrompt, inputWithInfo } from '../utils/input.js';
import { callWithConfigCreationLogs } from './utils.js';
// TODO: deprecate in favor of CoreConfigSchema
const HooksConfigSchema = z.object({
default: HookConfigSchema,
required: HookConfigSchema,
});
const HooksConfigMapSchema = z.record(HooksConfigSchema);
const MAX_PROTOCOL_FEE_DEFAULT = toWei('0.1');
const PROTOCOL_FEE_DEFAULT = toWei('0');
export function presetHookConfigs(owner) {
return {
required: {
type: HookType.PROTOCOL_FEE,
maxProtocolFee: ethers.utils.parseUnits('1', 'gwei').toString(),
protocolFee: ethers.utils.parseUnits('0', 'wei').toString(),
beneficiary: owner,
owner: owner,
},
default: {
type: HookType.MERKLE_TREE,
},
};
}
export function readHooksConfigMap(filePath) {
const config = readYamlOrJson(filePath);
if (!config) {
logRed(`No hook config found at ${filePath}`);
return;
}
const parsedConfig = HooksConfigMapSchema.parse(config);
const hooks = objMap(parsedConfig, (_, config) => config);
logGreen(`All hook configs in ${filePath} are valid for ${hooks}`);
return hooks;
}
export async function createHookConfig({ context, selectMessage = 'Select hook type', advanced = false, }) {
const hookType = await select({
message: selectMessage,
choices: [
{
value: HookType.AGGREGATION,
name: HookType.AGGREGATION,
description: 'Aggregate multiple hooks into a single hook (e.g. merkle tree + IGP) which will be called in sequence',
},
{
value: HookType.MERKLE_TREE,
name: HookType.MERKLE_TREE,
description: 'Add messages to the incremental merkle tree on origin chain (needed for the merkleRootMultisigIsm on the remote chain)',
},
{
value: HookType.PROTOCOL_FEE,
name: HookType.PROTOCOL_FEE,
description: 'Charge fees for each message dispatch from this chain',
},
],
pageSize: 10,
});
switch (hookType) {
case HookType.AGGREGATION:
return createAggregationConfig(context, advanced);
case HookType.MERKLE_TREE:
return createMerkleTreeConfig();
case HookType.PROTOCOL_FEE:
return createProtocolFeeConfig(context, advanced);
default:
throw new Error(`Invalid hook type: ${hookType}`);
}
}
export const createMerkleTreeConfig = callWithConfigCreationLogs(async () => {
return { type: HookType.MERKLE_TREE };
}, HookType.MERKLE_TREE);
export const createProtocolFeeConfig = callWithConfigCreationLogs(async (context, advanced = false) => {
const unnormalizedOwner = !advanced && context.signer
? await context.signer.getAddress()
: await detectAndConfirmOrPrompt(async () => context.signer?.getAddress(), 'For protocol fee hook, enter', 'owner address', 'signer');
const owner = normalizeAddressEvm(unnormalizedOwner);
let beneficiary = owner;
const isBeneficiarySameAsOwner = advanced
? await confirm({
message: `Use this same address (${owner}) for the beneficiary?`,
})
: true;
if (!isBeneficiarySameAsOwner) {
const unnormalizedBeneficiary = await input({
message: 'Enter beneficiary address for protocol fee hook:',
});
beneficiary = normalizeAddressEvm(unnormalizedBeneficiary);
}
// TODO: input in gwei, wei, etc
const maxProtocolFee = advanced
? toWei(await inputWithInfo({
message: `Enter max protocol fee for protocol fee hook (wei):`,
info: `The max protocol fee (ProtocolFee.MAX_PROTOCOL_FEE) is the maximum value the protocol fee on the ProtocolFee hook contract can ever be set to.\nDefault is set to ${MAX_PROTOCOL_FEE_DEFAULT} wei; between 0.001 and 0.1 wei is recommended.`,
}))
: MAX_PROTOCOL_FEE_DEFAULT;
const protocolFee = advanced
? toWei(await inputWithInfo({
message: `Enter protocol fee for protocol fee hook (wei):`,
info: `The protocol fee is the fee collected by the beneficiary of the ProtocolFee hook for every transaction executed with this hook.\nDefault is set to 0 wei; must be less than max protocol fee of ${maxProtocolFee}.`,
}))
: PROTOCOL_FEE_DEFAULT;
if (BigNumberJs(protocolFee).gt(maxProtocolFee)) {
errorRed(`Protocol fee (${protocolFee}) cannot be greater than max protocol fee (${maxProtocolFee}).`);
throw new Error(`Invalid protocol fee (${protocolFee}).`);
}
return {
type: HookType.PROTOCOL_FEE,
maxProtocolFee,
protocolFee,
beneficiary,
owner,
};
}, HookType.PROTOCOL_FEE);
// TODO: make this usable
export const createIGPConfig = callWithConfigCreationLogs(async (remotes) => {
const unnormalizedOwner = await input({
message: 'Enter owner address for IGP hook',
});
const owner = normalizeAddressEvm(unnormalizedOwner);
let beneficiary = owner;
let oracleKey = owner;
const beneficiarySameAsOwner = await confirm({
message: 'Use this same address for the beneficiary and gasOracleKey?',
});
if (!beneficiarySameAsOwner) {
const unnormalizedBeneficiary = await input({
message: 'Enter beneficiary address for IGP hook',
});
beneficiary = normalizeAddressEvm(unnormalizedBeneficiary);
const unnormalizedOracleKey = await input({
message: 'Enter gasOracleKey address for IGP hook',
});
oracleKey = normalizeAddressEvm(unnormalizedOracleKey);
}
const overheads = {};
for (const chain of remotes) {
const overhead = parseInt(await input({
message: `Enter overhead for ${chain} (eg 75000) for IGP hook`,
}));
overheads[chain] = overhead;
}
return {
type: HookType.INTERCHAIN_GAS_PAYMASTER,
beneficiary,
owner,
oracleKey,
overhead: overheads,
oracleConfig: {},
};
}, HookType.INTERCHAIN_GAS_PAYMASTER);
export const createAggregationConfig = callWithConfigCreationLogs(async (context, advanced = false) => {
const hooksNum = parseInt(await input({
message: 'Enter the number of hooks to aggregate (number)',
}), 10);
const hooks = [];
for (let i = 0; i < hooksNum; i++) {
logBlue(`Creating hook ${i + 1} of ${hooksNum} ...`);
hooks.push(await createHookConfig({
context,
advanced,
}));
}
return {
type: HookType.AGGREGATION,
hooks,
};
}, HookType.AGGREGATION);
export const createRoutingConfig = callWithConfigCreationLogs(async (context, advanced = false) => {
const owner = await input({
message: 'Enter owner address for routing Hook',
});
const ownerAddress = owner;
const chains = await runMultiChainSelectionStep(context.chainMetadata, 'Select chains for routing Hook', 1);
const domainsMap = {};
for (const chain of chains) {
await confirm({
message: `You are about to configure hook for remote chain ${chain}. Continue?`,
});
const config = await createHookConfig({ context, advanced });
domainsMap[chain] = config;
}
return {
type: HookType.ROUTING,
owner: ownerAddress,
domains: domainsMap,
};
}, HookType.ROUTING);
//# sourceMappingURL=hooks.js.map