Minimal multi-protocol SDK support (#2529)

### Description

- Create MultiProtocolProvider
- Create ProviderType enum and TypedProvider
- Add viem, solana, and ethers6 dev deps to SDK
- Upgrade Typescript and Eslint across monorepo (required for Viem)
- Break out metadata mgmt code into new ChainMetadataManager class
- Implement basic MultiProtocolApp and MultiProtocolRouterApp
- Create Evm and Sealevel router adapters
- Refactor contract types into separate file to avoid circular dep

### Related issues

https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2503

### Backward compatibility

Yes
pull/2607/head
J M Rossy 1 year ago committed by GitHub
parent 465112db6f
commit d901ca2c6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      .eslintrc
  2. 4
      package.json
  3. 2
      solidity/package.json
  4. 7
      tsconfig.json
  5. 8
      typescript/helloworld/.eslintrc
  6. 6
      typescript/helloworld/package.json
  7. 7
      typescript/helloworld/tsconfig.json
  8. 2
      typescript/infra/package.json
  9. 10
      typescript/sdk/.eslintrc
  10. 8
      typescript/sdk/README.md
  11. 5
      typescript/sdk/package.json
  12. 11
      typescript/sdk/src/app/HyperlaneApp.ts
  13. 19
      typescript/sdk/src/app/MultiProtocolApp.test.ts
  14. 98
      typescript/sdk/src/app/MultiProtocolApp.ts
  15. 32
      typescript/sdk/src/contracts/contracts.ts
  16. 29
      typescript/sdk/src/contracts/types.ts
  17. 2
      typescript/sdk/src/core/CoreDeployer.hardhat-test.ts
  18. 5
      typescript/sdk/src/core/HyperlaneCore.ts
  19. 2
      typescript/sdk/src/core/HyperlaneCoreDeployer.ts
  20. 2
      typescript/sdk/src/core/TestCoreApp.ts
  21. 2
      typescript/sdk/src/core/TestCoreDeployer.ts
  22. 4
      typescript/sdk/src/deploy/HyperlaneAppChecker.ts
  23. 2
      typescript/sdk/src/deploy/HyperlaneDeployer.ts
  24. 5
      typescript/sdk/src/gas/HyperlaneIgp.ts
  25. 2
      typescript/sdk/src/gas/HyperlaneIgpDeployer.ts
  26. 2
      typescript/sdk/src/hook/HyperlaneHookDeployer.ts
  27. 94
      typescript/sdk/src/index.ts
  28. 9
      typescript/sdk/src/ism/HyperlaneIsmFactory.ts
  29. 2
      typescript/sdk/src/ism/HyperlaneIsmFactoryDeployer.ts
  30. 289
      typescript/sdk/src/metadata/ChainMetadataManager.ts
  31. 5
      typescript/sdk/src/metadata/agentConfig.ts
  32. 3
      typescript/sdk/src/metadata/chainMetadataTypes.ts
  33. 5
      typescript/sdk/src/metadata/deploymentArtifacts.ts
  34. 4
      typescript/sdk/src/middleware/account/InterchainAccount.ts
  35. 2
      typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts
  36. 2
      typescript/sdk/src/middleware/account/accounts.hardhat-test.ts
  37. 4
      typescript/sdk/src/middleware/liquidity-layer/LiquidityLayerApp.ts
  38. 5
      typescript/sdk/src/middleware/liquidity-layer/LiquidityLayerRouterDeployer.ts
  39. 4
      typescript/sdk/src/middleware/query/InterchainQuery.ts
  40. 2
      typescript/sdk/src/middleware/query/queries.hardhat-test.ts
  41. 26
      typescript/sdk/src/providers/MultiProtocolProvider.test.ts
  42. 159
      typescript/sdk/src/providers/MultiProtocolProvider.ts
  43. 302
      typescript/sdk/src/providers/MultiProvider.ts
  44. 134
      typescript/sdk/src/providers/ProviderType.ts
  45. 131
      typescript/sdk/src/providers/providerBuilders.ts
  46. 2
      typescript/sdk/src/router/GasRouterDeployer.ts
  47. 2
      typescript/sdk/src/router/HyperlaneRouterChecker.ts
  48. 4
      typescript/sdk/src/router/HyperlaneRouterDeployer.ts
  49. 22
      typescript/sdk/src/router/MultiProtocolRouterApps.test.ts
  50. 64
      typescript/sdk/src/router/MultiProtocolRouterApps.ts
  51. 2
      typescript/sdk/src/router/ProxiedRouterDeployer.ts
  52. 14
      typescript/sdk/src/router/RouterApps.ts
  53. 89
      typescript/sdk/src/router/adapters/EvmRouterAdapter.ts
  54. 30
      typescript/sdk/src/router/adapters/SealevelRouterAdapter.test.ts
  55. 197
      typescript/sdk/src/router/adapters/SealevelRouterAdapter.ts
  56. 29
      typescript/sdk/src/router/adapters/types.ts
  57. 6
      typescript/sdk/src/router/types.ts
  58. 2
      typescript/sdk/src/test/testUtils.ts
  59. 4
      typescript/sdk/src/utils/MultiGeneric.ts
  60. 28
      typescript/token/.eslintrc
  61. 6
      typescript/token/package.json
  62. 25
      typescript/token/tsconfig.json
  63. 2
      typescript/utils/package.json
  64. 296
      yarn.lock

@ -29,5 +29,23 @@
"@typescript-eslint/no-floating-promises": ["error"],
"@typescript-eslint/no-non-null-assertion": ["off"],
"@typescript-eslint/no-require-imports": ["warn"],
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrorsIgnorePattern": "^_"
}
],
"@typescript-eslint/ban-types": [
"error",
{
"types": {
// Unban the {} type which is a useful shorthand for non-nullish value
"{}": false
},
"extendDefaults": true
}
]
}
}

@ -4,8 +4,8 @@
"version": "0.0.0",
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^3.2.0",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"eslint": "^8.16.0",
"eslint-config-prettier": "^8.5.0",
"husky": "^8.0.0",

@ -25,7 +25,7 @@
"solidity-coverage": "^0.8.3",
"ts-generator": "^0.1.1",
"typechain": "^8.1.1",
"typescript": "^4.7.2"
"typescript": "^5.1.6"
},
"directories": {
"test": "test"

@ -5,7 +5,10 @@
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"incremental": false,
"lib": ["es2015", "es5", "dom", "es2021"],
"lib": [
"ES2015", "ES2016", "ES2017", "ES2018",
"ES2019", "ES2020","ES2021", "DOM"
],
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": true,
@ -18,7 +21,7 @@
"pretty": false,
"resolveJsonModule": true,
"sourceMap": true,
"target": "es6",
"target": "ES2020",
"strict": true
}
}

@ -27,5 +27,13 @@
"@typescript-eslint/no-floating-promises": ["error"],
"@typescript-eslint/no-non-null-assertion": ["off"],
"@typescript-eslint/no-require-imports": ["warn"],
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrorsIgnorePattern": "^_"
}
]
}
}

@ -14,8 +14,8 @@
"@typechain/ethers-v5": "10.0.0",
"@typechain/hardhat": "^6.0.0",
"@types/mocha": "^9.1.0",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"chai": "^4.3.0",
"eslint": "^8.16.0",
"eslint-config-prettier": "^8.5.0",
@ -29,7 +29,7 @@
"solidity-coverage": "^0.8.3",
"ts-node": "^10.8.0",
"typechain": "8.0.0",
"typescript": "^4.7.2"
"typescript": "^5.1.6"
},
"files": [
"/dist",

@ -5,7 +5,10 @@
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"incremental": false,
"lib": ["es2015", "es5", "dom"],
"lib": [
"ES2015", "ES2016", "ES2017", "ES2018",
"ES2019", "ES2020","ES2021", "DOM"
],
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": true,
@ -17,7 +20,7 @@
"preserveWatchOutput": true,
"pretty": false,
"sourceMap": true,
"target": "es6",
"target": "ES2020",
"strict": true,
"outDir": "./dist",
"rootDir": "./",

@ -39,7 +39,7 @@
"hardhat": "^2.16.1",
"prettier": "^2.4.1",
"ts-node": "^10.8.0",
"typescript": "^4.7.2"
"typescript": "^5.1.6"
},
"private": true,
"homepage": "https://www.hyperlane.xyz",

@ -1,13 +1,5 @@
{
"rules": {
"@typescript-eslint/explicit-module-boundary-types": ["error"],
"@typescript-eslint/no-unused-vars": [
"warn", // or "error"
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrorsIgnorePattern": "^_"
}
],
"@typescript-eslint/explicit-module-boundary-types": ["error"]
}
}

@ -6,7 +6,13 @@ For details on how to use the various abstractions and utilities, [see the docum
## Install
`yarn install @hyperlane-xyz/sdk`
```bash
# Install with NPM
npm install @hyperlane-xyz/sdk
# Or with Yarn
yarn add @hyperlane-xyz/sdk
```
## Contents

@ -5,6 +5,7 @@
"dependencies": {
"@hyperlane-xyz/core": "1.4.2",
"@hyperlane-xyz/utils": "1.4.2",
"@solana/web3.js": "^1.78.0",
"@types/coingecko-api": "^1.0.10",
"@types/debug": "^4.1.7",
"@wagmi/chains": "^0.2.6",
@ -12,12 +13,14 @@
"cross-fetch": "^3.1.5",
"debug": "^4.3.4",
"ethers": "^5.7.2",
"viem": "^1.3.1",
"zod": "^3.21.2"
},
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.2.1",
"@nomiclabs/hardhat-waffle": "^2.0.3",
"@types/node": "^16.9.1",
"@types/ws": "^8.5.5",
"chai": "^4.3.6",
"dotenv": "^10.0.0",
"eslint": "^8.43.0",
@ -28,7 +31,7 @@
"prettier": "^2.4.1",
"sinon": "^13.0.2",
"ts-node": "^10.8.0",
"typescript": "^4.7.2"
"typescript": "^5.1.6"
},
"files": [
"/dist",

@ -2,17 +2,16 @@ import debug from 'debug';
import { objMap } from '@hyperlane-xyz/utils';
import { connectContracts, serializeContracts } from '../contracts/contracts';
import {
HyperlaneAddresses,
HyperlaneContracts,
HyperlaneContractsMap,
HyperlaneFactories,
connectContracts,
serializeContracts,
} from './contracts';
import { MultiProvider } from './providers/MultiProvider';
import { ChainName } from './types';
import { MultiGeneric } from './utils/MultiGeneric';
} from '../contracts/types';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainName } from '../types';
import { MultiGeneric } from '../utils/MultiGeneric';
export class HyperlaneApp<
Factories extends HyperlaneFactories,

@ -0,0 +1,19 @@
import { expect } from 'chai';
import { Chains } from '../consts/chains';
import { MultiProtocolProvider } from '../providers/MultiProtocolProvider';
import { MultiProtocolApp } from './MultiProtocolApp';
class TestMultiProtocolApp extends MultiProtocolApp {}
describe('MultiProtocolApp', () => {
describe('constructs', () => {
const multiProvider = new MultiProtocolProvider();
it('creates an app class and gleans types from generic', async () => {
const app = new TestMultiProtocolApp(multiProvider);
expect(app).to.be.instanceOf(MultiProtocolApp);
expect(app.adapter(Chains.ethereum).protocol).to.eql(Chains.ethereum);
});
});
});

@ -0,0 +1,98 @@
import debug from 'debug';
import { ProtocolType, objMap, promiseObjAll } from '@hyperlane-xyz/utils';
import { ChainMetadata } from '../metadata/chainMetadataTypes';
import { MultiProtocolProvider } from '../providers/MultiProtocolProvider';
import { ChainMap, ChainName } from '../types';
import { MultiGeneric } from '../utils/MultiGeneric';
/**
* A minimal interface for an adapter that can be used with MultiProtocolApp
* The purpose of adapters is to implement protocol-specific functionality
* E.g. EvmRouterAdapter implements EVM-specific router functionality
* whereas SealevelRouterAdapter implements the same logic for Solana
*/
export abstract class BaseAppAdapter<ContractAddrs = {}> {
public abstract readonly protocol: ProtocolType;
constructor(
public readonly multiProvider: MultiProtocolProvider<ContractAddrs>,
) {}
}
export type AdapterClassType<ContractAddrs = {}, API = {}> = new (
multiProvider: MultiProtocolProvider<ContractAddrs>,
) => API;
export type AdapterProtocolMap<ContractAddrs = {}, API = {}> = Partial<
Record<ProtocolType, AdapterClassType<ContractAddrs, API>>
>;
export class BaseEvmAdapter<
ContractAddrs = {},
> extends BaseAppAdapter<ContractAddrs> {
public readonly protocol: ProtocolType = ProtocolType.Ethereum;
}
export class BaseSealevelAdapter<
ContractAddrs = {},
> extends BaseAppAdapter<ContractAddrs> {
public readonly protocol: ProtocolType = ProtocolType.Sealevel;
}
/**
* A version of HyperlaneApp that can support different
* provider types across different protocol types.
*
* Intentionally minimal as it's meant to be extended.
* Extend this class as needed to add useful methods/properties.
*
* @typeParam ContractAddrs - A map of contract names to addresses
* @typeParam IAdapterApi - The type of the adapters for implementing the app's
* functionality across different protocols.
*
* @param multiProvider - A MultiProtocolProvider instance that MUST include the app's
* contract addresses in its chain metadata
* @param logger - A logger instance
*/
export abstract class MultiProtocolApp<
ContractAddrs = {},
IAdapterApi extends BaseAppAdapter = BaseAppAdapter,
> extends MultiGeneric<ChainMetadata<ContractAddrs>> {
// Subclasses should override this with more specific adapters
public readonly protocolToAdapter: AdapterProtocolMap<any, BaseAppAdapter> = {
[ProtocolType.Ethereum]: BaseEvmAdapter,
[ProtocolType.Sealevel]: BaseSealevelAdapter,
};
constructor(
public readonly multiProvider: MultiProtocolProvider<ContractAddrs>,
public readonly logger = debug('hyperlane:MultiProtocolApp'),
) {
super(multiProvider.metadata);
}
metadata(chain: ChainName): ChainMetadata<ContractAddrs> {
return this.get(chain);
}
adapter(chain: ChainName): IAdapterApi {
const metadata = this.metadata(chain);
const Adapter = this.protocolToAdapter[
metadata.protocol
] as AdapterClassType<ContractAddrs, IAdapterApi>;
if (!Adapter)
throw new Error(`No adapter for protocol ${metadata.protocol}`);
return new Adapter(this.multiProvider);
}
adapters(): ChainMap<IAdapterApi> {
return this.map((chain, _) => this.adapter(chain));
}
adapterMap<Output>(
fn: (n: ChainName, a: IAdapterApi) => Promise<Output>,
): Promise<ChainMap<Output>> {
return promiseObjAll(objMap(this.adapters(), fn));
}
}

