Infra for neutron & neutrontestnet (#2869)

### Description

<!--
What's included in this PR?
-->

### Drive-by changes

<!--
Are there any minor or drive-by changes also included?
-->

### Related issues

<!--
- Fixes #[issue number here]
-->

### Backward compatibility

<!--
Are these changes backward compatible? Are there any infrastructure
implications, e.g. changes that would prohibit deploying older commits
using this infra tooling?

Yes/No
-->

### Testing

<!--
What kind of testing have these changes undergone?

None/Manual/Unit Tests
-->

---------

Co-authored-by: Trevor Porter <trkporter@ucdavis.edu>
pull/2990/head
Nam Chu Hoai 1 year ago committed by GitHub
parent df34198d4c
commit 68d4f2f149
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      rust/helm/hyperlane-agent/templates/external-secret.yaml
  2. 9
      rust/helm/hyperlane-agent/templates/relayer-external-secret.yaml
  3. 3
      rust/helm/hyperlane-agent/templates/validator-configmap.yaml
  4. 7
      rust/helm/hyperlane-agent/templates/validator-external-secret.yaml
  5. 1
      typescript/infra/config/contexts.ts
  6. 52
      typescript/infra/config/environments/mainnet3/agent.ts
  7. 25
      typescript/infra/config/environments/mainnet3/chains.ts
  8. 80
      typescript/infra/config/environments/mainnet3/core/verification.json
  9. 9
      typescript/infra/config/environments/mainnet3/gas-oracle.ts
  10. 1
      typescript/infra/config/environments/mainnet3/infrastructure.ts
  11. 1
      typescript/infra/config/environments/mainnet3/owners.ts
  12. 44
      typescript/infra/config/environments/mainnet3/validators.ts
  13. 27
      typescript/infra/config/environments/testnet4/agent.ts
  14. 22
      typescript/infra/config/environments/testnet4/chains.ts
  15. 2
      typescript/infra/config/environments/testnet4/gas-oracle.ts
  16. 1
      typescript/infra/config/environments/testnet4/infrastructure.ts
  17. 28
      typescript/infra/config/environments/testnet4/validators.ts
  18. 2
      typescript/infra/config/routingIsm.ts
  19. 1
      typescript/infra/package.json
  20. 23
      typescript/infra/scripts/agents/utils.ts
  21. 1
      typescript/infra/scripts/announce-validators.ts
  22. 9
      typescript/infra/src/agents/aws/s3.ts
  23. 8
      typescript/infra/src/agents/aws/validator.ts
  24. 16
      typescript/infra/src/agents/gcp.ts
  25. 39
      typescript/infra/src/agents/index.ts
  26. 48
      typescript/infra/src/agents/key-utils.ts
  27. 27
      typescript/infra/src/config/agent/agent.ts
  28. 50
      typescript/infra/src/config/agent/relayer.ts
  29. 35
      typescript/infra/src/config/agent/validator.ts
  30. 7
      typescript/infra/src/utils/utils.ts
  31. 28
      typescript/sdk/src/consts/chainMetadata.ts
  32. 1
      typescript/sdk/src/consts/chains.ts
  33. 16
      typescript/sdk/src/consts/environments/mainnet.json
  34. 30
      typescript/sdk/src/consts/multisigIsm.ts
  35. 103
      typescript/sdk/src/metadata/agentConfig.ts
  36. 1
      yarn.lock

@ -28,17 +28,25 @@ spec:
{{- range .Values.hyperlane.chains }}
{{- if not .disabled }}
HYP_CHAINS_{{ .name | upper }}_CUSTOMRPCURLS: {{ printf "'{{ .%s_rpcs | mustFromJson | join \",\" }}'" .name }}
{{- if eq .protocol "cosmos" }}
HYP_CHAINS_{{ .name | upper }}_GRPCURL: {{ printf "'{{ .%s_grpc }}'" .name }}
{{- end }}
{{- end }}
{{- end }}
data:
{{- /*
* For each network, load the secret in GCP secret manager with the form: environment-rpc-endpoint-network,
* and associate it with the secret key networkname_rpc.
* For each network, load the secret in GCP secret manager with the form: environment-rpc-endpoints-network,
* and associate it with the secret key networkname_rpcs.
*/}}
{{- range .Values.hyperlane.chains }}
{{- if not .disabled }}
- secretKey: {{ printf "%s_rpcs" .name }}
remoteRef:
key: {{ printf "%s-rpc-endpoints-%s" $.Values.hyperlane.runEnv .name }}
{{- if eq .protocol "cosmos" }}
- secretKey: {{ printf "%s_grpc" .name }}
remoteRef:
key: {{ printf "%s-grpc-endpoint-%s" $.Values.hyperlane.runEnv .name }}
{{- end }}
{{- end }}
{{- end }}

@ -22,20 +22,23 @@ spec:
{{- include "agent-common.labels" . | nindent 10 }}
data:
{{- range .Values.hyperlane.relayerChains }}
{{- if eq .signer.type "hexKey" }}
{{- if or (eq .signer.type "hexKey") (eq .signer.type "cosmosKey") }}
HYP_CHAINS_{{ .name | upper }}_SIGNER_KEY: {{ printf "'{{ .%s_signer_key | toString }}'" .name }}
{{- include "agent-common.config-env-vars" (dict "config" .signer "format" "config_map" "key_name_prefix" (printf "CHAINS_%s_SIGNER_" (.name | upper))) | nindent 8 }}
{{- end }}
{{- if and (eq .signer.type "aws") $.Values.hyperlane.relayer.aws }}
HYP_CHAINS_{{ .name | upper }}_SIGNER_TYPE: aws
HYP_CHAINS_{{ .name | upper }}_SIGNER_ID: {{ .signer.id }}
HYP_CHAINS_{{ .name | upper }}_SIGNER_REGION: {{ .signer.region}}
{{- end }}
{{- end }}
{{- if .Values.hyperlane.relayer.aws }}
AWS_ACCESS_KEY_ID: {{ print "'{{ .aws_access_key_id | toString }}'" }}
AWS_SECRET_ACCESS_KEY: {{ print "'{{ .aws_secret_access_key | toString }}'" }}
{{- end }}
{{- end }}
data:
{{- range .Values.hyperlane.relayerChains }}
{{- if eq .signer.type "hexKey" }}
{{- if or (eq .signer.type "hexKey") (eq .signer.type "cosmosKey") }}
- secretKey: {{ printf "%s_signer_key" .name }}
remoteRef:
{{- if $.Values.hyperlane.relayer.usingDefaultSignerKey }}

@ -8,6 +8,7 @@ metadata:
data:
{{- range $index, $config := .Values.hyperlane.validator.configs }}
validator-{{ $index }}.env: |
{{- include "agent-common.config-env-vars" (dict "config" $config "format" "dot_env") | nindent 4 }}
{{- include "agent-common.config-env-vars" (dict "config" (get $config "chainSigner") "format" "dot_env" "key_name_prefix" (printf "CHAINS_%s_SIGNER_" ($config.originChainName | upper))) | nindent 4 }}
{{- include "agent-common.config-env-vars" (dict "config" (omit $config "chainSigner") "format" "dot_env") | nindent 4 }}
{{- end }}
{{- end }}

@ -26,18 +26,21 @@ spec:
validator-{{ $index }}.env: |
{{- if eq .validator.type "hexKey" }}
HYP_VALIDATOR_KEY={{ printf "'{{ .signer_key_%d | toString }}'" $index }}
HYP_CHAINS_{{ .originChainName | upper }}_SIGNER_KEY={{ printf "'{{ .signer_key_%d | toString }}'" $index }}
{{- end }}
{{- if or (eq .checkpointSyncer.type "s3") $.Values.hyperlane.aws }}
AWS_ACCESS_KEY_ID={{ printf "'{{ .aws_access_key_id_%d | toString }}'" $index }}
AWS_SECRET_ACCESS_KEY={{ printf "'{{ .aws_secret_access_key_%d | toString }}'" $index }}
{{- end }}
{{- if or (eq .chainSigner.type "hexKey") (eq .chainSigner.type "cosmosKey") }}
HYP_CHAINS_{{ .originChainName | upper }}_SIGNER_KEY={{ printf "'{{ .signer_key_%d | toString }}'" $index }}
{{- end }}
{{ $index = add1 $index }}
{{- end }}
data:
{{ $index = 0 }}
{{- range .Values.hyperlane.validator.configs }}
{{- if eq .validator.type "hexKey" }}
{{- if or (eq .validator.type "hexKey") (eq .chainSigner.type "hexKey") (eq .chainSigner.type "cosmosKey") }}
- secretKey: signer_key_{{ $index }}
remoteRef:
key: {{ printf "%s-%s-key-%s-validator-%d" $.Values.hyperlane.context $.Values.hyperlane.runEnv .originChainName $index }}

@ -2,4 +2,5 @@
export enum Contexts {
Hyperlane = 'hyperlane',
ReleaseCandidate = 'rc',
Neutron = 'neutron',
}

@ -2,6 +2,7 @@ import {
GasPaymentEnforcementPolicyType,
RpcConsensusType,
chainMetadata,
getDomainId,
} from '@hyperlane-xyz/sdk';
import { RootAgentConfig, allAgentChainNames } from '../../../src/config';
@ -51,6 +52,14 @@ const hyperlane: RootAgentConfig = {
repo,
tag: '1bee32a-20231121-121303',
},
chainDockerOverrides: {
[chainMetadata.neutron.name]: {
tag: '5070398-20231108-172634',
},
[chainMetadata.mantapacific.name]: {
tag: '5070398-20231108-172634',
},
},
rpcConsensusType: RpcConsensusType.Quorum,
chains: validatorChainConfig(Contexts.Hyperlane),
},
@ -90,7 +99,50 @@ const releaseCandidate: RootAgentConfig = {
},
};
const neutron: RootAgentConfig = {
...contextBase,
contextChainNames: {
validator: [],
relayer: [
chainMetadata.neutron.name,
chainMetadata.mantapacific.name,
chainMetadata.arbitrum.name,
],
scraper: [],
},
context: Contexts.Neutron,
rolesWithKeys: [Role.Relayer],
relayer: {
rpcConsensusType: RpcConsensusType.Fallback,
docker: {
repo,
tag: '68bad33-20231109-024958',
},
gasPaymentEnforcement: [
{
type: GasPaymentEnforcementPolicyType.None,
matchingList: [
{
originDomain: getDomainId(chainMetadata.neutron),
destinationDomain: getDomainId(chainMetadata.mantapacific),
senderAddress: '*',
recipientAddress: '*',
},
{
originDomain: getDomainId(chainMetadata.neutron),
destinationDomain: getDomainId(chainMetadata.arbitrum),
senderAddress: '*',
recipientAddress: '*',
},
],
},
...gasPaymentEnforcement,
],
},
};
export const agents = {
[Contexts.Hyperlane]: hyperlane,
[Contexts.ReleaseCandidate]: releaseCandidate,
[Contexts.Neutron]: neutron,
};