@ -1,4 +1,4 @@
import { Contract, ethers } from 'ethers';
import { Contract } from 'ethers';
import { Ownable } from '@hyperlane-xyz/core';
import {
@ -10,28 +10,16 @@ import {
promiseObjAll,
} from '@hyperlane-xyz/utils';
import { MultiProvider } from './providers/MultiProvider';
import { ChainMap, Connection } from './types';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainMap, Connection } from '../types';
export type HyperlaneFactories = {
[key: string]: ethers.ContractFactory;
};
export type HyperlaneContracts<F extends HyperlaneFactories> = {
[P in keyof F]: Awaited<ReturnType<F[P]['deploy']>>;
};
export type HyperlaneContractsMap<F extends HyperlaneFactories> = ChainMap<
HyperlaneContracts<F>
>;
export type HyperlaneAddresses<F extends HyperlaneFactories> = {
[P in keyof F]: Address;
};
export type HyperlaneAddressesMap<F extends HyperlaneFactories> = ChainMap<
HyperlaneAddresses<F>
>;
import {
HyperlaneAddresses,
HyperlaneAddressesMap,
HyperlaneContracts,
HyperlaneContractsMap,
HyperlaneFactories,
} from './types';
export function serializeContractsMap<F extends HyperlaneFactories>(
contractsMap: HyperlaneContractsMap<F>,

@ -0,0 +1,29 @@
import type { ethers } from 'ethers';
import type { Address } from '@hyperlane-xyz/utils';
import type { ChainMap } from '../types';
export type AddressesMap = {
[key: string]: Address;
};
export type HyperlaneFactories = {
[key: string]: ethers.ContractFactory;
};
export type HyperlaneContracts<F extends HyperlaneFactories> = {
[P in keyof F]: Awaited<ReturnType<F[P]['deploy']>>;
};
export type HyperlaneContractsMap<F extends HyperlaneFactories> = ChainMap<
HyperlaneContracts<F>
>;
export type HyperlaneAddresses<F extends HyperlaneFactories> = {
[P in keyof F]: Address;
};
export type HyperlaneAddressesMap<F extends HyperlaneFactories> = ChainMap<
HyperlaneAddresses<F>
>;

@ -4,7 +4,7 @@ import { ethers } from 'hardhat';
import sinon from 'sinon';
import { TestChains } from '../consts/chains';
import { HyperlaneContractsMap } from '../contracts';
import { HyperlaneContractsMap } from '../contracts/types';
import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory';
import { HyperlaneIsmFactoryDeployer } from '../ism/HyperlaneIsmFactoryDeployer';
import { MultiProvider } from '../providers/MultiProvider';

@ -8,12 +8,13 @@ import {
pollAsync,
} from '@hyperlane-xyz/utils';
import { HyperlaneApp } from '../HyperlaneApp';
import { HyperlaneApp } from '../app/HyperlaneApp';
import {
HyperlaneEnvironment,
hyperlaneEnvironments,
} from '../consts/environments';
import { HyperlaneAddressesMap, appFromAddressesMapHelper } from '../contracts';
import { appFromAddressesMapHelper } from '../contracts/contracts';
import { HyperlaneAddressesMap } from '../contracts/types';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainName } from '../types';

@ -9,7 +9,7 @@ import {
} from '@hyperlane-xyz/core';
import { Address } from '@hyperlane-xyz/utils';
import { HyperlaneContracts } from '../contracts';
import { HyperlaneContracts } from '../contracts/types';
import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer';
import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory';
import { IsmConfig } from '../ism/types';

@ -3,7 +3,7 @@ import { ethers } from 'ethers';
import { TestMailbox, TestMailbox__factory } from '@hyperlane-xyz/core';
import { messageId } from '@hyperlane-xyz/utils';
import { HyperlaneContracts } from '../contracts';
import { HyperlaneContracts } from '../contracts/types';
import { ChainName } from '../types';
import { HyperlaneCore } from './HyperlaneCore';

@ -5,7 +5,7 @@ import {
} from '@hyperlane-xyz/core';
import { TestChains } from '../consts/chains';
import { HyperlaneContracts } from '../contracts';
import { HyperlaneContracts } from '../contracts/types';
import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory';
import { MultiProvider } from '../providers/MultiProvider';
import { testCoreConfig } from '../test/testUtils';

@ -9,8 +9,8 @@ import {
promiseObjAll,
} from '@hyperlane-xyz/utils';
import { HyperlaneApp } from '../HyperlaneApp';
import { filterOwnableContracts } from '../contracts';
import { HyperlaneApp } from '../app/HyperlaneApp';
import { filterOwnableContracts } from '../contracts/contracts';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainMap, ChainName } from '../types';

@ -20,7 +20,7 @@ import {
HyperlaneContracts,
HyperlaneContractsMap,
HyperlaneFactories,
} from '../contracts';
} from '../contracts/types';
import {
HyperlaneIsmFactory,
moduleMatchesConfig,

@ -3,12 +3,13 @@ import { BigNumber } from 'ethers';
import { InterchainGasPaymaster__factory } from '@hyperlane-xyz/core';
import { Address } from '@hyperlane-xyz/utils';
import { HyperlaneApp } from '../HyperlaneApp';
import { HyperlaneApp } from '../app/HyperlaneApp';
import {
HyperlaneEnvironment,
hyperlaneEnvironments,
} from '../consts/environments';
import { HyperlaneAddressesMap, appFromAddressesMapHelper } from '../contracts';
import { appFromAddressesMapHelper } from '../contracts/contracts';
import { HyperlaneAddressesMap } from '../contracts/types';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainName } from '../types';

@ -11,7 +11,7 @@ import {
} from '@hyperlane-xyz/core';
import { Address, eqAddress } from '@hyperlane-xyz/utils';
import { HyperlaneContracts } from '../contracts';
import { HyperlaneContracts } from '../contracts/types';
import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainName } from '../types';

@ -10,7 +10,7 @@ import {
} from '@hyperlane-xyz/core';
import { Address, addressToBytes32 } from '@hyperlane-xyz/utils';
import { HyperlaneContracts, HyperlaneContractsMap } from '../contracts';
import { HyperlaneContracts, HyperlaneContractsMap } from '../contracts/types';
import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainMap, ChainName } from '../types';

@ -1,4 +1,12 @@
export { HyperlaneApp } from './HyperlaneApp';
export { HyperlaneApp } from './app/HyperlaneApp';
export {
AdapterClassType,
AdapterProtocolMap,
BaseAppAdapter,
BaseEvmAdapter,
BaseSealevelAdapter,
MultiProtocolApp,
} from './app/MultiProtocolApp';
export {
chainIdToMetadata,
chainMetadata,
@ -16,16 +24,13 @@ export {
Testnets,
} from './consts/chains';
export {
HyperlaneEnvironment,
HyperlaneEnvironmentChain,
hyperlaneContractAddresses,
hyperlaneEnvironments,
} from './consts/environments';
export { defaultMultisigIsmConfigs } from './consts/multisigIsm';
export {
HyperlaneAddresses,
HyperlaneAddressesMap,
HyperlaneContracts,
HyperlaneContractsMap,
HyperlaneFactories,
attachContracts,
attachContractsMap,
connectContracts,
@ -33,7 +38,15 @@ export {
filterAddressesMap,
serializeContracts,
serializeContractsMap,
} from './contracts';
} from './contracts/contracts';
export {
AddressesMap,
HyperlaneAddresses,
HyperlaneAddressesMap,
HyperlaneContracts,
HyperlaneContractsMap,
HyperlaneFactories,
} from './contracts/types';
export { DispatchedMessage, HyperlaneCore } from './core/HyperlaneCore';
export { HyperlaneCoreChecker } from './core/HyperlaneCoreChecker';
export { HyperlaneCoreDeployer } from './core/HyperlaneCoreDeployer';
@ -41,7 +54,14 @@ export { TestCoreApp } from './core/TestCoreApp';
export { TestCoreDeployer } from './core/TestCoreDeployer';
export { CoreFactories, coreFactories } from './core/contracts';
export { HyperlaneLifecyleEvent } from './core/events';
export { CoreConfig, CoreViolationType } from './core/types';
export {
CoreConfig,
CoreViolationType,
MailboxMultisigIsmViolation,
MailboxViolation,
MailboxViolationType,
ValidatorAnnounceViolation,
} from './core/types';
export { HyperlaneAppChecker } from './deploy/HyperlaneAppChecker';
export { DeployerOptions, HyperlaneDeployer } from './deploy/HyperlaneDeployer';
export {
@ -145,24 +165,74 @@ export {
} from './middleware/query/InterchainQueryDeployer';
export { interchainQueryFactories } from './middleware/query/contracts';
export {
MultiProvider,
ProviderBuilderFn,
defaultProviderBuilder,
} from './providers/MultiProvider';
MultiProtocolProvider,
MultiProtocolProviderOptions,
} from './providers/MultiProtocolProvider';
export { MultiProvider, MultiProviderOptions } from './providers/MultiProvider';
export {
EthersV5Contract,
EthersV5Provider,
EthersV5Transaction,
ProviderMap,
ProviderType,
SolanaWeb3Contract,
SolanaWeb3Provider,
SolanaWeb3Transaction,
TypedContract,
TypedProvider,
TypedTransaction,
ViemContract,
ViemProvider,
ViemTransaction,
} from './providers/ProviderType';
export {
RetryJsonRpcProvider,
RetryProviderOptions,
} from './providers/RetryProvider';
export {
DEFAULT_RETRY_OPTIONS,
ProviderBuilderFn,
ProviderBuilderMap,
TypedProviderBuilderFn,
defaultEthersV5ProviderBuilder,
defaultFuelProviderBuilder,
defaultProviderBuilder,
defaultProviderBuilderMap,
defaultSolProviderBuilder,
defaultViemProviderBuilder,
protocolToDefaultProviderBuilder,
} from './providers/providerBuilders';
export { GasRouterDeployer } from './router/GasRouterDeployer';
export { HyperlaneRouterChecker } from './router/HyperlaneRouterChecker';
export { HyperlaneRouterDeployer } from './router/HyperlaneRouterDeployer';
export {
MultiProtocolGasRouterApp,
MultiProtocolRouterApp,
} from './router/MultiProtocolRouterApps';
export { GasRouterApp, Router, RouterApp } from './router/RouterApps';
export {
EvmGasRouterAdapter,
EvmRouterAdapter,
} from './router/adapters/EvmRouterAdapter';
export {
SealevelGasRouterAdapter,
SealevelRouterAdapter,
SealevelTokenDataSchema,
} from './router/adapters/SealevelRouterAdapter';
export { IGasRouterAdapter, IRouterAdapter } from './router/adapters/types';
export {
ConnectionClientConfig,
ConnectionClientViolation,
ConnectionClientViolationType,
ForeignDeploymentConfig,
GasConfig,
GasRouterConfig,
OwnableConfig,
ProxiedFactories,
ProxiedRouterConfig,
RouterAddress,
RouterConfig,
proxiedFactories,
} from './router/types';
export {
createRouterConfigMap,

@ -13,16 +13,13 @@ import {
} from '@hyperlane-xyz/core';
import { Address, eqAddress, formatMessage, warn } from '@hyperlane-xyz/utils';
import { HyperlaneApp } from '../HyperlaneApp';
import { HyperlaneApp } from '../app/HyperlaneApp';
import {
HyperlaneEnvironment,
hyperlaneEnvironments,
} from '../consts/environments';
import {
HyperlaneAddressesMap,
HyperlaneContracts,
appFromAddressesMapHelper,
} from '../contracts';
import { appFromAddressesMapHelper } from '../contracts/contracts';
import { HyperlaneAddressesMap, HyperlaneContracts } from '../contracts/types';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainMap, ChainName } from '../types';

@ -2,7 +2,7 @@ import debug from 'debug';
import { isObject } from '@hyperlane-xyz/utils';
import { HyperlaneContracts, HyperlaneContractsMap } from '../contracts';
import { HyperlaneContracts, HyperlaneContractsMap } from '../contracts/types';
import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainMap, ChainName } from '../types';

@ -0,0 +1,289 @@
import { Debugger, debug } from 'debug';
import { exclude, isNumeric } from '@hyperlane-xyz/utils';
import { chainMetadata as defaultChainMetadata } from '../consts/chainMetadata';
import { ChainMap, ChainName } from '../types';
import {
ChainMetadata,
getDomainId,
isValidChainMetadata,
} from './chainMetadataTypes';
export interface ChainMetadataManagerOptions {
loggerName?: string;
}
/**
* A set of utilities to manage chain metadata
* Validates metadata on construction and provides useful methods
* for interacting with the data
*/
export class ChainMetadataManager<MetaExt = {}> {
public readonly metadata: ChainMap<ChainMetadata<MetaExt>> = {};
protected readonly logger: Debugger;
/**
* Create a new ChainMetadataManager with the given chainMetadata,
* or the SDK's default metadata if not provided
*/
constructor(
chainMetadata: ChainMap<
ChainMetadata<MetaExt>
> = defaultChainMetadata as ChainMap<ChainMetadata<MetaExt>>,
options: ChainMetadataManagerOptions = {},
) {
Object.entries(chainMetadata).forEach(([key, cm]) => {
if (key !== cm.name)
throw new Error(
`Chain name mismatch: Key was ${key}, but name is ${cm.name}`,
);
this.addChain(cm);
});
this.logger = debug(options?.loggerName || 'hyperlane:MetadataManager');
}
/**
* Add a chain to the MultiProvider
* @throws if chain's name or domain/chain ID collide
*/
addChain(metadata: ChainMetadata<MetaExt>): void {
if (!isValidChainMetadata(metadata))
throw new Error(`Invalid chain metadata for ${metadata.name}`);
// Ensure no two chains have overlapping names/domainIds/chainIds
for (const chainMetadata of Object.values(this.metadata)) {
const { name, chainId, domainId } = chainMetadata;
if (name == metadata.name)
throw new Error(`Duplicate chain name: ${name}`);
// Chain and Domain Ids should be globally unique
const idCollision =
chainId == metadata.chainId ||
domainId == metadata.chainId ||
(metadata.domainId &&
(chainId == metadata.domainId || domainId === metadata.domainId));
if (idCollision)
throw new Error(
`Chain/Domain id collision: ${name} and ${metadata.name}`,
);
}
this.metadata[metadata.name] = metadata;
}
/**
* Get the metadata for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
tryGetChainMetadata(
chainNameOrId: ChainName | number,
): ChainMetadata<MetaExt> | null {
let chainMetadata: ChainMetadata<MetaExt> | undefined;
if (isNumeric(chainNameOrId)) {
// Should be chain id or domain id
chainMetadata = Object.values(this.metadata).find(
(m) => m.chainId == chainNameOrId || m.domainId == chainNameOrId,
);
} else if (typeof chainNameOrId === 'string') {
// Should be chain name
chainMetadata = this.metadata[chainNameOrId];
}
return chainMetadata || null;
}
/**
* Get the metadata for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
getChainMetadata(chainNameOrId: ChainName | number): ChainMetadata<MetaExt> {
const chainMetadata = this.tryGetChainMetadata(chainNameOrId);
if (!chainMetadata)
throw new Error(`No chain metadata set for ${chainNameOrId}`);
return chainMetadata;
}
/**
* Get the name for a given chain name, chain id, or domain id
*/
tryGetChainName(chainNameOrId: ChainName | number): string | null {
return this.tryGetChainMetadata(chainNameOrId)?.name ?? null;
}
/**
* Get the name for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
getChainName(chainNameOrId: ChainName | number): string {
return this.getChainMetadata(chainNameOrId).name;
}
/**
* Get the names for all chains known to this MultiProvider
*/
getKnownChainNames(): string[] {
return Object.keys(this.metadata);
}
/**
* Get the id for a given chain name, chain id, or domain id
*/
tryGetChainId(chainNameOrId: ChainName | number): number | null {
return this.tryGetChainMetadata(chainNameOrId)?.chainId ?? null;
}
/**
* Get the id for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
getChainId(chainNameOrId: ChainName | number): number {
return this.getChainMetadata(chainNameOrId).chainId;
}
/**
* Get the ids for all chains known to this MultiProvider
*/
getKnownChainIds(): number[] {
return Object.values(this.metadata).map((c) => c.chainId);
}
/**
* Get the domain id for a given chain name, chain id, or domain id
*/
tryGetDomainId(chainNameOrId: ChainName | number): number | null {
const metadata = this.tryGetChainMetadata(chainNameOrId);
return metadata?.domainId ?? metadata?.chainId ?? null;
}
/**
* Get the domain id for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
getDomainId(chainNameOrId: ChainName | number): number {
const metadata = this.getChainMetadata(chainNameOrId);
return getDomainId(metadata);
}
/**
* Get the domain ids for a list of chain names, chain ids, or domain ids
* @throws if any chain's metadata has not been set
*/
getDomainIds(chainNamesOrIds: Array<ChainName | number>): number[] {
return chainNamesOrIds.map((c) => this.getDomainId(c));
}
/**
* Get the ids for all chains known to this MultiProvider
*/
getKnownDomainIds(): number[] {
return this.getKnownChainNames().map(this.getDomainId);
}
/**
* Get chain names excluding given chain name
*/
getRemoteChains(name: ChainName): ChainName[] {
return exclude(name, this.getKnownChainNames());
}
/**
* Run given function on all known chains
*/
mapKnownChains<Output>(fn: (n: ChainName) => Output): ChainMap<Output> {
const result: ChainMap<Output> = {};
for (const chain of this.getKnownChainNames()) {
result[chain] = fn(chain);
}
return result;
}
/**
* Get an RPC URL for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
getRpcUrl(chainNameOrId: ChainName | number): string {
const { rpcUrls } = this.getChainMetadata(chainNameOrId);
if (!rpcUrls?.length || !rpcUrls[0].http)
throw new Error(`No RPC URl configured for ${chainNameOrId}`);
return rpcUrls[0].http;
}
/**
* Get a block explorer URL for a given chain name, chain id, or domain id
*/
tryGetExplorerUrl(chainNameOrId: ChainName | number): string | null {
const explorers = this.tryGetChainMetadata(chainNameOrId)?.blockExplorers;
if (!explorers?.length) return null;
return explorers[0].url;
}
/**
* Get a block explorer URL for a given chain name, chain id, or domain id
* @throws if chain's metadata or block explorer data has no been set
*/
getExplorerUrl(chainNameOrId: ChainName | number): string {
const url = this.tryGetExplorerUrl(chainNameOrId);
if (!url) throw new Error(`No explorer url set for ${chainNameOrId}`);
return url;
}
/**
* Get a block explorer's API URL for a given chain name, chain id, or domain id
*/
tryGetExplorerApiUrl(chainNameOrId: ChainName | number): string | null {
const explorers = this.tryGetChainMetadata(chainNameOrId)?.blockExplorers;
if (!explorers?.length || !explorers[0].apiUrl) return null;
const { apiUrl, apiKey } = explorers[0];
if (!apiKey) return apiUrl;
const url = new URL(apiUrl);
url.searchParams.set('apikey', apiKey);
return url.toString();
}
/**
* Get a block explorer API URL for a given chain name, chain id, or domain id
* @throws if chain's metadata or block explorer data has no been set
*/
getExplorerApiUrl(chainNameOrId: ChainName | number): string {
const url = this.tryGetExplorerApiUrl(chainNameOrId);
if (!url) throw new Error(`No explorer api url set for ${chainNameOrId}`);
return url;
}
/**
* Get a block explorer URL for given chain's tx
*/
tryGetExplorerTxUrl(
chainNameOrId: ChainName | number,
response: { hash: string },
): string | null {
const baseUrl = this.tryGetExplorerUrl(chainNameOrId);
return baseUrl ? `${baseUrl}/tx/${response.hash}` : null;
}
/**
* Get a block explorer URL for given chain's tx
* @throws if chain's metadata or block explorer data has no been set
*/
getExplorerTxUrl(
chainNameOrId: ChainName | number,
response: { hash: string },
): string {
return `${this.getExplorerUrl(chainNameOrId)}/tx/${response.hash}`;
}
/**
* Creates a new ChainMetadataManager with the extended metadata
* @param additionalMetadata extra fields to add to the metadata for each chain
* @returns a new ChainMetadataManager
*/
extendChainMetadata<NewExt = {}>(
additionalMetadata: ChainMap<NewExt>,
): ChainMetadataManager<MetaExt & NewExt> {
const newMetadata: ChainMap<ChainMetadata<MetaExt & NewExt>> = {};
for (const [name, meta] of Object.entries(this.metadata)) {
if (!additionalMetadata[name])
throw new Error(`No additional data provided for chain ${name}`);
newMetadata[name] = { ...meta, ...additionalMetadata[name] };
}
return new ChainMetadataManager(newMetadata);
}
}

@ -53,7 +53,10 @@ export type AgentMetadataExtension = z.infer<typeof AgentMetadataExtSchema>;
export const ChainMetadataForAgentSchema =
ChainMetadataWithArtifactsSchema.merge(AgentMetadataExtSchema);
export type ChainMetadataForAgent = z.infer<typeof ChainMetadataForAgentSchema>;
export type ChainMetadataForAgent<Ext = object> = z.infer<
typeof ChainMetadataForAgentSchema
> &
Ext;
/**
* Deprecated agent config shapes.

@ -185,7 +185,8 @@ export const ChainMetadataSchema = z.object({
.describe('Whether the chain is considered a testnet or a mainnet.'),
});
export type ChainMetadata = z.infer<typeof ChainMetadataSchema>;
export type ChainMetadata<Ext = object> = z.infer<typeof ChainMetadataSchema> &
Ext;
export function isValidChainMetadata(c: ChainMetadata): boolean {
return ChainMetadataSchema.safeParse(c).success;

@ -24,6 +24,7 @@ export const ChainMetadataWithArtifactsSchema = ChainMetadataSchema.merge(
HyperlaneDeploymentArtifactsSchema,
);
export type ChainMetadataWithArtifacts = z.infer<
export type ChainMetadataWithArtifacts<Ext = object> = z.infer<
typeof ChainMetadataWithArtifactsSchema
>;
> &
Ext;

@ -4,11 +4,11 @@ import {
HyperlaneEnvironment,
hyperlaneEnvironments,
} from '../../consts/environments';
import { appFromAddressesMapHelper } from '../../contracts/contracts';
import {
HyperlaneAddressesMap,
HyperlaneContracts,
appFromAddressesMapHelper,
} from '../../contracts';
} from '../../contracts/types';
import { MultiProvider } from '../../providers/MultiProvider';
import { RouterApp } from '../../router/RouterApps';

@ -1,6 +1,6 @@
import { ethers } from 'ethers';
import { HyperlaneContracts } from '../../contracts';
import { HyperlaneContracts } from '../../contracts/types';
import { MultiProvider } from '../../providers/MultiProvider';
import { ProxiedRouterDeployer } from '../../router/ProxiedRouterDeployer';
import { ProxiedRouterConfig, RouterConfig } from '../../router/types';

@ -8,7 +8,7 @@ import {
} from '@hyperlane-xyz/core';
import { Chains } from '../../consts/chains';
import { HyperlaneContractsMap } from '../../contracts';
import { HyperlaneContractsMap } from '../../contracts/types';
import { TestCoreApp } from '../../core/TestCoreApp';
import { TestCoreDeployer } from '../../core/TestCoreDeployer';
import { MultiProvider } from '../../providers/MultiProvider';

@ -14,8 +14,8 @@ import {
strip0x,
} from '@hyperlane-xyz/utils';
import { HyperlaneApp } from '../../HyperlaneApp';
import { HyperlaneContracts } from '../../contracts';
import { HyperlaneApp } from '../../app/HyperlaneApp';
import { HyperlaneContracts } from '../../contracts/types';
import { MultiProvider } from '../../providers/MultiProvider';
import { ChainMap, ChainName } from '../../types';
import { fetchWithTimeout } from '../../utils/fetch';

@ -7,7 +7,10 @@ import {
} from '@hyperlane-xyz/core';
import { Address, eqAddress, objFilter, objMap } from '@hyperlane-xyz/utils';
import { HyperlaneContracts, HyperlaneContractsMap } from '../../contracts';
import {
HyperlaneContracts,
HyperlaneContractsMap,
} from '../../contracts/types';
import { MultiProvider } from '../../providers/MultiProvider';
import { ProxiedRouterDeployer } from '../../router/ProxiedRouterDeployer';
import { RouterConfig } from '../../router/types';

@ -4,11 +4,11 @@ import {
HyperlaneEnvironment,
hyperlaneEnvironments,
} from '../../consts/environments';
import { appFromAddressesMapHelper } from '../../contracts/contracts';
import {
HyperlaneAddressesMap,
HyperlaneContracts,
appFromAddressesMapHelper,
} from '../../contracts';
} from '../../contracts/types';
import { MultiProvider } from '../../providers/MultiProvider';
import { RouterApp } from '../../router/RouterApps';

@ -11,7 +11,7 @@ import { addressToBytes32 } from '@hyperlane-xyz/utils';
import { chainMetadata } from '../../consts/chainMetadata';
import { Chains } from '../../consts/chains';
import { HyperlaneContractsMap } from '../../contracts';
import { HyperlaneContractsMap } from '../../contracts/types';
import { TestCoreApp } from '../../core/TestCoreApp';
import { TestCoreDeployer } from '../../core/TestCoreDeployer';
import { MultiProvider } from '../../providers/MultiProvider';

@ -0,0 +1,26 @@
import { expect } from 'chai';
import { ethereum } from '../consts/chainMetadata';
import { Chains } from '../consts/chains';
import { MultiProtocolProvider } from '../providers/MultiProtocolProvider';
describe('MultiProtocolProvider', () => {
describe('constructs', () => {
it('creates a multi protocol provider without type extension', async () => {
const multiProvider = new MultiProtocolProvider();
const ethMetadata = multiProvider.getChainMetadata(Chains.ethereum);
expect(ethMetadata.name).to.equal(Chains.ethereum);
});
it('creates a multi protocol provider with type extension', async () => {
const multiProvider = new MultiProtocolProvider<{
foo: string;
bar: number;
}>({
[Chains.ethereum]: { ...ethereum, foo: '0x123', bar: 1 },
});
const ethMetadata = multiProvider.getChainMetadata(Chains.ethereum);
expect(ethMetadata.foo).to.equal('0x123');
expect(ethMetadata.bar).to.equal(1);
});
});
});

@ -0,0 +1,159 @@
import { Debugger, debug } from 'debug';
import { objMap } from '@hyperlane-xyz/utils';
import { chainMetadata as defaultChainMetadata } from '../consts/chainMetadata';
import { ChainMetadataManager } from '../metadata/ChainMetadataManager';
import type { ChainMetadata } from '../metadata/chainMetadataTypes';
import type { ChainMap, ChainName } from '../types';
import type { MultiProvider } from './MultiProvider';
import {
EthersV5Provider,
ProviderMap,
ProviderType,
SolanaWeb3Provider,
TypedProvider,
ViemProvider,
} from './ProviderType';
import {
ProviderBuilderMap,
defaultProviderBuilderMap,
} from './providerBuilders';
export interface MultiProtocolProviderOptions {
loggerName?: string;
providerBuilders?: Partial<ProviderBuilderMap>;
}
/**
* A version of MultiProvider that can support different
* provider types across different protocol types.
*
* This uses a different interface for provider/signer related methods
* so it isn't strictly backwards compatible with MultiProvider.
*
* Unlike MultiProvider, this class does not support signer/signing methods (yet).
* @typeParam MetaExt - Extra metadata fields for chains (such as contract addresses)
*/
export class MultiProtocolProvider<
MetaExt = {},
> extends ChainMetadataManager<MetaExt> {
protected readonly providers: ChainMap<ProviderMap<TypedProvider>> = {};
protected signers: ChainMap<ProviderMap<never>> = {}; // TODO signer support
protected readonly logger: Debugger;
protected readonly providerBuilders: Partial<ProviderBuilderMap>;
constructor(
chainMetadata: ChainMap<
ChainMetadata<MetaExt>
> = defaultChainMetadata as ChainMap<ChainMetadata<MetaExt>>,
protected readonly options: MultiProtocolProviderOptions = {},
) {
super(chainMetadata, options);
this.logger = debug(
options?.loggerName || 'hyperlane:MultiProtocolProvider',
);
this.providerBuilders =
options.providerBuilders || defaultProviderBuilderMap;
}
static fromMultiProvider<MetaExt = {}>(
mp: MultiProvider<MetaExt>,
options: MultiProtocolProviderOptions = {},
): MultiProtocolProvider<MetaExt> {
const newMp = new MultiProtocolProvider<MetaExt>(mp.metadata, options);
const typedProviders = objMap(mp.providers, (_, provider) => ({
type: ProviderType.EthersV5,
provider,
})) as ChainMap<TypedProvider>;
newMp.setProviders(typedProviders);
return newMp;
}
override extendChainMetadata<NewExt = {}>(
additionalMetadata: ChainMap<NewExt>,
): MultiProtocolProvider<MetaExt & NewExt> {
const newMetadata = super.extendChainMetadata(additionalMetadata).metadata;
return new MultiProtocolProvider(newMetadata, this.options);
}
tryGetProvider(
chainNameOrId: ChainName | number,
type: ProviderType,
): TypedProvider | null {
const metadata = this.tryGetChainMetadata(chainNameOrId);
if (!metadata) return null;
const { name, chainId, rpcUrls } = metadata;
if (this.providers[name]?.[type]) return this.providers[name][type]!;
const builder = this.providerBuilders[type];
if (!rpcUrls.length || !builder) return null;
const provider = builder(rpcUrls, chainId);
this.providers[name] ||= {};
this.providers[name][type] = provider;
return provider;
}
getProvider(
chainNameOrId: ChainName | number,
type: ProviderType,
): TypedProvider {
const provider = this.tryGetProvider(chainNameOrId, type);
if (!provider)
throw new Error(`No provider available for ${chainNameOrId}`);
return provider;
}
getEthersV5Provider(
chainNameOrId: ChainName | number,
): EthersV5Provider['provider'] {
const provider = this.getProvider(chainNameOrId, ProviderType.EthersV5);
if (provider.type !== ProviderType.EthersV5)
throw new Error('Invalid provider type');
return provider.provider;
}
// getEthersV6Provider(
// chainNameOrId: ChainName | number,
// ): EthersV6Provider['provider'] {
// const provider = this.getProvider(chainNameOrId, ProviderType.EthersV5);
// if (provider.type !== ProviderType.EthersV6)
// throw new Error('Invalid provider type');
// return provider.provider;
// }
getViemProvider(chainNameOrId: ChainName | number): ViemProvider['provider'] {
const provider = this.getProvider(chainNameOrId, ProviderType.EthersV5);
if (provider.type !== ProviderType.Viem)
throw new Error('Invalid provider type');
return provider.provider;
}
getSolanaWeb3Provider(
chainNameOrId: ChainName | number,
): SolanaWeb3Provider['provider'] {
const provider = this.getProvider(chainNameOrId, ProviderType.EthersV5);
if (provider.type !== ProviderType.SolanaWeb3)
throw new Error('Invalid provider type');
return provider.provider;
}
setProvider(
chainNameOrId: ChainName | number,
provider: TypedProvider,
): TypedProvider {
const chainName = this.getChainName(chainNameOrId);
this.providers[chainName] ||= {};
this.providers[chainName][provider.type] = provider;
return provider;
}
setProviders(providers: ChainMap<TypedProvider>): void {
for (const chain of Object.keys(providers)) {
this.setProvider(chain, providers[chain]);
}
}
}