@ -41,16 +41,18 @@ export const ethereumMainnetConfigs: ChainMap<ChainMetadata> = {
},
moonbeam: chainMetadata.moonbeam,
gnosis: chainMetadata.gnosis,
mantapacific: chainMetadata.mantapacific,
};
// Blessed non-Ethereum chains.
// export const nonEthereumMainnetConfigs: ChainMap<ChainMetadata> = {
// solana: chainMetadata.solana,
// };
export const nonEthereumMainnetConfigs: ChainMap<ChainMetadata> = {
// solana: chainMetadata.solana,
neutron: chainMetadata.neutron,
};
export const mainnetConfigs: ChainMap<ChainMetadata> = {
...ethereumMainnetConfigs,
// ...nonEthereumMainnetConfigs,
...nonEthereumMainnetConfigs,
};
export type MainnetChains = keyof typeof mainnetConfigs;
@ -63,16 +65,11 @@ export const ethereumChainNames = Object.keys(
ethereumMainnetConfigs,
) as MainnetChains[];
const validatorChainNames = [
...supportedChainNames,
// chainMetadata.solana.name,
// chainMetadata.nautilus.name,
];
const relayerChainNames = validatorChainNames;
// Hyperlane & RC context agent chain names.
export const agentChainNames: AgentChainNames = {
[Role.Validator]: validatorChainNames,
[Role.Relayer]: relayerChainNames,
// Run validators for all chains.
[Role.Validator]: supportedChainNames,
// Only run relayers for Ethereum chains at the moment.
[Role.Relayer]: ethereumChainNames,
[Role.Scraper]: ethereumChainNames,
};

@ -1288,5 +1288,85 @@
"constructorArguments": "0000000000000000000000005d934f4e2f797775e53561bb72aca21ba36b96bb",
"isProxy": false
}
],
"mantapacific": [
{
"name": "ProxyAdmin",
"address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"constructorArguments": "",
"isProxy": false
},
{
"name": "Mailbox",
"address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D",
"constructorArguments": "00000000000000000000000000000000000000000000000000000000000000a9",
"isProxy": false
},
{
"name": "TransparentUpgradeableProxy",
"address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E",
"constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000",
"isProxy": true
},
{
"name": "MerkleTreeHook",
"address": "0x149db7afD694722747035d5AEC7007ccb6F8f112",
"constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e",
"isProxy": false
},
{
"name": "StorageGasOracle",
"address": "0x19dc38aeae620380430C200a6E990D5Af5480117",
"constructorArguments": "",
"isProxy": false
},
{
"name": "InterchainGasPaymaster",
"address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2",
"constructorArguments": "",
"isProxy": false
},
{
"name": "TransparentUpgradeableProxy",
"address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4",
"constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000",
"isProxy": true
},
{
"name": "MerkleTreeHook",
"address": "0x149db7afD694722747035d5AEC7007ccb6F8f112",
"constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e",
"isProxy": false
},
{
"name": "StorageGasOracle",
"address": "0x19dc38aeae620380430C200a6E990D5Af5480117",
"constructorArguments": "",
"isProxy": false
},
{
"name": "InterchainGasPaymaster",
"address": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2",
"constructorArguments": "",
"isProxy": false
},
{
"name": "TransparentUpgradeableProxy",
"address": "0x0D63128D887159d63De29497dfa45AFc7C699AE4",
"constructorArguments": "000000000000000000000000bf12ef4b9f307463d3fb59c3604f294ddce287e20000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000",
"isProxy": true
},
{
"name": "ProtocolFee",
"address": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638",
"constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba",
"isProxy": false
},
{
"name": "ValidatorAnnounce",
"address": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9",
"constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e",
"isProxy": false
}
]
}