@ -8,104 +8,53 @@ import {
providers,
} from 'ethers';
import { Address, exclude, pick } from '@hyperlane-xyz/utils';
import { Address, pick } from '@hyperlane-xyz/utils';
import { chainMetadata as defaultChainMetadata } from '../consts/chainMetadata';
import { CoreChainName, TestChains } from '../consts/chains';
import {
ChainMetadata,
getDomainId,
isValidChainMetadata,
} from '../metadata/chainMetadataTypes';
import { ChainMetadataManager } from '../metadata/ChainMetadataManager';
import { ChainMetadata } from '../metadata/chainMetadataTypes';
import { ChainMap, ChainName } from '../types';
import { RetryJsonRpcProvider, RetryProviderOptions } from './RetryProvider';
import {
DEFAULT_RETRY_OPTIONS,
ProviderBuilderFn,
defaultProviderBuilder,
} from './providerBuilders';
type Provider = providers.Provider;
const DEFAULT_RETRY_OPTIONS: RetryProviderOptions = {
maxRequests: 3,
baseRetryMs: 250,
};
export function defaultProviderBuilder(
rpcUrls: ChainMetadata['rpcUrls'],
network: providers.Networkish,
retryOverride?: RetryProviderOptions,
): Provider {
const createProvider = (r: ChainMetadata['rpcUrls'][number]) => {
const retry = r.retry || retryOverride;
return retry
? new RetryJsonRpcProvider(retry, r.http, network)
: new providers.StaticJsonRpcProvider(r.http, network);
};
if (rpcUrls.length > 1) {
return new providers.FallbackProvider(rpcUrls.map(createProvider), 1);
} else if (rpcUrls.length === 1) {
return createProvider(rpcUrls[0]);
} else {
throw new Error('No RPC URLs provided');
}
}
export type ProviderBuilderFn = typeof defaultProviderBuilder;
interface MultiProviderOptions {
export interface MultiProviderOptions {
loggerName?: string;
providerBuilder?: ProviderBuilderFn;
providerBuilder?: ProviderBuilderFn<Provider>;
}
export class MultiProvider {
public readonly metadata: ChainMap<ChainMetadata> = {};
protected readonly providers: ChainMap<Provider> = {};
protected readonly providerBuilder: ProviderBuilderFn;
protected signers: ChainMap<Signer> = {};
protected useSharedSigner = false; // A single signer to be used for all chains
protected readonly logger: Debugger;
/**
* A utility class to create and manage providers and signers for multiple chains
* @typeParam MetaExt - Extra metadata fields for chains (such as contract addresses)
*/
export class MultiProvider<MetaExt = {}> extends ChainMetadataManager<MetaExt> {
readonly providers: ChainMap<Provider> = {};
readonly providerBuilder: ProviderBuilderFn<Provider>;
signers: ChainMap<Signer> = {};
useSharedSigner = false; // A single signer to be used for all chains
readonly logger: Debugger;
/**
* Create a new MultiProvider with the given chainMetadata,
* or the SDK's default metadata if not provided
*/
constructor(
chainMetadata: ChainMap<ChainMetadata> = defaultChainMetadata,
options: MultiProviderOptions = {},
chainMetadata?: ChainMap<ChainMetadata<MetaExt>>,
readonly options: MultiProviderOptions = {},
) {
Object.entries(chainMetadata).forEach(([key, cm]) => {
if (key !== cm.name)
throw new Error(
`Chain name mismatch: Key was ${key}, but name is ${cm.name}`,
);
this.addChain(cm);
});
super(chainMetadata, options);
this.logger = debug(options?.loggerName || 'hyperlane:MultiProvider');
this.providerBuilder = options?.providerBuilder || defaultProviderBuilder;
}
/**
* Add a chain to the MultiProvider
* @throws if chain's name or domain/chain ID collide
*/
addChain(metadata: ChainMetadata): void {
if (!isValidChainMetadata(metadata))
throw new Error(`Invalid chain metadata for ${metadata.name}`);
// Ensure no two chains have overlapping names/domainIds/chainIds
for (const chainMetadata of Object.values(this.metadata)) {
const { name, chainId, domainId } = chainMetadata;
if (name == metadata.name)
throw new Error(`Duplicate chain name: ${name}`);
// Chain and Domain Ids should be globally unique
const idCollision =
chainId == metadata.chainId ||
domainId == metadata.chainId ||
(metadata.domainId &&
(chainId == metadata.domainId || domainId === metadata.domainId));
if (idCollision)
throw new Error(
`Chain/Domain id collision: ${name} and ${metadata.name}`,
);
}
this.metadata[metadata.name] = metadata;
override addChain(metadata: ChainMetadata<MetaExt>): void {
super.addChain(metadata);
if (this.useSharedSigner) {
const signers = Object.values(this.signers);
if (signers.length > 0) {
@ -114,107 +63,11 @@ export class MultiProvider {
}
}
/**
* Get the metadata for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
tryGetChainMetadata(chainNameOrId: ChainName | number): ChainMetadata | null {
let chainMetadata: ChainMetadata | undefined;
if (typeof chainNameOrId === 'string') {
chainMetadata = this.metadata[chainNameOrId];
} else if (typeof chainNameOrId === 'number') {
chainMetadata = Object.values(this.metadata).find(
(m) => m.chainId === chainNameOrId || m.domainId === chainNameOrId,
);
}
return chainMetadata || null;
}
/**
* Get the metadata for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
getChainMetadata(chainNameOrId: ChainName | number): ChainMetadata {
const chainMetadata = this.tryGetChainMetadata(chainNameOrId);
if (!chainMetadata)
throw new Error(`No chain metadata set for ${chainNameOrId}`);
return chainMetadata;
}
/**
* Get the name for a given chain name, chain id, or domain id
*/
tryGetChainName(chainNameOrId: ChainName | number): string | null {
return this.tryGetChainMetadata(chainNameOrId)?.name ?? null;
}
/**
* Get the name for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
getChainName(chainNameOrId: ChainName | number): string {
return this.getChainMetadata(chainNameOrId).name;
}
/**
* Get the names for all chains known to this MultiProvider
*/
getKnownChainNames(): string[] {
return Object.keys(this.metadata);
}
/**
* Get the id for a given chain name, chain id, or domain id
*/
tryGetChainId(chainNameOrId: ChainName | number): number | null {
return this.tryGetChainMetadata(chainNameOrId)?.chainId ?? null;
}
/**
* Get the id for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
getChainId(chainNameOrId: ChainName | number): number {
return this.getChainMetadata(chainNameOrId).chainId;
}
/**
* Get the ids for all chains known to this MultiProvider
*/
getKnownChainIds(): number[] {
return Object.values(this.metadata).map((c) => c.chainId);
}
/**
* Get the domain id for a given chain name, chain id, or domain id
*/
tryGetDomainId(chainNameOrId: ChainName | number): number | null {
const metadata = this.tryGetChainMetadata(chainNameOrId);
return metadata?.domainId ?? metadata?.chainId ?? null;
}
/**
* Get the domain id for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
getDomainId(chainNameOrId: ChainName | number): number {
const metadata = this.getChainMetadata(chainNameOrId);
return getDomainId(metadata);
}
/**
* Get the domain ids for a list of chain names, chain ids, or domain ids
* @throws if any chain's metadata has not been set
*/
getDomainIds(chainNamesOrIds: Array<ChainName | number>): number[] {
return chainNamesOrIds.map((c) => this.getDomainId(c));
}
/**
* Get the ids for all chains known to this MultiProvider
*/
getKnownDomainIds(): number[] {
return this.getKnownChainNames().map(this.getDomainId);
override extendChainMetadata<NewExt = {}>(
additionalMetadata: ChainMap<NewExt>,
): MultiProvider<MetaExt & NewExt> {
const newMetadata = super.extendChainMetadata(additionalMetadata).metadata;
return new MultiProvider(newMetadata, this.options);
}
/**
@ -393,7 +246,7 @@ export class MultiProvider {
throwIfNotSubset = false,
): {
intersection: ChainName[];
multiProvider: MultiProvider;
multiProvider: MultiProvider<MetaExt>;
} {
const ownChains = this.getKnownChainNames();
const intersection: ChainName[] = [];
@ -424,88 +277,6 @@ export class MultiProvider {
return { intersection, multiProvider };
}
/**
* Get chain names excluding given chain name
*/
getRemoteChains(name: ChainName): ChainName[] {
return exclude(name, this.getKnownChainNames());
}
/**
* Get an RPC URL for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
getRpcUrl(chainNameOrId: ChainName | number): string {
const { rpcUrls } = this.getChainMetadata(chainNameOrId);
if (!rpcUrls?.length || !rpcUrls[0].http)
throw new Error(`No RPC URl configured for ${chainNameOrId}`);
return rpcUrls[0].http;
}
/**
* Get a block explorer URL for a given chain name, chain id, or domain id
*/
tryGetExplorerUrl(chainNameOrId: ChainName | number): string | null {
const explorers = this.tryGetChainMetadata(chainNameOrId)?.blockExplorers;
if (!explorers?.length) return null;
return explorers[0].url;
}
/**
* Get a block explorer URL for a given chain name, chain id, or domain id
* @throws if chain's metadata or block explorer data has no been set
*/
getExplorerUrl(chainNameOrId: ChainName | number): string {
const url = this.tryGetExplorerUrl(chainNameOrId);
if (!url) throw new Error(`No explorer url set for ${chainNameOrId}`);
return url;
}
/**
* Get a block explorer's API URL for a given chain name, chain id, or domain id
*/
tryGetExplorerApiUrl(chainNameOrId: ChainName | number): string | null {
const explorers = this.tryGetChainMetadata(chainNameOrId)?.blockExplorers;
if (!explorers?.length || !explorers[0].apiUrl) return null;
const { apiUrl, apiKey } = explorers[0];
if (!apiKey) return apiUrl;
const url = new URL(apiUrl);
url.searchParams.set('apikey', apiKey);
return url.toString();
}
/**
* Get a block explorer API URL for a given chain name, chain id, or domain id
* @throws if chain's metadata or block explorer data has no been set
*/
getExplorerApiUrl(chainNameOrId: ChainName | number): string {
const url = this.tryGetExplorerApiUrl(chainNameOrId);
if (!url) throw new Error(`No explorer api url set for ${chainNameOrId}`);
return url;
}
/**
* Get a block explorer URL for given chain's tx
*/
tryGetExplorerTxUrl(
chainNameOrId: ChainName | number,
response: { hash: string },
): string | null {
const baseUrl = this.tryGetExplorerUrl(chainNameOrId);
return baseUrl ? `${baseUrl}/tx/${response.hash}` : null;
}
/**
* Get a block explorer URL for given chain's tx
* @throws if chain's metadata or block explorer data has no been set
*/
getExplorerTxUrl(
chainNameOrId: ChainName | number,
response: { hash: string },
): string {
return `${this.getExplorerUrl(chainNameOrId)}/tx/${response.hash}`;
}
/**
* Get a block explorer URL for given chain's address
*/
@ -619,17 +390,6 @@ export class MultiProvider {
return this.handleTx(chainNameOrId, response);
}
/**
* Run given function on all known chains
*/
mapKnownChains<Output>(fn: (n: ChainName) => Output): ChainMap<Output> {
const result: ChainMap<Output> = {};
for (const chain of this.getKnownChainNames()) {
result[chain] = fn(chain);
}
return result;
}
/**
* Creates a MultiProvider using the given signer for all test networks
*/

@ -0,0 +1,134 @@
import type {
Connection,
Transaction as SolTransaction,
} from '@solana/web3.js';
import type {
Contract as EV5Contract,
providers as EV5Providers,
PopulatedTransaction as EV5Transaction,
} from 'ethers';
// import type { Contract as Ev6Contract, Provider as Ev6Provider } from 'ethers6';
import type {
GetContractReturnType,
PublicClient,
Transaction as VTransaction,
} from 'viem';
export enum ProviderType {
EthersV5 = 'ethers-v5',
// EthersV6 = 'ethers-v6', Disabled for now to simplify build tooling
Viem = 'viem',
SolanaWeb3 = 'solana-web3',
}
export type ProviderMap<Value> = Partial<Record<ProviderType, Value>>;
/**
* Providers with discriminated union of type
*/
interface TypedProviderBase<T> {
type: ProviderType;
provider: T;
}
export interface EthersV5Provider
extends TypedProviderBase<EV5Providers.Provider> {
type: ProviderType.EthersV5;
provider: EV5Providers.Provider;
}
// export interface EthersV6Provider extends TypedProviderBase<Ev6Provider> {
// type: ProviderType.EthersV6;
// provider: Ev6Provider;
// }
export interface ViemProvider extends TypedProviderBase<PublicClient> {
type: ProviderType.Viem;
provider: PublicClient;
}
export interface SolanaWeb3Provider extends TypedProviderBase<Connection> {
type: ProviderType.SolanaWeb3;
provider: Connection;
}
export type TypedProvider =
| EthersV5Provider
// | EthersV6Provider
| ViemProvider
| SolanaWeb3Provider;
/**
* Contracts with discriminated union of provider type
*/
interface TypedContractBase<T> {
type: ProviderType;
contract: T;
}
export interface EthersV5Contract extends TypedContractBase<EV5Contract> {
type: ProviderType.EthersV5;
transaction: EV5Contract;
}
// export interface EthersV6Contract extends TypedContractBase<Ev6Contract> {
// type: ProviderType.EthersV6;
// contract: Ev6Contract;
// }
export interface ViemContract extends TypedContractBase<GetContractReturnType> {
type: ProviderType.Viem;
transaction: GetContractReturnType;
}
export interface SolanaWeb3Contract extends TypedContractBase<never> {
type: ProviderType.SolanaWeb3;
// Contract concept doesn't exist in @solana/web3.js
transaction: never;
}
export type TypedContract =
| EthersV5Contract
// | EthersV6Contract
| ViemContract
| SolanaWeb3Contract;
/**
* Transactions with discriminated union of provider type
*/
interface TypedTransactionBase<T> {
type: ProviderType;
transaction: T;
}
export interface EthersV5Transaction
extends TypedTransactionBase<EV5Transaction> {
type: ProviderType.EthersV5;
transaction: EV5Transaction;
}
// export interface EthersV6Transaction extends TypedTransactionBase<Ev6Transaction> {
// type: ProviderType.EthersV6;
// contract: Ev6Transaction;
// }
export interface ViemTransaction extends TypedTransactionBase<VTransaction> {
type: ProviderType.Viem;
transaction: VTransaction;
}
export interface SolanaWeb3Transaction
extends TypedTransactionBase<SolTransaction> {
type: ProviderType.SolanaWeb3;
// Transaction concept doesn't exist in @solana/web3.js
transaction: SolTransaction;
}
export type TypedTransaction =
| EthersV5Transaction
// | EthersV6Transaction
| ViemTransaction
| SolanaWeb3Transaction;

@ -0,0 +1,131 @@
import { Connection } from '@solana/web3.js';
import { providers } from 'ethers';
import { createPublicClient, http } from 'viem';
import { ProtocolType, isNumeric } from '@hyperlane-xyz/utils';
import { ChainMetadata } from '../metadata/chainMetadataTypes';
import {
EthersV5Provider,
ProviderType,
SolanaWeb3Provider,
TypedProvider,
ViemProvider,
} from './ProviderType';
import { RetryJsonRpcProvider, RetryProviderOptions } from './RetryProvider';
export type ProviderBuilderFn<P> = (
rpcUrls: ChainMetadata['rpcUrls'],
network: number | string,
retryOverride?: RetryProviderOptions,
) => P;
export type TypedProviderBuilderFn = ProviderBuilderFn<TypedProvider>;
export const DEFAULT_RETRY_OPTIONS: RetryProviderOptions = {
maxRequests: 3,
baseRetryMs: 250,
};
export function defaultEthersV5ProviderBuilder(
rpcUrls: ChainMetadata['rpcUrls'],
network: number | string,
retryOverride?: RetryProviderOptions,
): EthersV5Provider {
const createProvider = (r: ChainMetadata['rpcUrls'][number]) => {
const retry = r.retry || retryOverride;
return retry
? new RetryJsonRpcProvider(retry, r.http, network)
: new providers.StaticJsonRpcProvider(r.http, network);
};
let provider: providers.Provider;
if (rpcUrls.length > 1) {
provider = new providers.FallbackProvider(rpcUrls.map(createProvider), 1);
} else if (rpcUrls.length === 1) {
provider = createProvider(rpcUrls[0]);
} else {
throw new Error('No RPC URLs provided');
}
return { type: ProviderType.EthersV5, provider };
}
// export function defaultEthersV6ProviderBuilder(
// rpcUrls: ChainMetadata['rpcUrls'],
// network: number | string,
// ): EthersV6Provider {
// // TODO add support for retry providers here
// if (!rpcUrls.length) throw new Error('No RPC URLs provided');
// return {
// type: ProviderType.EthersV6,
// provider: new Ev6JsonRpcProvider(rpcUrls[0].http, network),
// };
// }
export function defaultViemProviderBuilder(
rpcUrls: ChainMetadata['rpcUrls'],
network: number | string,
): ViemProvider {
if (!rpcUrls.length) throw new Error('No RPC URLs provided');
if (!isNumeric(network)) throw new Error('Viem requires a numeric network');
const id = parseInt(network.toString(), 10);
const name = network.toString(); // TODO get more descriptive name
const url = rpcUrls[0].http;
const client = createPublicClient({
chain: {
id,
name,
network: name,
nativeCurrency: { name: '', symbol: '', decimals: 0 },
rpcUrls: { default: { http: [url] }, public: { http: [url] } },
},
transport: http(rpcUrls[0].http),
});
return { type: ProviderType.Viem, provider: client };
}
export function defaultSolProviderBuilder(
rpcUrls: ChainMetadata['rpcUrls'],
_network: number | string,
): SolanaWeb3Provider {
if (!rpcUrls.length) throw new Error('No RPC URLs provided');
return {
type: ProviderType.SolanaWeb3,
provider: new Connection(rpcUrls[0].http, 'confirmed'),
};
}
export function defaultFuelProviderBuilder(
rpcUrls: ChainMetadata['rpcUrls'],
_network: number | string,
): EthersV5Provider {
if (!rpcUrls.length) throw new Error('No RPC URLs provided');
throw new Error('TODO fuel support');
}
// Kept for backwards compatibility
export function defaultProviderBuilder(
rpcUrls: ChainMetadata['rpcUrls'],
_network: number | string,
): providers.Provider {
return defaultEthersV5ProviderBuilder(rpcUrls, _network).provider;
}
export type ProviderBuilderMap = Record<
ProviderType,
ProviderBuilderFn<TypedProvider>
>;
export const defaultProviderBuilderMap: ProviderBuilderMap = {
[ProviderType.EthersV5]: defaultEthersV5ProviderBuilder,
// [ProviderType.EthersV6]: defaultEthersV6ProviderBuilder,
[ProviderType.Viem]: defaultViemProviderBuilder,
[ProviderType.SolanaWeb3]: defaultSolProviderBuilder,
};
export const protocolToDefaultProviderBuilder: Record<
ProtocolType,
ProviderBuilderFn<TypedProvider>
> = {
[ProtocolType.Ethereum]: defaultEthersV5ProviderBuilder,
[ProtocolType.Sealevel]: defaultSolProviderBuilder,
[ProtocolType.Fuel]: defaultFuelProviderBuilder,
};

@ -5,7 +5,7 @@ import {
HyperlaneContracts,
HyperlaneContractsMap,
HyperlaneFactories,
} from '../contracts';
} from '../contracts/types';
import { ChainMap } from '../types';
import { HyperlaneRouterDeployer } from './HyperlaneRouterDeployer';

@ -2,7 +2,7 @@ import { ethers } from 'ethers';
import { addressToBytes32, assert, eqAddress } from '@hyperlane-xyz/utils';
import { HyperlaneFactories } from '../contracts';
import { HyperlaneFactories } from '../contracts/types';
import { HyperlaneAppChecker } from '../deploy/HyperlaneAppChecker';
import { ChainName } from '../types';

@ -13,12 +13,12 @@ import {
objMerge,
} from '@hyperlane-xyz/utils';
import { filterOwnableContracts } from '../contracts/contracts';
import {
HyperlaneContracts,
HyperlaneContractsMap,
HyperlaneFactories,
filterOwnableContracts,
} from '../contracts';
} from '../contracts/types';
import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer';
import { moduleCanCertainlyVerify } from '../ism/HyperlaneIsmFactory';
import { RouterConfig } from '../router/types';

@ -0,0 +1,22 @@
import { expect } from 'chai';
import { Address } from '@hyperlane-xyz/utils';
import { Chains } from '../consts/chains';
import { MultiProtocolProvider } from '../providers/MultiProtocolProvider';
import { MultiProtocolRouterApp } from './MultiProtocolRouterApps';
import { EvmRouterAdapter } from './adapters/EvmRouterAdapter';
describe('MultiProtocolRouterApp', () => {
describe('constructs', () => {
const multiProvider = new MultiProtocolProvider<{ router: Address }>();
it('creates an app class', async () => {
const app = new MultiProtocolRouterApp(multiProvider);
expect(app).to.be.instanceOf(MultiProtocolRouterApp);
const ethAdapter = app.adapter(Chains.ethereum);
expect(ethAdapter).to.be.instanceOf(EvmRouterAdapter);
expect(!!ethAdapter.remoteRouter).to.be.true;
});
});
});

@ -0,0 +1,64 @@
import { Address, Domain, ProtocolType } from '@hyperlane-xyz/utils';
import { MultiProtocolApp } from '../app/MultiProtocolApp';
import { ChainMap, ChainName } from '../types';
import {
EvmGasRouterAdapter,
EvmRouterAdapter,
} from './adapters/EvmRouterAdapter';
import {
SealevelGasRouterAdapter,
SealevelRouterAdapter,
} from './adapters/SealevelRouterAdapter';
import { IGasRouterAdapter, IRouterAdapter } from './adapters/types';
import { RouterAddress } from './types';
export { Router } from '@hyperlane-xyz/core';
export class MultiProtocolRouterApp<
ContractAddrs extends RouterAddress = RouterAddress,
IAdapterApi extends IRouterAdapter = IRouterAdapter,
> extends MultiProtocolApp<ContractAddrs, IAdapterApi> {
public override readonly protocolToAdapter = {
[ProtocolType.Ethereum]: EvmRouterAdapter,
[ProtocolType.Sealevel]: SealevelRouterAdapter,
};
router(chain: ChainName): Address {
return this.metadata(chain).router;
}
interchainSecurityModules(): Promise<ChainMap<Address>> {
return this.adapterMap((chain, adapter) =>
adapter.interchainSecurityModule(chain),
);
}
owners(): Promise<ChainMap<Address>> {
return this.adapterMap((chain, adapter) => adapter.owner(chain));
}
remoteRouters(
origin: ChainName,
): Promise<Array<{ domain: Domain; address: Address }>> {
return this.adapter(origin).remoteRouters(origin);
}
}
export class MultiProtocolGasRouterApp<
ContractAddrs extends RouterAddress = RouterAddress,
IAdapterApi extends IGasRouterAdapter = IGasRouterAdapter,
> extends MultiProtocolRouterApp<ContractAddrs, IAdapterApi> {
public override readonly protocolToAdapter = {
[ProtocolType.Ethereum]: EvmGasRouterAdapter,
[ProtocolType.Sealevel]: SealevelGasRouterAdapter,
};
async quoteGasPayment(
origin: ChainName,
destination: ChainName,
): Promise<string> {
return this.adapter(origin).quoteGasPayment(origin, destination);
}
}

@ -7,7 +7,7 @@ import {
} from '@hyperlane-xyz/core';
import { eqAddress } from '@hyperlane-xyz/utils';
import { HyperlaneContracts } from '../contracts';
import { HyperlaneContracts } from '../contracts/types';
import { ChainName } from '../types';
import { HyperlaneRouterDeployer } from './HyperlaneRouterDeployer';

@ -3,8 +3,8 @@ import type { BigNumber } from 'ethers';
import { GasRouter, Router } from '@hyperlane-xyz/core';
import { Address, objMap, promiseObjAll } from '@hyperlane-xyz/utils';
import { HyperlaneApp } from '../HyperlaneApp';
import { HyperlaneContracts, HyperlaneFactories } from '../contracts';
import { HyperlaneApp } from '../app/HyperlaneApp';
import { HyperlaneContracts, HyperlaneFactories } from '../contracts/types';
import { ChainMap, ChainName } from '../types';
export { Router } from '@hyperlane-xyz/core';
@ -14,17 +14,19 @@ export abstract class RouterApp<
> extends HyperlaneApp<Factories> {
abstract router(contracts: HyperlaneContracts<Factories>): Router;
getSecurityModules = (): Promise<ChainMap<Address>> =>
promiseObjAll(
getSecurityModules(): Promise<ChainMap<Address>> {
return promiseObjAll(
objMap(this.chainMap, (_, contracts) =>
this.router(contracts).interchainSecurityModule(),
),
);
}
getOwners = (): Promise<ChainMap<Address>> =>
promiseObjAll(
getOwners(): Promise<ChainMap<Address>> {
return promiseObjAll(
objMap(this.chainMap, (_, contracts) => this.router(contracts).owner()),
);
}
}
export abstract class GasRouterApp<

@ -0,0 +1,89 @@
import {
GasRouter,
GasRouter__factory,
Router,
Router__factory,
} from '@hyperlane-xyz/core';
import { Address, Domain, bytes32ToAddress } from '@hyperlane-xyz/utils';
import { BaseEvmAdapter } from '../../app/MultiProtocolApp';
import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider';
import { ChainName } from '../../types';
import { RouterAddress } from '../types';
import { IGasRouterAdapter, IRouterAdapter } from './types';
export class EvmRouterAdapter<
ContractAddrs extends RouterAddress = RouterAddress,
>
extends BaseEvmAdapter<ContractAddrs>
implements IRouterAdapter<ContractAddrs>
{
constructor(
public readonly multiProvider: MultiProtocolProvider<ContractAddrs>,
) {
super(multiProvider);
}
interchainSecurityModule(chain: ChainName): Promise<Address> {
return this.getConnectedContract(chain).interchainSecurityModule();
}
owner(chain: ChainName): Promise<Address> {
return this.getConnectedContract(chain).owner();
}
remoteDomains(originChain: ChainName): Promise<Domain[]> {
return this.getConnectedContract(originChain).domains();
}
async remoteRouter(
originChain: ChainName,
remoteDomain: Domain,
): Promise<Address> {
const routerAddressesAsBytes32 = await this.getConnectedContract(
originChain,
).routers(remoteDomain);
return bytes32ToAddress(routerAddressesAsBytes32);
}
async remoteRouters(
originChain: ChainName,
): Promise<Array<{ domain: Domain; address: Address }>> {
const domains = await this.remoteDomains(originChain);
const routers: Address[] = await Promise.all(
domains.map((d) => this.remoteRouter(originChain, d)),
);
return domains.map((d, i) => ({ domain: d, address: routers[i] }));
}
getConnectedContract(chain: ChainName): Router {
const address = this.multiProvider.getChainMetadata(chain).router;
const provider = this.multiProvider.getEthersV5Provider(chain);
return Router__factory.connect(address, provider);
}
}
export class EvmGasRouterAdapter<
ContractAddrs extends RouterAddress = RouterAddress,
>
extends EvmRouterAdapter<ContractAddrs>
implements IGasRouterAdapter<ContractAddrs>
{
async quoteGasPayment(
origin: ChainName,
destination: ChainName,
): Promise<string> {
const destDomain = this.multiProvider.getDomainId(destination);
const amount = await this.getConnectedContract(origin).quoteGasPayment(
destDomain,
);
return amount.toString();
}
override getConnectedContract(chain: ChainName): GasRouter {
const address = this.multiProvider.getChainMetadata(chain).router;
const provider = this.multiProvider.getEthersV5Provider(chain);
return GasRouter__factory.connect(address, provider);
}
}

@ -0,0 +1,30 @@
import { deserializeUnchecked } from 'borsh';
import { expect } from 'chai';
import {
SealevelAccountDataWrapper,
SealevelTokenDataSchema,
} from './SealevelRouterAdapter';
// Copied from the warp token router program on Solana devnet
const RAW_ACCOUNT_INFO =
'01fe3a280e8466d26bc4e1a5d3d17e73f7b307c082156dd0ffbf8c5f9ae75506d6f1e9fd9f0b53dbdafc27f42dc88acef25b6cea358c214ced8144165b842148eddbfe0606014d8d3746cf8844ccba8c2f0662b0f1be404229c75c88c93985bd07881eea18f5000200000069a80000000000000000000000000000a97f4eacbc363f82d25a540440afc6f78920299b742d627aa7b75e9d68d3a8f9a84720e039cbb1f9ef1c515f2b3aa99619ecb6f07792bde406ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9e92839550965ffd4d64acaaf46d45df7318e5b4f57c90c487d60625d829b837b36b9e506fe298843d50359492a8b4ce739c8fa85da27ac0bb53a1ad5fa66fcc9fffd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
const OWNER_PUB_KEY = '6DjHX6Ezjpq3zZMZ8KsqyoFYo1zPSDoiZmLLkxD4xKXS';
describe('SealevelRouterAdapter', () => {
describe('account info', () => {
it('correctly deserializes router account info', () => {
const rawData = Buffer.from(RAW_ACCOUNT_INFO, 'hex');
const accountData = deserializeUnchecked(
SealevelTokenDataSchema,
SealevelAccountDataWrapper,
rawData,
);
expect(accountData.initialized).to.eql(1);
expect(accountData.data.decimals).to.eql(6);
expect(accountData.data.owner_pub_key?.toBase58()).to.eql(OWNER_PUB_KEY);
expect(accountData.data.remote_router_pubkeys.size).to.eql(2);
});
});
});

@ -0,0 +1,197 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { PublicKey } from '@solana/web3.js';
import { deserializeUnchecked } from 'borsh';
import { Address, Domain } from '@hyperlane-xyz/utils';
import { BaseSealevelAdapter } from '../../app/MultiProtocolApp';
import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider';
import { ChainName } from '../../types';
import { RouterAddress } from '../types';
import { IGasRouterAdapter, IRouterAdapter } from './types';
// Hyperlane Token Borsh Schema
export class SealevelAccountDataWrapper {
initialized!: boolean;
data!: SealevelTokenData;
constructor(public readonly fields: any) {
Object.assign(this, fields);
}
}
// Should match https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/trevor/sealevel-validator-rebase/rust/sealevel/libraries/hyperlane-sealevel-token/src/accounts.rs#L21
export class SealevelTokenData {
/// The bump seed for this PDA.
bump!: number;
/// The address of the mailbox contract.
mailbox!: Uint8Array;
mailbox_pubkey!: PublicKey;
/// The Mailbox process authority specific to this program as the recipient.
mailbox_process_authority!: Uint8Array;
mailbox_process_authority_pubkey!: PublicKey;
/// The dispatch authority PDA's bump seed.
dispatch_authority_bump!: number;
/// The decimals of the local token.
decimals!: number;
/// The decimals of the remote token.
remote_decimals!: number;
/// Access control owner.
owner?: Uint8Array;
owner_pub_key?: PublicKey;
/// The interchain security module.
interchain_security_module?: Uint8Array;
interchain_security_module_pubkey?: PublicKey;
/// Remote routers.
remote_routers?: Map<Domain, Uint8Array>;
remote_router_pubkeys: Map<Domain, PublicKey>;
constructor(public readonly fields: any) {
Object.assign(this, fields);
this.mailbox_pubkey = new PublicKey(this.mailbox);
this.mailbox_pubkey = new PublicKey(this.mailbox_process_authority);
this.owner_pub_key = this.owner ? new PublicKey(this.owner) : undefined;
this.interchain_security_module_pubkey = this.interchain_security_module
? new PublicKey(this.interchain_security_module)
: undefined;
this.remote_router_pubkeys = new Map<number, PublicKey>();
if (this.remote_routers) {
for (const [k, v] of this.remote_routers.entries()) {
this.remote_router_pubkeys.set(k, new PublicKey(v));
}
}
}
}
export const SealevelTokenDataSchema = new Map<any, any>([
[
SealevelAccountDataWrapper,
{
kind: 'struct',
fields: [
['initialized', 'u8'],
['data', SealevelTokenData],
],
},
],
[
SealevelTokenData,
{
kind: 'struct',
fields: [
['bump', 'u8'],
['mailbox', [32]],
['mailbox_process_authority', [32]],
['dispatch_authority_bump', 'u8'],
['decimals', 'u8'],
['remote_decimals', 'u8'],
['owner', { kind: 'option', type: [32] }],
['interchain_security_module', { kind: 'option', type: [32] }],
['remote_routers', { kind: 'map', key: 'u32', value: [32] }],
],
},
],
]);
export class SealevelRouterAdapter<
ContractAddrs extends RouterAddress = RouterAddress,
>
extends BaseSealevelAdapter<ContractAddrs>
implements IRouterAdapter<ContractAddrs>
{
constructor(
public readonly multiProvider: MultiProtocolProvider<ContractAddrs>,
) {
super(multiProvider);
}
async interchainSecurityModule(chain: ChainName): Promise<Address> {
const routerAccountInfo = await this.getRouterAccountInfo(chain);
if (!routerAccountInfo.interchain_security_module_pubkey)
throw new Error(`No ism found for router on ${chain}`);
return routerAccountInfo.interchain_security_module_pubkey.toBase58();
}
async owner(chain: ChainName): Promise<Address> {
const routerAccountInfo = await this.getRouterAccountInfo(chain);
if (!routerAccountInfo.owner_pub_key)
throw new Error(`No owner found for router on ${chain}`);
return routerAccountInfo.owner_pub_key.toBase58();
}
async remoteDomains(originChain: ChainName): Promise<Domain[]> {
const routers = await this.remoteRouters(originChain);
return routers.map((router) => router.domain);
}
async remoteRouter(
originChain: ChainName,
remoteDomain: Domain,
): Promise<Address> {
const routers = await this.remoteRouters(originChain);
const addr = routers.find(
(router) => router.domain === remoteDomain,
)?.address;
if (!addr) throw new Error(`No router found for ${remoteDomain}`);
return addr;
}
async remoteRouters(
originChain: ChainName,
): Promise<Array<{ domain: Domain; address: Address }>> {
const routerAccountInfo = await this.getRouterAccountInfo(originChain);
const domainToPubKey = routerAccountInfo.remote_router_pubkeys;
return Array.from(domainToPubKey.entries()).map(([domain, pubKey]) => ({
domain,
address: pubKey.toBase58(),
}));
}
// TODO this incorrectly assumes all sealevel routers will have the TokenRouter's data schema
// This will need to change when other types of routers are supported
async getRouterAccountInfo(chain: ChainName): Promise<SealevelTokenData> {
const address = this.multiProvider.getChainMetadata(chain).router;
const connection = this.multiProvider.getSolanaWeb3Provider(chain);
const msgRecipientPda = this.deriveMessageRecipientPda(address);
const accountInfo = await connection.getAccountInfo(msgRecipientPda);
if (!accountInfo)
throw new Error(
`No account info found for ${msgRecipientPda.toBase58()}}`,
);
const accountData = deserializeUnchecked(
SealevelTokenDataSchema,
SealevelAccountDataWrapper,
accountInfo.data,
);
return accountData.data;
}
// Should match https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/trevor/sealevel-validator-rebase/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs#LL49C1-L53C30
deriveMessageRecipientPda(routerAddress: Address): PublicKey {
const [pda] = PublicKey.findProgramAddressSync(
[
Buffer.from('hyperlane_message_recipient'),
Buffer.from('-'),
Buffer.from('handle'),
Buffer.from('-'),
Buffer.from('account_metas'),
],
new PublicKey(routerAddress),
);
return pda;
}
}
export class SealevelGasRouterAdapter<
ContractAddrs extends RouterAddress = RouterAddress,
>
extends SealevelRouterAdapter<ContractAddrs>
implements IGasRouterAdapter<ContractAddrs>
{
async quoteGasPayment(
_origin: ChainName,
_destination: ChainName,
): Promise<string> {
throw new Error('Gas payments not yet supported for sealevel');
}
}

@ -0,0 +1,29 @@
import { Address, Domain } from '@hyperlane-xyz/utils';
import { BaseAppAdapter } from '../../app/MultiProtocolApp';
import { ChainName } from '../../types';
import { RouterAddress } from '../types';
export interface IRouterAdapter<
ContractAddrs extends RouterAddress = RouterAddress,
> extends BaseAppAdapter<ContractAddrs> {
interchainSecurityModule(chain: ChainName): Promise<Address>;
owner: (chain: ChainName) => Promise<Address>;
remoteDomains(originChain: ChainName): Promise<Domain[]>;
remoteRouter: (
originChain: ChainName,
remoteDomain: Domain,
) => Promise<Address>;
remoteRouters: (
originChain: ChainName,
) => Promise<Array<{ domain: Domain; address: Address }>>;
}
export interface IGasRouterAdapter<
ContractAddrs extends RouterAddress = RouterAddress,
> extends IRouterAdapter<ContractAddrs> {
quoteGasPayment: (
origin: ChainName,
destination: ChainName,
) => Promise<string>;
}

@ -5,11 +5,15 @@ import {
} from '@hyperlane-xyz/core';
import type { Address } from '@hyperlane-xyz/utils';
import { HyperlaneFactories } from '../contracts';
import { HyperlaneFactories } from '../contracts/types';
import { UpgradeConfig } from '../deploy/proxy';
import { CheckerViolation } from '../deploy/types';
import { IsmConfig } from '../ism/types';
export type RouterAddress = {
router: Address;
};
export type OwnableConfig = {
owner: Address;
};

@ -7,7 +7,7 @@ import {
import { Address, objMap } from '@hyperlane-xyz/utils';
import { chainMetadata } from '../consts/chainMetadata';
import { HyperlaneContractsMap } from '../contracts';
import { HyperlaneContractsMap } from '../contracts/types';
import { CoreFactories } from '../core/contracts';
import { CoreConfig } from '../core/types';
import { IgpFactories } from '../gas/contracts';

@ -46,9 +46,7 @@ export class MultiGeneric<Value> {
}
}
map<Output>(
fn: (n: ChainName, dc: Value) => Output,
): Record<ChainName, Output> {
map<Output>(fn: (n: ChainName, dc: Value) => Output): ChainMap<Output> {
const entries: [ChainName, Output][] = [];
for (const chain of this.chains()) {
entries.push([chain, fn(chain, this.chainMap[chain])]);

@ -1,31 +1,5 @@
{
"env": {
"node": true,
"browser": true,
"es2021": true
},
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module",
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"no-eval": ["error"],
"no-ex-assign": ["error"],
"no-constant-condition": ["off"],
"@typescript-eslint/ban-ts-comment": ["off"],
"@typescript-eslint/explicit-module-boundary-types": ["off"],
"@typescript-eslint/no-explicit-any": ["off"],
"@typescript-eslint/no-floating-promises": ["off"],
"@typescript-eslint/no-non-null-assertion": ["off"],
"@typescript-eslint/no-require-imports": ["warn"]
"no-console": ["off"]
}
}

@ -16,8 +16,8 @@
"@typechain/ethers-v5": "10.0.0",
"@typechain/hardhat": "^6.0.0",
"@types/mocha": "^9.1.0",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"chai": "^4.3.0",
"eslint": "^8.16.0",
"eslint-config-prettier": "^8.5.0",
@ -31,7 +31,7 @@
"solidity-coverage": "^0.8.3",
"ts-node": "^10.8.0",
"typechain": "8.0.0",
"typescript": "^4.7.2"
"typescript": "^5.1.6"
},
"files": [
"/dist",

@ -1,27 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"incremental": false,
"lib": ["es2015", "es5", "dom"],
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": false,
"noImplicitReturns": true,
"noUnusedLocals": true,
"preserveSymlinks": true,
"preserveWatchOutput": true,
"pretty": false,
"sourceMap": true,
"target": "es6",
"strict": true,
"resolveJsonModule": true,
"outDir": "./dist",
"rootDir": "./src",
"outDir": "./dist/",
"rootDir": "./src/"
},
"exclude": [
"./node_modules/",

@ -10,7 +10,7 @@
"devDependencies": {
"chai": "^4.3.0",
"prettier": "^2.4.1",
"typescript": "^4.7.2"
"typescript": "^5.1.6"
},
"homepage": "https://www.hyperlane.xyz",
"keywords": [

@ -5,6 +5,13 @@ __metadata:
version: 6
cacheKey: 8
"@adraffy/ens-normalize@npm:1.9.0":
version: 1.9.0
resolution: "@adraffy/ens-normalize@npm:1.9.0"
checksum: a8d47f85db7a0bba01227fae8781a3245a4517875503d6848a47ca29e7f7da271742a2f93b55afc1b9201e74eda1fd1f1e6e79e70f967359fd7f6ec3d45bf243
languageName: node
linkType: hard
"@arbitrum/sdk@npm:^3.0.0":
version: 3.0.0
resolution: "@arbitrum/sdk@npm:3.0.0"
@ -3938,7 +3945,7 @@ __metadata:
solidity-coverage: ^0.8.3
ts-generator: ^0.1.1
typechain: ^8.1.1
typescript: ^4.7.2
typescript: ^5.1.6
languageName: unknown
linkType: soft
@ -3954,8 +3961,8 @@ __metadata:
"@typechain/ethers-v5": 10.0.0
"@typechain/hardhat": ^6.0.0
"@types/mocha": ^9.1.0
"@typescript-eslint/eslint-plugin": ^5.27.0
"@typescript-eslint/parser": ^5.27.0
"@typescript-eslint/eslint-plugin": ^5.62.0
"@typescript-eslint/parser": ^5.62.0
chai: ^4.3.0
eslint: ^8.16.0
eslint-config-prettier: ^8.5.0
@ -3970,7 +3977,7 @@ __metadata:
solidity-coverage: ^0.8.3
ts-node: ^10.8.0
typechain: 8.0.0
typescript: ^4.7.2
typescript: ^5.1.6
languageName: unknown
linkType: soft
@ -3988,8 +3995,8 @@ __metadata:
"@typechain/ethers-v5": 10.0.0
"@typechain/hardhat": ^6.0.0
"@types/mocha": ^9.1.0
"@typescript-eslint/eslint-plugin": ^5.27.0
"@typescript-eslint/parser": ^5.27.0
"@typescript-eslint/eslint-plugin": ^5.62.0
"@typescript-eslint/parser": ^5.62.0
chai: ^4.3.0
eslint: ^8.16.0
eslint-config-prettier: ^8.5.0
@ -4004,7 +4011,7 @@ __metadata:
solidity-coverage: ^0.8.3
ts-node: ^10.8.0
typechain: 8.0.0
typescript: ^4.7.2
typescript: ^5.1.6
languageName: unknown
linkType: soft
@ -4045,7 +4052,7 @@ __metadata:
prom-client: ^14.0.1
prompts: ^2.4.2
ts-node: ^10.8.0
typescript: ^4.7.2
typescript: ^5.1.6
yargs: ^17.4.1
languageName: unknown
linkType: soft
@ -4055,8 +4062,8 @@ __metadata:
resolution: "@hyperlane-xyz/monorepo@workspace:."
dependencies:
"@trivago/prettier-plugin-sort-imports": ^3.2.0
"@typescript-eslint/eslint-plugin": ^5.27.0
"@typescript-eslint/parser": ^5.27.0
"@typescript-eslint/eslint-plugin": ^5.62.0
"@typescript-eslint/parser": ^5.62.0
eslint: ^8.16.0
eslint-config-prettier: ^8.5.0
husky: ^8.0.0
@ -4073,9 +4080,11 @@ __metadata:
"@hyperlane-xyz/utils": 1.4.2
"@nomiclabs/hardhat-ethers": ^2.2.1
"@nomiclabs/hardhat-waffle": ^2.0.3
"@solana/web3.js": ^1.78.0
"@types/coingecko-api": ^1.0.10
"@types/debug": ^4.1.7
"@types/node": ^16.9.1
"@types/ws": ^8.5.5
"@wagmi/chains": ^0.2.6
chai: ^4.3.6
coingecko-api: ^1.0.10
@ -4091,7 +4100,8 @@ __metadata:
prettier: ^2.4.1
sinon: ^13.0.2
ts-node: ^10.8.0
typescript: ^4.7.2
typescript: ^5.1.6
viem: ^1.3.1
zod: ^3.21.2
languageName: unknown
linkType: soft
@ -4105,7 +4115,7 @@ __metadata:
chai: ^4.3.0
ethers: ^5.7.2
prettier: ^2.4.1
typescript: ^4.7.2
typescript: ^5.1.6
languageName: unknown
linkType: soft
@ -4285,6 +4295,15 @@ __metadata:
languageName: node
linkType: hard
"@noble/curves@npm:1.0.0, @noble/curves@npm:~1.0.0":
version: 1.0.0
resolution: "@noble/curves@npm:1.0.0"
dependencies:
"@noble/hashes": 1.3.0
checksum: 6bcef44d626c640dc8961819d68dd67dffb907e3b973b7c27efe0ecdd9a5c6ce62c7b9e3dfc930c66605dced7f1ec0514d191c09a2ce98d6d52b66e3315ffa79
languageName: node
linkType: hard
"@noble/curves@npm:^1.0.0":
version: 1.1.0
resolution: "@noble/curves@npm:1.1.0"
@ -4301,7 +4320,14 @@ __metadata:
languageName: node
linkType: hard
"@noble/hashes@npm:1.3.1, @noble/hashes@npm:^1.3.0":
"@noble/hashes@npm:1.3.0":
version: 1.3.0
resolution: "@noble/hashes@npm:1.3.0"
checksum: d7ddb6d7c60f1ce1f87facbbef5b724cdea536fc9e7f59ae96e0fc9de96c8f1a2ae2bdedbce10f7dcc621338dfef8533daa73c873f2b5c87fa1a4e05a95c2e2e
languageName: node
linkType: hard
"@noble/hashes@npm:1.3.1, @noble/hashes@npm:^1.3.0, @noble/hashes@npm:~1.3.0":
version: 1.3.1
resolution: "@noble/hashes@npm:1.3.1"
checksum: 7fdefc0f7a0c1ec27acc6ff88841793e3f93ec4ce6b8a6a12bfc0dd70ae6b7c4c82fe305fdfeda1735d5ad4a9eebe761e6693b3d355689c559e91242f4bc95b1
@ -4960,6 +4986,13 @@ __metadata:
languageName: node
linkType: hard
"@scure/base@npm:~1.1.0":
version: 1.1.1
resolution: "@scure/base@npm:1.1.1"
checksum: b4fc810b492693e7e8d0107313ac74c3646970c198bbe26d7332820886fa4f09441991023ec9aa3a2a51246b74409ab5ebae2e8ef148bbc253da79ac49130309
languageName: node
linkType: hard
"@scure/bip32@npm:1.0.1":
version: 1.0.1
resolution: "@scure/bip32@npm:1.0.1"
@ -4971,6 +5004,17 @@ __metadata:
languageName: node
linkType: hard
"@scure/bip32@npm:1.3.0":
version: 1.3.0
resolution: "@scure/bip32@npm:1.3.0"
dependencies:
"@noble/curves": ~1.0.0
"@noble/hashes": ~1.3.0
"@scure/base": ~1.1.0
checksum: 6eae997f9bdf41fe848134898960ac48e645fa10e63d579be965ca331afd0b7c1b8ebac170770d237ab4099dafc35e5a82995384510025ccf2abe669f85e8918
languageName: node
linkType: hard
"@scure/bip39@npm:1.0.0":
version: 1.0.0
resolution: "@scure/bip39@npm:1.0.0"
@ -4981,6 +5025,16 @@ __metadata:
languageName: node
linkType: hard
"@scure/bip39@npm:1.2.0":
version: 1.2.0
resolution: "@scure/bip39@npm:1.2.0"
dependencies:
"@noble/hashes": ~1.3.0
"@scure/base": ~1.1.0
checksum: 980d761f53e63de04a9e4db840eb13bfb1bd1b664ecb04a71824c12c190f4972fd84146f3ed89b2a8e4c6bd2c17c15f8b592b7ac029e903323b0f9e2dae6916b
languageName: node
linkType: hard
"@sentry/core@npm:5.30.0":
version: 5.30.0
resolution: "@sentry/core@npm:5.30.0"
@ -5548,6 +5602,13 @@ __metadata:
languageName: node
linkType: hard
"@types/semver@npm:^7.3.12":
version: 7.5.0
resolution: "@types/semver@npm:7.5.0"
checksum: 0a64b9b9c7424d9a467658b18dd70d1d781c2d6f033096a6e05762d20ebbad23c1b69b0083b0484722aabf35640b78ccc3de26368bcae1129c87e9df028a22e2
languageName: node
linkType: hard
"@types/sinon-chai@npm:^3.2.3":
version: 3.2.8
resolution: "@types/sinon-chai@npm:3.2.8"
@ -5600,6 +5661,15 @@ __metadata:
languageName: node
linkType: hard
"@types/ws@npm:^8.5.5":
version: 8.5.5
resolution: "@types/ws@npm:8.5.5"
dependencies:
"@types/node": "*"
checksum: d00bf8070e6938e3ccf933010921c6ce78ac3606696ce37a393b27a9a603f7bd93ea64f3c5fa295a2f743575ba9c9a9fdb904af0f5fe2229bf2adf0630386e4a
languageName: node
linkType: hard
"@types/yargs-parser@npm:*":
version: 21.0.0
resolution: "@types/yargs-parser@npm:21.0.0"
@ -5616,17 +5686,18 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:^5.27.0":
version: 5.27.1
resolution: "@typescript-eslint/eslint-plugin@npm:5.27.1"
"@typescript-eslint/eslint-plugin@npm:^5.62.0":
version: 5.62.0
resolution: "@typescript-eslint/eslint-plugin@npm:5.62.0"
dependencies:
"@typescript-eslint/scope-manager": 5.27.1
"@typescript-eslint/type-utils": 5.27.1
"@typescript-eslint/utils": 5.27.1
"@eslint-community/regexpp": ^4.4.0
"@typescript-eslint/scope-manager": 5.62.0
"@typescript-eslint/type-utils": 5.62.0
"@typescript-eslint/utils": 5.62.0
debug: ^4.3.4
functional-red-black-tree: ^1.0.1
graphemer: ^1.4.0
ignore: ^5.2.0
regexpp: ^3.2.0
natural-compare-lite: ^1.4.0
semver: ^7.3.7
tsutils: ^3.21.0
peerDependencies:
@ -5635,42 +5706,43 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: ee00d8d3a7b395e346801b7bf30209e278f06b5c283ad71c03b34db9e2d68a43ca0e292e315fa7e5bf131a8839ff4a24e0ed76c37811d230f97aae7e123d73ea
checksum: fc104b389c768f9fa7d45a48c86d5c1ad522c1d0512943e782a56b1e3096b2cbcc1eea3fcc590647bf0658eef61aac35120a9c6daf979bf629ad2956deb516a1
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:^5.27.0":
version: 5.27.1
resolution: "@typescript-eslint/parser@npm:5.27.1"
"@typescript-eslint/parser@npm:^5.62.0":
version: 5.62.0
resolution: "@typescript-eslint/parser@npm:5.62.0"
dependencies:
"@typescript-eslint/scope-manager": 5.27.1
"@typescript-eslint/types": 5.27.1
"@typescript-eslint/typescript-estree": 5.27.1
"@typescript-eslint/scope-manager": 5.62.0
"@typescript-eslint/types": 5.62.0
"@typescript-eslint/typescript-estree": 5.62.0
debug: ^4.3.4
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
typescript:
optional: true
checksum: 0f1df76142c9d7a6c6dbfc5d19fdee02bbc0e79def6e6df4b126c7eaae1c3a46a3871ad498d4b1fc7ad5cb58d6eb70f020807f600d99c0b9add98441fc12f23b
checksum: d168f4c7f21a7a63f47002e2d319bcbb6173597af5c60c1cf2de046b46c76b4930a093619e69faf2d30214c29ab27b54dcf1efc7046a6a6bd6f37f59a990e752
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:5.27.1":
version: 5.27.1
resolution: "@typescript-eslint/scope-manager@npm:5.27.1"
"@typescript-eslint/scope-manager@npm:5.62.0":
version: 5.62.0
resolution: "@typescript-eslint/scope-manager@npm:5.62.0"
dependencies:
"@typescript-eslint/types": 5.27.1
"@typescript-eslint/visitor-keys": 5.27.1
checksum: 401bf2b46de08ddb80ec9f36df7d58bf5de7837185a472b190b670d421d685743aad4c9fa8a6893f65ba933b822c5d7060c640e87cf0756d7aa56abdd25689cc
"@typescript-eslint/types": 5.62.0
"@typescript-eslint/visitor-keys": 5.62.0
checksum: 6062d6b797fe1ce4d275bb0d17204c827494af59b5eaf09d8a78cdd39dadddb31074dded4297aaf5d0f839016d601032857698b0e4516c86a41207de606e9573
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:5.27.1":
version: 5.27.1
resolution: "@typescript-eslint/type-utils@npm:5.27.1"
"@typescript-eslint/type-utils@npm:5.62.0":
version: 5.62.0
resolution: "@typescript-eslint/type-utils@npm:5.62.0"
dependencies:
"@typescript-eslint/utils": 5.27.1
"@typescript-eslint/typescript-estree": 5.62.0
"@typescript-eslint/utils": 5.62.0
debug: ^4.3.4
tsutils: ^3.21.0
peerDependencies:
@ -5678,23 +5750,23 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 43b7da26ea1bd7d249c45d168ec88f971fb71362bbc21ec4748d73b1ecb43f4ca59f5ed338e8dbc74272ae4ebac1cab87a9b62c0fa616c6f9bd833a212dc8a40
checksum: fc41eece5f315dfda14320be0da78d3a971d650ea41300be7196934b9715f3fe1120a80207551eb71d39568275dbbcf359bde540d1ca1439d8be15e9885d2739
languageName: node
linkType: hard
"@typescript-eslint/types@npm:5.27.1":
version: 5.27.1
resolution: "@typescript-eslint/types@npm:5.27.1"
checksum: 81faa50256ba67c23221273744c51676774fe6a1583698c3a542f3e2fd21ab34a4399019527c9cf7ab4e5a1577272f091d5848d3af937232ddb2dbf558a7c39a
"@typescript-eslint/types@npm:5.62.0":
version: 5.62.0
resolution: "@typescript-eslint/types@npm:5.62.0"
checksum: 48c87117383d1864766486f24de34086155532b070f6264e09d0e6139449270f8a9559cfef3c56d16e3bcfb52d83d42105d61b36743626399c7c2b5e0ac3b670
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:5.27.1":
version: 5.27.1
resolution: "@typescript-eslint/typescript-estree@npm:5.27.1"
"@typescript-eslint/typescript-estree@npm:5.62.0":
version: 5.62.0
resolution: "@typescript-eslint/typescript-estree@npm:5.62.0"
dependencies:
"@typescript-eslint/types": 5.27.1
"@typescript-eslint/visitor-keys": 5.27.1
"@typescript-eslint/types": 5.62.0
"@typescript-eslint/visitor-keys": 5.62.0
debug: ^4.3.4
globby: ^11.1.0
is-glob: ^4.0.3
@ -5703,33 +5775,35 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 59d2a0885be7d54bd86472a446d84930cc52d2690ea432d9164075ea437b3b4206dadd49799764ad0fb68f3e4ebb4e36db9717c7a443d0f3c82d5659e41fbd05
checksum: 3624520abb5807ed8f57b1197e61c7b1ed770c56dfcaca66372d584ff50175225798bccb701f7ef129d62c5989070e1ee3a0aa2d84e56d9524dcf011a2bb1a52
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:5.27.1":
version: 5.27.1
resolution: "@typescript-eslint/utils@npm:5.27.1"
"@typescript-eslint/utils@npm:5.62.0":
version: 5.62.0
resolution: "@typescript-eslint/utils@npm:5.62.0"
dependencies:
"@eslint-community/eslint-utils": ^4.2.0
"@types/json-schema": ^7.0.9
"@typescript-eslint/scope-manager": 5.27.1
"@typescript-eslint/types": 5.27.1
"@typescript-eslint/typescript-estree": 5.27.1
"@types/semver": ^7.3.12
"@typescript-eslint/scope-manager": 5.62.0
"@typescript-eslint/types": 5.62.0
"@typescript-eslint/typescript-estree": 5.62.0
eslint-scope: ^5.1.1
eslint-utils: ^3.0.0
semver: ^7.3.7
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
checksum: 51add038226cddad2b3322225de18d53bc1ed44613f7b3a379eb597114b8830a632990b0f4321e0ddf3502b460d80072d7e789be89135b5e11e8dae167005625
checksum: ee9398c8c5db6d1da09463ca7bf36ed134361e20131ea354b2da16a5fdb6df9ba70c62a388d19f6eebb421af1786dbbd79ba95ddd6ab287324fc171c3e28d931
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:5.27.1":
version: 5.27.1
resolution: "@typescript-eslint/visitor-keys@npm:5.27.1"
"@typescript-eslint/visitor-keys@npm:5.62.0":
version: 5.62.0
resolution: "@typescript-eslint/visitor-keys@npm:5.62.0"
dependencies:
"@typescript-eslint/types": 5.27.1
"@typescript-eslint/types": 5.62.0
eslint-visitor-keys: ^3.3.0
checksum: 8f104eda321cd6c613daf284fbebbd32b149d4213d137b0ce1caec7a1334c9f46c82ed64aff1243b712ac8c13f67ac344c996cd36d21fbb15032c24d9897a64a
checksum: 976b05d103fe8335bef5c93ad3f76d781e3ce50329c0243ee0f00c0fcfb186c81df50e64bfdd34970148113f8ade90887f53e3c4938183afba830b4ba8e30a35
languageName: node
linkType: hard
@ -5740,6 +5814,18 @@ __metadata:
languageName: node
linkType: hard
"@wagmi/chains@npm:1.6.0":
version: 1.6.0
resolution: "@wagmi/chains@npm:1.6.0"
peerDependencies:
typescript: ">=5.0.4"
peerDependenciesMeta:
typescript:
optional: true
checksum: 6c3d477936622f763b9e44c4a66c080020f1119165c8f6b840c51f5822880fedd5031036461d4b28df5cf1ae19cb12e139e4a4b3c129b475f0363fd9c4c8bb0d
languageName: node
linkType: hard
"@wagmi/chains@npm:^0.2.6":
version: 0.2.6
resolution: "@wagmi/chains@npm:0.2.6"
@ -5785,6 +5871,21 @@ __metadata:
languageName: node
linkType: hard
"abitype@npm:0.9.3":
version: 0.9.3
resolution: "abitype@npm:0.9.3"
peerDependencies:
typescript: ">=5.0.4"
zod: ^3 >=3.19.1
peerDependenciesMeta:
typescript:
optional: true
zod:
optional: true
checksum: f97c5a118180563b9ed8b97da492a82d3ce53dcd7d96c87764e90dbe84c04ae72dd5703d1ed5a54601033ab1772b8a235a1b5aadaf7aad6c4b5fdad7fd3a69a7
languageName: node
linkType: hard
"abort-controller@npm:^3.0.0":
version: 3.0.0
resolution: "abort-controller@npm:3.0.0"
@ -12701,6 +12802,15 @@ __metadata:
languageName: node
linkType: hard
"isomorphic-ws@npm:5.0.0":
version: 5.0.0
resolution: "isomorphic-ws@npm:5.0.0"
peerDependencies:
ws: "*"
checksum: e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398
languageName: node
linkType: hard
"isomorphic-ws@npm:^4.0.1":
version: 4.0.1
resolution: "isomorphic-ws@npm:4.0.1"
@ -14489,6 +14599,13 @@ __metadata:
languageName: node
linkType: hard
"natural-compare-lite@npm:^1.4.0":
version: 1.4.0
resolution: "natural-compare-lite@npm:1.4.0"
checksum: 5222ac3986a2b78dd6069ac62cbb52a7bf8ffc90d972ab76dfe7b01892485d229530ed20d0c62e79a6b363a663b273db3bde195a1358ce9e5f779d4453887225
languageName: node
linkType: hard
"natural-compare@npm:^1.4.0":
version: 1.4.0
resolution: "natural-compare@npm:1.4.0"
@ -18362,23 +18479,23 @@ __metadata:
languageName: node
linkType: hard
"typescript@npm:^4.7.2":
version: 4.7.3
resolution: "typescript@npm:4.7.3"
"typescript@npm:^5.1.6":
version: 5.1.6
resolution: "typescript@npm:5.1.6"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: fd13a1ce53790a36bb8350e1f5e5e384b5f6cb9b0635114a6d01d49cb99916abdcfbc13c7521cdae2f2d3f6d8bc4a8ae7625edf645a04ee940588cd5e7597b2f
checksum: b2f2c35096035fe1f5facd1e38922ccb8558996331405eb00a5111cc948b2e733163cc22fab5db46992aba7dd520fff637f2c1df4996ff0e134e77d3249a7350
languageName: node
linkType: hard
"typescript@patch:typescript@^4.7.2#~builtin<compat/typescript>":
version: 4.7.3
resolution: "typescript@patch:typescript@npm%3A4.7.3#~builtin<compat/typescript>::version=4.7.3&hash=bda367"
"typescript@patch:typescript@^5.1.6#~builtin<compat/typescript>":
version: 5.1.6
resolution: "typescript@patch:typescript@npm%3A5.1.6#~builtin<compat/typescript>::version=5.1.6&hash=bda367"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 8257ce7ecbbf9416da60045a76a99d473698ca9e973fa0ddab7137cacb1587255431176cbbcc801a650938c4dc8109ab88355774829a714fabe56a53a2fe4524
checksum: 21e88b0a0c0226f9cb9fd25b9626fb05b4c0f3fddac521844a13e1f30beb8f14e90bd409a9ac43c812c5946d714d6e0dee12d5d02dfc1c562c5aacfa1f49b606
languageName: node
linkType: hard
@ -18784,6 +18901,28 @@ __metadata:
languageName: node
linkType: hard
"viem@npm:^1.3.1":
version: 1.3.1
resolution: "viem@npm:1.3.1"
dependencies:
"@adraffy/ens-normalize": 1.9.0
"@noble/curves": 1.0.0
"@noble/hashes": 1.3.0
"@scure/bip32": 1.3.0
"@scure/bip39": 1.2.0
"@wagmi/chains": 1.6.0
abitype: 0.9.3
isomorphic-ws: 5.0.0
ws: 8.12.0
peerDependencies:
typescript: ">=5.0.4"
peerDependenciesMeta:
typescript:
optional: true
checksum: f93eff83a50d201c06270966eec12eb1cbb69821bab7c9bd6ba8877390e35d247f09345b04c7d9304b9f418e567d4cf898c80528800c6a57fd23d88519c91e74
languageName: node
linkType: hard
"web3-bzz@npm:1.10.0":
version: 1.10.0
resolution: "web3-bzz@npm:1.10.0"
@ -19768,6 +19907,21 @@ __metadata:
languageName: node
linkType: hard
"ws@npm:8.12.0":
version: 8.12.0
resolution: "ws@npm:8.12.0"
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ">=5.0.2"
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
checksum: 818ff3f8749c172a95a114cceb8b89cedd27e43a82d65c7ad0f7882b1e96a2ee6709e3746a903c3fa88beec0c8bae9a9fcd75f20858b32a166dfb7519316a5d7
languageName: node
linkType: hard
"ws@npm:^3.0.0":
version: 3.3.3
resolution: "ws@npm:3.3.3"

Loading…
Cancel
Save