@ -48,6 +48,8 @@ const gasPrices: ChainMap<BigNumber> = {
base: ethers.utils.parseUnits('1', 'gwei'),
scroll: ethers.utils.parseUnits('1', 'gwei'),
polygonzkevm: ethers.utils.parseUnits('2', 'gwei'),
neutron: ethers.utils.parseUnits('1', 'gwei'),
mantapacific: ethers.utils.parseUnits('1', 'gwei'),
};
// Accurate from coingecko as of Mar 9, 2023.
@ -83,6 +85,13 @@ const tokenUsdPrices: ChainMap<BigNumber> = {
'1619.00',
TOKEN_EXCHANGE_RATE_DECIMALS,
),
// https://www.coingecko.com/en/coins/neutron
neutron: ethers.utils.parseUnits('0.304396', TOKEN_EXCHANGE_RATE_DECIMALS),
// https://www.coingecko.com/en/coins/ethereum
mantapacific: ethers.utils.parseUnits(
'1619.00',
TOKEN_EXCHANGE_RATE_DECIMALS,
),
};
// Gets the exchange rate of the remote quoted in local tokens

@ -40,6 +40,7 @@ export const infrastructure: InfrastructureConfig = {
'mainnet2-',
'hyperlane-mainnet3-',
'rc-mainnet3-',
'neutron-mainnet3-',
'mainnet3-',
],
},

@ -16,6 +16,7 @@ export const safes: ChainMap<Address> = {
base: '',
scroll: '',
polygonzkevm: '',
mantapacific: '',
};
// export const owners = safes;

@ -22,6 +22,7 @@ export const validatorChainConfig = (
'0x7bf30afcb6a7d92146d5a910ea4c154fba38d25e',
],
[Contexts.ReleaseCandidate]: [],
[Contexts.Neutron]: [],
},
'celo',
),
@ -37,6 +38,7 @@ export const validatorChainConfig = (
'0x749d6e7ad949e522c92181dc77f7bbc1c5d71506',
],
[Contexts.ReleaseCandidate]: [],
[Contexts.Neutron]: [],
},
'ethereum',
),
@ -54,6 +56,7 @@ export const validatorChainConfig = (
[Contexts.ReleaseCandidate]: [
'0x706976391e23dea28152e0207936bd942aba01ce',
],
[Contexts.Neutron]: [],
},
'avalanche',
),
@ -69,6 +72,7 @@ export const validatorChainConfig = (
'0xdbf3666de031bea43ec35822e8c33b9a9c610322',
],
[Contexts.ReleaseCandidate]: [],
[Contexts.Neutron]: [],
},
'polygon',
),
@ -84,6 +88,7 @@ export const validatorChainConfig = (
'0x03047213365800f065356b4a2fe97c3c3a52296a',
],
[Contexts.ReleaseCandidate]: [],
[Contexts.Neutron]: [],
},
'bsc',
),
@ -99,6 +104,7 @@ export const validatorChainConfig = (
'0x3369e12edd52570806f126eb50be269ba5e65843',
],
[Contexts.ReleaseCandidate]: [],
[Contexts.Neutron]: [],
},
'arbitrum',
),
@ -116,6 +122,7 @@ export const validatorChainConfig = (
[Contexts.ReleaseCandidate]: [
'0x60e938bf280bbc21bacfd8bf435459d9003a8f98',
],
[Contexts.Neutron]: [],
},
'optimism',
),
@ -131,6 +138,7 @@ export const validatorChainConfig = (
'0xcc4a78aa162482bea43313cd836ba7b560b44fc4',
],
[Contexts.ReleaseCandidate]: [],
[Contexts.Neutron]: [],
},
'moonbeam',
),
@ -146,6 +154,7 @@ export const validatorChainConfig = (
'0xb93a72cee19402553c9dd7fed2461aebd04e2454',
],
[Contexts.ReleaseCandidate]: [],
[Contexts.Neutron]: [],
},
'gnosis',
),
@ -161,6 +170,7 @@ export const validatorChainConfig = (
'0xb144bb2f599a5af095bc30367856f27ea8a8adc7',
],
[Contexts.ReleaseCandidate]: [],
[Contexts.Neutron]: [],
},
'base',
),
@ -176,6 +186,7 @@ export const validatorChainConfig = (
'0x7210fa0a6be39a75cb14d682ebfb37e2b53ecbe5',
],
[Contexts.ReleaseCandidate]: [],
[Contexts.Neutron]: [],
},
'scroll',
),
@ -191,9 +202,42 @@ export const validatorChainConfig = (
'0x6a1da2e0b7ae26aaece1377c0a4dbe25b85fa3ca',
],
[Contexts.ReleaseCandidate]: [],
[Contexts.Neutron]: [],
},
'polygonzkevm',
),
},
neutron: {
interval: 5,
reorgPeriod: 0,
validators: validatorsConfig(
{
[Contexts.Hyperlane]: [
'0xa9b8c1f4998f781f958c63cfcd1708d02f004ff0',
'0x60e890b34cb44ce3fa52f38684f613f31b47a1a6',
'0x7885fae56dbcf5176657f54adbbd881dc6714132',
],
[Contexts.ReleaseCandidate]: [],
[Contexts.Neutron]: [],
},
'neutron',
),
},
mantapacific: {
interval: 5,
reorgPeriod: 0,
validators: validatorsConfig(
{
[Contexts.Hyperlane]: [
'0x8e668c97ad76d0e28375275c41ece4972ab8a5bc',
'0x80afdde2a81f3fb056fd088a97f0af3722dbc4f3',
'0x5dda0c4cf18de3b3ab637f8df82b24921082b54c',
],
[Contexts.ReleaseCandidate]: [],
[Contexts.Neutron]: [],
},
'mantapacific',
),
},
};
};

@ -1,4 +1,5 @@
import {
Chains,
GasPaymentEnforcementPolicyType,
RpcConsensusType,
chainMetadata,
@ -68,6 +69,11 @@ const hyperlane: RootAgentConfig = {
repo,
tag: '1bee32a-20231121-121303',
},
chainDockerOverrides: {
neutrontestnet: {
tag: '5070398-20231108-172634',
},
},
chains: validatorChainConfig(Contexts.Hyperlane),
},
scraper: {
@ -106,7 +112,28 @@ const releaseCandidate: RootAgentConfig = {
},
};
const neutron: RootAgentConfig = {
...contextBase,
context: Contexts.Neutron,
rolesWithKeys: [Role.Relayer],
contextChainNames: {
relayer: [Chains.neutrontestnet, Chains.goerli],
validator: [],
scraper: [],
},
relayer: {
rpcConsensusType: RpcConsensusType.Fallback,
docker: {
repo,
tag: '5070398-20231108-172634',
},
gasPaymentEnforcement,
transactionGasLimit: 750000,
},
};
export const agents = {
[Contexts.Hyperlane]: hyperlane,
[Contexts.ReleaseCandidate]: releaseCandidate,
[Contexts.Neutron]: neutron,
};

@ -25,13 +25,14 @@ export const ethereumTestnetConfigs: ChainMap<ChainMetadata> = {
};
// Blessed non-Ethereum chains.
// export const nonEthereumTestnetConfigs: ChainMap<ChainMetadata> = {
// solanadevnet: chainMetadata.solanadevnet,
// };
export const nonEthereumTestnetConfigs: ChainMap<ChainMetadata> = {
// solanadevnet: chainMetadata.solanadevnet,
neutrontestnet: chainMetadata.neutrontestnet,
};
export const testnetConfigs: ChainMap<ChainMetadata> = {
...ethereumTestnetConfigs,
// ...nonEthereumTestnetConfigs,
...nonEthereumTestnetConfigs,
};
export type TestnetChains = keyof typeof testnetConfigs;
@ -43,15 +44,12 @@ export const environment = 'testnet4';
export const ethereumChainNames = Object.keys(
ethereumTestnetConfigs,
) as TestnetChains[];
const validatorChainNames = [
...supportedChainNames,
// chainMetadata.solanadevnet.name,
// chainMetadata.proteustestnet.name,
];
const relayerChainNames = validatorChainNames;
// Hyperlane & RC context agent chain names.
export const agentChainNames: AgentChainNames = {
[Role.Validator]: validatorChainNames,
[Role.Relayer]: relayerChainNames,
// Run validators for all chains.
[Role.Validator]: supportedChainNames,
// Only run relayers for Ethereum chains at the moment.
[Role.Relayer]: ethereumChainNames,
[Role.Scraper]: ethereumChainNames,
};

@ -30,6 +30,7 @@ const gasPrices: ChainMap<BigNumber> = {
polygonzkevmtestnet: ethers.utils.parseUnits('1', 'gwei'),
chiado: ethers.utils.parseUnits('2', 'gwei'),
// solanadevnet: ethers.BigNumber.from('28'),
neutrontestnet: ethers.utils.parseUnits('0.1', 'gwei'),
};
// Used to categorize rarity of testnet tokens & approximate exchange rates.
@ -64,6 +65,7 @@ const chainTokenRarity: ChainMap<Rarity> = {
polygonzkevmtestnet: Rarity.Common,
chiado: Rarity.Common,
// solanadevnet: Rarity.Common,
neutrontestnet: Rarity.Common,
};
// Gets the "value" of a testnet chain

@ -40,6 +40,7 @@ export const infrastructure: InfrastructureConfig = {
'testnet3-',
'hyperlane-testnet4-',
'rc-testnet4-',
'neutron-testnet4-',
'testnet4-',
],
},

@ -26,6 +26,7 @@ export const validatorChainConfig = (
'0x6c8bfdfb8c40aba10cc9fb2cf0e3e856e0e5dbb3',
'0x54c65eb7677e6086cdde3d5ccef89feb2103a11d',
],
[Contexts.Neutron]: [],
},
'alfajores',
),
@ -45,6 +46,7 @@ export const validatorChainConfig = (
'0x36de434527b8f83851d83f1b1d72ec11a5903533',
'0x4b65f7527c267e420bf62a0c5a139cb8c3906277',
],
[Contexts.Neutron]: [],
},
'basegoerli',
),
@ -64,6 +66,7 @@ export const validatorChainConfig = (
'0x0a636e76df4124b092cabb4321d6aaef9defb514',
'0xbf86037899efe97bca4cea865607e10b849b5878',
],
[Contexts.Neutron]: [],
},
'fuji',
),
@ -121,6 +124,7 @@ export const validatorChainConfig = (
'0x954168cf13faeaa248d412e145a17dc697556636',
'0x98a9f2610e44246ac0c749c20a07a6eb192ce9eb',
],
[Contexts.Neutron]: [],
},
'mumbai',
),
@ -140,6 +144,7 @@ export const validatorChainConfig = (
'0xcb5be62b19c52b78cd3993c71c3fa74d821475ae',
'0xc50ddb8f03133611853b7f03ffe0a8098e08ae15',
],
[Contexts.Neutron]: [],
},
'bsctestnet',
),
@ -159,6 +164,7 @@ export const validatorChainConfig = (
'0x4711d476a5929840196def397a156c5253b44b96',
'0xb0add42f2a4b824ba5fab2628f930dc1dcfc40f8',
],
[Contexts.Neutron]: [],
},
'goerli',
),
@ -178,6 +184,7 @@ export const validatorChainConfig = (
'0x10fa7a657a06a47bcca1bacc436d61619e5d104c',
'0xa0f1cf3b23bd0f8a5e2ad438657097b8287816b4',
],
[Contexts.Neutron]: [],
},
'scrollsepolia',
),
@ -197,6 +204,7 @@ export const validatorChainConfig = (
'0x13b51805e9af68e154778d973165f32e10b7446b',
'0x7f699c3fc3de4928f1c0abfba1eac3fbb5a00d1b',
],
[Contexts.Neutron]: [],
},
'sepolia',
),
@ -216,6 +224,7 @@ export const validatorChainConfig = (
'0x776623e8be8d7218940b7c77d02162af4ff97985',
'0xb4c81facd992a6c7c4a187bcce35a6fc968399a0',
],
[Contexts.Neutron]: [],
},
'moonbasealpha',
),
@ -235,6 +244,7 @@ export const validatorChainConfig = (
'0xec6b5ddfd20ee64ff0dcbc7472ad757dce151685',
'0x4acd2983a51f1c33c2ab41669184c7679e0316f1',
],
[Contexts.Neutron]: [],
},
'optimismgoerli',
),
@ -254,6 +264,7 @@ export const validatorChainConfig = (
'0x9be82c7a063b47b2d04c890daabcb666b670a9a4',
'0x92c62f4b9cd60a7fe4216d1f12134d34cf827c41',
],
[Contexts.Neutron]: [],
},
'arbitrumgoerli',
),
@ -273,6 +284,7 @@ export const validatorChainConfig = (
'0x989bbbfa753431169556f69be1b0a496b252e8a6',
'0x292d5788587bb5efd5c2c911115527e57f50cd05',
],
[Contexts.Neutron]: [],
},
'polygonzkevmtestnet',
),
@ -311,5 +323,21 @@ export const validatorChainConfig = (
// 'solanadevnet',
// ),
// },
neutrontestnet: {
interval: 5,
reorgPeriod: chainMetadata.neutrontestnet.blocks!.reorgPeriod!,
validators: validatorsConfig(
{
[Contexts.Hyperlane]: [
'0x5d2a99d67cd294a821de4fb25da6901ea8f89814',
'0xb57486243ce3bb3c38c50a582b8bbd20cb393589',
'0x661faee997654d14ead4ae48035883f05c3150cf',
],
[Contexts.ReleaseCandidate]: [],
[Contexts.Neutron]: [],
},
'neutrontestnet',
),
},
};
};

@ -43,7 +43,7 @@ export const routingIsm = (
context: Contexts,
): RoutingIsmConfig | string => {
const aggregationIsms: ChainMap<AggregationIsmConfig> = chains[environment]
.filter((_) => _ !== local)
.filter((chain) => chain !== local)
.reduce(
(acc, chain) => ({
...acc,

@ -7,6 +7,7 @@
"@aws-sdk/client-iam": "^3.74.0",
"@aws-sdk/client-kms": "3.48.0",
"@aws-sdk/client-s3": "^3.74.0",
"@cosmjs/amino": "^0.31.3",
"@eth-optimism/sdk": "^1.7.0",
"@ethersproject/experimental": "^5.7.0",
"@ethersproject/hardware-wallets": "^5.7.0",

@ -7,6 +7,7 @@ import {
import { EnvironmentConfig, RootAgentConfig } from '../../src/config';
import { Role } from '../../src/roles';
import { HelmCommand } from '../../src/utils/helm';
import { sleep } from '../../src/utils/utils';
import {
assertCorrectKubeContext,
getArgs,
@ -49,11 +50,10 @@ export class AgentCli {
}
if (this.dryRun) {
for (const m of Object.values(managers)) {
void m.helmValues().then((v) => {
console.log(JSON.stringify(v, null, 2));
});
}
const values = await Promise.all(
Object.values(managers).map(async (m) => m.helmValues()),
);
console.log('Dry run values:\n', JSON.stringify(values, null, 2));
}
for (const m of Object.values(managers)) {
@ -61,21 +61,18 @@ export class AgentCli {
}
}
protected async init(
argv?: GetConfigsArgv & { role: Role[]; 'dry-run'?: boolean },
) {
protected async init() {
if (this.initialized) return;
if (!argv)
argv = await withAgentRole(withContext(getArgs()))
.describe('dry-run', 'Run through the steps without making any changes')
.boolean('dry-run').argv;
const argv = await withAgentRole(withContext(getArgs()))
.describe('dry-run', 'Run through the steps without making any changes')
.boolean('dry-run').argv;
const { envConfig, agentConfig } = await getConfigsBasedOnArgs(argv);
await assertCorrectKubeContext(envConfig);
this.roles = argv.role;
this.envConfig = envConfig;
this.agentConfig = agentConfig;
this.dryRun = argv['dry-run'] || false;
this.dryRun = argv.dryRun || false;
this.initialized = true;
}
}

@ -95,6 +95,7 @@ async function main() {
contracts.mailbox.address,
validatorBaseConfig.checkpointSyncer.bucket,
validatorBaseConfig.checkpointSyncer.region,
undefined,
);
announcements.push({
storageLocation: validator.storageLocation(),

@ -15,23 +15,26 @@ export class S3Wrapper {
private readonly client: S3Client;
readonly bucket: string;
readonly region: string;
readonly folder: string | undefined;
static fromBucketUrl(bucketUrl: string): S3Wrapper {
const match = bucketUrl.match(S3_BUCKET_REGEX);
if (!match) throw new Error('Could not parse bucket url');
return new S3Wrapper(match[1], match[2]);
return new S3Wrapper(match[1], match[2], undefined);
}
constructor(bucket: string, region: string) {
constructor(bucket: string, region: string, folder: string | undefined) {
this.bucket = bucket;
this.region = region;
this.folder = folder;
this.client = new S3Client({ region });
}
async getS3Obj<T>(key: string): Promise<S3Receipt<T> | undefined> {
const Key = this.folder ? `${this.folder}/${key}` : key;
const command = new GetObjectCommand({
Bucket: this.bucket,
Key: key,
Key,
});
try {
const response = await this.client.send(command);

@ -50,9 +50,10 @@ export class S3Validator extends BaseValidator {
mailbox: string,
s3Bucket: string,
s3Region: string,
s3Folder: string | undefined,
) {
super(address, localDomain, mailbox);
this.s3Bucket = new S3Wrapper(s3Bucket, s3Region);
this.s3Bucket = new S3Wrapper(s3Bucket, s3Region, s3Folder);
}
static async fromStorageLocation(
@ -61,8 +62,8 @@ export class S3Validator extends BaseValidator {
if (storageLocation.startsWith(LOCATION_PREFIX)) {
const suffix = storageLocation.slice(LOCATION_PREFIX.length);
const pieces = suffix.split('/');
if (pieces.length == 2) {
const s3Bucket = new S3Wrapper(pieces[0], pieces[1]);
if (pieces.length >= 2) {
const s3Bucket = new S3Wrapper(pieces[0], pieces[1], pieces[2]);
const announcement = await s3Bucket.getS3Obj<any>(ANNOUNCEMENT_KEY);
const address = announcement?.data.value.validator;
const mailbox = announcement?.data.value.mailbox_address;
@ -74,6 +75,7 @@ export class S3Validator extends BaseValidator {
mailbox,
pieces[0],
pieces[1],
pieces[2],
);
}
}

@ -1,3 +1,8 @@
import {
encodeSecp256k1Pubkey,
pubkeyToAddress,
rawSecp256k1PubkeyToRawAddress,
} from '@cosmjs/amino';
import { Keypair } from '@solana/web3.js';
import { Wallet, ethers } from 'ethers';
@ -101,6 +106,17 @@ export class AgentGCPKey extends CloudAgentKey {
return Keypair.fromSeed(
Buffer.from(strip0x(this.privateKey), 'hex'),
).publicKey.toBase58();
case ProtocolType.Cosmos:
const compressedPubkey = ethers.utils.computePublicKey(
this.privateKey,
true,
);
const encodedPubkey = encodeSecp256k1Pubkey(
new Uint8Array(Buffer.from(strip0x(compressedPubkey), 'hex')),
);
// TODO support other prefixes?
// https://cosmosdrops.io/en/tools/bech32-converter is useful for converting to other prefixes.
return pubkeyToAddress(encodedPubkey, 'neutron');
default:
return undefined;
}

@ -22,7 +22,7 @@ import {
buildHelmChartDependencies,
helmifyValues,
} from '../utils/helm';
import { execCmd } from '../utils/utils';
import { execCmd, isNotEthereumProtocolChain } from '../utils/utils';
import { AgentGCPKey } from './gcp';
@ -113,17 +113,27 @@ export abstract class AgentHelmManager {
runEnv: this.environment,
context: this.context,
aws: !!this.config.aws,
chains: this.config.environmentChainNames.map((name) => ({
name,
disabled: !this.config.contextChainNames[this.role].includes(name),
rpcConsensusType: this.rpcConsensusType(name),
})),
chains: this.config.environmentChainNames.map((chain) => {
const metadata = chainMetadata[chain];
const reorgPeriod = metadata.blocks?.reorgPeriod;
if (reorgPeriod === undefined) {
throw new Error(`No reorg period found for chain ${chain}`);
}
return {
name: chain,
disabled: !this.config.contextChainNames[this.role].includes(chain),
rpcConsensusType: this.rpcConsensusType(chain),
protocol: metadata.protocol,
blocks: { reorgPeriod },
};
}),
},
};
}
rpcConsensusType(chain: ChainName): RpcConsensusType {
if (chainMetadata[chain].protocol == ProtocolType.Sealevel) {
// Non-Ethereum chains only support Single
if (isNotEthereumProtocolChain(chain)) {
return RpcConsensusType.Single;
}
@ -195,12 +205,10 @@ export class RelayerHelmManager extends OmniscientAgentHelmManager {
};
const signers = await this.config.signers();
values.hyperlane.relayerChains = this.config.environmentChainNames.map(
(name) => ({
name,
signer: signers[name],
}),
);
values.hyperlane.relayerChains = this.config.relayChains.map((name) => ({
name,
signer: signers[name],
}));
return values;
}
@ -250,11 +258,6 @@ export class ValidatorHelmManager extends MultichainAgentHelmManager {
const helmValues = await super.helmValues();
const cfg = await this.config.buildConfig();
helmValues.hyperlane.chains.push({
name: cfg.originChainName,
blocks: { reorgPeriod: cfg.reorgPeriod },
});
helmValues.hyperlane.validator = {
enabled: true,
configs: cfg.validators.map((c) => ({

@ -9,7 +9,7 @@ import {
} from '../config';
import { Role } from '../roles';
import { fetchGCPSecret, setGCPSecret } from '../utils/gcloud';
import { execCmd } from '../utils/utils';
import { execCmd, isNotEthereumProtocolChain } from '../utils/utils';
import { AgentAwsKey } from './aws/key';
import { AgentGCPKey } from './gcp';
@ -96,25 +96,52 @@ export function getValidatorCloudAgentKeys(
): Array<CloudAgentKey> {
// For each chainName, create validatorCount keys
if (!agentConfig.validators) return [];
const validators = agentConfig.validators;
return agentConfig.contextChainNames[Role.Validator]
.filter((chainName) => !!validators.chains[chainName])
.flatMap((chainName) =>
validators.chains[chainName].validators.map((_, index) =>
getCloudAgentKey(agentConfig, Role.Validator, chainName, index),
),
);
validators.chains[chainName].validators.map((_, index) => {
const validatorKeys = [];
// If AWS is enabled, we want to use AWS keys for the validator signing key
// that actually signs checkpoints.
if (agentConfig.aws) {
validatorKeys.push(
new AgentAwsKey(agentConfig, Role.Validator, chainName, index),
);
}
// If the chain is not an EVM chain, we also want to use GCP keys for
// self-announcing. This key won't actually sign checkpoints, just the self-announcement tx.
// We also want to use a GCP key if AWS is not enabled.
if (isNotEthereumProtocolChain(chainName) || !agentConfig.aws) {
validatorKeys.push(
new AgentGCPKey(
agentConfig.runEnv,
agentConfig.context,
Role.Validator,
chainName,
index,
),
);
}
return validatorKeys;
}),
)
.flat();
}
export function getAllCloudAgentKeys(
agentConfig: RootAgentConfig,
): Array<CloudAgentKey> {
const keys = [];
if ((agentConfig.rolesWithKeys ?? []).includes(Role.Relayer))
if (agentConfig.rolesWithKeys.includes(Role.Relayer))
keys.push(...getRelayerCloudAgentKeys(agentConfig));
if ((agentConfig.rolesWithKeys ?? []).includes(Role.Validator))
if (agentConfig.rolesWithKeys.includes(Role.Validator))
keys.push(...getValidatorCloudAgentKeys(agentConfig));
if ((agentConfig.rolesWithKeys ?? []).includes(Role.Kathy))
if (agentConfig.rolesWithKeys.includes(Role.Kathy))
keys.push(...getKathyCloudAgentKeys(agentConfig));
for (const role of agentConfig.rolesWithKeys) {
@ -233,8 +260,3 @@ function addressesIdentifier(
) {
return `${context}-${environment}-key-addresses`;
}
function isNotEthereumProtocolChain(chainName: ChainName) {
if (!chainMetadata[chainName]) throw new Error(`Unknown chain ${chainName}`);
return chainMetadata[chainName].protocol !== ProtocolType.Ethereum;
}

@ -4,7 +4,9 @@ import {
AgentSignerKeyType,
ChainName,
RpcConsensusType,
chainMetadata,
} from '@hyperlane-xyz/sdk';
import { ProtocolType } from '@hyperlane-xyz/utils';
import { Contexts } from '../../../config/contexts';
import { AgentChainNames, Role } from '../../roles';
@ -94,7 +96,11 @@ interface AgentRoleConfig {
export type AwsKeyConfig = Required<AgentSignerAwsKey>;
// only require specifying that it's the "hex" type for helm since the hex key will be pulled from secrets.
export type HexKeyConfig = { type: AgentSignerKeyType.Hex };
export type KeyConfig = AwsKeyConfig | HexKeyConfig;
export type CosmosKeyConfig = {
type: AgentSignerKeyType.Cosmos;
prefix: string;
};
export type KeyConfig = AwsKeyConfig | HexKeyConfig | CosmosKeyConfig;
interface IndexingConfig {
from: number;
@ -182,3 +188,22 @@ export abstract class AgentConfigHelper<R = unknown>
export const allAgentChainNames = (agentChainNames: AgentChainNames) => [
...new Set(Object.values(agentChainNames).reduce((a, b) => a.concat(b), [])),
];
// Returns the default KeyConfig for the `chainName`'s chain signer.
// For Ethereum or Sealevel, this is a hexKey, for Cosmos, this is a cosmosKey.
export function defaultChainSignerKeyConfig(chainName: ChainName): KeyConfig {
const metadata = chainMetadata[chainName];
switch (metadata?.protocol) {
case ProtocolType.Cosmos:
if (metadata.bech32Prefix === undefined) {
throw new Error(`Bech32 prefix for cosmos chain ${name} is undefined`);
}
return { type: AgentSignerKeyType.Cosmos, prefix: metadata.bech32Prefix };
// For Ethereum and Sealevel, use a hex key
case ProtocolType.Ethereum:
case ProtocolType.Sealevel:
default:
return { type: AgentSignerKeyType.Hex };
}
}

@ -16,7 +16,12 @@ import { AgentAwsUser } from '../../agents/aws';
import { Role } from '../../roles';
import { HelmStatefulSetValues } from '../infrastructure';
import { AgentConfigHelper, KeyConfig, RootAgentConfig } from './agent';
import {
AgentConfigHelper,
KeyConfig,
RootAgentConfig,
defaultChainSignerKeyConfig,
} from './agent';
export { GasPaymentEnforcement as GasPaymentEnforcementConfig } from '@hyperlane-xyz/sdk';
@ -60,7 +65,7 @@ export class RelayerConfigHelper extends AgentConfigHelper<RelayerConfig> {
const baseConfig = this.#relayerConfig!;
const relayerConfig: RelayerConfig = {
relayChains: this.contextChainNames[Role.Relayer].join(','),
relayChains: this.relayChains.join(','),
gasPaymentEnforcement: JSON.stringify(baseConfig.gasPaymentEnforcement),
};
@ -84,6 +89,8 @@ export class RelayerConfigHelper extends AgentConfigHelper<RelayerConfig> {
// Get the signer configuration for each chain by the chain name.
async signers(): Promise<ChainMap<KeyConfig>> {
let chainSigners: ChainMap<KeyConfig> = {};
if (this.aws) {
const awsUser = new AgentAwsUser(
this.runEnv,
@ -93,25 +100,24 @@ export class RelayerConfigHelper extends AgentConfigHelper<RelayerConfig> {
);
await awsUser.createIfNotExists();
const awsKey = (await awsUser.createKeyIfNotExists(this)).keyConfig;
return Object.fromEntries(
this.contextChainNames[Role.Relayer].map((name) => {
const chain = chainMetadata[name];
// Sealevel chains always use hex keys
if (chain?.protocol == ProtocolType.Sealevel) {
return [name, { type: AgentSignerKeyType.Hex }];
} else {
return [name, awsKey];
}
}),
);
} else {
return Object.fromEntries(
this.contextChainNames[Role.Relayer].map((name) => [
name,
{ type: AgentSignerKeyType.Hex },
]),
);
// AWS keys only work for Ethereum chains
for (const chainName of this.relayChains) {
if (chainMetadata[chainName].protocol === ProtocolType.Ethereum) {
chainSigners[chainName] = awsKey;
}
}
}
// For any chains that were not configured with AWS keys, fill in the defaults
for (const chainName of this.relayChains) {
if (chainSigners[chainName] !== undefined) {
continue;
}
chainSigners[chainName] = defaultChainSignerKeyConfig(chainName);
}
return chainSigners;
}
// Returns whether the relayer requires AWS credentials
@ -130,6 +136,10 @@ export class RelayerConfigHelper extends AgentConfigHelper<RelayerConfig> {
get role(): Role {
return Role.Relayer;
}
get relayChains(): Array<string> {
return this.contextChainNames[Role.Relayer];
}
}
// Create a matching list for the given router addresses

@ -4,13 +4,20 @@ import {
ValidatorConfig as AgentValidatorConfig,
ChainMap,
ChainName,
chainMetadata,
} from '@hyperlane-xyz/sdk';
import { ProtocolType } from '@hyperlane-xyz/utils';
import { ValidatorAgentAwsUser } from '../../agents/aws';
import { AgentAwsUser, ValidatorAgentAwsUser } from '../../agents/aws';
import { Role } from '../../roles';
import { HelmStatefulSetValues } from '../infrastructure';
import { AgentConfigHelper, KeyConfig, RootAgentConfig } from './agent';
import {
AgentConfigHelper,
KeyConfig,
RootAgentConfig,
defaultChainSignerKeyConfig,
} from './agent';
// Validator agents for each chain.
export type ValidatorBaseChainConfigMap = ChainMap<ValidatorBaseChainConfig>;
@ -33,11 +40,13 @@ export interface ValidatorBaseConfig {
export interface ValidatorConfig {
interval: number;
reorgPeriod: number;
originChainName: ChainName;
validators: Array<{
checkpointSyncer: CheckpointSyncerConfig;
// The key that signs checkpoints
validator: KeyConfig;
// The key that signs txs (e.g. self-announcements)
chainSigner: KeyConfig | undefined;
}>;
}
@ -88,7 +97,6 @@ export class ValidatorConfigHelper extends AgentConfigHelper<ValidatorConfig> {
async buildConfig(): Promise<ValidatorConfig> {
return {
interval: this.#chainConfig.interval,
reorgPeriod: this.#chainConfig.reorgPeriod,
originChainName: this.chainName!,
validators: await Promise.all(
this.#chainConfig.validators.map((val, i) =>
@ -110,7 +118,12 @@ export class ValidatorConfigHelper extends AgentConfigHelper<ValidatorConfig> {
cfg: ValidatorBaseConfig,
idx: number,
): Promise<ValidatorConfig['validators'][number]> {
const metadata = chainMetadata[this.chainName];
const protocol = metadata.protocol;
let validator: KeyConfig = { type: AgentSignerKeyType.Hex };
let chainSigner: KeyConfig | undefined = undefined;
if (cfg.checkpointSyncer.type == CheckpointSyncerType.S3) {
const awsUser = new ValidatorAgentAwsUser(
this.runEnv,
@ -123,17 +136,29 @@ export class ValidatorConfigHelper extends AgentConfigHelper<ValidatorConfig> {
await awsUser.createIfNotExists();
await awsUser.createBucketIfNotExists();
if (this.aws)
if (this.aws) {
validator = (await awsUser.createKeyIfNotExists(this)).keyConfig;
// AWS-based chain signer keys are only used for Ethereum
if (protocol === ProtocolType.Ethereum) {
chainSigner = validator;
}
}
} else {
console.warn(
`Validator ${cfg.address}'s checkpoint syncer is not S3-based. Be sure this is a non-k8s-based environment!`,
);
}
// If the chainSigner isn't set to the AWS-based key above, then set the default.
if (chainSigner === undefined) {
chainSigner = defaultChainSignerKeyConfig(this.chainName);
}
return {
checkpointSyncer: cfg.checkpointSyncer,
validator,
chainSigner,
};
}

@ -11,7 +11,7 @@ import {
CoreChainName,
chainMetadata,
} from '@hyperlane-xyz/sdk';
import { objMerge } from '@hyperlane-xyz/utils';
import { ProtocolType, objMerge } from '@hyperlane-xyz/utils';
import { Contexts } from '../../config/contexts';
import { Role } from '../roles';
@ -273,3 +273,8 @@ export function mustGetChainNativeTokenDecimals(chain: ChainName): number {
}
return metadata.nativeToken.decimals;
}
export function isNotEthereumProtocolChain(chainName: ChainName) {
if (!chainMetadata[chainName]) throw new Error(`Unknown chain ${chainName}`);
return chainMetadata[chainName].protocol !== ProtocolType.Ethereum;
}

@ -807,7 +807,7 @@ export const neutron: ChainMetadata = {
],
blocks: {
confirmations: 1,
reorgPeriod: 0,
reorgPeriod: 1,
estimateBlockTime: 3,
},
blockExplorers: [
@ -1008,6 +1008,31 @@ export const polygonzkevm: ChainMetadata = {
},
};
export const neutrontestnet: ChainMetadata = {
protocol: ProtocolType.Cosmos,
domainId: 33333,
chainId: 'duality-devnet',
name: Chains.neutrontestnet,
displayName: 'Neutron Testnet',
nativeToken: {
name: 'Neutron',
symbol: 'NTRN',
decimals: 6,
},
blocks: {
confirmations: 1,
reorgPeriod: 1,
estimateBlockTime: 3,
},
// First URL RPC, second REST
rpcUrls: [
{ http: 'http://54.149.31.83:26657' },
{ http: 'http://54.149.31.83:1317' },
],
bech32Prefix: 'dual',
isTestnet: true,
};
/**
* Collection maps
*
@ -1051,6 +1076,7 @@ export const chainMetadata: ChainMap<ChainMetadata> = {
solanatestnet,
solanadevnet,
nautilus,
neutrontestnet,
};
export const chainIdToMetadata = Object.values(chainMetadata).reduce<

@ -35,6 +35,7 @@ export enum Chains {
proteustestnet = 'proteustestnet',
solana = 'solana',
solanadevnet = 'solanadevnet',
neutrontestnet = 'neutrontestnet',
test1 = 'test1',
test2 = 'test2',
test3 = 'test3',

@ -178,5 +178,21 @@
"protocolFee": "0xEc4AdA26E51f2685279F37C8aE62BeAd8212D597",
"mailbox": "0xFf06aFcaABaDDd1fb08371f9ccA15D73D51FeBD6",
"validatorAnnounce": "0x9Cad0eC82328CEE2386Ec14a12E81d070a27712f"
},
"mantapacific": {
"merkleRootMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A",
"messageIdMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6",
"aggregationIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908",
"aggregationHookFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004",
"routingIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1",
"proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
"mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E",
"domainRoutingIsm": "0xDEed16fe4b1c9b2a93483EDFf34C77A9b57D31Ff",
"storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117",
"interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4",
"merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112",
"aggregationHook": "0x8464aF853363B8d6844070F68b0AB34Cb6523d0F",
"protocolFee": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638",
"validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9"
}
}

@ -101,12 +101,16 @@ export const defaultMultisigConfigs: ChainMap<MultisigConfig> = {
'0x57231619fea13d85270ca6943298046c75a6dd01', // everstake
],
},
solana: {
threshold: 2,
mantapacific: {
threshold: 5,
validators: [
'0x3cd1a081f38874bbb075bf10b62adcb858db864c', // abacus
'0x2b0c45f6111ae1c1684d4287792e3bd6ebd1abcc', // ZKV
'0x7b9ec253a8ba38994457eb9dbe386938d545351a', // everstake
'0x8e668c97ad76d0e28375275c41ece4972ab8a5bc', //abacusworks
'0x521a3e6bf8d24809fde1c1fd3494a859a16f132c', //cosmostation
'0x14025fe092f5f8a401dd9819704d9072196d2125', //p2p
'0x25b9a0961c51e74fd83295293bc029131bf1e05a', //neutron
'0xa0eE95e280D46C14921e524B075d0C341e7ad1C8', //cosmos spaces
'0xcc9a0b6de7fe314bd99223687d784730a75bb957', //dsrv
'0x42b6de2edbaa62c2ea2309ad85d20b3e37d38acf', //sg-1
],
},
// ----------------- Testnets -----------------
@ -231,4 +235,20 @@ export const defaultMultisigConfigs: ChainMap<MultisigConfig> = {
'0x967c5ecdf2625ae86580bd203b630abaaf85cd62',
],
},
neutrontestnet: {
threshold: 2,
validators: [
'0x5d2a99d67cd294a821de4fb25da6901ea8f89814',
'0xb57486243ce3bb3c38c50a582b8bbd20cb393589',
'0x661faee997654d14ead4ae48035883f05c3150cf',
],
},
neutron: {
threshold: 2,
validators: [
'0xa9b8c1f4998f781f958c63cfcd1708d02f004ff0',
'0x60e890b34cb44ce3fa52f38684f613f31b47a1a6',
'0x7885fae56dbcf5176657f54adbbd881dc6714132',
],
},
};

@ -4,6 +4,8 @@
*/
import { z } from 'zod';
import { ProtocolType } from '@hyperlane-xyz/utils';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainMap, ChainName } from '../types';
@ -46,6 +48,7 @@ export enum AgentSignerKeyType {
Aws = 'aws',
Hex = 'hexKey',
Node = 'node',
Cosmos = 'cosmosKey',
}
const AgentSignerHexKeySchema = z
@ -63,6 +66,13 @@ const AgentSignerAwsKeySchema = z
.describe(
'An AWS signer. Note that AWS credentials must be inserted into the env separately.',
);
const AgentSignerCosmosKeySchema = z
.object({
type: z.literal(AgentSignerKeyType.Cosmos),
prefix: z.string().describe('The bech32 prefix for the cosmos address'),
key: ZHash,
})
.describe('Cosmos key');
const AgentSignerNodeSchema = z
.object({
type: z.literal(AgentSignerKeyType.Node),
@ -72,47 +82,82 @@ const AgentSignerNodeSchema = z
const AgentSignerSchema = z.union([
AgentSignerHexKeySchema,
AgentSignerAwsKeySchema,
AgentSignerCosmosKeySchema,
AgentSignerNodeSchema,
]);
export type AgentSignerHexKey = z.infer<typeof AgentSignerHexKeySchema>;
export type AgentSignerAwsKey = z.infer<typeof AgentSignerAwsKeySchema>;
export type AgentSignerCosmosKey = z.infer<typeof AgentSignerNodeSchema>;
export type AgentSignerNode = z.infer<typeof AgentSignerNodeSchema>;
export type AgentSigner = z.infer<typeof AgentSignerSchema>;
export const AgentChainMetadataSchema = ChainMetadataSchemaObject.merge(
HyperlaneDeploymentArtifactsSchema,
).extend({
customRpcUrls: z
.string()
.optional()
.describe(
'Specify a comma seperated list of custom RPC URLs to use for this chain. If not specified, the default RPC urls will be used.',
),
rpcConsensusType: z
.nativeEnum(RpcConsensusType)
.describe('The consensus type to use when multiple RPCs are configured.')
.optional(),
signer: AgentSignerSchema.optional().describe(
'The signer to use for this chain',
),
index: z
.object({
from: ZUint.optional().describe(
'The starting block from which to index events.',
),
chunk: ZNzUint.optional().describe(
'The number of blocks to index at a time.',
)
.extend({
customRpcUrls: z
.string()
.optional()
.describe(
'Specify a comma seperated list of custom RPC URLs to use for this chain. If not specified, the default RPC urls will be used.',
),
mode: z
.nativeEnum(AgentIndexMode)
.optional()
.describe(
'The indexing method to use for this chain; will attempt to choose a suitable default if not specified.',
rpcConsensusType: z
.nativeEnum(RpcConsensusType)
.describe('The consensus type to use when multiple RPCs are configured.')
.optional(),
signer: AgentSignerSchema.optional().describe(
'The signer to use for this chain',
),
index: z
.object({
from: ZUint.optional().describe(
'The starting block from which to index events.',
),
})
.optional(),
});
chunk: ZNzUint.optional().describe(
'The number of blocks to index at a time.',
),
mode: z
.nativeEnum(AgentIndexMode)
.optional()
.describe(
'The indexing method to use for this chain; will attempt to choose a suitable default if not specified.',
),
})
.optional(),
})
.refine((metadata) => {
// Make sure that the signer is valid for the protocol
const signerType = metadata.signer?.type;
// If no signer is specified, no validation is needed
if (signerType === undefined) {
return true;
}
switch (metadata.protocol) {
case ProtocolType.Ethereum:
return [
AgentSignerKeyType.Hex,
signerType === AgentSignerKeyType.Aws,
signerType === AgentSignerKeyType.Node,
].includes(signerType);
case ProtocolType.Cosmos:
return [AgentSignerKeyType.Cosmos].includes(signerType);
case ProtocolType.Sealevel:
return [AgentSignerKeyType.Hex].includes(signerType);
case ProtocolType.Fuel:
return [AgentSignerKeyType.Hex].includes(signerType);
default:
// Just default to true if we don't know the protocol
return true;
}
});
export type AgentChainMetadata = z.infer<typeof AgentChainMetadataSchema>;

@ -4335,6 +4335,7 @@ __metadata:
"@aws-sdk/client-iam": "npm:^3.74.0"
"@aws-sdk/client-kms": "npm:3.48.0"
"@aws-sdk/client-s3": "npm:^3.74.0"
"@cosmjs/amino": "npm:^0.31.3"
"@eth-optimism/sdk": "npm:^1.7.0"
"@ethersproject/experimental": "npm:^5.7.0"
"@ethersproject/hardware-wallets": "npm:^5.7.0"

Loading…
Cancel
Save