From 65bc79f8aeec5324649ee7ef6d91c0ba231f97da Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 26 Oct 2023 12:43:51 -0400 Subject: [PATCH 01/30] Add CW token adapter stubs --- typescript/sdk/src/app/MultiProtocolApp.ts | 9 ++++ .../sdk/src/token/adapters/CwTokenAdapter.ts | 42 +++++++++++++++++++ typescript/utils/src/types.ts | 1 + 3 files changed, 52 insertions(+) create mode 100644 typescript/sdk/src/token/adapters/CwTokenAdapter.ts diff --git a/typescript/sdk/src/app/MultiProtocolApp.ts b/typescript/sdk/src/app/MultiProtocolApp.ts index 1a027924fa..8d08fcb60c 100644 --- a/typescript/sdk/src/app/MultiProtocolApp.ts +++ b/typescript/sdk/src/app/MultiProtocolApp.ts @@ -49,6 +49,15 @@ export class BaseEvmAdapter extends BaseAppAdapter { } } +export class BaseCwAdapter extends BaseAppAdapter { + public readonly protocol: ProtocolType = ProtocolType.Cosmos; + + public getProvider(): EthersV5Provider['provider'] { + throw new Error('Method not implemented.'); + // return this.multiProvider.getEthersV5Provider(this.chainName); + } +} + export class BaseSealevelAdapter extends BaseAppAdapter { public readonly protocol: ProtocolType = ProtocolType.Sealevel; diff --git a/typescript/sdk/src/token/adapters/CwTokenAdapter.ts b/typescript/sdk/src/token/adapters/CwTokenAdapter.ts new file mode 100644 index 0000000000..72c49b91af --- /dev/null +++ b/typescript/sdk/src/token/adapters/CwTokenAdapter.ts @@ -0,0 +1,42 @@ +import { PopulatedTransaction } from 'ethers'; + +import { Address } from '@hyperlane-xyz/utils'; + +import { BaseCwAdapter } from '../../app/MultiProtocolApp'; +import { MinimalTokenMetadata } from '../config'; + +import { ITokenAdapter, TransferParams } from './ITokenAdapter'; + +// Interacts with ERC20/721 contracts +export class CwTokenAdapter extends BaseCwAdapter implements ITokenAdapter { + async getBalance(address: Address): Promise { + throw new Error('Balance unimplemented'); + } + + async getMetadata(): Promise { + // TODO get metadata from chainMetadata config + throw new Error('Metadata not available to native tokens'); + } + + async populateApproveTx( + _params: TransferParams, + ): Promise { + throw new Error('Approve not required for native tokens'); + } + + async populateTransferTx({ + weiAmountOrId, + recipient, + }: TransferParams): Promise { + throw new Error('Transfer unimplemented'); + } +} + +// Interacts with native currencies +export class CwNativeTokenAdapter extends CwTokenAdapter {} + +// Interacts with Hyp Synthetic token contracts (aka 'HypTokens') +export class CwHypSyntheticAdapter extends CwTokenAdapter {} + +// Interacts with HypCollateral and HypNative contracts +export class CwHypCollateralAdapter extends CwTokenAdapter {} diff --git a/typescript/utils/src/types.ts b/typescript/utils/src/types.ts index 525f26feb2..c5a5774c45 100644 --- a/typescript/utils/src/types.ts +++ b/typescript/utils/src/types.ts @@ -4,6 +4,7 @@ export enum ProtocolType { Ethereum = 'ethereum', Sealevel = 'sealevel', Fuel = 'fuel', + Cosmos = 'cosmos', } // A type that also allows for literal values of the enum export type ProtocolTypeValue = `${ProtocolType}`; From c08449b968c4d86c35d2c4811466181e7caa9a56 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 26 Oct 2023 15:54:14 -0400 Subject: [PATCH 02/30] Implement getbalance --- .../sdk/src/token/adapters/CwTokenAdapter.ts | 82 ++++++++++++++++--- 1 file changed, 72 insertions(+), 10 deletions(-) diff --git a/typescript/sdk/src/token/adapters/CwTokenAdapter.ts b/typescript/sdk/src/token/adapters/CwTokenAdapter.ts index 72c49b91af..c1854b4236 100644 --- a/typescript/sdk/src/token/adapters/CwTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CwTokenAdapter.ts @@ -1,16 +1,51 @@ -import { PopulatedTransaction } from 'ethers'; - import { Address } from '@hyperlane-xyz/utils'; import { BaseCwAdapter } from '../../app/MultiProtocolApp'; +import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; import { MinimalTokenMetadata } from '../config'; import { ITokenAdapter, TransferParams } from './ITokenAdapter'; +import { WarpCw20QueryClient } from './WarpCw20.client'; +import { TokenTypeResponse } from './WarpCw20.types'; +import { WarpNativeQueryClient } from './WarpNative.client'; + +// Interacts with CW20/721 contracts +export class Cw20TokenAdapter extends BaseCwAdapter implements ITokenAdapter { + public readonly contract: WarpCw20QueryClient; + + constructor( + chainName: string, + multiProvider: MultiProtocolProvider, + addresses: { token: Address }, + ) { + super(chainName, multiProvider, addresses); + this.contract = new WarpNativeQueryClient( + this.getProvider(), + addresses.token, + ); + } -// Interacts with ERC20/721 contracts -export class CwTokenAdapter extends BaseCwAdapter implements ITokenAdapter { async getBalance(address: Address): Promise { - throw new Error('Balance unimplemented'); + const tokenTypeResponse: TokenTypeResponse = + await this.contract.tokenDefault({ + token_type: {}, + }); + + const tokenType = tokenTypeResponse.type; + if ('native' in tokenType && 'fungible' in tokenType.native) { + const ibcDenom = tokenType.native.fungible.denom; + const coin = await this.getProvider().getBalance(address, ibcDenom); + return coin.amount; + } else if ('c_w20' in tokenType) { + const cw20 = tokenType.c_w20.contract; + return this.getProvider().queryContractSmart(cw20, { + balance: { + address, + }, + }); + } else { + throw new Error(`Unsupported token type ${tokenType}`); + } } async getMetadata(): Promise { @@ -33,10 +68,37 @@ export class CwTokenAdapter extends BaseCwAdapter implements ITokenAdapter { } // Interacts with native currencies -export class CwNativeTokenAdapter extends CwTokenAdapter {} +export class CwNativeTokenAdapter + extends BaseCwAdapter + implements ITokenAdapter +{ + public readonly contract: WarpNativeQueryClient; + + constructor( + chainName: string, + multiProvider: MultiProtocolProvider, + addresses: { token: Address }, + ) { + super(chainName, multiProvider, addresses); + this.contract = new WarpNativeQueryClient( + this.getProvider(), + addresses.token, + ); + } -// Interacts with Hyp Synthetic token contracts (aka 'HypTokens') -export class CwHypSyntheticAdapter extends CwTokenAdapter {} + getBalance(address: string): Promise { + throw new Error('Method not implemented.'); + } -// Interacts with HypCollateral and HypNative contracts -export class CwHypCollateralAdapter extends CwTokenAdapter {} + getMetadata(isNft?: boolean | undefined): Promise { + throw new Error('Method not implemented.'); + } + + populateApproveTx(TransferParams: TransferParams): unknown { + throw new Error('Approve not required for native tokens'); + } + + populateTransferTx(TransferParams: TransferParams): unknown { + throw new Error('Method not implemented.'); + } +} From c3cae523a48167732045c4744d82eaf94e30a941 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 26 Oct 2023 16:37:20 -0400 Subject: [PATCH 03/30] Stash progress on cw20 token --- typescript/sdk/src/app/MultiProtocolApp.ts | 4 +- .../sdk/src/token/adapters/CwTokenAdapter.ts | 154 +++++++++++------- 2 files changed, 100 insertions(+), 58 deletions(-) diff --git a/typescript/sdk/src/app/MultiProtocolApp.ts b/typescript/sdk/src/app/MultiProtocolApp.ts index 8d08fcb60c..60aa3a4007 100644 --- a/typescript/sdk/src/app/MultiProtocolApp.ts +++ b/typescript/sdk/src/app/MultiProtocolApp.ts @@ -1,3 +1,4 @@ +import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'; import { PublicKey } from '@solana/web3.js'; import debug from 'debug'; @@ -52,9 +53,8 @@ export class BaseEvmAdapter extends BaseAppAdapter { export class BaseCwAdapter extends BaseAppAdapter { public readonly protocol: ProtocolType = ProtocolType.Cosmos; - public getProvider(): EthersV5Provider['provider'] { + public getProvider(): CosmWasmClient { throw new Error('Method not implemented.'); - // return this.multiProvider.getEthersV5Provider(this.chainName); } } diff --git a/typescript/sdk/src/token/adapters/CwTokenAdapter.ts b/typescript/sdk/src/token/adapters/CwTokenAdapter.ts index c1854b4236..26626ee05b 100644 --- a/typescript/sdk/src/token/adapters/CwTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CwTokenAdapter.ts @@ -1,14 +1,37 @@ +import { ExecuteInstruction } from '@cosmjs/cosmwasm-stargate'; + import { Address } from '@hyperlane-xyz/utils'; import { BaseCwAdapter } from '../../app/MultiProtocolApp'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; -import { MinimalTokenMetadata } from '../config'; +import { ERC20Metadata } from '../config'; import { ITokenAdapter, TransferParams } from './ITokenAdapter'; import { WarpCw20QueryClient } from './WarpCw20.client'; -import { TokenTypeResponse } from './WarpCw20.types'; -import { WarpNativeQueryClient } from './WarpNative.client'; +import { TokenType, TokenTypeResponse } from './WarpCw20.types'; + +export type CW20Metadata = ERC20Metadata; + +// TODO: import from cw20 bindings +type TokenInfoResponse = { + name: string; + symbol: string; + decimals: number; + total_supply: string; +}; + +type AllowanceResponse = { + allowance: { + owner: string; + spender: string; + }; +}; +type BalanceResponse = { + balance: string; +}; + +// https://github.com/CosmWasm/cw-plus/blob/main/packages/cw20/README.md // Interacts with CW20/721 contracts export class Cw20TokenAdapter extends BaseCwAdapter implements ITokenAdapter { public readonly contract: WarpCw20QueryClient; @@ -19,86 +42,105 @@ export class Cw20TokenAdapter extends BaseCwAdapter implements ITokenAdapter { addresses: { token: Address }, ) { super(chainName, multiProvider, addresses); - this.contract = new WarpNativeQueryClient( + this.contract = new WarpCw20QueryClient( this.getProvider(), addresses.token, ); } - async getBalance(address: Address): Promise { + async getTokenType(): Promise { const tokenTypeResponse: TokenTypeResponse = await this.contract.tokenDefault({ token_type: {}, }); + return tokenTypeResponse.type; + } - const tokenType = tokenTypeResponse.type; + async getBalance(address: Address): Promise { + const tokenType = await this.getTokenType(); if ('native' in tokenType && 'fungible' in tokenType.native) { const ibcDenom = tokenType.native.fungible.denom; const coin = await this.getProvider().getBalance(address, ibcDenom); return coin.amount; } else if ('c_w20' in tokenType) { const cw20 = tokenType.c_w20.contract; - return this.getProvider().queryContractSmart(cw20, { - balance: { - address, - }, - }); + const balanceResponse: BalanceResponse = + await this.getProvider().queryContractSmart(cw20, { + balance: { + address, + }, + }); + return balanceResponse.balance; } else { throw new Error(`Unsupported token type ${tokenType}`); } } - async getMetadata(): Promise { - // TODO get metadata from chainMetadata config - throw new Error('Metadata not available to native tokens'); + async getMetadata(): Promise { + const tokenType = await this.getTokenType(); + if ('native' in tokenType && 'fungible' in tokenType.native) { + // const ibcDenom = tokenType.native.fungible.denom; + throw new Error('Native tokens not supported'); + } else if ('c_w20' in tokenType) { + const cw20 = tokenType.c_w20.contract; + const tokenInfo: TokenInfoResponse = + await this.getProvider().queryContractSmart(cw20, { + token_info: {}, + }); + return { + ...tokenInfo, + totalSupply: tokenInfo.total_supply, + }; + } else { + throw new Error(`Unsupported token type ${tokenType}`); + } } - async populateApproveTx( - _params: TransferParams, - ): Promise { - throw new Error('Approve not required for native tokens'); + async populateApproveTx({ + weiAmountOrId, + recipient, + }: TransferParams): Promise { + const tokenType = await this.getTokenType(); + if ('native' in tokenType && 'fungible' in tokenType.native) { + throw new Error('Native tokens do not require approval'); + } else if ('c_w20' in tokenType) { + // TODO: check existing allowance + return { + contractAddress: tokenType.c_w20.contract, + msg: { + increase_allowance: { + spender: recipient, + amount: weiAmountOrId, + expires: { + never: {}, + }, + }, + }, + }; + } else { + throw new Error(`Unsupported token type ${tokenType}`); + } } async populateTransferTx({ weiAmountOrId, recipient, - }: TransferParams): Promise { - throw new Error('Transfer unimplemented'); - } -} - -// Interacts with native currencies -export class CwNativeTokenAdapter - extends BaseCwAdapter - implements ITokenAdapter -{ - public readonly contract: WarpNativeQueryClient; - - constructor( - chainName: string, - multiProvider: MultiProtocolProvider, - addresses: { token: Address }, - ) { - super(chainName, multiProvider, addresses); - this.contract = new WarpNativeQueryClient( - this.getProvider(), - addresses.token, - ); - } - - getBalance(address: string): Promise { - throw new Error('Method not implemented.'); - } - - getMetadata(isNft?: boolean | undefined): Promise { - throw new Error('Method not implemented.'); - } - - populateApproveTx(TransferParams: TransferParams): unknown { - throw new Error('Approve not required for native tokens'); - } - - populateTransferTx(TransferParams: TransferParams): unknown { - throw new Error('Method not implemented.'); + }: TransferParams): Promise { + const tokenType = await this.getTokenType(); + if ('native' in tokenType && 'fungible' in tokenType.native) { + throw new Error('Native tokens do not require approval'); + } else if ('c_w20' in tokenType) { + return { + contractAddress: tokenType.c_w20.contract, + msg: { + transfer: { + recipient, + amount: weiAmountOrId.toString(), + }, + }, + }; + } else { + throw new Error(`Unsupported token type ${tokenType}`); + } } } From ffe081f0b47383e82620e9eb541b919dbf426df1 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 26 Oct 2023 16:51:42 -0400 Subject: [PATCH 04/30] Implement just token adapters first --- .../sdk/src/token/adapters/CwTokenAdapter.ts | 171 +++++++++--------- 1 file changed, 81 insertions(+), 90 deletions(-) diff --git a/typescript/sdk/src/token/adapters/CwTokenAdapter.ts b/typescript/sdk/src/token/adapters/CwTokenAdapter.ts index 26626ee05b..b949306588 100644 --- a/typescript/sdk/src/token/adapters/CwTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CwTokenAdapter.ts @@ -4,11 +4,52 @@ import { Address } from '@hyperlane-xyz/utils'; import { BaseCwAdapter } from '../../app/MultiProtocolApp'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; -import { ERC20Metadata } from '../config'; import { ITokenAdapter, TransferParams } from './ITokenAdapter'; -import { WarpCw20QueryClient } from './WarpCw20.client'; -import { TokenType, TokenTypeResponse } from './WarpCw20.types'; + +// Interacts with IBC denom tokens +export class NativeTokenAdapter extends BaseCwAdapter implements ITokenAdapter { + constructor( + chainName: string, + multiProvider: MultiProtocolProvider, + addresses: any, + public readonly ibcDenom: string, + ) { + super(chainName, multiProvider, addresses); + } + + async getBalance(address: Address): Promise { + const balance = await this.getProvider().getBalance(address, this.ibcDenom); + return balance.amount; + } + + async getMetadata(): Promise { + throw new Error('Metadata not available to native tokens'); + } + + async populateApproveTx( + _params: TransferParams, + ): Promise { + throw new Error('Approve not required for native tokens'); + } + + async populateTransferTx({ + recipient, + weiAmountOrId, + }: TransferParams): Promise { + // TODO: check if this works with execute instruction? (contract type, empty message) + return { + contractAddress: recipient, + msg: {}, + funds: [ + { + amount: weiAmountOrId.toString(), + denom: this.ibcDenom, + }, + ], + }; + } +} export type CW20Metadata = ERC20Metadata; @@ -20,13 +61,6 @@ type TokenInfoResponse = { total_supply: string; }; -type AllowanceResponse = { - allowance: { - owner: string; - spender: string; - }; -}; - type BalanceResponse = { balance: string; }; @@ -34,7 +68,8 @@ type BalanceResponse = { // https://github.com/CosmWasm/cw-plus/blob/main/packages/cw20/README.md // Interacts with CW20/721 contracts export class Cw20TokenAdapter extends BaseCwAdapter implements ITokenAdapter { - public readonly contract: WarpCw20QueryClient; + // public readonly contract: CW20QueryClient; + public readonly contractAddress: string; constructor( chainName: string, @@ -42,105 +77,61 @@ export class Cw20TokenAdapter extends BaseCwAdapter implements ITokenAdapter { addresses: { token: Address }, ) { super(chainName, multiProvider, addresses); - this.contract = new WarpCw20QueryClient( - this.getProvider(), - addresses.token, - ); - } - - async getTokenType(): Promise { - const tokenTypeResponse: TokenTypeResponse = - await this.contract.tokenDefault({ - token_type: {}, - }); - return tokenTypeResponse.type; + this.contractAddress = addresses.token; } async getBalance(address: Address): Promise { - const tokenType = await this.getTokenType(); - if ('native' in tokenType && 'fungible' in tokenType.native) { - const ibcDenom = tokenType.native.fungible.denom; - const coin = await this.getProvider().getBalance(address, ibcDenom); - return coin.amount; - } else if ('c_w20' in tokenType) { - const cw20 = tokenType.c_w20.contract; - const balanceResponse: BalanceResponse = - await this.getProvider().queryContractSmart(cw20, { - balance: { - address, - }, - }); - return balanceResponse.balance; - } else { - throw new Error(`Unsupported token type ${tokenType}`); - } + const balanceResponse: BalanceResponse = + await this.getProvider().queryContractSmart(this.contractAddress, { + balance: { + address, + }, + }); + return balanceResponse.balance; } async getMetadata(): Promise { - const tokenType = await this.getTokenType(); - if ('native' in tokenType && 'fungible' in tokenType.native) { - // const ibcDenom = tokenType.native.fungible.denom; - throw new Error('Native tokens not supported'); - } else if ('c_w20' in tokenType) { - const cw20 = tokenType.c_w20.contract; - const tokenInfo: TokenInfoResponse = - await this.getProvider().queryContractSmart(cw20, { - token_info: {}, - }); - return { - ...tokenInfo, - totalSupply: tokenInfo.total_supply, - }; - } else { - throw new Error(`Unsupported token type ${tokenType}`); - } + const tokenInfo: TokenInfoResponse = + await this.getProvider().queryContractSmart(this.contractAddress, { + token_info: {}, + }); + return { + ...tokenInfo, + totalSupply: tokenInfo.total_supply, + }; } async populateApproveTx({ weiAmountOrId, recipient, }: TransferParams): Promise { - const tokenType = await this.getTokenType(); - if ('native' in tokenType && 'fungible' in tokenType.native) { - throw new Error('Native tokens do not require approval'); - } else if ('c_w20' in tokenType) { - // TODO: check existing allowance - return { - contractAddress: tokenType.c_w20.contract, - msg: { - increase_allowance: { - spender: recipient, - amount: weiAmountOrId, - expires: { - never: {}, - }, + // TODO: check existing allowance + return { + contractAddress: this.contractAddress, + msg: { + increase_allowance: { + spender: recipient, + amount: weiAmountOrId, + expires: { + never: {}, }, }, - }; - } else { - throw new Error(`Unsupported token type ${tokenType}`); - } + }, + }; } async populateTransferTx({ weiAmountOrId, recipient, }: TransferParams): Promise { - const tokenType = await this.getTokenType(); - if ('native' in tokenType && 'fungible' in tokenType.native) { - throw new Error('Native tokens do not require approval'); - } else if ('c_w20' in tokenType) { - return { - contractAddress: tokenType.c_w20.contract, - msg: { - transfer: { - recipient, - amount: weiAmountOrId.toString(), - }, + return { + contractAddress: this.contractAddress, + msg: { + transfer: { + recipient, + amount: weiAmountOrId.toString(), }, - }; - } else { - throw new Error(`Unsupported token type ${tokenType}`); - } + }, + }; } } From 18203777625a7f0d46601ef0e4b73aaf1d5083a4 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 26 Oct 2023 16:55:28 -0400 Subject: [PATCH 05/30] Small fixes for build --- typescript/sdk/package.json | 5 ++ .../sdk/src/token/adapters/CwTokenAdapter.ts | 53 +++++++++++++++++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 9716ab2af7..f40a357d64 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -3,6 +3,8 @@ "description": "The official SDK for the Hyperlane Network", "version": "1.5.4-beta0", "dependencies": { + "@cosmjs/cosmwasm-stargate": "^0.31.1", + "@cosmjs/encoding": "^0.31.1", "@hyperlane-xyz/core": "1.5.4-beta0", "@hyperlane-xyz/utils": "1.5.4-beta0", "@solana/spl-token": "^0.3.8", @@ -11,7 +13,10 @@ "@types/debug": "^4.1.7", "@wagmi/chains": "^0.2.6", "coingecko-api": "^1.0.10", + "cosmjs-types": "^0.8.0", + "cosmwasm": "^1.1.1", "cross-fetch": "^3.1.5", + "cw-hyperlane-sdk": "0.0.1", "debug": "^4.3.4", "ethers": "^5.7.2", "viem": "^1.3.1", diff --git a/typescript/sdk/src/token/adapters/CwTokenAdapter.ts b/typescript/sdk/src/token/adapters/CwTokenAdapter.ts index b949306588..7c672f7063 100644 --- a/typescript/sdk/src/token/adapters/CwTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CwTokenAdapter.ts @@ -4,8 +4,14 @@ import { Address } from '@hyperlane-xyz/utils'; import { BaseCwAdapter } from '../../app/MultiProtocolApp'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; +import { ERC20Metadata } from '../config'; -import { ITokenAdapter, TransferParams } from './ITokenAdapter'; +import { + IHypTokenAdapter, + ITokenAdapter, + TransferParams, + TransferRemoteParams, +} from './ITokenAdapter'; // Interacts with IBC denom tokens export class NativeTokenAdapter extends BaseCwAdapter implements ITokenAdapter { @@ -67,8 +73,7 @@ type BalanceResponse = { // https://github.com/CosmWasm/cw-plus/blob/main/packages/cw20/README.md // Interacts with CW20/721 contracts -export class Cw20TokenAdapter extends BaseCwAdapter implements ITokenAdapter { - // public readonly contract: CW20QueryClient; +export class CW20TokenAdapter extends BaseCwAdapter implements ITokenAdapter { public readonly contractAddress: string; constructor( @@ -135,3 +140,45 @@ export class Cw20TokenAdapter extends BaseCwAdapter implements ITokenAdapter { }; } } + +export class WarpCW20TokenAdapter + extends CW20TokenAdapter + implements IHypTokenAdapter +{ + getDomains(): Promise { + throw new Error('Method not implemented.'); + } + getRouterAddress(domain: number): Promise { + throw new Error('Method not implemented.'); + } + getAllRouters(): Promise<{ domain: number; address: Buffer }[]> { + throw new Error('Method not implemented.'); + } + quoteGasPayment(destination: number): Promise { + throw new Error('Method not implemented.'); + } + populateTransferRemoteTx(TransferParams: TransferRemoteParams): unknown { + throw new Error('Method not implemented.'); + } +} + +export class WarpNativeTokenAdapter + extends NativeTokenAdapter + implements IHypTokenAdapter +{ + getDomains(): Promise { + throw new Error('Method not implemented.'); + } + getRouterAddress(domain: number): Promise { + throw new Error('Method not implemented.'); + } + getAllRouters(): Promise<{ domain: number; address: Buffer }[]> { + throw new Error('Method not implemented.'); + } + quoteGasPayment(destination: number): Promise { + throw new Error('Method not implemented.'); + } + populateTransferRemoteTx(TransferParams: TransferRemoteParams): unknown { + throw new Error('Method not implemented.'); + } +} From 2a5384bf3cc35064a6af3e3e493fb1fc07e92738 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 26 Oct 2023 17:17:42 -0400 Subject: [PATCH 06/30] Add cosmos libraries to SDK and Utils Implement address utilties for cosmos Define provider types for cosmos and cosmwasm --- typescript/sdk/package.json | 3 + typescript/sdk/src/index.ts | 8 + .../src/providers/MultiProtocolProvider.ts | 65 ++- typescript/sdk/src/providers/ProviderType.ts | 78 +++- .../sdk/src/providers/providerBuilders.ts | 29 ++ typescript/utils/index.ts | 9 + typescript/utils/package.json | 1 + typescript/utils/src/addresses.ts | 160 ++++--- typescript/utils/src/types.ts | 2 + yarn.lock | 398 +++++++++++++++++- 10 files changed, 670 insertions(+), 83 deletions(-) diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 9716ab2af7..368a433562 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -3,6 +3,8 @@ "description": "The official SDK for the Hyperlane Network", "version": "1.5.4-beta0", "dependencies": { + "@cosmjs/cosmwasm-stargate": "^0.31.3", + "@cosmjs/stargate": "^0.31.3", "@hyperlane-xyz/core": "1.5.4-beta0", "@hyperlane-xyz/utils": "1.5.4-beta0", "@solana/spl-token": "^0.3.8", @@ -11,6 +13,7 @@ "@types/debug": "^4.1.7", "@wagmi/chains": "^0.2.6", "coingecko-api": "^1.0.10", + "cosmjs-types": "^0.9.0", "cross-fetch": "^3.1.5", "debug": "^4.3.4", "ethers": "^5.7.2", diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 95da574d2d..64cb739f30 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -201,6 +201,14 @@ export { } from './providers/MultiProtocolProvider'; export { MultiProvider, MultiProviderOptions } from './providers/MultiProvider'; export { + CosmJsContract, + CosmJsProvider, + CosmJsTransaction, + CosmJsTransactionReceipt, + CosmJsWasmContract, + CosmJsWasmProvider, + CosmJsWasmTransaction, + CosmJsWasmTransactionReceipt, EthersV5Contract, EthersV5Provider, EthersV5Transaction, diff --git a/typescript/sdk/src/providers/MultiProtocolProvider.ts b/typescript/sdk/src/providers/MultiProtocolProvider.ts index 559d6049df..1ed91bb058 100644 --- a/typescript/sdk/src/providers/MultiProtocolProvider.ts +++ b/typescript/sdk/src/providers/MultiProtocolProvider.ts @@ -9,6 +9,8 @@ import type { ChainMap, ChainName } from '../types'; import { MultiProvider, MultiProviderOptions } from './MultiProvider'; import { + CosmJsProvider, + CosmJsWasmProvider, EthersV5Provider, ProviderMap, ProviderType, @@ -143,38 +145,59 @@ export class MultiProtocolProvider< return provider; } + protected getSpecificProvider( + chainNameOrId: ChainName | number, + type: ProviderType, + ): T { + const provider = this.getProvider(chainNameOrId, type); + if (provider.type !== type) + throw new Error( + `Invalid provider type, expected ${type} but found ${provider.type}`, + ); + return provider.provider as T; + } + 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; + return this.getSpecificProvider( + chainNameOrId, + ProviderType.EthersV5, + ); } - // getEthersV6Provider( - // chainNameOrId: ChainName | number, - // ): EthersV6Provider['provider'] { - // const provider = this.getProvider(chainNameOrId, ProviderType.EthersV6); - // 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.Viem); - if (provider.type !== ProviderType.Viem) - throw new Error('Invalid provider type'); - return provider.provider; + return this.getSpecificProvider( + chainNameOrId, + ProviderType.Viem, + ); } getSolanaWeb3Provider( chainNameOrId: ChainName | number, ): SolanaWeb3Provider['provider'] { - const provider = this.getProvider(chainNameOrId, ProviderType.SolanaWeb3); - if (provider.type !== ProviderType.SolanaWeb3) - throw new Error('Invalid provider type'); - return provider.provider; + return this.getSpecificProvider( + chainNameOrId, + ProviderType.SolanaWeb3, + ); + } + + getCosmJsProvider( + chainNameOrId: ChainName | number, + ): CosmJsProvider['provider'] { + return this.getSpecificProvider( + chainNameOrId, + ProviderType.CosmJs, + ); + } + + getCosmJsWasmProvider( + chainNameOrId: ChainName | number, + ): CosmJsWasmProvider['provider'] { + return this.getSpecificProvider( + chainNameOrId, + ProviderType.CosmJsWasm, + ); } setProvider( diff --git a/typescript/sdk/src/providers/ProviderType.ts b/typescript/sdk/src/providers/ProviderType.ts index b5e9bdcc95..b59a71e1bc 100644 --- a/typescript/sdk/src/providers/ProviderType.ts +++ b/typescript/sdk/src/providers/ProviderType.ts @@ -1,3 +1,10 @@ +import type { + CosmWasmClient, + Contract as CosmWasmContract, + ExecuteInstruction, +} from '@cosmjs/cosmwasm-stargate'; +import type { EncodeObject as CmTransaction } from '@cosmjs/proto-signing'; +import type { DeliverTxResponse, StargateClient } from '@cosmjs/stargate'; import type { Connection, Transaction as SolTransaction, @@ -21,6 +28,8 @@ export enum ProviderType { // EthersV6 = 'ethers-v6', Disabled for now to simplify build tooling Viem = 'viem', SolanaWeb3 = 'solana-web3', + CosmJs = 'cosmjs', + CosmJsWasm = 'cosmjs-wasm', } export type ProviderMap = Partial>; @@ -55,11 +64,25 @@ export interface SolanaWeb3Provider extends TypedProviderBase { provider: Connection; } +export interface CosmJsProvider + extends TypedProviderBase> { + type: ProviderType.CosmJs; + provider: Promise; +} + +export interface CosmJsWasmProvider + extends TypedProviderBase> { + type: ProviderType.CosmJsWasm; + provider: Promise; +} + export type TypedProvider = | EthersV5Provider // | EthersV6Provider | ViemProvider - | SolanaWeb3Provider; + | SolanaWeb3Provider + | CosmJsProvider + | CosmJsWasmProvider; /** * Contracts with discriminated union of provider type @@ -72,7 +95,7 @@ interface TypedContractBase { export interface EthersV5Contract extends TypedContractBase { type: ProviderType.EthersV5; - transaction: EV5Contract; + contract: EV5Contract; } // export interface EthersV6Contract extends TypedContractBase { @@ -82,20 +105,34 @@ export interface EthersV5Contract extends TypedContractBase { export interface ViemContract extends TypedContractBase { type: ProviderType.Viem; - transaction: GetContractReturnType; + contract: GetContractReturnType; } export interface SolanaWeb3Contract extends TypedContractBase { type: ProviderType.SolanaWeb3; // Contract concept doesn't exist in @solana/web3.js - transaction: never; + contract: never; +} + +export interface CosmJsContract extends TypedContractBase { + type: ProviderType.CosmJs; + // TODO, research if cosmos sdk modules have an equivalent for here + contract: never; +} + +export interface CosmJsWasmContract + extends TypedContractBase { + type: ProviderType.CosmJsWasm; + contract: CosmWasmContract; } export type TypedContract = | EthersV5Contract // | EthersV6Contract | ViemContract - | SolanaWeb3Contract; + | SolanaWeb3Contract + | CosmJsContract + | CosmJsWasmContract; /** * Transactions with discriminated union of provider type @@ -128,11 +165,24 @@ export interface SolanaWeb3Transaction transaction: SolTransaction; } +export interface CosmJsTransaction extends TypedTransactionBase { + type: ProviderType.CosmJs; + transaction: CmTransaction; +} + +export interface CosmJsWasmTransaction + extends TypedTransactionBase { + type: ProviderType.CosmJs; + transaction: ExecuteInstruction; +} + export type TypedTransaction = | EthersV5Transaction // | EthersV6Transaction | ViemTransaction - | SolanaWeb3Transaction; + | SolanaWeb3Transaction + | CosmJsTransaction + | CosmJsWasmTransaction; /** * Transaction receipt/response with discriminated union of provider type @@ -161,7 +211,21 @@ export interface SolanaWeb3TransactionReceipt receipt: SolTransactionReceipt; } +export interface CosmJsTransactionReceipt + extends TypedTransactionReceiptBase { + type: ProviderType.CosmJs; + receipt: DeliverTxResponse; +} + +export interface CosmJsWasmTransactionReceipt + extends TypedTransactionReceiptBase { + type: ProviderType.CosmJsWasm; + receipt: DeliverTxResponse; +} + export type TypedTransactionReceipt = | EthersV5TransactionReceipt | ViemTransactionReceipt - | SolanaWeb3TransactionReceipt; + | SolanaWeb3TransactionReceipt + | CosmJsTransactionReceipt + | CosmJsWasmTransactionReceipt; diff --git a/typescript/sdk/src/providers/providerBuilders.ts b/typescript/sdk/src/providers/providerBuilders.ts index f30a360001..5cab494530 100644 --- a/typescript/sdk/src/providers/providerBuilders.ts +++ b/typescript/sdk/src/providers/providerBuilders.ts @@ -1,3 +1,5 @@ +import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'; +import { StargateClient } from '@cosmjs/stargate'; import { Connection } from '@solana/web3.js'; import { providers } from 'ethers'; import { createPublicClient, http } from 'viem'; @@ -7,6 +9,8 @@ import { ProtocolType, isNumeric } from '@hyperlane-xyz/utils'; import { ChainMetadata } from '../metadata/chainMetadataTypes'; import { + CosmJsProvider, + CosmJsWasmProvider, EthersV5Provider, ProviderType, SolanaWeb3Provider, @@ -102,6 +106,28 @@ export function defaultFuelProviderBuilder( throw new Error('TODO fuel support'); } +export function defaultCosmJsProviderBuilder( + rpcUrls: ChainMetadata['rpcUrls'], + _network: number | string, +): CosmJsProvider { + if (!rpcUrls.length) throw new Error('No RPC URLs provided'); + return { + type: ProviderType.CosmJs, + provider: StargateClient.connect(rpcUrls[0].http), + }; +} + +export function defaultCosmJsWasmProviderBuilder( + rpcUrls: ChainMetadata['rpcUrls'], + _network: number | string, +): CosmJsWasmProvider { + if (!rpcUrls.length) throw new Error('No RPC URLs provided'); + return { + type: ProviderType.CosmJsWasm, + provider: CosmWasmClient.connect(rpcUrls[0].http), + }; +} + // Kept for backwards compatibility export function defaultProviderBuilder( rpcUrls: ChainMetadata['rpcUrls'], @@ -119,6 +145,8 @@ export const defaultProviderBuilderMap: ProviderBuilderMap = { // [ProviderType.EthersV6]: defaultEthersV6ProviderBuilder, [ProviderType.Viem]: defaultViemProviderBuilder, [ProviderType.SolanaWeb3]: defaultSolProviderBuilder, + [ProviderType.CosmJs]: defaultCosmJsProviderBuilder, + [ProviderType.CosmJsWasm]: defaultCosmJsWasmProviderBuilder, }; export const protocolToDefaultProviderBuilder: Record< @@ -128,4 +156,5 @@ export const protocolToDefaultProviderBuilder: Record< [ProtocolType.Ethereum]: defaultEthersV5ProviderBuilder, [ProtocolType.Sealevel]: defaultSolProviderBuilder, [ProtocolType.Fuel]: defaultFuelProviderBuilder, + [ProtocolType.Cosmos]: defaultCosmJsWasmProviderBuilder, }; diff --git a/typescript/utils/index.ts b/typescript/utils/index.ts index a60d68b8d0..e2351b6f6c 100644 --- a/typescript/utils/index.ts +++ b/typescript/utils/index.ts @@ -2,27 +2,36 @@ export { addressToByteHexString, addressToBytes, addressToBytes32, + addressToBytesCosmos, addressToBytesEvm, addressToBytesSol, bytes32ToAddress, + bytesToAddressCosmos, + bytesToAddressEvm, + bytesToAddressSol, bytesToProtocolAddress, capitalizeAddress, convertToProtocolAddress, ensure0x, eqAddress, + eqAddressCosmos, eqAddressEvm, eqAddressSol, getAddressProtocolType, + isAddressCosmos, isAddressEvm, isAddressSealevel, isValidAddress, + isValidAddressCosmos, isValidAddressEvm, isValidAddressSealevel, isValidTransactionHash, + isValidTransactionHashCosmos, isValidTransactionHashEvm, isValidTransactionHashSealevel, isZeroishAddress, normalizeAddress, + normalizeAddressCosmos, normalizeAddressEvm, normalizeAddressSealevel, shortenAddress, diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 54ff1856de..e7caa7799c 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -3,6 +3,7 @@ "description": "General utilities and types for the Hyperlane network", "version": "1.5.4-beta0", "dependencies": { + "@cosmjs/encoding": "^0.31.3", "@solana/web3.js": "^1.78.0", "bignumber.js": "^9.1.1", "ethers": "^5.7.2" diff --git a/typescript/utils/src/addresses.ts b/typescript/utils/src/addresses.ts index 4ad170803b..02ef3d5d6a 100644 --- a/typescript/utils/src/addresses.ts +++ b/typescript/utils/src/addresses.ts @@ -1,3 +1,4 @@ +import { fromBech32, normalizeBech32, toBech32 } from '@cosmjs/encoding'; import { PublicKey } from '@solana/web3.js'; import { utils as ethersUtils } from 'ethers'; @@ -5,11 +6,15 @@ import { Address, HexString, ProtocolType } from './types'; const EVM_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/; const SEALEVEL_ADDRESS_REGEX = /^[a-zA-Z0-9]{36,44}$/; +const COSMOS_ADDRESS_REGEX = + /^[a-z]{1,10}1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38}$/; // Bech32 address with 32 chars and 6 checksum chars const EVM_TX_HASH_REGEX = /^0x([A-Fa-f0-9]{64})$/; const SEALEVEL_TX_HASH_REGEX = /^[a-zA-Z1-9]{88}$/; +const COSMOS_TX_HASH_REGEX = /^(0x)?[A-Fa-f0-9]{64}$/; const ZEROISH_ADDRESS_REGEX = /^(0x)?0*$/; +const COSMOS_ZEROISH_ADDRESS_REGEX = /^[a-z]{1,10}?1[0]{38}$/; export function isAddressEvm(address: Address) { return EVM_ADDRESS_REGEX.test(address); @@ -19,32 +24,33 @@ export function isAddressSealevel(address: Address) { return SEALEVEL_ADDRESS_REGEX.test(address); } +export function isAddressCosmos(address: Address) { + return COSMOS_ADDRESS_REGEX.test(address); +} + export function getAddressProtocolType(address: Address) { if (!address) return undefined; if (isAddressEvm(address)) { return ProtocolType.Ethereum; } else if (isAddressSealevel(address)) { return ProtocolType.Sealevel; + } else if (isAddressCosmos(address)) { + return ProtocolType.Cosmos; } else { return undefined; } } function routeAddressUtil( - evmFn: (param: string) => T, - sealevelFn: (param: string) => T, - fallback: T, + fns: Partial T>>, param: string, + fallback?: T, protocol?: ProtocolType, ) { - protocol = protocol || getAddressProtocolType(param); - if (protocol === ProtocolType.Ethereum) { - return evmFn(param); - } else if (protocol === ProtocolType.Sealevel) { - return sealevelFn(param); - } else { - return fallback; - } + protocol ||= getAddressProtocolType(param); + if (protocol && fns[protocol]) return fns[protocol]!(param); + else if (fallback) return fallback; + else throw new Error(`Unsupported protocol ${protocol}`); } // Slower than isAddressEvm above but actually validates content and checksum @@ -68,12 +74,25 @@ export function isValidAddressSealevel(address: Address) { } } +// Slower than isAddressCosmos above but actually validates content and checksum +export function isValidAddressCosmos(address: Address) { + try { + const isValid = address && fromBech32(address); + return !!isValid; + } catch (error) { + return false; + } +} + export function isValidAddress(address: Address, protocol?: ProtocolType) { return routeAddressUtil( - isValidAddressEvm, - isValidAddressSealevel, - false, + { + [ProtocolType.Ethereum]: isValidAddressEvm, + [ProtocolType.Sealevel]: isValidAddressSealevel, + [ProtocolType.Cosmos]: isValidAddressCosmos, + }, address, + false, protocol, ); } @@ -96,10 +115,22 @@ export function normalizeAddressSealevel(address: Address) { } } +export function normalizeAddressCosmos(address: Address) { + if (isZeroishAddress(address)) return address; + try { + return normalizeBech32(address); + } catch (error) { + return address; + } +} + export function normalizeAddress(address: Address, protocol?: ProtocolType) { return routeAddressUtil( - normalizeAddressEvm, - normalizeAddressSealevel, + { + [ProtocolType.Ethereum]: normalizeAddressEvm, + [ProtocolType.Sealevel]: normalizeAddressSealevel, + [ProtocolType.Cosmos]: normalizeAddressCosmos, + }, address, address, protocol, @@ -114,15 +145,22 @@ export function eqAddressSol(a1: Address, a2: Address) { return normalizeAddressSealevel(a1) === normalizeAddressSealevel(a2); } +export function eqAddressCosmos(a1: Address, a2: Address) { + return normalizeAddressCosmos(a1) === normalizeAddressCosmos(a2); +} + export function eqAddress(a1: Address, a2: Address) { const p1 = getAddressProtocolType(a1); const p2 = getAddressProtocolType(a2); if (p1 !== p2) return false; return routeAddressUtil( - (_a1) => eqAddressEvm(_a1, a2), - (_a1) => eqAddressSol(_a1, a2), - false, + { + [ProtocolType.Ethereum]: (_a1) => eqAddressEvm(_a1, a2), + [ProtocolType.Sealevel]: (_a1) => eqAddressSol(_a1, a2), + [ProtocolType.Cosmos]: (_a1) => eqAddressCosmos(_a1, a2), + }, a1, + false, p1, ); } @@ -135,18 +173,27 @@ export function isValidTransactionHashSealevel(input: string) { return SEALEVEL_TX_HASH_REGEX.test(input); } +export function isValidTransactionHashCosmos(input: string) { + return COSMOS_TX_HASH_REGEX.test(input); +} + export function isValidTransactionHash(input: string, protocol: ProtocolType) { if (protocol === ProtocolType.Ethereum) { return isValidTransactionHashEvm(input); } else if (protocol === ProtocolType.Sealevel) { return isValidTransactionHashSealevel(input); + } else if (protocol === ProtocolType.Cosmos) { + return isValidTransactionHashCosmos(input); } else { return false; } } export function isZeroishAddress(address: Address) { - return ZEROISH_ADDRESS_REGEX.test(address); + return ( + ZEROISH_ADDRESS_REGEX.test(address) || + COSMOS_ZEROISH_ADDRESS_REGEX.test(address) + ); } export function shortenAddress(address: Address, capitalize?: boolean) { @@ -166,31 +213,40 @@ export function capitalizeAddress(address: Address) { else return address.toUpperCase(); } +// For EVM addresses only, kept for backwards compatibility and convenience export function addressToBytes32(address: Address): string { return ethersUtils .hexZeroPad(ethersUtils.hexStripZeros(address), 32) .toLowerCase(); } +// For EVM addresses only, kept for backwards compatibility and convenience export function bytes32ToAddress(bytes32: HexString): Address { return ethersUtils.getAddress(bytes32.slice(-40)); } export function addressToBytesEvm(address: Address): Uint8Array { const addrBytes32 = addressToBytes32(address); - return Buffer.from(addrBytes32.substring(2), 'hex'); + return Buffer.from(strip0x(addrBytes32), 'hex'); } export function addressToBytesSol(address: Address): Uint8Array { return new PublicKey(address).toBytes(); } +export function addressToBytesCosmos(address: Address): Uint8Array { + return fromBech32(address).data; +} + export function addressToBytes(address: Address, protocol?: ProtocolType) { return routeAddressUtil( - addressToBytesEvm, - addressToBytesSol, - new Uint8Array(), + { + [ProtocolType.Ethereum]: addressToBytesEvm, + [ProtocolType.Sealevel]: addressToBytesSol, + [ProtocolType.Cosmos]: addressToBytesCosmos, + }, address, + new Uint8Array(), protocol, ); } @@ -199,17 +255,38 @@ export function addressToByteHexString( address: string, protocol?: ProtocolType, ) { - return '0x' + Buffer.from(addressToBytes(address, protocol)).toString('hex'); + return ensure0x( + Buffer.from(addressToBytes(address, protocol)).toString('hex'), + ); +} + +export function bytesToAddressEvm(bytes: Uint8Array): Address { + return bytes32ToAddress(Buffer.from(bytes).toString('hex')); +} + +export function bytesToAddressSol(bytes: Uint8Array): Address { + return new PublicKey(bytes).toBase58(); +} + +export function bytesToAddressCosmos( + bytes: Uint8Array, + prefix: string, +): Address { + if (!prefix) throw new Error('Prefix required for Cosmos address'); + return toBech32(prefix, bytes); } export function bytesToProtocolAddress( - bytes: Buffer, + bytes: Uint8Array, toProtocol: ProtocolType, + prefix?: string, ) { - if (toProtocol === ProtocolType.Sealevel) { - return new PublicKey(bytes).toBase58(); - } else if (toProtocol === ProtocolType.Ethereum) { - return bytes32ToAddress(bytes.toString('hex')); + if (toProtocol === ProtocolType.Ethereum) { + return bytesToAddressEvm(bytes); + } else if (toProtocol === ProtocolType.Sealevel) { + return bytesToAddressSol(bytes); + } else if (toProtocol === ProtocolType.Cosmos) { + return bytesToAddressCosmos(bytes, prefix!); } else { throw new Error(`Unsupported protocol for address ${toProtocol}`); } @@ -221,27 +298,8 @@ export function convertToProtocolAddress( ) { const currentProtocol = getAddressProtocolType(address); if (currentProtocol === protocol) return address; - if ( - currentProtocol === ProtocolType.Ethereum && - protocol === ProtocolType.Sealevel - ) { - return new PublicKey( - addressToBytes(address, ProtocolType.Ethereum), - ).toBase58(); - } else if ( - currentProtocol === ProtocolType.Sealevel && - protocol === ProtocolType.Ethereum - ) { - return bytes32ToAddress( - Buffer.from(addressToBytes(address, ProtocolType.Sealevel)).toString( - 'hex', - ), - ); - } else { - throw new Error( - `Unsupported protocol combination ${currentProtocol} -> ${protocol}`, - ); - } + const addressBytes = addressToBytes(address, currentProtocol); + return bytesToProtocolAddress(addressBytes, protocol); } export function ensure0x(hexstr: string) { diff --git a/typescript/utils/src/types.ts b/typescript/utils/src/types.ts index 525f26feb2..81799c6b4e 100644 --- a/typescript/utils/src/types.ts +++ b/typescript/utils/src/types.ts @@ -4,6 +4,7 @@ export enum ProtocolType { Ethereum = 'ethereum', Sealevel = 'sealevel', Fuel = 'fuel', + Cosmos = 'cosmos', } // A type that also allows for literal values of the enum export type ProtocolTypeValue = `${ProtocolType}`; @@ -11,6 +12,7 @@ export type ProtocolTypeValue = `${ProtocolType}`; export const ProtocolSmallestUnit = { [ProtocolType.Ethereum]: 'wei', [ProtocolType.Sealevel]: 'lamports', + [ProtocolType.Cosmos]: 'uATOM', }; /********* BASIC TYPES *********/ diff --git a/yarn.lock b/yarn.lock index a59e9cfca2..006e2c9640 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2758,6 +2758,173 @@ __metadata: languageName: node linkType: hard +"@confio/ics23@npm:^0.6.8": + version: 0.6.8 + resolution: "@confio/ics23@npm:0.6.8" + dependencies: + "@noble/hashes": ^1.0.0 + protobufjs: ^6.8.8 + checksum: 376d72f6440db60611b002b00a13e3a5bfd0d3503e7682358dbcf79641e74d8c26c234c321452fb4a758baf66eecef25d950e08bdea270486d9d03ee489e2960 + languageName: node + linkType: hard + +"@cosmjs/amino@npm:^0.31.3": + version: 0.31.3 + resolution: "@cosmjs/amino@npm:0.31.3" + dependencies: + "@cosmjs/crypto": ^0.31.3 + "@cosmjs/encoding": ^0.31.3 + "@cosmjs/math": ^0.31.3 + "@cosmjs/utils": ^0.31.3 + checksum: 30e55ed256e1ba8a84b8a92062fd48aed43b1d638b8917af8b28ae007a1eff3ffc9ffb743400db23c583dc2fefae12c3dd8b315451a09f6da9c10a07ce714dfa + languageName: node + linkType: hard + +"@cosmjs/cosmwasm-stargate@npm:^0.31.3": + version: 0.31.3 + resolution: "@cosmjs/cosmwasm-stargate@npm:0.31.3" + dependencies: + "@cosmjs/amino": ^0.31.3 + "@cosmjs/crypto": ^0.31.3 + "@cosmjs/encoding": ^0.31.3 + "@cosmjs/math": ^0.31.3 + "@cosmjs/proto-signing": ^0.31.3 + "@cosmjs/stargate": ^0.31.3 + "@cosmjs/tendermint-rpc": ^0.31.3 + "@cosmjs/utils": ^0.31.3 + cosmjs-types: ^0.8.0 + long: ^4.0.0 + pako: ^2.0.2 + checksum: 7f5a4c809fb3b54fa70887a2e7cf2bc9c2a460891a1cd01f6721f8dbe43efa0d6611b642c2e3601c779295008dbe922e8a9985ffecc3bca55533fb43d83d000d + languageName: node + linkType: hard + +"@cosmjs/crypto@npm:^0.31.3": + version: 0.31.3 + resolution: "@cosmjs/crypto@npm:0.31.3" + dependencies: + "@cosmjs/encoding": ^0.31.3 + "@cosmjs/math": ^0.31.3 + "@cosmjs/utils": ^0.31.3 + "@noble/hashes": ^1 + bn.js: ^5.2.0 + elliptic: ^6.5.4 + libsodium-wrappers-sumo: ^0.7.11 + checksum: e562bbcb7cce2c2992aa7fc808fb2b9bcc6d6a27b2567323f41349e7e1aca1b8a4e5b6e0442512cdd7e2bbe54f4b6a0b7ccf71eb574522d0bc405e609dcece8c + languageName: node + linkType: hard + +"@cosmjs/encoding@npm:^0.31.3": + version: 0.31.3 + resolution: "@cosmjs/encoding@npm:0.31.3" + dependencies: + base64-js: ^1.3.0 + bech32: ^1.1.4 + readonly-date: ^1.0.0 + checksum: dadef0579828299be20a64edf820ac8770c0cc47a842594bc9b494f160a347b745941d795360755ccbe385b9d0912aa54753479d1a70ff762d2d334693952ff9 + languageName: node + linkType: hard + +"@cosmjs/json-rpc@npm:^0.31.3": + version: 0.31.3 + resolution: "@cosmjs/json-rpc@npm:0.31.3" + dependencies: + "@cosmjs/stream": ^0.31.3 + xstream: ^11.14.0 + checksum: 6f050cf0d02f2a4f9df5391cc77e661684e5c7cc1df0fb71ae903984cb4f10cc765c08e866e417323910cbc63b91e30c38b7f2585ef5e473a8b086cddacc882a + languageName: node + linkType: hard + +"@cosmjs/math@npm:^0.31.3": + version: 0.31.3 + resolution: "@cosmjs/math@npm:0.31.3" + dependencies: + bn.js: ^5.2.0 + checksum: 1685ad41ed78e78854649ca933817c56d39f4b36bba59b5dbdb1728048f431da5531265f4d77bfc9280cdea6c368817109b9f4540d5cfc2093f6ea6ff9e9a8d2 + languageName: node + linkType: hard + +"@cosmjs/proto-signing@npm:^0.31.3": + version: 0.31.3 + resolution: "@cosmjs/proto-signing@npm:0.31.3" + dependencies: + "@cosmjs/amino": ^0.31.3 + "@cosmjs/crypto": ^0.31.3 + "@cosmjs/encoding": ^0.31.3 + "@cosmjs/math": ^0.31.3 + "@cosmjs/utils": ^0.31.3 + cosmjs-types: ^0.8.0 + long: ^4.0.0 + checksum: c27c4d921c99f5c06ac92ebba59e78c53b7c115334932dd1365263b98c1a67c7323e3a69ae933babf5a36682c019bbc7da3c9597ca1bf1a4858546bdd681453a + languageName: node + linkType: hard + +"@cosmjs/socket@npm:^0.31.3": + version: 0.31.3 + resolution: "@cosmjs/socket@npm:0.31.3" + dependencies: + "@cosmjs/stream": ^0.31.3 + isomorphic-ws: ^4.0.1 + ws: ^7 + xstream: ^11.14.0 + checksum: 29cc5120732a3badd0d3e4358aa645aa6ad50fedf4a619e2a99a2ec85274bc6df9791f0fb9674417b6eca72762916e8f25277fafb318f3e0a77effa2c52da16b + languageName: node + linkType: hard + +"@cosmjs/stargate@npm:^0.31.3": + version: 0.31.3 + resolution: "@cosmjs/stargate@npm:0.31.3" + dependencies: + "@confio/ics23": ^0.6.8 + "@cosmjs/amino": ^0.31.3 + "@cosmjs/encoding": ^0.31.3 + "@cosmjs/math": ^0.31.3 + "@cosmjs/proto-signing": ^0.31.3 + "@cosmjs/stream": ^0.31.3 + "@cosmjs/tendermint-rpc": ^0.31.3 + "@cosmjs/utils": ^0.31.3 + cosmjs-types: ^0.8.0 + long: ^4.0.0 + protobufjs: ~6.11.3 + xstream: ^11.14.0 + checksum: 9b680d50f0818e3cfaffccd022d6034c283c1e350a1b8d8f74ffa22352e342ce1cb00533007ba7b5a6a1c1bc30fe327bd09c23ac8b7486691ad127a34c47690c + languageName: node + linkType: hard + +"@cosmjs/stream@npm:^0.31.3": + version: 0.31.3 + resolution: "@cosmjs/stream@npm:0.31.3" + dependencies: + xstream: ^11.14.0 + checksum: 0d273604af4d7093b877582e223eedbcce4a1a4d7d9f80a4f5e215fd8be42ea6546f3778cc918cb0cdb144de52e7d8d4c476b9b4c6f678cebe914224f54d19ad + languageName: node + linkType: hard + +"@cosmjs/tendermint-rpc@npm:^0.31.3": + version: 0.31.3 + resolution: "@cosmjs/tendermint-rpc@npm:0.31.3" + dependencies: + "@cosmjs/crypto": ^0.31.3 + "@cosmjs/encoding": ^0.31.3 + "@cosmjs/json-rpc": ^0.31.3 + "@cosmjs/math": ^0.31.3 + "@cosmjs/socket": ^0.31.3 + "@cosmjs/stream": ^0.31.3 + "@cosmjs/utils": ^0.31.3 + axios: ^0.21.2 + readonly-date: ^1.0.0 + xstream: ^11.14.0 + checksum: 403e220ee4aeb65977a4416d48930d7381e3d4c10bf300fa6f07698c72b85a55f2314ba1a3d45849ce8549de2ff2005988188fc5fe60ac09188edbb89692115d + languageName: node + linkType: hard + +"@cosmjs/utils@npm:^0.31.3": + version: 0.31.3 + resolution: "@cosmjs/utils@npm:0.31.3" + checksum: 2ff2b270954ab00cc5ae8f23625b562676d0a061c8076905509a5f0701e302e46d24a51a0c3283072e0ce01fbd860baceb25e62303ff17826672fe5f8674b00d + languageName: node + linkType: hard + "@cspotcode/source-map-support@npm:^0.8.0": version: 0.8.1 resolution: "@cspotcode/source-map-support@npm:0.8.1" @@ -4053,6 +4220,8 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: + "@cosmjs/cosmwasm-stargate": ^0.31.3 + "@cosmjs/stargate": ^0.31.3 "@hyperlane-xyz/core": 1.5.4-beta0 "@hyperlane-xyz/utils": 1.5.4-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 @@ -4066,6 +4235,7 @@ __metadata: "@wagmi/chains": ^0.2.6 chai: ^4.3.6 coingecko-api: ^1.0.10 + cosmjs-types: ^0.9.0 cross-fetch: ^3.1.5 debug: ^4.3.4 dotenv: ^10.0.0 @@ -4088,6 +4258,7 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: + "@cosmjs/encoding": ^0.31.3 "@solana/web3.js": ^1.78.0 bignumber.js: ^9.1.1 chai: ^4.3.0 @@ -4312,7 +4483,7 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:^1.3.1": +"@noble/hashes@npm:^1, @noble/hashes@npm:^1.0.0, @noble/hashes@npm:^1.3.1": version: 1.3.2 resolution: "@noble/hashes@npm:1.3.2" checksum: fe23536b436539d13f90e4b9be843cc63b1b17666a07634a2b1259dded6f490be3d050249e6af98076ea8f2ea0d56f578773c2197f2aa0eeaa5fba5bc18ba474 @@ -4869,6 +5040,79 @@ __metadata: languageName: node linkType: hard +"@protobufjs/aspromise@npm:^1.1.1, @protobufjs/aspromise@npm:^1.1.2": + version: 1.1.2 + resolution: "@protobufjs/aspromise@npm:1.1.2" + checksum: 011fe7ef0826b0fd1a95935a033a3c0fd08483903e1aa8f8b4e0704e3233406abb9ee25350ec0c20bbecb2aad8da0dcea58b392bbd77d6690736f02c143865d2 + languageName: node + linkType: hard + +"@protobufjs/base64@npm:^1.1.2": + version: 1.1.2 + resolution: "@protobufjs/base64@npm:1.1.2" + checksum: 67173ac34de1e242c55da52c2f5bdc65505d82453893f9b51dc74af9fe4c065cf4a657a4538e91b0d4a1a1e0a0642215e31894c31650ff6e3831471061e1ee9e + languageName: node + linkType: hard + +"@protobufjs/codegen@npm:^2.0.4": + version: 2.0.4 + resolution: "@protobufjs/codegen@npm:2.0.4" + checksum: 59240c850b1d3d0b56d8f8098dd04787dcaec5c5bd8de186fa548de86b86076e1c50e80144b90335e705a044edf5bc8b0998548474c2a10a98c7e004a1547e4b + languageName: node + linkType: hard + +"@protobufjs/eventemitter@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/eventemitter@npm:1.1.0" + checksum: 0369163a3d226851682f855f81413cbf166cd98f131edb94a0f67f79e75342d86e89df9d7a1df08ac28be2bc77e0a7f0200526bb6c2a407abbfee1f0262d5fd7 + languageName: node + linkType: hard + +"@protobufjs/fetch@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/fetch@npm:1.1.0" + dependencies: + "@protobufjs/aspromise": ^1.1.1 + "@protobufjs/inquire": ^1.1.0 + checksum: 3fce7e09eb3f1171dd55a192066450f65324fd5f7cc01a431df01bb00d0a895e6bfb5b0c5561ce157ee1d886349c90703d10a4e11a1a256418ff591b969b3477 + languageName: node + linkType: hard + +"@protobufjs/float@npm:^1.0.2": + version: 1.0.2 + resolution: "@protobufjs/float@npm:1.0.2" + checksum: 5781e1241270b8bd1591d324ca9e3a3128d2f768077a446187a049e36505e91bc4156ed5ac3159c3ce3d2ba3743dbc757b051b2d723eea9cd367bfd54ab29b2f + languageName: node + linkType: hard + +"@protobufjs/inquire@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/inquire@npm:1.1.0" + checksum: ca06f02eaf65ca36fb7498fc3492b7fc087bfcc85c702bac5b86fad34b692bdce4990e0ef444c1e2aea8c034227bd1f0484be02810d5d7e931c55445555646f4 + languageName: node + linkType: hard + +"@protobufjs/path@npm:^1.1.2": + version: 1.1.2 + resolution: "@protobufjs/path@npm:1.1.2" + checksum: 856eeb532b16a7aac071cacde5c5620df800db4c80cee6dbc56380524736205aae21e5ae47739114bf669ab5e8ba0e767a282ad894f3b5e124197cb9224445ee + languageName: node + linkType: hard + +"@protobufjs/pool@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/pool@npm:1.1.0" + checksum: d6a34fbbd24f729e2a10ee915b74e1d77d52214de626b921b2d77288bd8f2386808da2315080f2905761527cceffe7ec34c7647bd21a5ae41a25e8212ff79451 + languageName: node + linkType: hard + +"@protobufjs/utf8@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/utf8@npm:1.1.0" + checksum: f9bf3163d13aaa3b6f5e6fbf37a116e094ea021c0e1f2a7ccd0e12a29e2ce08dafba4e8b36e13f8ed7397e1591610ce880ed1289af4d66cf4ace8a36a9557278 + languageName: node + linkType: hard + "@resolver-engine/core@npm:^0.3.3": version: 0.3.3 resolution: "@resolver-engine/core@npm:0.3.3" @@ -5486,6 +5730,13 @@ __metadata: languageName: node linkType: hard +"@types/long@npm:^4.0.1": + version: 4.0.2 + resolution: "@types/long@npm:4.0.2" + checksum: d16cde7240d834cf44ba1eaec49e78ae3180e724cd667052b194a372f350d024cba8dd3f37b0864931683dab09ca935d52f0c4c1687178af5ada9fc85b0635f4 + languageName: node + linkType: hard + "@types/lru-cache@npm:^5.1.0": version: 5.1.1 resolution: "@types/lru-cache@npm:5.1.1" @@ -5540,6 +5791,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:>=13.7.0": + version: 20.8.9 + resolution: "@types/node@npm:20.8.9" + dependencies: + undici-types: ~5.26.4 + checksum: 0c05f3502a9507ff27e91dd6fd574fa6f391b3fafedcfe8e0c8d33351fb22d02c0121f854e5b6b3ecb9a8a468407ddf6e7ac0029fb236d4c7e1361ffc758a01f + languageName: node + linkType: hard + "@types/node@npm:^10.0.3": version: 10.17.60 resolution: "@types/node@npm:10.17.60" @@ -6601,6 +6861,15 @@ __metadata: languageName: node linkType: hard +"axios@npm:^0.21.2": + version: 0.21.4 + resolution: "axios@npm:0.21.4" + dependencies: + follow-redirects: ^1.14.0 + checksum: 44245f24ac971e7458f3120c92f9d66d1fc695e8b97019139de5b0cc65d9b8104647db01e5f46917728edfc0cfd88eb30fc4c55e6053eef4ace76768ce95ff3c + languageName: node + linkType: hard + "babel-code-frame@npm:^6.26.0": version: 6.26.0 resolution: "babel-code-frame@npm:6.26.0" @@ -7255,7 +7524,7 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.3.1": +"base64-js@npm:^1.3.0, base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" checksum: 669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 @@ -7286,7 +7555,7 @@ __metadata: languageName: node linkType: hard -"bech32@npm:1.1.4": +"bech32@npm:1.1.4, bech32@npm:^1.1.4": version: 1.1.4 resolution: "bech32@npm:1.1.4" checksum: 0e98db619191548390d6f09ff68b0253ba7ae6a55db93dfdbb070ba234c1fd3308c0606fbcc95fad50437227b10011e2698b89f0181f6e7f845c499bd14d0f4b @@ -8559,6 +8828,23 @@ __metadata: languageName: node linkType: hard +"cosmjs-types@npm:^0.8.0": + version: 0.8.0 + resolution: "cosmjs-types@npm:0.8.0" + dependencies: + long: ^4.0.0 + protobufjs: ~6.11.2 + checksum: 99714ec956d2cb2e521d39896c9c9a24cf9df0d370265c203646ea015b51e86472efc0cb11f67a80f0649d178b0bcff77ac659e67fdfc8b2437cd7a42018577f + languageName: node + linkType: hard + +"cosmjs-types@npm:^0.9.0": + version: 0.9.0 + resolution: "cosmjs-types@npm:0.9.0" + checksum: 9b00d169eca334f27418bb80b39e0cff0196af40b0079e1f85536246059279207b853bdb6ec224ead0a02d15d4b7f6bf16bc096d41c436426aa5f8976ed2b430 + languageName: node + linkType: hard + "crc-32@npm:^1.2.0": version: 1.2.2 resolution: "crc-32@npm:1.2.2" @@ -10866,6 +11152,16 @@ __metadata: languageName: node linkType: hard +"follow-redirects@npm:^1.14.0": + version: 1.15.3 + resolution: "follow-redirects@npm:1.15.3" + peerDependenciesMeta: + debug: + optional: true + checksum: 584da22ec5420c837bd096559ebfb8fe69d82512d5585004e36a3b4a6ef6d5905780e0c74508c7b72f907d1fa2b7bd339e613859e9c304d0dc96af2027fd0231 + languageName: node + linkType: hard + "for-each@npm:^0.3.3, for-each@npm:~0.3.3": version: 0.3.3 resolution: "for-each@npm:0.3.3" @@ -11501,6 +11797,15 @@ __metadata: languageName: node linkType: hard +"globalthis@npm:^1.0.1": + version: 1.0.3 + resolution: "globalthis@npm:1.0.3" + dependencies: + define-properties: ^1.1.3 + checksum: fbd7d760dc464c886d0196166d92e5ffb4c84d0730846d6621a39fbbc068aeeb9c8d1421ad330e94b7bca4bb4ea092f5f21f3d36077812af5d098b4dc006c998 + languageName: node + linkType: hard + "globby@npm:^10.0.1": version: 10.0.2 resolution: "globby@npm:10.0.2" @@ -13531,6 +13836,22 @@ __metadata: languageName: node linkType: hard +"libsodium-sumo@npm:^0.7.13": + version: 0.7.13 + resolution: "libsodium-sumo@npm:0.7.13" + checksum: d0905530c53c27a0c01348eed8abc2ecf3725c0647545cc528ea4bbd0ee63b7a471b56abefec5b293086ee64b5ba7cf911a655cd2c36f400a4bfec6e2d152ebd + languageName: node + linkType: hard + +"libsodium-wrappers-sumo@npm:^0.7.11": + version: 0.7.13 + resolution: "libsodium-wrappers-sumo@npm:0.7.13" + dependencies: + libsodium-sumo: ^0.7.13 + checksum: cdaa7ae5d64e71e860b40b5f2fbaec156adc7bc5606f7d32655b6ab84c9878fd90b3a41e99cb96380f0b5727d1ee1c6ad5b440bff35ce8289832e5c8cac99973 + languageName: node + linkType: hard + "lilconfig@npm:2.0.5": version: 2.0.5 resolution: "lilconfig@npm:2.0.5" @@ -13698,6 +14019,13 @@ __metadata: languageName: node linkType: hard +"long@npm:^4.0.0": + version: 4.0.0 + resolution: "long@npm:4.0.0" + checksum: 16afbe8f749c7c849db1f4de4e2e6a31ac6e617cead3bdc4f9605cb703cd20e1e9fc1a7baba674ffcca57d660a6e5b53a9e236d7b25a295d3855cca79cc06744 + languageName: node + linkType: hard + "looper@npm:^2.0.0": version: 2.0.0 resolution: "looper@npm:2.0.0" @@ -15325,6 +15653,13 @@ __metadata: languageName: node linkType: hard +"pako@npm:^2.0.2": + version: 2.1.0 + resolution: "pako@npm:2.1.0" + checksum: 71666548644c9a4d056bcaba849ca6fd7242c6cf1af0646d3346f3079a1c7f4a66ffec6f7369ee0dc88f61926c10d6ab05da3e1fca44b83551839e89edd75a3e + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -15841,6 +16176,30 @@ __metadata: languageName: node linkType: hard +"protobufjs@npm:^6.8.8, protobufjs@npm:~6.11.2, protobufjs@npm:~6.11.3": + version: 6.11.4 + resolution: "protobufjs@npm:6.11.4" + dependencies: + "@protobufjs/aspromise": ^1.1.2 + "@protobufjs/base64": ^1.1.2 + "@protobufjs/codegen": ^2.0.4 + "@protobufjs/eventemitter": ^1.1.0 + "@protobufjs/fetch": ^1.1.0 + "@protobufjs/float": ^1.0.2 + "@protobufjs/inquire": ^1.1.0 + "@protobufjs/path": ^1.1.2 + "@protobufjs/pool": ^1.1.0 + "@protobufjs/utf8": ^1.1.0 + "@types/long": ^4.0.1 + "@types/node": ">=13.7.0" + long: ^4.0.0 + bin: + pbjs: bin/pbjs + pbts: bin/pbts + checksum: b2fc6a01897b016c2a7e43a854ab4a3c57080f61be41e552235436e7a730711b8e89e47cb4ae52f0f065b5ab5d5989fc932f390337ce3a8ccf07203415700850 + languageName: node + linkType: hard + "proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" @@ -16193,6 +16552,13 @@ __metadata: languageName: node linkType: hard +"readonly-date@npm:^1.0.0": + version: 1.0.0 + resolution: "readonly-date@npm:1.0.0" + checksum: 78481e2abf3c2f9bc526029458aee3e2b1c476ca1434c4cc9db5c9aba51bf8f1323c1995d764ff01f2055b01f13e05416b2e14b387f644b0a5a56554c3ee9d0a + languageName: node + linkType: hard + "rechoir@npm:^0.6.2": version: 0.6.2 resolution: "rechoir@npm:0.6.2" @@ -17901,6 +18267,13 @@ __metadata: languageName: node linkType: hard +"symbol-observable@npm:^2.0.3": + version: 2.0.3 + resolution: "symbol-observable@npm:2.0.3" + checksum: 533dcf7a7925bada60dbaa06d678e7c4966dbf0959ccba7f60c22b0494ba5d9160d6a66f2951d45a80bf20e655a89f8b91c5f0458dd12faef28716b54f91f49c + languageName: node + linkType: hard + "sync-request@npm:^6.0.0": version: 6.1.0 resolution: "sync-request@npm:6.1.0" @@ -18648,6 +19021,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~5.26.4": + version: 5.26.5 + resolution: "undici-types@npm:5.26.5" + checksum: 3192ef6f3fd5df652f2dc1cd782b49d6ff14dc98e5dced492aa8a8c65425227da5da6aafe22523c67f035a272c599bb89cfe803c1db6311e44bed3042fc25487 + languageName: node + linkType: hard + "undici@npm:^5.11": version: 5.11.0 resolution: "undici@npm:5.11.0" @@ -20005,7 +20385,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^7.4.5": +"ws@npm:^7, ws@npm:^7.4.5": version: 7.5.9 resolution: "ws@npm:7.5.9" peerDependencies: @@ -20102,6 +20482,16 @@ __metadata: languageName: node linkType: hard +"xstream@npm:^11.14.0": + version: 11.14.0 + resolution: "xstream@npm:11.14.0" + dependencies: + globalthis: ^1.0.1 + symbol-observable: ^2.0.3 + checksum: eb96b5f9cd7e6a30d18688f337b8d1c658c85bb08754f2af4247275e25c0605c8435ad8125e04ad7d606c1b760fab4679841906f92718f35f8ce327074e1375a + languageName: node + linkType: hard + "xtend@npm:^4.0.0, xtend@npm:^4.0.1, xtend@npm:~4.0.0, xtend@npm:~4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2" From c66fa690517750754c6e2181bf4412f748d6d07e Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 26 Oct 2023 17:17:54 -0400 Subject: [PATCH 07/30] Implement IHypToken adapters --- .../sdk/src/token/adapters/CwTokenAdapter.ts | 63 +++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/typescript/sdk/src/token/adapters/CwTokenAdapter.ts b/typescript/sdk/src/token/adapters/CwTokenAdapter.ts index 7c672f7063..6edffbcb99 100644 --- a/typescript/sdk/src/token/adapters/CwTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CwTokenAdapter.ts @@ -80,6 +80,7 @@ export class CW20TokenAdapter extends BaseCwAdapter implements ITokenAdapter { chainName: string, multiProvider: MultiProtocolProvider, addresses: { token: Address }, + public readonly ibcDenom: string, ) { super(chainName, multiProvider, addresses); this.contractAddress = addresses.token; @@ -148,17 +149,43 @@ export class WarpCW20TokenAdapter getDomains(): Promise { throw new Error('Method not implemented.'); } + getRouterAddress(domain: number): Promise { throw new Error('Method not implemented.'); } + getAllRouters(): Promise<{ domain: number; address: Buffer }[]> { throw new Error('Method not implemented.'); } + quoteGasPayment(destination: number): Promise { throw new Error('Method not implemented.'); } - populateTransferRemoteTx(TransferParams: TransferRemoteParams): unknown { - throw new Error('Method not implemented.'); + + populateTransferRemoteTx({ + destination, + recipient, + weiAmountOrId, + txValue, + }: TransferRemoteParams): ExecuteInstruction { + return { + contractAddress: this.contractAddress, + msg: { + transfer_remote: { + dest_domain: destination, + recipient, + amount: weiAmountOrId.toString(), + }, + }, + funds: txValue + ? [ + { + amount: txValue.toString(), + denom: this.ibcDenom, + }, + ] + : [], + }; } } @@ -166,19 +193,47 @@ export class WarpNativeTokenAdapter extends NativeTokenAdapter implements IHypTokenAdapter { + public readonly contractAddress = this.addresses.token; + getDomains(): Promise { throw new Error('Method not implemented.'); } + getRouterAddress(domain: number): Promise { throw new Error('Method not implemented.'); } + getAllRouters(): Promise<{ domain: number; address: Buffer }[]> { throw new Error('Method not implemented.'); } + quoteGasPayment(destination: number): Promise { throw new Error('Method not implemented.'); } - populateTransferRemoteTx(TransferParams: TransferRemoteParams): unknown { - throw new Error('Method not implemented.'); + + populateTransferRemoteTx({ + destination, + recipient, + weiAmountOrId, + txValue, + }: TransferRemoteParams): ExecuteInstruction { + return { + contractAddress: this.contractAddress, + msg: { + transfer_remote: { + dest_domain: destination, + recipient, + amount: weiAmountOrId.toString(), + }, + }, + funds: txValue + ? [ + { + amount: txValue.toString(), + denom: this.ibcDenom, + }, + ] + : [], + }; } } From 56aa34df2a1cb55a8dc1faa99d3bca921410d2f5 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 26 Oct 2023 17:35:38 -0400 Subject: [PATCH 08/30] Rename new adapters for consistency Add some TODO comments --- .../sdk/src/app/MultiProtocolApp.test.ts | 2 + typescript/sdk/src/app/MultiProtocolApp.ts | 8 +-- typescript/sdk/src/core/MultiProtocolCore.ts | 1 + typescript/sdk/src/index.ts | 7 +++ .../sdk/src/metadata/ChainMetadataManager.ts | 1 + .../sdk/src/router/MultiProtocolRouterApps.ts | 1 + ...okenAdapter.ts => CosmWasmTokenAdapter.ts} | 52 ++++++++++++------- 7 files changed, 48 insertions(+), 24 deletions(-) rename typescript/sdk/src/token/adapters/{CwTokenAdapter.ts => CosmWasmTokenAdapter.ts} (81%) diff --git a/typescript/sdk/src/app/MultiProtocolApp.test.ts b/typescript/sdk/src/app/MultiProtocolApp.test.ts index 4239bfa89d..68f695ba52 100644 --- a/typescript/sdk/src/app/MultiProtocolApp.test.ts +++ b/typescript/sdk/src/app/MultiProtocolApp.test.ts @@ -7,6 +7,7 @@ import { MultiProtocolProvider } from '../providers/MultiProtocolProvider'; import { BaseAppAdapter, + BaseCosmWasmAdapter, BaseEvmAdapter, BaseSealevelAdapter, MultiProtocolApp, @@ -16,6 +17,7 @@ class TestMultiProtocolApp extends MultiProtocolApp { override protocolToAdapter(protocol: ProtocolType) { if (protocol === ProtocolType.Ethereum) return BaseEvmAdapter; if (protocol === ProtocolType.Sealevel) return BaseSealevelAdapter; + if (protocol === ProtocolType.Cosmos) return BaseCosmWasmAdapter; throw new Error(`No adapter for protocol ${protocol}`); } } diff --git a/typescript/sdk/src/app/MultiProtocolApp.ts b/typescript/sdk/src/app/MultiProtocolApp.ts index 60aa3a4007..10b98d56d3 100644 --- a/typescript/sdk/src/app/MultiProtocolApp.ts +++ b/typescript/sdk/src/app/MultiProtocolApp.ts @@ -1,4 +1,3 @@ -import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'; import { PublicKey } from '@solana/web3.js'; import debug from 'debug'; @@ -12,6 +11,7 @@ import { import { ChainMetadata } from '../metadata/chainMetadataTypes'; import { MultiProtocolProvider } from '../providers/MultiProtocolProvider'; import { + CosmJsWasmProvider, EthersV5Provider, SolanaWeb3Provider, TypedProvider, @@ -50,11 +50,11 @@ export class BaseEvmAdapter extends BaseAppAdapter { } } -export class BaseCwAdapter extends BaseAppAdapter { +export class BaseCosmWasmAdapter extends BaseAppAdapter { public readonly protocol: ProtocolType = ProtocolType.Cosmos; - public getProvider(): CosmWasmClient { - throw new Error('Method not implemented.'); + public getProvider(): CosmJsWasmProvider['provider'] { + return this.multiProvider.getCosmJsWasmProvider(this.chainName); } } diff --git a/typescript/sdk/src/core/MultiProtocolCore.ts b/typescript/sdk/src/core/MultiProtocolCore.ts index 971394f54d..517d2c9b4f 100644 --- a/typescript/sdk/src/core/MultiProtocolCore.ts +++ b/typescript/sdk/src/core/MultiProtocolCore.ts @@ -54,6 +54,7 @@ export class MultiProtocolCore extends MultiProtocolApp< ): AdapterClassType { if (protocol === ProtocolType.Ethereum) return EvmCoreAdapter; if (protocol === ProtocolType.Sealevel) return SealevelCoreAdapter; + // TODO cosmos core adapter here throw new Error(`No adapter for protocol ${protocol}`); } diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 64cb739f30..3d85507023 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -279,6 +279,13 @@ export { RouterViolationType, proxiedFactories, } from './router/types'; +export { + CW20Metadata, + CwHypNativeTokenAdapter, + CwHypTokenAdapter, + CwNativeTokenAdapter, + CwTokenAdapter, +} from './token/adapters/CosmWasmTokenAdapter'; export { EvmHypCollateralAdapter, EvmHypSyntheticAdapter, diff --git a/typescript/sdk/src/metadata/ChainMetadataManager.ts b/typescript/sdk/src/metadata/ChainMetadataManager.ts index 2651dbea1c..1f84c810b3 100644 --- a/typescript/sdk/src/metadata/ChainMetadataManager.ts +++ b/typescript/sdk/src/metadata/ChainMetadataManager.ts @@ -224,6 +224,7 @@ export class ChainMetadataManager { ) { url.searchParams.set('cluster', solanaChainToClusterName[metadata.name]); } + // TODO cosmos support here return url.toString(); } diff --git a/typescript/sdk/src/router/MultiProtocolRouterApps.ts b/typescript/sdk/src/router/MultiProtocolRouterApps.ts index 5b7248d956..b60bd9f910 100644 --- a/typescript/sdk/src/router/MultiProtocolRouterApps.ts +++ b/typescript/sdk/src/router/MultiProtocolRouterApps.ts @@ -27,6 +27,7 @@ export class MultiProtocolRouterApp< // enabling extensible generic types if (protocol === ProtocolType.Ethereum) return EvmRouterAdapter as any; if (protocol === ProtocolType.Sealevel) return SealevelRouterAdapter as any; + // TODO cosmos support here throw new Error(`No adapter for protocol ${protocol}`); } diff --git a/typescript/sdk/src/token/adapters/CwTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts similarity index 81% rename from typescript/sdk/src/token/adapters/CwTokenAdapter.ts rename to typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts index 6edffbcb99..ecb5452f33 100644 --- a/typescript/sdk/src/token/adapters/CwTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts @@ -2,7 +2,7 @@ import { ExecuteInstruction } from '@cosmjs/cosmwasm-stargate'; import { Address } from '@hyperlane-xyz/utils'; -import { BaseCwAdapter } from '../../app/MultiProtocolApp'; +import { BaseCosmWasmAdapter } from '../../app/MultiProtocolApp'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; import { ERC20Metadata } from '../config'; @@ -14,18 +14,21 @@ import { } from './ITokenAdapter'; // Interacts with IBC denom tokens -export class NativeTokenAdapter extends BaseCwAdapter implements ITokenAdapter { +export class CwNativeTokenAdapter + extends BaseCosmWasmAdapter + implements ITokenAdapter +{ constructor( chainName: string, multiProvider: MultiProtocolProvider, - addresses: any, public readonly ibcDenom: string, ) { - super(chainName, multiProvider, addresses); + super(chainName, multiProvider, {}); } async getBalance(address: Address): Promise { - const balance = await this.getProvider().getBalance(address, this.ibcDenom); + const provider = await this.getProvider(); + const balance = await provider.getBalance(address, this.ibcDenom); return balance.amount; } @@ -73,7 +76,10 @@ type BalanceResponse = { // https://github.com/CosmWasm/cw-plus/blob/main/packages/cw20/README.md // Interacts with CW20/721 contracts -export class CW20TokenAdapter extends BaseCwAdapter implements ITokenAdapter { +export class CwTokenAdapter + extends BaseCosmWasmAdapter + implements ITokenAdapter +{ public readonly contractAddress: string; constructor( @@ -87,20 +93,26 @@ export class CW20TokenAdapter extends BaseCwAdapter implements ITokenAdapter { } async getBalance(address: Address): Promise { - const balanceResponse: BalanceResponse = - await this.getProvider().queryContractSmart(this.contractAddress, { + const provider = await this.getProvider(); + const balanceResponse: BalanceResponse = await provider.queryContractSmart( + this.contractAddress, + { balance: { address, }, - }); + }, + ); return balanceResponse.balance; } async getMetadata(): Promise { - const tokenInfo: TokenInfoResponse = - await this.getProvider().queryContractSmart(this.contractAddress, { + const provider = await this.getProvider(); + const tokenInfo: TokenInfoResponse = await provider.queryContractSmart( + this.contractAddress, + { token_info: {}, - }); + }, + ); return { ...tokenInfo, totalSupply: tokenInfo.total_supply, @@ -142,15 +154,15 @@ export class CW20TokenAdapter extends BaseCwAdapter implements ITokenAdapter { } } -export class WarpCW20TokenAdapter - extends CW20TokenAdapter +export class CwHypTokenAdapter + extends CwTokenAdapter implements IHypTokenAdapter { getDomains(): Promise { throw new Error('Method not implemented.'); } - getRouterAddress(domain: number): Promise { + getRouterAddress(_domain: number): Promise { throw new Error('Method not implemented.'); } @@ -158,7 +170,7 @@ export class WarpCW20TokenAdapter throw new Error('Method not implemented.'); } - quoteGasPayment(destination: number): Promise { + quoteGasPayment(_destination: number): Promise { throw new Error('Method not implemented.'); } @@ -189,8 +201,8 @@ export class WarpCW20TokenAdapter } } -export class WarpNativeTokenAdapter - extends NativeTokenAdapter +export class CwHypNativeTokenAdapter + extends CwNativeTokenAdapter implements IHypTokenAdapter { public readonly contractAddress = this.addresses.token; @@ -199,7 +211,7 @@ export class WarpNativeTokenAdapter throw new Error('Method not implemented.'); } - getRouterAddress(domain: number): Promise { + getRouterAddress(_domain: number): Promise { throw new Error('Method not implemented.'); } @@ -207,7 +219,7 @@ export class WarpNativeTokenAdapter throw new Error('Method not implemented.'); } - quoteGasPayment(destination: number): Promise { + quoteGasPayment(_destination: number): Promise { throw new Error('Method not implemented.'); } From 71ef1d222dacd4c265c5c32b946430074ef6d667 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Fri, 27 Oct 2023 12:36:20 -0400 Subject: [PATCH 09/30] Expand ChainMetadata types for cosmos --- .../environments/mainnet2/liquidityLayer.ts | 11 +++- .../environments/mainnet2/token-bridge.ts | 8 ++- .../environments/testnet4/liquidityLayer.ts | 27 ++++++-- .../environments/testnet4/token-bridge.ts | 27 ++++++-- typescript/infra/src/config/agent/relayer.ts | 9 +-- typescript/sdk/package.json | 1 - typescript/sdk/src/index.ts | 2 + .../sdk/src/metadata/ChainMetadataManager.ts | 14 ++-- typescript/sdk/src/metadata/agentConfig.ts | 4 +- .../sdk/src/metadata/chainMetadata.test.ts | 25 +++++++ .../sdk/src/metadata/chainMetadataTypes.ts | 65 +++++++++++++++++-- typescript/sdk/src/utils/wagmi.ts | 7 +- 12 files changed, 162 insertions(+), 38 deletions(-) diff --git a/typescript/infra/config/environments/mainnet2/liquidityLayer.ts b/typescript/infra/config/environments/mainnet2/liquidityLayer.ts index e81d7564a8..6d0759f82c 100644 --- a/typescript/infra/config/environments/mainnet2/liquidityLayer.ts +++ b/typescript/infra/config/environments/mainnet2/liquidityLayer.ts @@ -5,6 +5,7 @@ import { Chains, RpcConsensusType, chainMetadata, + getDomainId, } from '@hyperlane-xyz/sdk'; import { LiquidityLayerRelayerConfig } from '../../../src/config/middleware'; @@ -12,8 +13,14 @@ import { LiquidityLayerRelayerConfig } from '../../../src/config/middleware'; import { environment } from './chains'; const circleDomainMapping = [ - { hyperlaneDomain: chainMetadata[Chains.ethereum].chainId, circleDomain: 0 }, - { hyperlaneDomain: chainMetadata[Chains.avalanche].chainId, circleDomain: 1 }, + { + hyperlaneDomain: getDomainId(chainMetadata[Chains.ethereum]), + circleDomain: 0, + }, + { + hyperlaneDomain: getDomainId(chainMetadata[Chains.avalanche]), + circleDomain: 1, + }, ]; export const bridgeAdapterConfigs: ChainMap = { diff --git a/typescript/infra/config/environments/mainnet2/token-bridge.ts b/typescript/infra/config/environments/mainnet2/token-bridge.ts index 66838a802a..16e568a413 100644 --- a/typescript/infra/config/environments/mainnet2/token-bridge.ts +++ b/typescript/infra/config/environments/mainnet2/token-bridge.ts @@ -4,11 +4,15 @@ import { Chains, CircleBridgeAdapterConfig, chainMetadata, + getDomainId, } from '@hyperlane-xyz/sdk'; const circleDomainMapping = [ - { hyperlaneDomain: chainMetadata[Chains.goerli].chainId, circleDomain: 0 }, - { hyperlaneDomain: chainMetadata[Chains.fuji].chainId, circleDomain: 1 }, + { + hyperlaneDomain: getDomainId(chainMetadata[Chains.goerli]), + circleDomain: 0, + }, + { hyperlaneDomain: getDomainId(chainMetadata[Chains.fuji]), circleDomain: 1 }, ]; // Circle deployed contracts diff --git a/typescript/infra/config/environments/testnet4/liquidityLayer.ts b/typescript/infra/config/environments/testnet4/liquidityLayer.ts index bba05105db..0ae83c4a03 100644 --- a/typescript/infra/config/environments/testnet4/liquidityLayer.ts +++ b/typescript/infra/config/environments/testnet4/liquidityLayer.ts @@ -4,23 +4,36 @@ import { ChainMap, Chains, chainMetadata, + getDomainId, } from '@hyperlane-xyz/sdk'; const circleDomainMapping = [ - { hyperlaneDomain: chainMetadata[Chains.goerli].chainId, circleDomain: 0 }, - { hyperlaneDomain: chainMetadata[Chains.fuji].chainId, circleDomain: 1 }, + { + hyperlaneDomain: getDomainId(chainMetadata[Chains.goerli]), + circleDomain: 0, + }, + { hyperlaneDomain: getDomainId(chainMetadata[Chains.fuji]), circleDomain: 1 }, ]; const wormholeDomainMapping = [ - { hyperlaneDomain: chainMetadata[Chains.goerli].chainId, wormholeDomain: 2 }, - { hyperlaneDomain: chainMetadata[Chains.fuji].chainId, wormholeDomain: 6 }, - { hyperlaneDomain: chainMetadata[Chains.mumbai].chainId, wormholeDomain: 5 }, { - hyperlaneDomain: chainMetadata[Chains.bsctestnet].chainId, + hyperlaneDomain: getDomainId(chainMetadata[Chains.goerli]), + wormholeDomain: 2, + }, + { + hyperlaneDomain: getDomainId(chainMetadata[Chains.fuji]), + wormholeDomain: 6, + }, + { + hyperlaneDomain: getDomainId(chainMetadata[Chains.mumbai]), + wormholeDomain: 5, + }, + { + hyperlaneDomain: getDomainId(chainMetadata[Chains.bsctestnet]), wormholeDomain: 4, }, { - hyperlaneDomain: chainMetadata[Chains.alfajores].chainId, + hyperlaneDomain: getDomainId(chainMetadata[Chains.alfajores]), wormholeDomain: 14, }, ]; diff --git a/typescript/infra/config/environments/testnet4/token-bridge.ts b/typescript/infra/config/environments/testnet4/token-bridge.ts index bba05105db..0ae83c4a03 100644 --- a/typescript/infra/config/environments/testnet4/token-bridge.ts +++ b/typescript/infra/config/environments/testnet4/token-bridge.ts @@ -4,23 +4,36 @@ import { ChainMap, Chains, chainMetadata, + getDomainId, } from '@hyperlane-xyz/sdk'; const circleDomainMapping = [ - { hyperlaneDomain: chainMetadata[Chains.goerli].chainId, circleDomain: 0 }, - { hyperlaneDomain: chainMetadata[Chains.fuji].chainId, circleDomain: 1 }, + { + hyperlaneDomain: getDomainId(chainMetadata[Chains.goerli]), + circleDomain: 0, + }, + { hyperlaneDomain: getDomainId(chainMetadata[Chains.fuji]), circleDomain: 1 }, ]; const wormholeDomainMapping = [ - { hyperlaneDomain: chainMetadata[Chains.goerli].chainId, wormholeDomain: 2 }, - { hyperlaneDomain: chainMetadata[Chains.fuji].chainId, wormholeDomain: 6 }, - { hyperlaneDomain: chainMetadata[Chains.mumbai].chainId, wormholeDomain: 5 }, { - hyperlaneDomain: chainMetadata[Chains.bsctestnet].chainId, + hyperlaneDomain: getDomainId(chainMetadata[Chains.goerli]), + wormholeDomain: 2, + }, + { + hyperlaneDomain: getDomainId(chainMetadata[Chains.fuji]), + wormholeDomain: 6, + }, + { + hyperlaneDomain: getDomainId(chainMetadata[Chains.mumbai]), + wormholeDomain: 5, + }, + { + hyperlaneDomain: getDomainId(chainMetadata[Chains.bsctestnet]), wormholeDomain: 4, }, { - hyperlaneDomain: chainMetadata[Chains.alfajores].chainId, + hyperlaneDomain: getDomainId(chainMetadata[Chains.alfajores]), wormholeDomain: 14, }, ]; diff --git a/typescript/infra/src/config/agent/relayer.ts b/typescript/infra/src/config/agent/relayer.ts index f68f37c155..a4e4f2a1ee 100644 --- a/typescript/infra/src/config/agent/relayer.ts +++ b/typescript/infra/src/config/agent/relayer.ts @@ -4,11 +4,12 @@ import { AgentConfig, AgentSignerKeyType, ChainMap, + GasPaymentEnforcement, MatchingList, + RelayerConfig as RelayerAgentConfig, chainMetadata, + getDomainId, } from '@hyperlane-xyz/sdk'; -import { GasPaymentEnforcement } from '@hyperlane-xyz/sdk'; -import { RelayerConfig as RelayerAgentConfig } from '@hyperlane-xyz/sdk'; import { ProtocolType } from '@hyperlane-xyz/utils'; import { AgentAwsUser } from '../../agents/aws'; @@ -147,9 +148,9 @@ export function routerMatchingList( } matchingList.push({ - originDomain: chainMetadata[source].chainId, + originDomain: getDomainId(chainMetadata[source]), senderAddress: routers[source].router, - destinationDomain: chainMetadata[destination].chainId, + destinationDomain: getDomainId(chainMetadata[destination]), recipientAddress: routers[destination].router, }); } diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 29757ed786..368a433562 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -15,7 +15,6 @@ "coingecko-api": "^1.0.10", "cosmjs-types": "^0.9.0", "cross-fetch": "^3.1.5", - "cw-hyperlane-sdk": "0.0.1", "debug": "^4.3.4", "ethers": "^5.7.2", "viem": "^1.3.1", diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 3d85507023..5ec865d353 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -159,10 +159,12 @@ export { export { ChainMetadata, ChainMetadataSchema, + ChainMetadataSchemaObject, ExplorerFamily, ExplorerFamilyValue, RpcUrl, RpcUrlSchema, + getChainIdNumber, getDomainId, isValidChainMetadata, } from './metadata/chainMetadataTypes'; diff --git a/typescript/sdk/src/metadata/ChainMetadataManager.ts b/typescript/sdk/src/metadata/ChainMetadataManager.ts index 1f84c810b3..08fe55d72e 100644 --- a/typescript/sdk/src/metadata/ChainMetadataManager.ts +++ b/typescript/sdk/src/metadata/ChainMetadataManager.ts @@ -130,7 +130,7 @@ export class ChainMetadataManager { /** * Get the id for a given chain name, chain id, or domain id */ - tryGetChainId(chainNameOrId: ChainName | number): number | null { + tryGetChainId(chainNameOrId: ChainName | number): number | string | null { return this.tryGetChainMetadata(chainNameOrId)?.chainId ?? null; } @@ -138,14 +138,14 @@ export class ChainMetadataManager { * 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 { + getChainId(chainNameOrId: ChainName | number): number | string { return this.getChainMetadata(chainNameOrId).chainId; } /** * Get the ids for all chains known to this MultiProvider */ - getKnownChainIds(): number[] { + getKnownChainIds(): Array { return Object.values(this.metadata).map((c) => c.chainId); } @@ -154,7 +154,8 @@ export class ChainMetadataManager { */ tryGetDomainId(chainNameOrId: ChainName | number): number | null { const metadata = this.tryGetChainMetadata(chainNameOrId); - return metadata?.domainId ?? metadata?.chainId ?? null; + if (!metadata) return null; + return getDomainId(metadata) ?? null; } /** @@ -162,8 +163,9 @@ export class ChainMetadataManager { * @throws if chain's metadata has not been set */ getDomainId(chainNameOrId: ChainName | number): number { - const metadata = this.getChainMetadata(chainNameOrId); - return getDomainId(metadata); + const domainId = this.tryGetDomainId(chainNameOrId); + if (!domainId) throw new Error(`No domain id set for ${chainNameOrId}`); + return domainId; } /** diff --git a/typescript/sdk/src/metadata/agentConfig.ts b/typescript/sdk/src/metadata/agentConfig.ts index b9c1a8545c..8431ea7385 100644 --- a/typescript/sdk/src/metadata/agentConfig.ts +++ b/typescript/sdk/src/metadata/agentConfig.ts @@ -7,7 +7,7 @@ import { z } from 'zod'; import { MultiProvider } from '../providers/MultiProvider'; import { ChainMap, ChainName } from '../types'; -import { ChainMetadata, ChainMetadataSchema } from './chainMetadataTypes'; +import { ChainMetadata, ChainMetadataSchemaObject } from './chainMetadataTypes'; import { ZHash, ZNzUint, ZUWei, ZUint } from './customZodTypes'; import { HyperlaneDeploymentArtifacts, @@ -80,7 +80,7 @@ export type AgentSignerAwsKey = z.infer; export type AgentSignerNode = z.infer; export type AgentSigner = z.infer; -export const AgentChainMetadataSchema = ChainMetadataSchema.merge( +export const AgentChainMetadataSchema = ChainMetadataSchemaObject.merge( HyperlaneDeploymentArtifactsSchema, ).extend({ customRpcUrls: z diff --git a/typescript/sdk/src/metadata/chainMetadata.test.ts b/typescript/sdk/src/metadata/chainMetadata.test.ts index e58f0dfac4..16de256ba1 100644 --- a/typescript/sdk/src/metadata/chainMetadata.test.ts +++ b/typescript/sdk/src/metadata/chainMetadata.test.ts @@ -50,6 +50,16 @@ describe('ChainMetadataSchema', () => { blocks, }), ).to.eq(true); + + expect( + isValidChainMetadata({ + ...minimalSchema, + protocol: ProtocolType.Cosmos, + chainId: 'cosmos', + bech32Prefix: 'cosmos', + slip44: 118, + }), + ).to.eq(true); }); it('Rejects invalid schemas', () => { expect( @@ -80,5 +90,20 @@ describe('ChainMetadataSchema', () => { name: 'Invalid name', }), ).to.eq(false); + + expect( + isValidChainMetadata({ + ...minimalSchema, + chainId: 'string-id', + }), + ).to.eq(false); + + expect( + isValidChainMetadata({ + ...minimalSchema, + protocol: ProtocolType.Cosmos, + chainId: 'string-id', + }), + ).to.eq(false); }); }); diff --git a/typescript/sdk/src/metadata/chainMetadataTypes.ts b/typescript/sdk/src/metadata/chainMetadataTypes.ts index caa9297e29..7e185878ee 100644 --- a/typescript/sdk/src/metadata/chainMetadataTypes.ts +++ b/typescript/sdk/src/metadata/chainMetadataTypes.ts @@ -59,7 +59,7 @@ export type RpcUrl = z.infer; * A collection of useful properties and settings for chains using Hyperlane * Specified as a Zod schema */ -export const ChainMetadataSchema = z.object({ +export const ChainMetadataSchemaObject = z.object({ name: z .string() .regex(/^[a-z][a-z0-9]*$/) @@ -71,9 +71,9 @@ export const ChainMetadataSchema = z.object({ .describe( 'The type of protocol used by this chain. See ProtocolType for valid values.', ), - chainId: ZNzUint.describe( - `The chainId of the chain. Uses EIP-155 for EVM chains`, - ), + chainId: z + .union([ZNzUint, z.string()]) + .describe(`The chainId of the chain. Uses EIP-155 for EVM chains`), domainId: ZNzUint.optional().describe( 'The domainId of the chain, should generally default to `chainId`. Consumer of `ChainMetadata` should use this value if present, but otherwise fallback to `chainId`.', ), @@ -161,12 +161,59 @@ export const ChainMetadataSchema = z.object({ .string() .optional() .describe('The URL of the gnosis safe transaction service.'), + bech32Prefix: z + .string() + .optional() + .describe('The human readable address prefix for the chains using bech32.'), + slip44: z.number().optional().describe('The SLIP-0044 coin type.'), isTestnet: z .boolean() .optional() .describe('Whether the chain is considered a testnet or a mainnet.'), }); +// Add refinements to the object schema to conditionally validate certain fields +export const ChainMetadataSchema = ChainMetadataSchemaObject.refine( + (metadata) => { + if ( + [ProtocolType.Ethereum, ProtocolType.Sealevel].includes( + metadata.protocol, + ) && + typeof metadata.chainId !== 'number' + ) + return false; + else if ( + metadata.protocol === ProtocolType.Cosmos && + typeof metadata.chainId !== 'string' + ) + return false; + else return true; + }, + { message: 'Invalid Chain Id', path: ['chainId'] }, +) + .refine( + (metadata) => { + if (typeof metadata.chainId === 'string' && !metadata.domainId) + return false; + else return true; + }, + { message: 'Domain Id required', path: ['domainId'] }, + ) + .refine( + (metadata) => { + if ( + metadata.protocol === ProtocolType.Cosmos && + (!metadata.bech32Prefix || !metadata.slip44) + ) + return false; + else return true; + }, + { + message: 'Bech32Prefix and Slip44 required for Cosmos chains', + path: ['bech32Prefix', 'slip44'], + }, + ); + export type ChainMetadata = z.infer & Ext; @@ -175,5 +222,13 @@ export function isValidChainMetadata(c: ChainMetadata): boolean { } export function getDomainId(chainMetadata: ChainMetadata): number { - return chainMetadata.domainId ?? chainMetadata.chainId; + if (chainMetadata.domainId) return chainMetadata.domainId; + else if (typeof chainMetadata.chainId === 'number') + return chainMetadata.chainId; + else throw new Error('Invalid chain metadata, no valid domainId'); +} + +export function getChainIdNumber(chainMetadata: ChainMetadata): number { + if (typeof chainMetadata.chainId === 'number') return chainMetadata.chainId; + else throw new Error('ChainId is not a number, chain may be of Cosmos type'); } diff --git a/typescript/sdk/src/utils/wagmi.ts b/typescript/sdk/src/utils/wagmi.ts index 2f08d80b78..e579c0ea03 100644 --- a/typescript/sdk/src/utils/wagmi.ts +++ b/typescript/sdk/src/utils/wagmi.ts @@ -3,7 +3,10 @@ import type { Chain as WagmiChain } from '@wagmi/chains'; import { objMap } from '@hyperlane-xyz/utils'; import { chainMetadata, etherToken } from '../consts/chainMetadata'; -import type { ChainMetadata } from '../metadata/chainMetadataTypes'; +import { + ChainMetadata, + getChainIdNumber, +} from '../metadata/chainMetadataTypes'; import type { ChainMap } from '../types'; // For convenient use in wagmi-based apps @@ -14,7 +17,7 @@ export const wagmiChainMetadata: ChainMap = objMap( export function chainMetadataToWagmiChain(metadata: ChainMetadata): WagmiChain { return { - id: metadata.chainId, + id: getChainIdNumber(metadata), name: metadata.displayName || metadata.name, network: metadata.name as string, nativeCurrency: metadata.nativeToken || etherToken, From 98869e381cc5676946d51fd2912bb10ac75b134f Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 26 Oct 2023 17:44:32 -0400 Subject: [PATCH 10/30] Implement cw router adapter --- .../sdk/src/core/adapters/CwCoreAdapter.ts | 35 ++++++ .../src/router/adapters/CwRouterAdapter.ts | 100 ++++++++++++++++++ .../sdk/src/token/adapters/ITokenAdapter.ts | 9 +- 3 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 typescript/sdk/src/core/adapters/CwCoreAdapter.ts create mode 100644 typescript/sdk/src/router/adapters/CwRouterAdapter.ts diff --git a/typescript/sdk/src/core/adapters/CwCoreAdapter.ts b/typescript/sdk/src/core/adapters/CwCoreAdapter.ts new file mode 100644 index 0000000000..c57f62b48d --- /dev/null +++ b/typescript/sdk/src/core/adapters/CwCoreAdapter.ts @@ -0,0 +1,35 @@ +import { HexString } from '@hyperlane-xyz/utils'; + +import { BaseCwAdapter } from '../../app/MultiProtocolApp'; +import { ChainName } from '../../types'; + +import { ProviderType, TypedTransactionReceipt } from '../../providers/ProviderType'; +import { ICoreAdapter } from './types'; + +// This adapter just routes to the HyperlaneCore +// Which implements the needed functionality for Cw chains +// TODO deprecate HyperlaneCore and replace all Cw-specific classes with adapters +export class CwCoreAdapter extends BaseCwAdapter implements ICoreAdapter { + public readonly contractAddress = this.addresses.mailbox; + + extractMessageIds( + sourceTx: TypedTransactionReceipt, + ): Array<{ messageId: string; destination: ChainName }> { + if (sourceTx.type !== ProviderType.Cosmos) { + throw new Error( + `Unsupported provider type for CosmosCoreAdapter ${sourceTx.type}`, + ); + } + // TODO: parse mailbox logs and extract message ids + throw new Error("Method not implemented."); + } + + async waitForMessageProcessed( + messageId: HexString, + destination: ChainName, + delayMs?: number, + maxAttempts?: number, + ): Promise { + throw new Error("Method not implemented."); + } +} diff --git a/typescript/sdk/src/router/adapters/CwRouterAdapter.ts b/typescript/sdk/src/router/adapters/CwRouterAdapter.ts new file mode 100644 index 0000000000..4ba3afd9c8 --- /dev/null +++ b/typescript/sdk/src/router/adapters/CwRouterAdapter.ts @@ -0,0 +1,100 @@ +import { Address, Domain } from '@hyperlane-xyz/utils'; + +import { BaseCwAdapter } from '../../app/MultiProtocolApp'; +import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; +import { ChainName } from '../../types'; + +import { IGasRouterAdapter, IRouterAdapter } from './types'; + +// TODO: import from ts bindings +type IsmResponse = { + ism: Address; +}; + +type OwnerResponse = { + owner: Address; +}; + +type DomainsResponse = { + domains: number[]; +}; + +type DomainRouteSet = { + domain: number; + route: string; +}; + +type RouteResponse = { + route: DomainRouteSet; +}; + +type RoutesResponse = { + routes: DomainRouteSet[]; +}; + +export class CwRouterAdapter extends BaseCwAdapter implements IRouterAdapter { + public readonly contractAddress: Address; + + constructor( + public readonly chainName: ChainName, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: { router: Address }, + ) { + super(chainName, multiProvider, addresses); + this.contractAddress = addresses.router; + } + + async interchainSecurityModule(): Promise
{ + const ismResponse: IsmResponse = + await this.getProvider().queryContractSmart(this.contractAddress, { + get_ism: {}, + }); + return ismResponse.ism; + } + + async owner(): Promise
{ + const ownerResponse: OwnerResponse = + await this.getProvider().queryContractSmart(this.contractAddress, { + owner: {}, + }); + return ownerResponse.owner; + } + + async remoteDomains(): Promise { + const domainsResponse: DomainsResponse = + await this.getProvider().queryContractSmart(this.contractAddress, { + domains: {}, + }); + return domainsResponse.domains; + } + + async remoteRouter(remoteDomain: Domain): Promise
{ + const routeResponse: RouteResponse = + await this.getProvider().queryContractSmart(this.contractAddress, { + get_route: { + domain: remoteDomain, + }, + }); + return routeResponse.route.route; + } + + async remoteRouters(): Promise> { + const routesResponse: RoutesResponse = + await this.getProvider().queryContractSmart(this.contractAddress, { + list_routes: {}, + }); + return routesResponse.routes.map((r) => ({ + domain: r.domain, + address: r.route, + })); + } +} + +export class CwGasRouterAdapter + extends CwRouterAdapter + implements IGasRouterAdapter +{ + async quoteGasPayment(_: ChainName): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/typescript/sdk/src/token/adapters/ITokenAdapter.ts b/typescript/sdk/src/token/adapters/ITokenAdapter.ts index b41c42ebae..d8fc1f233e 100644 --- a/typescript/sdk/src/token/adapters/ITokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/ITokenAdapter.ts @@ -27,10 +27,11 @@ export interface ITokenAdapter { } export interface IHypTokenAdapter extends ITokenAdapter { - getDomains(): Promise; - getRouterAddress(domain: Domain): Promise; - getAllRouters(): Promise>; - quoteGasPayment(destination: Domain): Promise; + // TODO: migrate into IRouterAdapter? + // getDomains(): Promise; + // getRouterAddress(domain: Domain): Promise; + // getAllRouters(): Promise>; + // quoteGasPayment(destination: Domain): Promise; populateTransferRemoteTx( TransferParams: TransferRemoteParams, ): unknown | Promise; From 3643f6339903cd9c00d6ca4b40a72a982bd8c22d Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 27 Oct 2023 13:59:23 -0400 Subject: [PATCH 11/30] Complete token adapters with type checking --- .../src/core/adapters/CosmWasmCoreAdapter.ts | 63 ++++ .../sdk/src/core/adapters/CwCoreAdapter.ts | 35 --- typescript/sdk/src/cw-types/Cw20Base.types.ts | 228 ++++++++++++++ typescript/sdk/src/cw-types/Mailbox.types.ts | 154 ++++++++++ typescript/sdk/src/cw-types/WarpCw20.types.ts | 225 ++++++++++++++ .../src/router/adapters/CwRouterAdapter.ts | 100 ------- .../token/adapters/CosmWasmTokenAdapter.ts | 281 +++++++++++------- .../sdk/src/token/adapters/ITokenAdapter.ts | 9 +- 8 files changed, 854 insertions(+), 241 deletions(-) create mode 100644 typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.ts delete mode 100644 typescript/sdk/src/core/adapters/CwCoreAdapter.ts create mode 100644 typescript/sdk/src/cw-types/Cw20Base.types.ts create mode 100644 typescript/sdk/src/cw-types/Mailbox.types.ts create mode 100644 typescript/sdk/src/cw-types/WarpCw20.types.ts delete mode 100644 typescript/sdk/src/router/adapters/CwRouterAdapter.ts diff --git a/typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.ts b/typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.ts new file mode 100644 index 0000000000..0d4c340935 --- /dev/null +++ b/typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.ts @@ -0,0 +1,63 @@ +import { Address, HexString } from '@hyperlane-xyz/utils'; + +import { BaseCosmWasmAdapter } from '../../app/MultiProtocolApp'; +import { + MessageDeliveredResponse, + QueryMsg, +} from '../../cw-types/Mailbox.types'; +import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; +import { + ProviderType, + TypedTransactionReceipt, +} from '../../providers/ProviderType'; +import { ChainName } from '../../types'; + +import { ICoreAdapter } from './types'; + +type MailboxResponse = MessageDeliveredResponse; + +// This adapter just routes to the HyperlaneCore +// Which implements the needed functionality for Cw chains +// TODO deprecate HyperlaneCore and replace all Cw-specific classes with adapters +export class CosmWasmCoreAdapter + extends BaseCosmWasmAdapter + implements ICoreAdapter +{ + constructor( + public readonly chainName: ChainName, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: { mailbox: Address }, + ) { + super(chainName, multiProvider, addresses); + } + + async queryMailbox(msg: QueryMsg): Promise { + const provider = await this.getProvider(); + const response: R = await provider.queryContractSmart( + this.addresses.mailbox, + msg, + ); + return response; + } + + extractMessageIds( + sourceTx: TypedTransactionReceipt, + ): Array<{ messageId: string; destination: ChainName }> { + if (sourceTx.type !== ProviderType.CosmJsWasm) { + throw new Error( + `Unsupported provider type for CosmosCoreAdapter ${sourceTx.type}`, + ); + } + // TODO: parse mailbox logs and extract message ids + throw new Error('Method not implemented.'); + } + + async waitForMessageProcessed( + messageId: HexString, + destination: ChainName, + delayMs?: number, + maxAttempts?: number, + ): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/typescript/sdk/src/core/adapters/CwCoreAdapter.ts b/typescript/sdk/src/core/adapters/CwCoreAdapter.ts deleted file mode 100644 index c57f62b48d..0000000000 --- a/typescript/sdk/src/core/adapters/CwCoreAdapter.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { HexString } from '@hyperlane-xyz/utils'; - -import { BaseCwAdapter } from '../../app/MultiProtocolApp'; -import { ChainName } from '../../types'; - -import { ProviderType, TypedTransactionReceipt } from '../../providers/ProviderType'; -import { ICoreAdapter } from './types'; - -// This adapter just routes to the HyperlaneCore -// Which implements the needed functionality for Cw chains -// TODO deprecate HyperlaneCore and replace all Cw-specific classes with adapters -export class CwCoreAdapter extends BaseCwAdapter implements ICoreAdapter { - public readonly contractAddress = this.addresses.mailbox; - - extractMessageIds( - sourceTx: TypedTransactionReceipt, - ): Array<{ messageId: string; destination: ChainName }> { - if (sourceTx.type !== ProviderType.Cosmos) { - throw new Error( - `Unsupported provider type for CosmosCoreAdapter ${sourceTx.type}`, - ); - } - // TODO: parse mailbox logs and extract message ids - throw new Error("Method not implemented."); - } - - async waitForMessageProcessed( - messageId: HexString, - destination: ChainName, - delayMs?: number, - maxAttempts?: number, - ): Promise { - throw new Error("Method not implemented."); - } -} diff --git a/typescript/sdk/src/cw-types/Cw20Base.types.ts b/typescript/sdk/src/cw-types/Cw20Base.types.ts new file mode 100644 index 0000000000..f8b45f50b6 --- /dev/null +++ b/typescript/sdk/src/cw-types/Cw20Base.types.ts @@ -0,0 +1,228 @@ +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export type Uint128 = string; +export type Logo = + | { + url: string; + } + | { + embedded: EmbeddedLogo; + }; +export type EmbeddedLogo = + | { + svg: Binary; + } + | { + png: Binary; + }; +export type Binary = string; +export interface InstantiateMsg { + decimals: number; + initial_balances: Cw20Coin[]; + marketing?: InstantiateMarketingInfo | null; + mint?: MinterResponse | null; + name: string; + symbol: string; +} +export interface Cw20Coin { + address: string; + amount: Uint128; +} +export interface InstantiateMarketingInfo { + description?: string | null; + logo?: Logo | null; + marketing?: string | null; + project?: string | null; +} +export interface MinterResponse { + cap?: Uint128 | null; + minter: string; +} +export type ExecuteMsg = + | { + transfer: { + amount: Uint128; + recipient: string; + }; + } + | { + burn: { + amount: Uint128; + }; + } + | { + send: { + amount: Uint128; + contract: string; + msg: Binary; + }; + } + | { + increase_allowance: { + amount: Uint128; + expires?: Expiration | null; + spender: string; + }; + } + | { + decrease_allowance: { + amount: Uint128; + expires?: Expiration | null; + spender: string; + }; + } + | { + transfer_from: { + amount: Uint128; + owner: string; + recipient: string; + }; + } + | { + send_from: { + amount: Uint128; + contract: string; + msg: Binary; + owner: string; + }; + } + | { + burn_from: { + amount: Uint128; + owner: string; + }; + } + | { + mint: { + amount: Uint128; + recipient: string; + }; + } + | { + update_minter: { + new_minter?: string | null; + }; + } + | { + update_marketing: { + description?: string | null; + marketing?: string | null; + project?: string | null; + }; + } + | { + upload_logo: Logo; + }; +export type Expiration = + | { + at_height: number; + } + | { + at_time: Timestamp; + } + | { + never: {}; + }; +export type Timestamp = Uint64; +export type Uint64 = string; +export type QueryMsg = + | { + balance: { + address: string; + }; + } + | { + token_info: {}; + } + | { + minter: {}; + } + | { + allowance: { + owner: string; + spender: string; + }; + } + | { + all_allowances: { + limit?: number | null; + owner: string; + start_after?: string | null; + }; + } + | { + all_spender_allowances: { + limit?: number | null; + spender: string; + start_after?: string | null; + }; + } + | { + all_accounts: { + limit?: number | null; + start_after?: string | null; + }; + } + | { + marketing_info: {}; + } + | { + download_logo: {}; + }; +export interface AllAccountsResponse { + accounts: string[]; + [k: string]: unknown; +} +export interface AllAllowancesResponse { + allowances: AllowanceInfo[]; + [k: string]: unknown; +} +export interface AllowanceInfo { + allowance: Uint128; + expires: Expiration; + spender: string; +} +export interface AllSpenderAllowancesResponse { + allowances: SpenderAllowanceInfo[]; + [k: string]: unknown; +} +export interface SpenderAllowanceInfo { + allowance: Uint128; + expires: Expiration; + owner: string; +} +export interface AllowanceResponse { + allowance: Uint128; + expires: Expiration; + [k: string]: unknown; +} +export interface BalanceResponse { + balance: Uint128; +} +export interface DownloadLogoResponse { + data: Binary; + mime_type: string; +} +export type LogoInfo = + | { + url: string; + } + | 'embedded'; +export type Addr = string; +export interface MarketingInfoResponse { + description?: string | null; + logo?: LogoInfo | null; + marketing?: Addr | null; + project?: string | null; + [k: string]: unknown; +} +export interface TokenInfoResponse { + decimals: number; + name: string; + symbol: string; + total_supply: Uint128; +} diff --git a/typescript/sdk/src/cw-types/Mailbox.types.ts b/typescript/sdk/src/cw-types/Mailbox.types.ts new file mode 100644 index 0000000000..b2e8c3236c --- /dev/null +++ b/typescript/sdk/src/cw-types/Mailbox.types.ts @@ -0,0 +1,154 @@ +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + domain: number; + hrp: string; + owner: string; +} +export type ExecuteMsg = + | { + ownable: OwnableMsg; + } + | { + set_default_ism: { + ism: string; + }; + } + | { + set_default_hook: { + hook: string; + }; + } + | { + set_required_hook: { + hook: string; + }; + } + | { + dispatch: DispatchMsg; + } + | { + process: { + message: HexBinary; + metadata: HexBinary; + }; + }; +export type OwnableMsg = + | { + init_ownership_transfer: { + next_owner: string; + }; + } + | { + revoke_ownership_transfer: {}; + } + | { + claim_ownership: {}; + }; +export type HexBinary = string; +export interface DispatchMsg { + dest_domain: number; + hook?: string | null; + metadata?: HexBinary | null; + msg_body: HexBinary; + recipient_addr: HexBinary; +} +export type QueryMsg = + | { + ownable: OwnableQueryMsg; + } + | { + hook: MailboxHookQueryMsg; + } + | { + mailbox: MailboxQueryMsg; + }; +export type OwnableQueryMsg = + | { + get_owner: {}; + } + | { + get_pending_owner: {}; + }; +export type MailboxHookQueryMsg = { + quote_dispatch: DispatchMsg; +}; +export type MailboxQueryMsg = + | { + hrp: {}; + } + | { + local_domain: {}; + } + | { + message_delivered: { + id: HexBinary; + }; + } + | { + default_ism: {}; + } + | { + default_hook: {}; + } + | { + required_hook: {}; + } + | { + nonce: {}; + } + | { + recipient_ism: { + recipient_addr: string; + }; + } + | { + latest_dispatch_id: {}; + }; +export interface DefaultHookResponse { + default_hook: string; +} +export interface DefaultIsmResponse { + default_ism: string; +} +export type Addr = string; +export interface OwnerResponse { + owner: Addr; +} +export interface PendingOwnerResponse { + pending_owner?: Addr | null; +} +export interface HrpResponse { + hrp: string; +} +export interface LatestDispatchedIdResponse { + message_id: HexBinary; +} +export interface LocalDomainResponse { + local_domain: number; +} +export interface MessageDeliveredResponse { + delivered: boolean; +} +export interface NonceResponse { + nonce: number; +} +export type Uint128 = string; +export interface QuoteDispatchResponse { + gas_amount?: Coin | null; +} +export interface Coin { + amount: Uint128; + denom: string; + [k: string]: unknown; +} +export interface RecipientIsmResponse { + ism: string; +} +export interface RequiredHookResponse { + required_hook: string; +} diff --git a/typescript/sdk/src/cw-types/WarpCw20.types.ts b/typescript/sdk/src/cw-types/WarpCw20.types.ts new file mode 100644 index 0000000000..f31b54da8f --- /dev/null +++ b/typescript/sdk/src/cw-types/WarpCw20.types.ts @@ -0,0 +1,225 @@ +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export type TokenModeMsgForCw20ModeBridgedAndCw20ModeCollateral = + | { + bridged: Cw20ModeBridged; + } + | { + collateral: Cw20ModeCollateral; + }; +export type Uint128 = string; +export type Logo = + | { + url: string; + } + | { + embedded: EmbeddedLogo; + }; +export type EmbeddedLogo = + | { + svg: Binary; + } + | { + png: Binary; + }; +export type Binary = string; +export interface InstantiateMsg { + hrp: string; + mailbox: string; + owner: string; + token: TokenModeMsgForCw20ModeBridgedAndCw20ModeCollateral; +} +export interface Cw20ModeBridged { + code_id: number; + init_msg: InstantiateMsg1; +} +export interface InstantiateMsg1 { + decimals: number; + initial_balances: Cw20Coin[]; + marketing?: InstantiateMarketingInfo | null; + mint?: MinterResponse | null; + name: string; + symbol: string; +} +export interface Cw20Coin { + address: string; + amount: Uint128; +} +export interface InstantiateMarketingInfo { + description?: string | null; + logo?: Logo | null; + marketing?: string | null; + project?: string | null; +} +export interface MinterResponse { + cap?: Uint128 | null; + minter: string; +} +export interface Cw20ModeCollateral { + address: string; +} +export type ExecuteMsg = + | { + ownable: OwnableMsg; + } + | { + router: RouterMsgForHexBinary; + } + | { + handle: HandleMsg; + } + | { + transfer_remote: { + amount: Uint128; + dest_domain: number; + recipient: HexBinary; + }; + } + | { + set_ism: { + ism: string; + }; + } + | { + set_hook: { + hook: string; + }; + }; +export type OwnableMsg = + | { + init_ownership_transfer: { + next_owner: string; + }; + } + | { + revoke_ownership_transfer: {}; + } + | { + claim_ownership: {}; + }; +export type RouterMsgForHexBinary = + | { + set_route: { + set: DomainRouteSetForHexBinary; + }; + } + | { + set_routes: { + set: DomainRouteSetForHexBinary[]; + }; + }; +export type HexBinary = string; +export interface DomainRouteSetForHexBinary { + domain: number; + route?: HexBinary | null; +} +export interface HandleMsg { + body: HexBinary; + origin: number; + sender: HexBinary; +} +export type QueryMsg = + | { + ownable: OwnableQueryMsg; + } + | { + router: RouterQueryForHexBinary; + } + | { + token_default: TokenWarpDefaultQueryMsg; + } + | { + ism_specifier: IsmSpecifierQueryMsg; + }; +export type OwnableQueryMsg = + | { + get_owner: {}; + } + | { + get_pending_owner: {}; + }; +export type RouterQueryForHexBinary = + | { + domains: {}; + } + | { + get_route: { + domain: number; + }; + } + | { + list_routes: { + limit?: number | null; + offset?: number | null; + order?: Order | null; + }; + }; +export type Order = 'asc' | 'desc'; +export type TokenWarpDefaultQueryMsg = + | { + token_type: {}; + } + | { + token_mode: {}; + }; +export type IsmSpecifierQueryMsg = { + interchain_security_module: []; +}; +export interface DomainsResponse { + domains: number[]; +} +export type Addr = string; +export interface OwnerResponse { + owner: Addr; +} +export interface PendingOwnerResponse { + pending_owner?: Addr | null; +} +export interface RouteResponseForHexBinary { + route: DomainRouteSetForHexBinary; +} +export interface InterchainSecurityModuleResponse { + ism?: Addr | null; +} +export interface RoutesResponseForHexBinary { + routes: DomainRouteSetForHexBinary[]; +} +export interface Empty { + [k: string]: unknown; +} +export type TokenMode = 'bridged' | 'collateral'; +export interface TokenModeResponse { + mode: TokenMode; +} +export type TokenType = + | { + native: TokenTypeNative; + } + | { + c_w20: { + contract: string; + }; + } + | { + c_w721: { + contract: string; + }; + }; +export type TokenTypeNative = + | { + fungible: { + denom: string; + }; + } + | { + non_fungible: { + class: string; + }; + }; +export interface TokenTypeResponse { + type: TokenType; +} diff --git a/typescript/sdk/src/router/adapters/CwRouterAdapter.ts b/typescript/sdk/src/router/adapters/CwRouterAdapter.ts deleted file mode 100644 index 4ba3afd9c8..0000000000 --- a/typescript/sdk/src/router/adapters/CwRouterAdapter.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Address, Domain } from '@hyperlane-xyz/utils'; - -import { BaseCwAdapter } from '../../app/MultiProtocolApp'; -import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; -import { ChainName } from '../../types'; - -import { IGasRouterAdapter, IRouterAdapter } from './types'; - -// TODO: import from ts bindings -type IsmResponse = { - ism: Address; -}; - -type OwnerResponse = { - owner: Address; -}; - -type DomainsResponse = { - domains: number[]; -}; - -type DomainRouteSet = { - domain: number; - route: string; -}; - -type RouteResponse = { - route: DomainRouteSet; -}; - -type RoutesResponse = { - routes: DomainRouteSet[]; -}; - -export class CwRouterAdapter extends BaseCwAdapter implements IRouterAdapter { - public readonly contractAddress: Address; - - constructor( - public readonly chainName: ChainName, - public readonly multiProvider: MultiProtocolProvider, - public readonly addresses: { router: Address }, - ) { - super(chainName, multiProvider, addresses); - this.contractAddress = addresses.router; - } - - async interchainSecurityModule(): Promise
{ - const ismResponse: IsmResponse = - await this.getProvider().queryContractSmart(this.contractAddress, { - get_ism: {}, - }); - return ismResponse.ism; - } - - async owner(): Promise
{ - const ownerResponse: OwnerResponse = - await this.getProvider().queryContractSmart(this.contractAddress, { - owner: {}, - }); - return ownerResponse.owner; - } - - async remoteDomains(): Promise { - const domainsResponse: DomainsResponse = - await this.getProvider().queryContractSmart(this.contractAddress, { - domains: {}, - }); - return domainsResponse.domains; - } - - async remoteRouter(remoteDomain: Domain): Promise
{ - const routeResponse: RouteResponse = - await this.getProvider().queryContractSmart(this.contractAddress, { - get_route: { - domain: remoteDomain, - }, - }); - return routeResponse.route.route; - } - - async remoteRouters(): Promise> { - const routesResponse: RoutesResponse = - await this.getProvider().queryContractSmart(this.contractAddress, { - list_routes: {}, - }); - return routesResponse.routes.map((r) => ({ - domain: r.domain, - address: r.route, - })); - } -} - -export class CwGasRouterAdapter - extends CwRouterAdapter - implements IGasRouterAdapter -{ - async quoteGasPayment(_: ChainName): Promise { - throw new Error('Method not implemented.'); - } -} diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts index ecb5452f33..77c82d8262 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts @@ -1,9 +1,26 @@ import { ExecuteInstruction } from '@cosmjs/cosmwasm-stargate'; +import { Coin } from '@cosmjs/stargate'; -import { Address } from '@hyperlane-xyz/utils'; +import { Address, Domain } from '@hyperlane-xyz/utils'; import { BaseCosmWasmAdapter } from '../../app/MultiProtocolApp'; +import { + BalanceResponse, + ExecuteMsg as Cw20Execute, + QueryMsg as Cw20Query, + TokenInfoResponse, +} from '../../cw-types/Cw20Base.types'; +import { + DomainsResponse, + InterchainSecurityModuleResponse, + OwnerResponse, + RouteResponseForHexBinary, + RoutesResponseForHexBinary, + ExecuteMsg as WarpCw20Execute, + QueryMsg as WarpCw20Query, +} from '../../cw-types/WarpCw20.types'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; +import { ChainName } from '../../types'; import { ERC20Metadata } from '../config'; import { @@ -61,61 +78,55 @@ export class CwNativeTokenAdapter } export type CW20Metadata = ERC20Metadata; +type CW20Response = TokenInfoResponse | BalanceResponse; -// TODO: import from cw20 bindings -type TokenInfoResponse = { - name: string; - symbol: string; - decimals: number; - total_supply: string; -}; - -type BalanceResponse = { - balance: string; -}; - -// https://github.com/CosmWasm/cw-plus/blob/main/packages/cw20/README.md // Interacts with CW20/721 contracts export class CwTokenAdapter extends BaseCosmWasmAdapter implements ITokenAdapter { - public readonly contractAddress: string; - constructor( - chainName: string, - multiProvider: MultiProtocolProvider, - addresses: { token: Address }, + public readonly chainName: string, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: { token: Address }, public readonly ibcDenom: string, ) { super(chainName, multiProvider, addresses); - this.contractAddress = addresses.token; } - async getBalance(address: Address): Promise { + async queryToken(msg: Cw20Query): Promise { const provider = await this.getProvider(); - const balanceResponse: BalanceResponse = await provider.queryContractSmart( - this.contractAddress, - { - balance: { - address, - }, - }, + const response: R = await provider.queryContractSmart( + this.addresses.token, + msg, ); - return balanceResponse.balance; + return response; } - async getMetadata(): Promise { - const provider = await this.getProvider(); - const tokenInfo: TokenInfoResponse = await provider.queryContractSmart( - this.contractAddress, - { - token_info: {}, + prepareToken(msg: Cw20Execute, funds?: Coin[]): ExecuteInstruction { + return { + contractAddress: this.addresses.token, + msg, + funds, + }; + } + + async getBalance(address: Address): Promise { + const resp = await this.queryToken({ + balance: { + address, }, - ); + }); + return resp.balance; + } + + async getMetadata(): Promise { + const resp = await this.queryToken({ + token_info: {}, + }); return { - ...tokenInfo, - totalSupply: tokenInfo.total_supply, + ...resp, + totalSupply: resp.total_supply, }; } @@ -124,53 +135,121 @@ export class CwTokenAdapter recipient, }: TransferParams): Promise { // TODO: check existing allowance - return { - contractAddress: this.contractAddress, - msg: { - increase_allowance: { - spender: recipient, - amount: weiAmountOrId, - expires: { - never: {}, - }, + return this.prepareToken({ + increase_allowance: { + spender: recipient, + amount: weiAmountOrId.toString(), + expires: { + never: {}, }, }, - }; + }); } async populateTransferTx({ weiAmountOrId, recipient, }: TransferParams): Promise { - return { - contractAddress: this.contractAddress, - msg: { - transfer: { - recipient, - amount: weiAmountOrId.toString(), - }, + return this.prepareToken({ + transfer: { + recipient, + amount: weiAmountOrId.toString(), }, - }; + }); } } +type TokenRouterResponse = + | InterchainSecurityModuleResponse + | DomainsResponse + | OwnerResponse + | RouteResponseForHexBinary + | RoutesResponseForHexBinary; + export class CwHypTokenAdapter extends CwTokenAdapter implements IHypTokenAdapter { - getDomains(): Promise { - throw new Error('Method not implemented.'); + constructor( + public readonly chainName: ChainName, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: { token: Address; router: Address }, + public readonly ibcDenom: string, + ) { + super(chainName, multiProvider, addresses, ibcDenom); } - getRouterAddress(_domain: number): Promise { - throw new Error('Method not implemented.'); + async queryRouter( + msg: WarpCw20Query, + ): Promise { + const provider = await this.getProvider(); + const response: R = await provider.queryContractSmart( + this.addresses.router, + msg, + ); + return response; } - getAllRouters(): Promise<{ domain: number; address: Buffer }[]> { - throw new Error('Method not implemented.'); + prepareRouter(msg: WarpCw20Execute, funds?: Coin[]): ExecuteInstruction { + return { + contractAddress: this.addresses.router, + msg, + funds, + }; + } + + async interchainSecurityModule(): Promise
{ + throw new Error('Router does not support ISM config yet.'); + } + + async owner(): Promise
{ + const resp = await this.queryRouter({ + ownable: { + get_owner: {}, + }, + }); + return resp.owner; } - quoteGasPayment(_destination: number): Promise { + async getDomains(): Promise { + const resp = await this.queryRouter({ + router: { + domains: {}, + }, + }); + return resp.domains; + } + + async getRouterAddress(domain: Domain): Promise { + const resp = await this.queryRouter({ + router: { + get_route: { + domain, + }, + }, + }); + const route = resp.route.route; + if (!route) { + throw new Error(`No route found for domain ${domain}`); + } + return Buffer.from(route); + } + + async getAllRouters(): Promise> { + const resp = await this.queryRouter({ + router: { + list_routes: {}, + }, + }); + return resp.routes + .filter((r) => r.route != null) + .map((r) => ({ + domain: r.domain, + address: Buffer.from(r.route!), + })); + } + + quoteGasPayment(destination: number): Promise { throw new Error('Method not implemented.'); } @@ -180,16 +259,15 @@ export class CwHypTokenAdapter weiAmountOrId, txValue, }: TransferRemoteParams): ExecuteInstruction { - return { - contractAddress: this.contractAddress, - msg: { + return this.prepareRouter( + { transfer_remote: { dest_domain: destination, recipient, amount: weiAmountOrId.toString(), }, }, - funds: txValue + txValue ? [ { amount: txValue.toString(), @@ -197,7 +275,7 @@ export class CwHypTokenAdapter }, ] : [], - }; + ); } } @@ -205,47 +283,48 @@ export class CwHypNativeTokenAdapter extends CwNativeTokenAdapter implements IHypTokenAdapter { - public readonly contractAddress = this.addresses.token; + private readonly cw20adapter: CwHypTokenAdapter; - getDomains(): Promise { - throw new Error('Method not implemented.'); + constructor( + public readonly chainName: ChainName, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: { router: Address }, + public readonly ibcDenom: string, + ) { + super(chainName, multiProvider, ibcDenom); + this.cw20adapter = new CwHypTokenAdapter( + chainName, + multiProvider, + { token: '', router: addresses.router }, + ibcDenom, + ); } - getRouterAddress(_domain: number): Promise { - throw new Error('Method not implemented.'); + async interchainSecurityModule(): Promise
{ + return this.cw20adapter.interchainSecurityModule(); } - getAllRouters(): Promise<{ domain: number; address: Buffer }[]> { - throw new Error('Method not implemented.'); + async owner(): Promise
{ + return this.cw20adapter.owner(); } - quoteGasPayment(_destination: number): Promise { - throw new Error('Method not implemented.'); + async getDomains(): Promise { + return this.cw20adapter.getDomains(); } - populateTransferRemoteTx({ - destination, - recipient, - weiAmountOrId, - txValue, - }: TransferRemoteParams): ExecuteInstruction { - return { - contractAddress: this.contractAddress, - msg: { - transfer_remote: { - dest_domain: destination, - recipient, - amount: weiAmountOrId.toString(), - }, - }, - funds: txValue - ? [ - { - amount: txValue.toString(), - denom: this.ibcDenom, - }, - ] - : [], - }; + async getRouterAddress(domain: Domain): Promise { + return this.cw20adapter.getRouterAddress(domain); + } + + async getAllRouters(): Promise> { + return this.cw20adapter.getAllRouters(); + } + + quoteGasPayment(destination: number): Promise { + return this.cw20adapter.quoteGasPayment(destination); + } + + populateTransferRemoteTx(params: TransferRemoteParams): ExecuteInstruction { + return this.cw20adapter.populateTransferRemoteTx(params); } } diff --git a/typescript/sdk/src/token/adapters/ITokenAdapter.ts b/typescript/sdk/src/token/adapters/ITokenAdapter.ts index d8fc1f233e..b41c42ebae 100644 --- a/typescript/sdk/src/token/adapters/ITokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/ITokenAdapter.ts @@ -27,11 +27,10 @@ export interface ITokenAdapter { } export interface IHypTokenAdapter extends ITokenAdapter { - // TODO: migrate into IRouterAdapter? - // getDomains(): Promise; - // getRouterAddress(domain: Domain): Promise; - // getAllRouters(): Promise>; - // quoteGasPayment(destination: Domain): Promise; + getDomains(): Promise; + getRouterAddress(domain: Domain): Promise; + getAllRouters(): Promise>; + quoteGasPayment(destination: Domain): Promise; populateTransferRemoteTx( TransferParams: TransferRemoteParams, ): unknown | Promise; From 453fbe452dad63e4bb703c8948b724b42df3549c Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 27 Oct 2023 14:05:15 -0400 Subject: [PATCH 12/30] Fix lint --- typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.ts | 8 ++++---- typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.ts b/typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.ts index 0d4c340935..a6ac5fb077 100644 --- a/typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.ts +++ b/typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.ts @@ -53,10 +53,10 @@ export class CosmWasmCoreAdapter } async waitForMessageProcessed( - messageId: HexString, - destination: ChainName, - delayMs?: number, - maxAttempts?: number, + _messageId: HexString, + _destination: ChainName, + _delayMs?: number, + _maxAttempts?: number, ): Promise { throw new Error('Method not implemented.'); } diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts index 77c82d8262..6d6bf5349d 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts @@ -249,7 +249,7 @@ export class CwHypTokenAdapter })); } - quoteGasPayment(destination: number): Promise { + quoteGasPayment(_destination: number): Promise { throw new Error('Method not implemented.'); } From 5cf0898003a81eac75638e20d2e4c63ec86b3027 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 27 Oct 2023 14:07:50 -0400 Subject: [PATCH 13/30] Update gh linguist generated attributes --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..e0732994f9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +typescript/sdk/src/cw-types/*.types.ts linguist-generated=true +rust/chains/hyperlane-ethereum/abis/*.abi.json linguist-generated=true From f7c49541c680f50d4dfcf3ef63ba09f101e57002 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 27 Oct 2023 15:34:56 -0400 Subject: [PATCH 14/30] Test native transferRemote with signer --- .../src/providers/MultiProtocolProvider.ts | 1 + .../adapters/CosmWasmTokenAdapter.test.ts | 114 ++++++++++++++++++ .../token/adapters/CosmWasmTokenAdapter.ts | 92 +++++++++++--- 3 files changed, 189 insertions(+), 18 deletions(-) create mode 100644 typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts diff --git a/typescript/sdk/src/providers/MultiProtocolProvider.ts b/typescript/sdk/src/providers/MultiProtocolProvider.ts index 1ed91bb058..868c65bb6a 100644 --- a/typescript/sdk/src/providers/MultiProtocolProvider.ts +++ b/typescript/sdk/src/providers/MultiProtocolProvider.ts @@ -28,6 +28,7 @@ export const PROTOCOL_DEFAULT_PROVIDER_TYPE: Partial< > = { [ProtocolType.Ethereum]: ProviderType.EthersV5, [ProtocolType.Sealevel]: ProviderType.SolanaWeb3, + [ProtocolType.Cosmos]: ProviderType.CosmJsWasm, }; export interface MultiProtocolProviderOptions { diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts new file mode 100644 index 0000000000..84b0105fef --- /dev/null +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts @@ -0,0 +1,114 @@ +import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; +import { Secp256k1, keccak256 } from '@cosmjs/crypto'; +import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; +import { GasPrice, SigningStargateClient } from '@cosmjs/stargate'; +import { Tendermint37Client } from '@cosmjs/tendermint-rpc'; + +import { ProtocolType } from '@hyperlane-xyz/utils'; + +import { + ChainMetadata, + ChainMetadataSchema, +} from '../../metadata/chainMetadataTypes'; +import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; + +import { CwHypNativeTokenAdapter } from './CosmWasmTokenAdapter'; + +const router = + 'dual1nzkcccxw00u9egqfuuq2ue23hjj6kxmfvmc5y0r7wchk5e6nypns6768kk'; + +const dualitydevnet: ChainMetadata = { + name: 'dualitydevnet', + chainId: 'duality-devnet', + domainId: 33333, + protocol: ProtocolType.Cosmos, + bech32Prefix: 'dual', + slip44: 118, // what is this + rpcUrls: [ + { + http: 'http://54.149.31.83:26657', + }, + ], + isTestnet: true, +}; + +const ibcDenom = + 'ibc/B5CB286F69D48B2C4F6F8D8CF59011C40590DCF8A91617A5FBA9FF0A7B21307F'; + +const signer = ''; + +export async function getSigningClient() { + const wallet = await DirectSecp256k1Wallet.fromKey( + Buffer.from(signer, 'hex'), + dualitydevnet.bech32Prefix!, + ); + + const [account] = await wallet.getAccounts(); + + const clientBase = await Tendermint37Client.connect( + dualitydevnet.rpcUrls[0].http, + ); + + const gasPrice = GasPrice.fromString('0.025token'); + + const wasm = await SigningCosmWasmClient.createWithSigner( + clientBase, + wallet, + { + gasPrice, + }, + ); + const stargate = await SigningStargateClient.createWithSigner( + clientBase, + wallet, + { + gasPrice, + }, + ); + + const pubkey = Secp256k1.uncompressPubkey(account.pubkey); + const ethaddr = keccak256(pubkey.slice(1)).slice(-20); + + return { + wasm, + stargate, + signer: account.address, + signer_addr: Buffer.from(ethaddr).toString('hex'), + signer_pubkey: Buffer.from(account.pubkey).toString('hex'), + }; +} + +async function main() { + const parsed = ChainMetadataSchema.parse(dualitydevnet); + console.log({ parsed }); + const multiProtocolProvider = new MultiProtocolProvider({ + dualitydevnet, + }); + + const adapter = new CwHypNativeTokenAdapter( + dualitydevnet.name, + multiProtocolProvider, + { router }, + ibcDenom, + ); + const owner = await adapter.owner(); + const routers = await adapter.getAllRouters(); + const domains = await adapter.getDomains(); + const balance = await adapter.getBalance(owner); + + console.log({ owner, routers, domains, balance }); + + const msg = await adapter.populateTransferRemoteTx({ + destination: domains[0], + recipient: '0xE000fA4E466831dB288290Dd97e66560fb3d7d28', + weiAmountOrId: 10, + txValue: '2500000', + }); + + const client = await getSigningClient(); + + const tx = await client.wasm.executeMultiple(client.signer, [msg], 'auto'); + console.log({ tx }); +} + +main().catch(console.error); diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts index 6d6bf5349d..49eb4dc57e 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts @@ -1,7 +1,12 @@ import { ExecuteInstruction } from '@cosmjs/cosmwasm-stargate'; import { Coin } from '@cosmjs/stargate'; -import { Address, Domain } from '@hyperlane-xyz/utils'; +import { + Address, + Domain, + addressToBytes32, + strip0x, +} from '@hyperlane-xyz/utils'; import { BaseCosmWasmAdapter } from '../../app/MultiProtocolApp'; import { @@ -16,6 +21,8 @@ import { OwnerResponse, RouteResponseForHexBinary, RoutesResponseForHexBinary, + TokenType, + TokenTypeResponse, ExecuteMsg as WarpCw20Execute, QueryMsg as WarpCw20Query, } from '../../cw-types/WarpCw20.types'; @@ -89,7 +96,6 @@ export class CwTokenAdapter public readonly chainName: string, public readonly multiProvider: MultiProtocolProvider, public readonly addresses: { token: Address }, - public readonly ibcDenom: string, ) { super(chainName, multiProvider, addresses); } @@ -160,6 +166,7 @@ export class CwTokenAdapter } type TokenRouterResponse = + | TokenTypeResponse | InterchainSecurityModuleResponse | DomainsResponse | OwnerResponse @@ -174,9 +181,9 @@ export class CwHypTokenAdapter public readonly chainName: ChainName, public readonly multiProvider: MultiProtocolProvider, public readonly addresses: { token: Address; router: Address }, - public readonly ibcDenom: string, + public readonly gasDenom = 'token', ) { - super(chainName, multiProvider, addresses, ibcDenom); + super(chainName, multiProvider, addresses); } async queryRouter( @@ -198,6 +205,15 @@ export class CwHypTokenAdapter }; } + async tokenType(): Promise { + const resp = await this.queryRouter({ + token_default: { + token_type: {}, + }, + }); + return resp.type; + } + async interchainSecurityModule(): Promise
{ throw new Error('Router does not support ISM config yet.'); } @@ -259,22 +275,23 @@ export class CwHypTokenAdapter weiAmountOrId, txValue, }: TransferRemoteParams): ExecuteInstruction { + if (!txValue) { + throw new Error('txValue is required for native tokens'); + } return this.prepareRouter( { transfer_remote: { dest_domain: destination, - recipient, + recipient: strip0x(addressToBytes32(recipient)), amount: weiAmountOrId.toString(), }, }, - txValue - ? [ - { - amount: txValue.toString(), - denom: this.ibcDenom, - }, - ] - : [], + [ + { + amount: txValue.toString(), + denom: this.gasDenom, + }, + ], ); } } @@ -289,14 +306,14 @@ export class CwHypNativeTokenAdapter public readonly chainName: ChainName, public readonly multiProvider: MultiProtocolProvider, public readonly addresses: { router: Address }, - public readonly ibcDenom: string, + public readonly gasDenom: string, ) { - super(chainName, multiProvider, ibcDenom); + super(chainName, multiProvider, gasDenom); this.cw20adapter = new CwHypTokenAdapter( chainName, multiProvider, { token: '', router: addresses.router }, - ibcDenom, + gasDenom, ); } @@ -324,7 +341,46 @@ export class CwHypNativeTokenAdapter return this.cw20adapter.quoteGasPayment(destination); } - populateTransferRemoteTx(params: TransferRemoteParams): ExecuteInstruction { - return this.cw20adapter.populateTransferRemoteTx(params); + async denom(): Promise { + const tokenType = await this.cw20adapter.tokenType(); + if ('native' in tokenType) { + if ('fungible' in tokenType.native) { + return tokenType.native.fungible.denom; + } + } + + throw new Error(`Token type not supported: ${tokenType}`); + } + + async populateTransferRemoteTx({ + destination, + recipient, + weiAmountOrId, + txValue, + }: TransferRemoteParams): Promise { + if (!txValue) { + throw new Error('txValue is required for native tokens'); + } + + const collateralDenom = await this.denom(); + return this.cw20adapter.prepareRouter( + { + transfer_remote: { + dest_domain: destination, + recipient: strip0x(addressToBytes32(recipient)), + amount: weiAmountOrId.toString(), + }, + }, + [ + { + amount: weiAmountOrId.toString(), + denom: collateralDenom, + }, + { + amount: txValue.toString(), + denom: this.gasDenom, + }, + ], + ); } } From 023c651e9c06e8ea9905a76384fcc22ff7e0a4a9 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 27 Oct 2023 15:45:08 -0400 Subject: [PATCH 15/30] Fix test ibc denom plumbing --- .../token/adapters/CosmWasmTokenAdapter.test.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts index 84b0105fef..c192d5ed03 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts @@ -6,10 +6,7 @@ import { Tendermint37Client } from '@cosmjs/tendermint-rpc'; import { ProtocolType } from '@hyperlane-xyz/utils'; -import { - ChainMetadata, - ChainMetadataSchema, -} from '../../metadata/chainMetadataTypes'; +import { ChainMetadata } from '../../metadata/chainMetadataTypes'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; import { CwHypNativeTokenAdapter } from './CosmWasmTokenAdapter'; @@ -32,9 +29,6 @@ const dualitydevnet: ChainMetadata = { isTestnet: true, }; -const ibcDenom = - 'ibc/B5CB286F69D48B2C4F6F8D8CF59011C40590DCF8A91617A5FBA9FF0A7B21307F'; - const signer = ''; export async function getSigningClient() { @@ -79,17 +73,17 @@ export async function getSigningClient() { } async function main() { - const parsed = ChainMetadataSchema.parse(dualitydevnet); - console.log({ parsed }); const multiProtocolProvider = new MultiProtocolProvider({ dualitydevnet, }); + const gasDenom = 'token'; + const adapter = new CwHypNativeTokenAdapter( dualitydevnet.name, multiProtocolProvider, { router }, - ibcDenom, + gasDenom, ); const owner = await adapter.owner(); const routers = await adapter.getAllRouters(); From 64068359b141af2984987a7ba045e66267f30181 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 28 Oct 2023 11:42:51 -0400 Subject: [PATCH 16/30] Minor fixes for cosmos support --- .../sdk/src/metadata/ChainMetadataManager.ts | 20 ++++++++----------- .../token/adapters/CosmWasmTokenAdapter.ts | 4 ++-- typescript/utils/src/addresses.ts | 7 +++++-- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/typescript/sdk/src/metadata/ChainMetadataManager.ts b/typescript/sdk/src/metadata/ChainMetadataManager.ts index 08fe55d72e..237cdccb24 100644 --- a/typescript/sdk/src/metadata/ChainMetadataManager.ts +++ b/typescript/sdk/src/metadata/ChainMetadataManager.ts @@ -1,6 +1,6 @@ import { Debugger, debug } from 'debug'; -import { ProtocolType, exclude, isNumeric, pick } from '@hyperlane-xyz/utils'; +import { ProtocolType, exclude, pick } from '@hyperlane-xyz/utils'; import { chainMetadata as defaultChainMetadata, @@ -64,7 +64,7 @@ export class ChainMetadataManager { chainId == metadata.chainId || domainId == metadata.chainId || (metadata.domainId && - (chainId == metadata.domainId || domainId === metadata.domainId)); + (chainId == metadata.domainId || domainId == metadata.domainId)); if (idCollision) throw new Error( `Chain/Domain id collision: ${name} and ${metadata.name}`, @@ -80,16 +80,12 @@ export class ChainMetadataManager { tryGetChainMetadata( chainNameOrId: ChainName | number, ): ChainMetadata | null { - let chainMetadata: ChainMetadata | 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]; - } + // First check if it's a chain name + if (this.metadata[chainNameOrId]) return this.metadata[chainNameOrId]; + // Otherwise search by chain id and domain id + const chainMetadata = Object.values(this.metadata).find( + (m) => m.chainId == chainNameOrId || m.domainId == chainNameOrId, + ); return chainMetadata || null; } diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts index 49eb4dc57e..b72476f7fe 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts @@ -248,7 +248,7 @@ export class CwHypTokenAdapter if (!route) { throw new Error(`No route found for domain ${domain}`); } - return Buffer.from(route); + return Buffer.from(route, 'hex'); } async getAllRouters(): Promise> { @@ -261,7 +261,7 @@ export class CwHypTokenAdapter .filter((r) => r.route != null) .map((r) => ({ domain: r.domain, - address: Buffer.from(r.route!), + address: Buffer.from(r.route!, 'hex'), })); } diff --git a/typescript/utils/src/addresses.ts b/typescript/utils/src/addresses.ts index 02ef3d5d6a..2b6c9bd48d 100644 --- a/typescript/utils/src/addresses.ts +++ b/typescript/utils/src/addresses.ts @@ -7,7 +7,7 @@ import { Address, HexString, ProtocolType } from './types'; const EVM_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/; const SEALEVEL_ADDRESS_REGEX = /^[a-zA-Z0-9]{36,44}$/; const COSMOS_ADDRESS_REGEX = - /^[a-z]{1,10}1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38}$/; // Bech32 address with 32 chars and 6 checksum chars + /^[a-z]{1,10}1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38,58}$/; // Bech32 const EVM_TX_HASH_REGEX = /^0x([A-Fa-f0-9]{64})$/; const SEALEVEL_TX_HASH_REGEX = /^[a-zA-Z1-9]{88}$/; @@ -295,11 +295,14 @@ export function bytesToProtocolAddress( export function convertToProtocolAddress( address: string, protocol: ProtocolType, + prefix?: string, ) { const currentProtocol = getAddressProtocolType(address); + if (!currentProtocol) + throw new Error(`Unknown address protocol for ${address}`); if (currentProtocol === protocol) return address; const addressBytes = addressToBytes(address, currentProtocol); - return bytesToProtocolAddress(addressBytes, protocol); + return bytesToProtocolAddress(addressBytes, protocol, prefix); } export function ensure0x(hexstr: string) { From edec28b695f6f5e420c9bc46ef24354530fa518e Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sat, 28 Oct 2023 18:06:24 -0400 Subject: [PATCH 17/30] Normalize cosmwasm adapter names and args --- typescript/sdk/src/index.ts | 6 +-- .../adapters/CosmWasmTokenAdapter.test.ts | 9 ++-- .../token/adapters/CosmWasmTokenAdapter.ts | 47 +++++++++++++------ .../token/adapters/SealevelTokenAdapter.ts | 2 +- 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 5ec865d353..89a8cb6a3a 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -283,8 +283,9 @@ export { } from './router/types'; export { CW20Metadata, - CwHypNativeTokenAdapter, - CwHypTokenAdapter, + CwHypCollateralAdapter, + CwHypNativeAdapter, + CwHypSyntheticAdapter, CwNativeTokenAdapter, CwTokenAdapter, } from './token/adapters/CosmWasmTokenAdapter'; @@ -304,7 +305,6 @@ export { SealevelHypCollateralAdapter, SealevelHypNativeAdapter, SealevelHypSyntheticAdapter, - SealevelHypTokenAdapter, SealevelNativeTokenAdapter, SealevelTokenAdapter, } from './token/adapters/SealevelTokenAdapter'; diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts index c192d5ed03..8ce9e7c9ee 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + +/* eslint-disable no-console */ import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; import { Secp256k1, keccak256 } from '@cosmjs/crypto'; import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; @@ -9,7 +12,7 @@ import { ProtocolType } from '@hyperlane-xyz/utils'; import { ChainMetadata } from '../../metadata/chainMetadataTypes'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; -import { CwHypNativeTokenAdapter } from './CosmWasmTokenAdapter'; +import { CwHypNativeAdapter } from './CosmWasmTokenAdapter'; const router = 'dual1nzkcccxw00u9egqfuuq2ue23hjj6kxmfvmc5y0r7wchk5e6nypns6768kk'; @@ -79,10 +82,10 @@ async function main() { const gasDenom = 'token'; - const adapter = new CwHypNativeTokenAdapter( + const adapter = new CwHypNativeAdapter( dualitydevnet.name, multiProtocolProvider, - { router }, + { warpRouter: router }, gasDenom, ); const owner = await adapter.owner(); diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts index b72476f7fe..e26cbeaa4b 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts @@ -43,11 +43,12 @@ export class CwNativeTokenAdapter implements ITokenAdapter { constructor( - chainName: string, - multiProvider: MultiProtocolProvider, - public readonly ibcDenom: string, + public readonly chainName: string, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: Record, + public readonly ibcDenom: string = 'token', ) { - super(chainName, multiProvider, {}); + super(chainName, multiProvider, addresses); } async getBalance(address: Address): Promise { @@ -96,6 +97,7 @@ export class CwTokenAdapter public readonly chainName: string, public readonly multiProvider: MultiProtocolProvider, public readonly addresses: { token: Address }, + public readonly denom = 'token', ) { super(chainName, multiProvider, addresses); } @@ -173,14 +175,14 @@ type TokenRouterResponse = | RouteResponseForHexBinary | RoutesResponseForHexBinary; -export class CwHypTokenAdapter +export class CwHypSyntheticAdapter extends CwTokenAdapter implements IHypTokenAdapter { constructor( public readonly chainName: ChainName, public readonly multiProvider: MultiProtocolProvider, - public readonly addresses: { token: Address; router: Address }, + public readonly addresses: { token: Address; warpRouter: Address }, public readonly gasDenom = 'token', ) { super(chainName, multiProvider, addresses); @@ -191,7 +193,7 @@ export class CwHypTokenAdapter ): Promise { const provider = await this.getProvider(); const response: R = await provider.queryContractSmart( - this.addresses.router, + this.addresses.warpRouter, msg, ); return response; @@ -199,7 +201,7 @@ export class CwHypTokenAdapter prepareRouter(msg: WarpCw20Execute, funds?: Coin[]): ExecuteInstruction { return { - contractAddress: this.addresses.router, + contractAddress: this.addresses.warpRouter, msg, funds, }; @@ -296,23 +298,23 @@ export class CwHypTokenAdapter } } -export class CwHypNativeTokenAdapter +export class CwHypNativeAdapter extends CwNativeTokenAdapter implements IHypTokenAdapter { - private readonly cw20adapter: CwHypTokenAdapter; + private readonly cw20adapter: CwHypSyntheticAdapter; constructor( public readonly chainName: ChainName, public readonly multiProvider: MultiProtocolProvider, - public readonly addresses: { router: Address }, - public readonly gasDenom: string, + public readonly addresses: { warpRouter: Address }, + public readonly gasDenom = 'token', ) { - super(chainName, multiProvider, gasDenom); - this.cw20adapter = new CwHypTokenAdapter( + super(chainName, multiProvider, addresses, gasDenom); + this.cw20adapter = new CwHypSyntheticAdapter( chainName, multiProvider, - { token: '', router: addresses.router }, + { token: '', warpRouter: addresses.warpRouter }, gasDenom, ); } @@ -384,3 +386,18 @@ export class CwHypNativeTokenAdapter ); } } + +export class CwHypCollateralAdapter + extends CwHypNativeAdapter + implements IHypTokenAdapter +{ + constructor( + public readonly chainName: ChainName, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: { warpRouter: Address; token: Address }, + public readonly gasDenom = 'token', + ) { + super(chainName, multiProvider, addresses, gasDenom); + throw new Error('Not yet implemented'); + } +} diff --git a/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts b/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts index b6d3b00244..362288169c 100644 --- a/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts @@ -162,7 +162,7 @@ export class SealevelTokenAdapter // we generously request 1M units. const TRANSFER_REMOTE_COMPUTE_LIMIT = 1_000_000; -export abstract class SealevelHypTokenAdapter +abstract class SealevelHypTokenAdapter extends SealevelTokenAdapter implements IHypTokenAdapter { From 2fe92ad424e198f425bf7310b8b5eeacfc2ce521 Mon Sep 17 00:00:00 2001 From: Nam Chu Hoai Date: Sun, 29 Oct 2023 17:21:12 -0400 Subject: [PATCH 18/30] Check for IBC denom collateral and cosmos addresess (#2868) ### Description ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- .../sdk/src/token/adapters/CosmWasmTokenAdapter.ts | 8 +++++++- typescript/utils/src/addresses.ts | 10 ++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts index e26cbeaa4b..19f0da329b 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts @@ -319,6 +319,13 @@ export class CwHypNativeAdapter ); } + async getBalance(address: string): Promise { + const provider = await this.getProvider(); + const denom = await this.denom(); + const balance = await provider.getBalance(address, denom); + return balance.amount; + } + async interchainSecurityModule(): Promise
{ return this.cw20adapter.interchainSecurityModule(); } @@ -398,6 +405,5 @@ export class CwHypCollateralAdapter public readonly gasDenom = 'token', ) { super(chainName, multiProvider, addresses, gasDenom); - throw new Error('Not yet implemented'); } } diff --git a/typescript/utils/src/addresses.ts b/typescript/utils/src/addresses.ts index 2b6c9bd48d..131c12c1c6 100644 --- a/typescript/utils/src/addresses.ts +++ b/typescript/utils/src/addresses.ts @@ -8,6 +8,7 @@ const EVM_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/; const SEALEVEL_ADDRESS_REGEX = /^[a-zA-Z0-9]{36,44}$/; const COSMOS_ADDRESS_REGEX = /^[a-z]{1,10}1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38,58}$/; // Bech32 +export const IBC_DENOM_REGEX = /^ibc\/([A-Fa-f0-9]{64})$/; const EVM_TX_HASH_REGEX = /^0x([A-Fa-f0-9]{64})$/; const SEALEVEL_TX_HASH_REGEX = /^[a-zA-Z1-9]{88}$/; @@ -25,17 +26,17 @@ export function isAddressSealevel(address: Address) { } export function isAddressCosmos(address: Address) { - return COSMOS_ADDRESS_REGEX.test(address); + return COSMOS_ADDRESS_REGEX.test(address) || IBC_DENOM_REGEX.test(address); } export function getAddressProtocolType(address: Address) { if (!address) return undefined; if (isAddressEvm(address)) { return ProtocolType.Ethereum; - } else if (isAddressSealevel(address)) { - return ProtocolType.Sealevel; } else if (isAddressCosmos(address)) { return ProtocolType.Cosmos; + } else if (isAddressSealevel(address)) { + return ProtocolType.Sealevel; } else { return undefined; } @@ -77,7 +78,8 @@ export function isValidAddressSealevel(address: Address) { // Slower than isAddressCosmos above but actually validates content and checksum export function isValidAddressCosmos(address: Address) { try { - const isValid = address && fromBech32(address); + const isValid = + address && (IBC_DENOM_REGEX.test(address) || fromBech32(address)); return !!isValid; } catch (error) { return false; From 7fb2dc7fd5158940beca04d4795e7791d5f3fe74 Mon Sep 17 00:00:00 2001 From: Nam Chu Hoai Date: Sun, 29 Oct 2023 17:23:09 -0400 Subject: [PATCH 19/30] Set hardcode to untrn --- .../sdk/src/token/adapters/CosmWasmTokenAdapter.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts index 19f0da329b..74d0a16d1a 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts @@ -46,7 +46,7 @@ export class CwNativeTokenAdapter public readonly chainName: string, public readonly multiProvider: MultiProtocolProvider, public readonly addresses: Record, - public readonly ibcDenom: string = 'token', + public readonly ibcDenom: string = 'untrn', ) { super(chainName, multiProvider, addresses); } @@ -97,7 +97,7 @@ export class CwTokenAdapter public readonly chainName: string, public readonly multiProvider: MultiProtocolProvider, public readonly addresses: { token: Address }, - public readonly denom = 'token', + public readonly denom = 'untrn', ) { super(chainName, multiProvider, addresses); } @@ -183,7 +183,7 @@ export class CwHypSyntheticAdapter public readonly chainName: ChainName, public readonly multiProvider: MultiProtocolProvider, public readonly addresses: { token: Address; warpRouter: Address }, - public readonly gasDenom = 'token', + public readonly gasDenom = 'untrn', ) { super(chainName, multiProvider, addresses); } @@ -308,7 +308,7 @@ export class CwHypNativeAdapter public readonly chainName: ChainName, public readonly multiProvider: MultiProtocolProvider, public readonly addresses: { warpRouter: Address }, - public readonly gasDenom = 'token', + public readonly gasDenom = 'untrn', ) { super(chainName, multiProvider, addresses, gasDenom); this.cw20adapter = new CwHypSyntheticAdapter( @@ -402,7 +402,7 @@ export class CwHypCollateralAdapter public readonly chainName: ChainName, public readonly multiProvider: MultiProtocolProvider, public readonly addresses: { warpRouter: Address; token: Address }, - public readonly gasDenom = 'token', + public readonly gasDenom = 'untrn', ) { super(chainName, multiProvider, addresses, gasDenom); } From 0636bc1c4e9eb7fad9c5ec4fc19d232f9f3aff07 Mon Sep 17 00:00:00 2001 From: Nam Chu Hoai Date: Sun, 29 Oct 2023 17:25:33 -0400 Subject: [PATCH 20/30] Prep 3.1.0-beta0 release --- solidity/package.json | 4 ++-- typescript/helloworld/package.json | 6 +++--- typescript/infra/package.json | 6 +++--- typescript/sdk/package.json | 6 +++--- typescript/utils/package.json | 2 +- yarn.lock | 24 ++++++++++++------------ 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/solidity/package.json b/solidity/package.json index d9bae0c386..cc1ed89cc4 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "1.5.4-beta0", + "version": "3.1.0-beta0", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "1.5.4-beta0", + "@hyperlane-xyz/utils": "3.1.0-beta0", "@openzeppelin/contracts": "^4.8.0", "@openzeppelin/contracts-upgradeable": "^4.8.0" }, diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index 8be3bda9e9..5b95890f78 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "1.5.4-beta0", + "version": "3.1.0-beta0", "dependencies": { - "@hyperlane-xyz/core": "1.5.4-beta0", - "@hyperlane-xyz/sdk": "1.5.4-beta0", + "@hyperlane-xyz/core": "3.1.0-beta0", + "@hyperlane-xyz/sdk": "3.1.0-beta0", "@openzeppelin/contracts-upgradeable": "^4.8.0", "ethers": "^5.7.2" }, diff --git a/typescript/infra/package.json b/typescript/infra/package.json index fcd9e55892..1cdba73ed7 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -11,9 +11,9 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "1.5.4-beta0", - "@hyperlane-xyz/sdk": "1.5.4-beta0", - "@hyperlane-xyz/utils": "1.5.4-beta0", + "@hyperlane-xyz/helloworld": "3.1.0-beta0", + "@hyperlane-xyz/sdk": "3.1.0-beta0", + "@hyperlane-xyz/utils": "3.1.0-beta0", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@safe-global/api-kit": "^1.3.0", "@safe-global/protocol-kit": "^1.2.0", diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 368a433562..c2260b2910 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,12 +1,12 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "1.5.4-beta0", + "version": "3.1.0-beta0", "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.31.3", "@cosmjs/stargate": "^0.31.3", - "@hyperlane-xyz/core": "1.5.4-beta0", - "@hyperlane-xyz/utils": "1.5.4-beta0", + "@hyperlane-xyz/core": "3.1.0-beta0", + "@hyperlane-xyz/utils": "3.1.0-beta0", "@solana/spl-token": "^0.3.8", "@solana/web3.js": "^1.78.0", "@types/coingecko-api": "^1.0.10", diff --git a/typescript/utils/package.json b/typescript/utils/package.json index e7caa7799c..f2ce16c190 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/utils", "description": "General utilities and types for the Hyperlane network", - "version": "1.5.4-beta0", + "version": "3.1.0-beta0", "dependencies": { "@cosmjs/encoding": "^0.31.3", "@solana/web3.js": "^1.78.0", diff --git a/yarn.lock b/yarn.lock index 006e2c9640..fcf86ba33d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4097,12 +4097,12 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/core@1.5.4-beta0, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@3.1.0-beta0, @hyperlane-xyz/core@workspace:solidity": version: 0.0.0-use.local resolution: "@hyperlane-xyz/core@workspace:solidity" dependencies: "@eth-optimism/contracts": ^0.6.0 - "@hyperlane-xyz/utils": 1.5.4-beta0 + "@hyperlane-xyz/utils": 3.1.0-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts": ^4.8.0 @@ -4125,12 +4125,12 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/helloworld@1.5.4-beta0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@3.1.0-beta0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: - "@hyperlane-xyz/core": 1.5.4-beta0 - "@hyperlane-xyz/sdk": 1.5.4-beta0 + "@hyperlane-xyz/core": 3.1.0-beta0 + "@hyperlane-xyz/sdk": 3.1.0-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 @@ -4170,9 +4170,9 @@ __metadata: "@ethersproject/experimental": ^5.7.0 "@ethersproject/hardware-wallets": ^5.7.0 "@ethersproject/providers": ^5.7.2 - "@hyperlane-xyz/helloworld": 1.5.4-beta0 - "@hyperlane-xyz/sdk": 1.5.4-beta0 - "@hyperlane-xyz/utils": 1.5.4-beta0 + "@hyperlane-xyz/helloworld": 3.1.0-beta0 + "@hyperlane-xyz/sdk": 3.1.0-beta0 + "@hyperlane-xyz/utils": 3.1.0-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-etherscan": ^3.0.3 "@nomiclabs/hardhat-waffle": ^2.0.3 @@ -4216,14 +4216,14 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/sdk@1.5.4-beta0, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@3.1.0-beta0, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: "@cosmjs/cosmwasm-stargate": ^0.31.3 "@cosmjs/stargate": ^0.31.3 - "@hyperlane-xyz/core": 1.5.4-beta0 - "@hyperlane-xyz/utils": 1.5.4-beta0 + "@hyperlane-xyz/core": 3.1.0-beta0 + "@hyperlane-xyz/utils": 3.1.0-beta0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@solana/spl-token": ^0.3.8 @@ -4254,7 +4254,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@1.5.4-beta0, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@3.1.0-beta0, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: From 92ab1e7079c8de32b3b66ede5d978f9a186b1d4d Mon Sep 17 00:00:00 2001 From: Kunal Arora <55632507+aroralanuk@users.noreply.github.com> Date: Wed, 1 Nov 2023 16:35:16 -0400 Subject: [PATCH 21/30] Add monitoring script for neutron (#2872) ### Description - Adding monitoring script for neutron and mantapacific ### Drive-by changes - filter wagmi chainMetadata ### Related issues ### Backward compatibility Yes ### Testing Manual --- .../helm/warp-routes/templates/_helpers.tpl | 4 +- .../warp-routes/deploy-warp-monitor.ts | 6 +- typescript/infra/scripts/warp-routes/helm.ts | 12 +- .../monitor-warp-routes-balances.ts | 168 ++++++++++++------ ...oken_config.ts => grafana_token_config.ts} | 55 +++++- typescript/sdk/src/consts/chainMetadata.ts | 64 +++++++ typescript/sdk/src/consts/chains.ts | 4 + typescript/sdk/src/utils/wagmi.ts | 8 +- 8 files changed, 253 insertions(+), 68 deletions(-) rename typescript/infra/src/config/{nautilus_token_config.ts => grafana_token_config.ts} (53%) diff --git a/typescript/infra/helm/warp-routes/templates/_helpers.tpl b/typescript/infra/helm/warp-routes/templates/_helpers.tpl index fe17261f46..338d8ad502 100644 --- a/typescript/infra/helm/warp-routes/templates/_helpers.tpl +++ b/typescript/infra/helm/warp-routes/templates/_helpers.tpl @@ -61,6 +61,8 @@ The warp-routes container command: - ./node_modules/.bin/ts-node - ./typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts - - -c + - -l - "10000" + - -c + - {{ .Values.config }} {{- end }} diff --git a/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts b/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts index e1a11aff56..f8cbb7cebe 100644 --- a/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts +++ b/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts @@ -3,7 +3,11 @@ import { HelmCommand } from '../../src/utils/helm'; import { runWarpRouteHelmCommand } from './helm'; async function main() { - await runWarpRouteHelmCommand(HelmCommand.InstallOrUpgrade, 'mainnet2'); + await runWarpRouteHelmCommand( + HelmCommand.InstallOrUpgrade, + 'mainnet2', + 'neutron', + ); } main() diff --git a/typescript/infra/scripts/warp-routes/helm.ts b/typescript/infra/scripts/warp-routes/helm.ts index 4a00d3ed3c..170800e003 100644 --- a/typescript/infra/scripts/warp-routes/helm.ts +++ b/typescript/infra/scripts/warp-routes/helm.ts @@ -6,17 +6,18 @@ import { assertCorrectKubeContext, getEnvironmentConfig } from '../utils'; export async function runWarpRouteHelmCommand( helmCommand: HelmCommand, runEnv: DeployEnvironment, + config: string, ) { const envConfig = getEnvironmentConfig(runEnv); await assertCorrectKubeContext(envConfig); - const values = getWarpRoutesHelmValues(); + const values = getWarpRoutesHelmValues(config); return execCmd( `helm ${helmCommand} ${getHelmReleaseName( - 'zebec', + config, )} ./helm/warp-routes --namespace ${runEnv} ${values.join( ' ', - )} --set fullnameOverride="${getHelmReleaseName('zebec')}"`, + )} --set fullnameOverride="${getHelmReleaseName(config)}"`, ); } @@ -24,12 +25,13 @@ function getHelmReleaseName(route: string): string { return `hyperlane-warp-route-${route}`; } -function getWarpRoutesHelmValues() { +function getWarpRoutesHelmValues(config: string) { const values = { image: { repository: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '962d34b-20230905-194531', + tag: 'ae8ce44-20231101-012032', }, + config: config, // nautilus or neutron }; return helmifyValues(values); } diff --git a/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts b/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts index b864a2f6fd..b2631a87cd 100644 --- a/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts +++ b/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts @@ -7,8 +7,8 @@ import { ERC20__factory } from '@hyperlane-xyz/core'; import { ChainMap, ChainName, + CwNativeTokenAdapter, MultiProtocolProvider, - MultiProvider, SealevelHypCollateralAdapter, TokenType, } from '@hyperlane-xyz/sdk'; @@ -21,8 +21,9 @@ import { import { WarpTokenConfig, - tokenList, -} from '../../src/config/nautilus_token_config'; + nautilusList, + neutronList, +} from '../../src/config/grafana_token_config'; import { startMetricsServer } from '../../src/utils/metrics'; const metricsRegister = new Registry(); @@ -40,21 +41,29 @@ const warpRouteTokenBalance = new Gauge({ }); async function main(): Promise { - const { checkFrequency } = await yargs(process.argv.slice(2)) + const { checkFrequency, config } = await yargs(process.argv.slice(2)) .describe('checkFrequency', 'frequency to check balances in ms') .demandOption('checkFrequency') - .alias('c', 'checkFrequency') + .alias('l', 'checkFrequency') .number('checkFrequency') + .alias('c', 'config') + .describe('config', 'choose warp token config') + .demandOption('config') + .choices('config', ['neutron', 'nautilus']) .parse(); + const tokenList: WarpTokenConfig = + config === 'neutron' ? neutronList : nautilusList; + startMetricsServer(metricsRegister); - const multiProvider = new MultiProvider(); + console.log('Starting Warp Route balance monitor'); + const multiProtocolProvider = new MultiProtocolProvider(); setInterval(async () => { try { - console.log('Checking balances'); - const balances = await checkBalance(tokenList, multiProvider); + debug('Checking balances'); + const balances = await checkBalance(tokenList, multiProtocolProvider); updateTokenBalanceMetrics(tokenList, balances); } catch (e) { console.error('Error checking balances', e); @@ -66,52 +75,107 @@ async function main(): Promise { // TODO: see issue https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2708 async function checkBalance( tokenConfig: WarpTokenConfig, - multiprovider: MultiProvider, + multiProtocolProvider: MultiProtocolProvider, ): Promise> { const output: ChainMap> = objMap( tokenConfig, async (chain: ChainName, token: WarpTokenConfig[ChainName]) => { - const provider = multiprovider.getProvider(chain); - if (token.type === TokenType.native) { - if (token.protocolType === ProtocolType.Ethereum) { - const nativeBalance = await provider.getBalance( - token.hypNativeAddress, - ); - return parseFloat( - ethers.utils.formatUnits(nativeBalance, token.decimals), - ); - } else { - // TODO - solana native - return 0; + switch (token.type) { + case TokenType.native: { + switch (token.protocolType) { + case ProtocolType.Ethereum: { + const provider = multiProtocolProvider.getEthersV5Provider(chain); + const nativeBalance = await provider.getBalance( + token.hypNativeAddress, + ); + return parseFloat( + ethers.utils.formatUnits(nativeBalance, token.decimals), + ); + } + case ProtocolType.Sealevel: + // TODO - solana native + return 0; + case ProtocolType.Cosmos: + // TODO - cosmos native + return 0; + } + break; } - } else { - if (token.protocolType === ProtocolType.Ethereum) { - const tokenContract = ERC20__factory.connect(token.address, provider); - const collateralBalance = await tokenContract.balanceOf( - token.hypCollateralAddress, - ); + case TokenType.collateral: { + switch (token.protocolType) { + case ProtocolType.Ethereum: { + const provider = multiProtocolProvider.getEthersV5Provider(chain); + const tokenContract = ERC20__factory.connect( + token.address, + provider, + ); + const collateralBalance = await tokenContract.balanceOf( + token.hypCollateralAddress, + ); - return parseFloat( - ethers.utils.formatUnits(collateralBalance, token.decimals), - ); - } else { - const adapter = new SealevelHypCollateralAdapter( - chain, - MultiProtocolProvider.fromMultiProvider(multiprovider), - { - token: token.address, - warpRouter: token.hypCollateralAddress, - // Mailbox only required for transfers, using system as placeholder - mailbox: SystemProgram.programId.toBase58(), - }, - token.isSpl2022, - ); - const collateralBalance = ethers.BigNumber.from( - await adapter.getBalance(token.hypCollateralAddress), - ); - return parseFloat( - ethers.utils.formatUnits(collateralBalance, token.decimals), - ); + return parseFloat( + ethers.utils.formatUnits(collateralBalance, token.decimals), + ); + } + case ProtocolType.Sealevel: { + const adapter = new SealevelHypCollateralAdapter( + chain, + multiProtocolProvider, + { + token: token.address, + warpRouter: token.hypCollateralAddress, + // Mailbox only required for transfers, using system as placeholder + mailbox: SystemProgram.programId.toBase58(), + }, + token.isSpl2022, + ); + const collateralBalance = ethers.BigNumber.from( + await adapter.getBalance(token.hypCollateralAddress), + ); + return parseFloat( + ethers.utils.formatUnits(collateralBalance, token.decimals), + ); + } + case ProtocolType.Cosmos: { + const adapter = new CwNativeTokenAdapter( + chain, + multiProtocolProvider, + { + token: token.address, + }, + token.address, + ); + const collateralBalance = ethers.BigNumber.from( + await adapter.getBalance(token.hypCollateralAddress), + ); + return parseFloat( + ethers.utils.formatUnits(collateralBalance, token.decimals), + ); + } + } + break; + } + case TokenType.synthetic: { + switch (token.protocolType) { + case ProtocolType.Ethereum: { + const provider = multiProtocolProvider.getEthersV5Provider(chain); + const tokenContract = ERC20__factory.connect( + token.hypSyntheticAddress, + provider, + ); + const syntheticBalance = await tokenContract.totalSupply(); + return parseFloat( + ethers.utils.formatUnits(syntheticBalance, token.decimals), + ); + } + case ProtocolType.Sealevel: + // TODO - solana native + return 0; + case ProtocolType.Cosmos: + // TODO - cosmos native + return 0; + } + break; } } }, @@ -128,11 +192,15 @@ function updateTokenBalanceMetrics( const tokenAddress = token.type === TokenType.native ? ethers.constants.AddressZero - : token.address; + : token.type === TokenType.collateral + ? token.address + : token.hypSyntheticAddress; const walletAddress = token.type === TokenType.native ? token.hypNativeAddress - : token.hypCollateralAddress; + : token.type === TokenType.collateral + ? token.hypCollateralAddress + : token.hypSyntheticAddress; warpRouteTokenBalance .labels({ diff --git a/typescript/infra/src/config/nautilus_token_config.ts b/typescript/infra/src/config/grafana_token_config.ts similarity index 53% rename from typescript/infra/src/config/nautilus_token_config.ts rename to typescript/infra/src/config/grafana_token_config.ts index 3f4eb601f6..9d7e6f4c34 100644 --- a/typescript/infra/src/config/nautilus_token_config.ts +++ b/typescript/infra/src/config/grafana_token_config.ts @@ -2,37 +2,53 @@ import { ChainMap, TokenType } from '@hyperlane-xyz/sdk'; import { ProtocolType } from '@hyperlane-xyz/utils'; interface NativeTokenConfig { - chainId: number; symbol: string; name: string; type: TokenType.native; decimals: number; hypNativeAddress: string; - protocolType: ProtocolType.Ethereum | ProtocolType.Sealevel; + protocolType: + | ProtocolType.Ethereum + | ProtocolType.Sealevel + | ProtocolType.Cosmos; } interface CollateralTokenConfig { type: TokenType.collateral; address: string; - chainId: number; decimals: number; symbol: string; name: string; hypCollateralAddress: string; isSpl2022?: boolean; - protocolType: ProtocolType.Ethereum | ProtocolType.Sealevel; + protocolType: + | ProtocolType.Ethereum + | ProtocolType.Sealevel + | ProtocolType.Cosmos; +} + +interface SyntheticTokenConfig { + type: TokenType.synthetic; + hypSyntheticAddress: string; + decimals: number; + symbol: string; + name: string; + protocolType: + | ProtocolType.Ethereum + | ProtocolType.Sealevel + | ProtocolType.Cosmos; } // TODO: migrate and dedupe to SDK from infra and Warp UI export type WarpTokenConfig = ChainMap< - CollateralTokenConfig | NativeTokenConfig + CollateralTokenConfig | NativeTokenConfig | SyntheticTokenConfig >; -export const tokenList: WarpTokenConfig = { +/// nautilus configs +export const nautilusList: WarpTokenConfig = { // bsc bsc: { type: TokenType.collateral, - chainId: 56, address: '0x37a56cdcD83Dce2868f721De58cB3830C44C6303', hypCollateralAddress: '0xC27980812E2E66491FD457D488509b7E04144b98', symbol: 'ZBC', @@ -44,7 +60,6 @@ export const tokenList: WarpTokenConfig = { // nautilus nautilus: { type: TokenType.native, - chainId: 22222, hypNativeAddress: '0x4501bBE6e731A4bC5c60C03A77435b2f6d5e9Fe7', symbol: 'ZBC', name: 'Zebec', @@ -55,7 +70,6 @@ export const tokenList: WarpTokenConfig = { // solana solana: { type: TokenType.collateral, - chainId: 1399811149, address: 'wzbcJyhGhQDLTV1S99apZiiBdE4jmYfbw99saMMdP59', hypCollateralAddress: 'EJqwFjvVJSAxH8Ur2PYuMfdvoJeutjmH6GkoEFQ4MdSa', name: 'Zebec', @@ -65,3 +79,26 @@ export const tokenList: WarpTokenConfig = { protocolType: ProtocolType.Sealevel, }, }; + +/// neutron configs +export const neutronList: WarpTokenConfig = { + neutron: { + type: TokenType.collateral, + address: + 'ibc/773B4D0A3CD667B2275D5A4A7A2F0909C0BA0F4059C0B9181E680DDF4965DCC7', + hypCollateralAddress: + 'neutron1ch7x3xgpnj62weyes8vfada35zff6z59kt2psqhnx9gjnt2ttqdqtva3pa', + name: 'Celestia', + symbol: 'TIA', + decimals: 6, + protocolType: ProtocolType.Cosmos, + }, + mantapacific: { + type: TokenType.synthetic, + hypSyntheticAddress: '0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa', + name: 'Celestia', + symbol: 'TIA', + decimals: 6, + protocolType: ProtocolType.Ethereum, + }, +}; diff --git a/typescript/sdk/src/consts/chainMetadata.ts b/typescript/sdk/src/consts/chainMetadata.ts index 8788c43156..aa225f1385 100644 --- a/typescript/sdk/src/consts/chainMetadata.ts +++ b/typescript/sdk/src/consts/chainMetadata.ts @@ -685,6 +685,35 @@ export const proteustestnet: ChainMetadata = { }, }; +export const mantapacific: ChainMetadata = { + chainId: 169, + domainId: 169, + name: Chains.mantapacific, + protocol: ProtocolType.Ethereum, + displayName: 'Manta Pacific', + displayNameShort: 'Manta', + nativeToken: { + name: 'Ether', + symbol: 'ETH', + decimals: 18, + }, + blocks: { + confirmations: 1, + reorgPeriod: 0, + estimateBlockTime: 3, + }, + blockExplorers: [ + { + name: 'Manta Pacific Explorer', + url: 'https://pacific-explorer.manta.network/', + apiUrl: 'https://pacific-explorer.manta.network/api', + family: ExplorerFamily.Blockscout, + }, + ], + rpcUrls: [{ http: 'https://pacific-rpc.manta.network/http' }], + isTestnet: false, +}; + export const nautilus: ChainMetadata = { chainId: 22222, domainId: 22222, @@ -708,6 +737,39 @@ export const nautilus: ChainMetadata = { }, }; +export const neutron: ChainMetadata = { + chainId: 'neutron-1', + domainId: 1853125230, + name: Chains.neutron, + protocol: ProtocolType.Cosmos, + displayName: 'Neutron', + bech32Prefix: 'neutron', + slip44: 118, + nativeToken: { + name: 'Neutron', + symbol: 'NTRN', + decimals: 6, + }, + rpcUrls: [ + { http: 'https://rpc-kralum.neutron-1.neutron.org' }, + { http: 'grpc-kralum.neutron-1.neutron.org:80' }, + ], + blocks: { + confirmations: 1, + reorgPeriod: 0, + estimateBlockTime: 3, + }, + blockExplorers: [ + { + name: 'Mintscan', + url: 'https://www.mintscan.io/neutron', + apiUrl: 'https://www.mintscan.io/neutron', + family: ExplorerFamily.Other, + }, + ], + isTestnet: false, +}; + /** * Metadata for local test chains */ @@ -887,9 +949,11 @@ export const chainMetadata: ChainMap = { lineagoerli, scrollsepolia, sepolia, + mantapacific, moonbasealpha, moonbeam, mumbai, + neutron, optimism, optimismgoerli, polygon, diff --git a/typescript/sdk/src/consts/chains.ts b/typescript/sdk/src/consts/chains.ts index f7b07cc6e1..108003610f 100644 --- a/typescript/sdk/src/consts/chains.ts +++ b/typescript/sdk/src/consts/chains.ts @@ -19,10 +19,12 @@ export enum Chains { lineagoerli = 'lineagoerli', scrollsepolia = 'scrollsepolia', sepolia = 'sepolia', + mantapacific = 'mantapacific', moonbasealpha = 'moonbasealpha', moonbeam = 'moonbeam', mumbai = 'mumbai', nautilus = 'nautilus', + neutron = 'neutron', optimism = 'optimism', optimismgoerli = 'optimismgoerli', polygon = 'polygon', @@ -54,6 +56,8 @@ export const Mainnets: Array = [ Chains.bsc, Chains.celo, Chains.ethereum, + Chains.neutron, + Chains.mantapacific, Chains.moonbeam, Chains.optimism, Chains.polygon, diff --git a/typescript/sdk/src/utils/wagmi.ts b/typescript/sdk/src/utils/wagmi.ts index e579c0ea03..43d1589fdd 100644 --- a/typescript/sdk/src/utils/wagmi.ts +++ b/typescript/sdk/src/utils/wagmi.ts @@ -1,6 +1,6 @@ import type { Chain as WagmiChain } from '@wagmi/chains'; -import { objMap } from '@hyperlane-xyz/utils'; +import { ProtocolType, objFilter, objMap } from '@hyperlane-xyz/utils'; import { chainMetadata, etherToken } from '../consts/chainMetadata'; import { @@ -11,7 +11,11 @@ import type { ChainMap } from '../types'; // For convenient use in wagmi-based apps export const wagmiChainMetadata: ChainMap = objMap( - chainMetadata, + objFilter( + chainMetadata, + (_, metadata): metadata is ChainMetadata => + metadata.protocol === ProtocolType.Ethereum, + ), (_, metadata) => chainMetadataToWagmiChain(metadata), ); From e917fb79a86a5925aa055079572bbece12ab44eb Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 2 Nov 2023 14:57:10 -0400 Subject: [PATCH 22/30] Implement cw core adapter (#2873) Co-authored-by: Nam Chu Hoai --- .../src/core/adapters/CosmWasmCoreAdapter.ts | 129 ++++- .../core/adapters/CosmWasmIgpAdapter.test.ts | 104 ++++ .../src/core/adapters/CosmWasmIgpAdapter.ts | 142 +++++ typescript/sdk/src/cw-types/Igp.types.ts | 215 +++++++ .../sdk/src/cw-types/IgpOracle.types.ts | 71 +++ typescript/sdk/src/cw-types/WarpCw20.types.ts | 51 +- .../sdk/src/cw-types/WarpNative.types.ts | 232 ++++++++ .../adapters/CosmWasmTokenAdapter.test.ts | 543 ++++++++++++++++-- 8 files changed, 1423 insertions(+), 64 deletions(-) create mode 100644 typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.test.ts create mode 100644 typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.ts create mode 100644 typescript/sdk/src/cw-types/Igp.types.ts create mode 100644 typescript/sdk/src/cw-types/IgpOracle.types.ts create mode 100644 typescript/sdk/src/cw-types/WarpNative.types.ts diff --git a/typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.ts b/typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.ts index a6ac5fb077..2046bcc93f 100644 --- a/typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.ts +++ b/typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.ts @@ -1,9 +1,19 @@ +import { ExecuteInstruction } from '@cosmjs/cosmwasm-stargate'; + import { Address, HexString } from '@hyperlane-xyz/utils'; import { BaseCosmWasmAdapter } from '../../app/MultiProtocolApp'; import { + Coin, + DefaultHookResponse, + DefaultIsmResponse, + ExecuteMsg, + LatestDispatchedIdResponse, MessageDeliveredResponse, + NonceResponse, + OwnerResponse, QueryMsg, + RequiredHookResponse, } from '../../cw-types/Mailbox.types'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; import { @@ -14,11 +24,15 @@ import { ChainName } from '../../types'; import { ICoreAdapter } from './types'; -type MailboxResponse = MessageDeliveredResponse; +type MailboxResponse = + | DefaultHookResponse + | RequiredHookResponse + | DefaultIsmResponse + | NonceResponse + | LatestDispatchedIdResponse + | OwnerResponse + | MessageDeliveredResponse; -// This adapter just routes to the HyperlaneCore -// Which implements the needed functionality for Cw chains -// TODO deprecate HyperlaneCore and replace all Cw-specific classes with adapters export class CosmWasmCoreAdapter extends BaseCosmWasmAdapter implements ICoreAdapter @@ -31,6 +45,48 @@ export class CosmWasmCoreAdapter super(chainName, multiProvider, addresses); } + prepareMailbox(msg: ExecuteMsg, funds?: Coin[]): ExecuteInstruction { + return { + contractAddress: this.addresses.mailbox, + msg, + funds, + }; + } + + initTransferOwner(newOwner: Address): ExecuteInstruction { + return this.prepareMailbox({ + ownable: { + init_ownership_transfer: { + next_owner: newOwner, + }, + }, + }); + } + + claimTransferOwner(): ExecuteInstruction { + return this.prepareMailbox({ + ownable: { + claim_ownership: {}, + }, + }); + } + + setDefaultHook(address: Address): ExecuteInstruction { + return this.prepareMailbox({ + set_default_hook: { + hook: address, + }, + }); + } + + setRequiredHook(address: Address): ExecuteInstruction { + return this.prepareMailbox({ + set_required_hook: { + hook: address, + }, + }); + } + async queryMailbox(msg: QueryMsg): Promise { const provider = await this.getProvider(); const response: R = await provider.queryContractSmart( @@ -40,6 +96,71 @@ export class CosmWasmCoreAdapter return response; } + async defaultHook(): Promise { + const response = await this.queryMailbox({ + mailbox: { + default_hook: {}, + }, + }); + return response.default_hook; + } + + async defaultIsm(): Promise { + const response = await this.queryMailbox({ + mailbox: { + default_ism: {}, + }, + }); + return response.default_ism; + } + + async requiredHook(): Promise { + const response = await this.queryMailbox({ + mailbox: { + required_hook: {}, + }, + }); + return response.required_hook; + } + + async nonce(): Promise { + const response = await this.queryMailbox({ + mailbox: { + nonce: {}, + }, + }); + return response.nonce; + } + + async latestDispatchedId(): Promise { + const response = await this.queryMailbox({ + mailbox: { + latest_dispatch_id: {}, + }, + }); + return response.message_id; + } + + async owner(): Promise { + const response = await this.queryMailbox({ + ownable: { + get_owner: {}, + }, + }); + return response.owner; + } + + async delivered(id: string): Promise { + const response = await this.queryMailbox({ + mailbox: { + message_delivered: { + id, + }, + }, + }); + return response.delivered; + } + extractMessageIds( sourceTx: TypedTransactionReceipt, ): Array<{ messageId: string; destination: ChainName }> { diff --git a/typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.test.ts b/typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.test.ts new file mode 100644 index 0000000000..4b57b020d3 --- /dev/null +++ b/typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.test.ts @@ -0,0 +1,104 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + +/* eslint-disable no-console */ +import { + ExecuteInstruction, + SigningCosmWasmClient, +} from '@cosmjs/cosmwasm-stargate'; +import { Secp256k1, keccak256 } from '@cosmjs/crypto'; +import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; +import { GasPrice, SigningStargateClient } from '@cosmjs/stargate'; +import { Tendermint37Client } from '@cosmjs/tendermint-rpc'; + +import { ProtocolType } from '@hyperlane-xyz/utils'; + +import { ChainMetadata } from '../../metadata/chainMetadataTypes'; +import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; + +import { CosmWasmIgpAdapter } from './CosmWasmIgpAdapter'; + +const neutron: ChainMetadata = { + protocol: ProtocolType.Cosmos, + name: 'neutron', + chainId: 'neutron-1', + displayName: 'Neutron', + domainId: 1853125230, + bech32Prefix: 'neutron', + slip44: 118, + rpcUrls: [ + { http: 'https://rpc-kralum.neutron-1.neutron.org' }, + { http: 'grpc-kralum.neutron-1.neutron.org:80' }, + ], + nativeToken: { + name: 'Neutron', + symbol: 'NTRN', + decimals: 6, + }, +}; + +const neutronAddresses = { + mailbox: 'neutron1sjzzd4gwkggy6hrrs8kxxatexzcuz3jecsxm3wqgregkulzj8r7qlnuef4', + igp: 'neutron12p8wntzra3vpfcqv05scdx5sa3ftaj6gjcmtm7ynkl0e6crtt4ns8cnrmx', +}; + +const signer = ''; + +export async function getSigningClient() { + const wallet = await DirectSecp256k1Wallet.fromKey( + Buffer.from(signer, 'hex'), + neutron.bech32Prefix!, + ); + + const [account] = await wallet.getAccounts(); + + const clientBase = await Tendermint37Client.connect(neutron.rpcUrls[0].http); + + const gasPrice = GasPrice.fromString('0.025token'); + + const wasm = await SigningCosmWasmClient.createWithSigner( + clientBase, + wallet, + { + gasPrice, + }, + ); + const stargate = await SigningStargateClient.createWithSigner( + clientBase, + wallet, + { + gasPrice, + }, + ); + + const pubkey = Secp256k1.uncompressPubkey(account.pubkey); + const ethaddr = keccak256(pubkey.slice(1)).slice(-20); + + return { + wasm, + stargate, + signer: account.address, + signer_addr: Buffer.from(ethaddr).toString('hex'), + signer_pubkey: Buffer.from(account.pubkey).toString('hex'), + }; +} + +async function main() { + const multiProtocolProvider = new MultiProtocolProvider({ + neutron, + }); + + const adapter = new CosmWasmIgpAdapter( + neutron.name, + multiProtocolProvider, + neutronAddresses, + ); + + const msg: ExecuteInstruction = adapter.prepareSetOracleMsg(); + + const client = await getSigningClient(); + + const tx = await client.wasm.executeMultiple(client.signer, [msg], 'auto'); + console.log({ tx }); +} + +main().catch(console.error); diff --git a/typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.ts b/typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.ts new file mode 100644 index 0000000000..8a35731d77 --- /dev/null +++ b/typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.ts @@ -0,0 +1,142 @@ +import { ExecuteInstruction } from '@cosmjs/cosmwasm-stargate'; + +import { Address } from '@hyperlane-xyz/utils'; + +import { BaseCosmWasmAdapter } from '../../app/MultiProtocolApp'; +import { + BeneficiaryResponse, + DefaultGasResponse, + DomainsResponse, + GetExchangeRateAndGasPriceResponse, + OwnerResponse, + QueryMsg, + QuoteGasPaymentResponse, + RouteResponseForAddr, + RoutesResponseForAddr, +} from '../../cw-types/Igp.types'; +import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; +import { ChainMap, ChainName } from '../../types'; + +// TODO: import more +type IgpResponse = + | OwnerResponse + | BeneficiaryResponse + | DomainsResponse + | GetExchangeRateAndGasPriceResponse + | RoutesResponseForAddr + | RouteResponseForAddr + | DefaultGasResponse + | QuoteGasPaymentResponse; + +export class CosmWasmIgpAdapter extends BaseCosmWasmAdapter { + constructor( + public readonly chainName: ChainName, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: { igp: Address }, + ) { + super(chainName, multiProvider, addresses); + } + + async queryIgp(msg: QueryMsg): Promise { + const provider = await this.getProvider(); + const response: R = await provider.queryContractSmart( + this.addresses.igp, + msg, + ); + return response; + } + + async owner(): Promise { + const response = await this.queryIgp({ + ownable: { + get_owner: {}, + }, + }); + return response.owner; + } + + async beneficiary(): Promise { + const beneficiaryResponse: BeneficiaryResponse = await this.queryIgp({ + igp: { + beneficiary: {}, + }, + }); + return beneficiaryResponse.beneficiary; + } + + async getOracles(): Promise> { + const domainResponse: RoutesResponseForAddr = await this.queryIgp({ + router: { + list_routes: {}, + }, + }); + + return Object.fromEntries( + domainResponse.routes.map((_) => [ + this.multiProvider.getChainName(_.domain), + _.route ?? '', + ]), + ); + } + + async defaultGas() { + const defaultGas = await this.queryIgp({ + igp: { + default_gas: {}, + }, + }); + return defaultGas.gas; + } + + async getOracleData( + chain: ChainName, + ): Promise { + const provider = await this.getProvider(); + const domain = this.multiProvider.getDomainId(chain); + const oracles = await this.getOracles(); + const oracle = oracles[chain]; + const oracleResponse: GetExchangeRateAndGasPriceResponse = + await provider.queryContractSmart(oracle, { + oracle: { + get_exchange_rate_and_gas_price: { + dest_domain: domain, + }, + }, + }); + return oracleResponse; + } + + async quoteGasPayment( + domain: number, + destinationGasAmount: number, + ): Promise { + const quote: QuoteGasPaymentResponse = await this.queryIgp({ + igp: { + quote_gas_payment: { + dest_domain: domain, + gas_amount: destinationGasAmount.toString(), + }, + }, + }); + return Number(quote.gas_needed); + } + + setOracleForDomain( + domain: number, + oracle: string, + oracleData: GetExchangeRateAndGasPriceResponse, + ): ExecuteInstruction { + return { + contractAddress: oracle, + msg: { + set_remote_gas_data: { + config: { + gas_price: oracleData.gas_price, + token_exchange_rate: oracleData.exchange_rate, + remote_domain: domain, + }, + }, + }, + }; + } +} diff --git a/typescript/sdk/src/cw-types/Igp.types.ts b/typescript/sdk/src/cw-types/Igp.types.ts new file mode 100644 index 0000000000..091e381d71 --- /dev/null +++ b/typescript/sdk/src/cw-types/Igp.types.ts @@ -0,0 +1,215 @@ +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + beneficiary: string; + default_gas_usage: number; + gas_token: string; + hrp: string; + owner: string; +} +export type ExecuteMsg = + | { + ownable: OwnableMsg; + } + | { + router: RouterMsgForAddr; + } + | { + post_dispatch: PostDispatchMsg; + } + | { + set_default_gas: { + gas: number; + }; + } + | { + set_gas_for_domain: { + config: [number, number][]; + }; + } + | { + unset_gas_for_domain: { + domains: number[]; + }; + } + | { + set_beneficiary: { + beneficiary: string; + }; + } + | { + pay_for_gas: { + dest_domain: number; + gas_amount: Uint256; + message_id: HexBinary; + refund_address: string; + }; + } + | { + claim: {}; + }; +export type OwnableMsg = + | { + init_ownership_transfer: { + next_owner: string; + }; + } + | { + revoke_ownership_transfer: {}; + } + | { + claim_ownership: {}; + }; +export type RouterMsgForAddr = + | { + set_route: { + set: DomainRouteSetForAddr; + }; + } + | { + set_routes: { + set: DomainRouteSetForAddr[]; + }; + }; +export type Addr = string; +export type HexBinary = string; +export type Uint256 = string; +export interface DomainRouteSetForAddr { + domain: number; + route?: Addr | null; +} +export interface PostDispatchMsg { + message: HexBinary; + metadata: HexBinary; +} +export type QueryMsg = + | { + ownable: OwnableQueryMsg; + } + | { + hook: HookQueryMsg; + } + | { + router: RouterQueryForAddr; + } + | { + oracle: IgpGasOracleQueryMsg; + } + | { + igp: IgpQueryMsg; + }; +export type OwnableQueryMsg = + | { + get_owner: {}; + } + | { + get_pending_owner: {}; + }; +export type HookQueryMsg = + | { + quote_dispatch: QuoteDispatchMsg; + } + | { + mailbox: {}; + }; +export type RouterQueryForAddr = + | { + domains: {}; + } + | { + get_route: { + domain: number; + }; + } + | { + list_routes: { + limit?: number | null; + offset?: number | null; + order?: Order | null; + }; + }; +export type Order = 'asc' | 'desc'; +export type IgpGasOracleQueryMsg = { + get_exchange_rate_and_gas_price: { + dest_domain: number; + }; +}; +export type IgpQueryMsg = + | { + default_gas: {}; + } + | { + gas_for_domain: { + domains: number[]; + }; + } + | { + list_gas_for_domains: { + limit?: number | null; + offset?: number | null; + order?: Order | null; + }; + } + | { + beneficiary: {}; + } + | { + quote_gas_payment: { + dest_domain: number; + gas_amount: Uint256; + }; + }; +export interface QuoteDispatchMsg { + message: HexBinary; + metadata: HexBinary; +} +export interface BeneficiaryResponse { + beneficiary: string; +} +export interface DefaultGasResponse { + gas: number; +} +export interface DomainsResponse { + domains: number[]; +} +export interface GasForDomainResponse { + gas: [number, number][]; +} +export type Uint128 = string; +export interface GetExchangeRateAndGasPriceResponse { + exchange_rate: Uint128; + gas_price: Uint128; +} +export interface OwnerResponse { + owner: Addr; +} +export interface PendingOwnerResponse { + pending_owner?: Addr | null; +} +export interface RouteResponseForAddr { + route: DomainRouteSetForAddr; +} +export interface RoutesResponseForAddr { + routes: DomainRouteSetForAddr[]; +} +export interface MailboxResponse { + mailbox: string; +} +export interface Empty { + [k: string]: unknown; +} +export interface QuoteDispatchResponse { + gas_amount?: Coin | null; +} +export interface Coin { + amount: Uint128; + denom: string; + [k: string]: unknown; +} +export interface QuoteGasPaymentResponse { + gas_needed: Uint256; +} diff --git a/typescript/sdk/src/cw-types/IgpOracle.types.ts b/typescript/sdk/src/cw-types/IgpOracle.types.ts new file mode 100644 index 0000000000..2a9255f7d5 --- /dev/null +++ b/typescript/sdk/src/cw-types/IgpOracle.types.ts @@ -0,0 +1,71 @@ +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + owner: string; +} +export type ExecuteMsg = + | { + ownership: OwnableMsg; + } + | { + set_remote_gas_data_configs: { + configs: RemoteGasDataConfig[]; + }; + } + | { + set_remote_gas_data: { + config: RemoteGasDataConfig; + }; + }; +export type OwnableMsg = + | { + init_ownership_transfer: { + next_owner: string; + }; + } + | { + revoke_ownership_transfer: {}; + } + | { + claim_ownership: {}; + }; +export type Uint128 = string; +export interface RemoteGasDataConfig { + gas_price: Uint128; + remote_domain: number; + token_exchange_rate: Uint128; +} +export type QueryMsg = + | { + ownable: OwnableQueryMsg; + } + | { + oracle: IgpGasOracleQueryMsg; + }; +export type OwnableQueryMsg = + | { + get_owner: {}; + } + | { + get_pending_owner: {}; + }; +export type IgpGasOracleQueryMsg = { + get_exchange_rate_and_gas_price: { + dest_domain: number; + }; +}; +export interface GetExchangeRateAndGasPriceResponse { + exchange_rate: Uint128; + gas_price: Uint128; +} +export type Addr = string; +export interface OwnerResponse { + owner: Addr; +} +export interface PendingOwnerResponse { + pending_owner?: Addr | null; +} diff --git a/typescript/sdk/src/cw-types/WarpCw20.types.ts b/typescript/sdk/src/cw-types/WarpCw20.types.ts index f31b54da8f..d7a4bc415f 100644 --- a/typescript/sdk/src/cw-types/WarpCw20.types.ts +++ b/typescript/sdk/src/cw-types/WarpCw20.types.ts @@ -69,6 +69,9 @@ export type ExecuteMsg = | { router: RouterMsgForHexBinary; } + | { + connection: ConnectionMsg; + } | { handle: HandleMsg; } @@ -78,16 +81,6 @@ export type ExecuteMsg = dest_domain: number; recipient: HexBinary; }; - } - | { - set_ism: { - ism: string; - }; - } - | { - set_hook: { - hook: string; - }; }; export type OwnableMsg = | { @@ -113,6 +106,22 @@ export type RouterMsgForHexBinary = }; }; export type HexBinary = string; +export type ConnectionMsg = + | { + set_mailbox: { + mailbox: string; + }; + } + | { + set_hook: { + hook: string; + }; + } + | { + set_ism: { + ism: string; + }; + }; export interface DomainRouteSetForHexBinary { domain: number; route?: HexBinary | null; @@ -129,6 +138,9 @@ export type QueryMsg = | { router: RouterQueryForHexBinary; } + | { + connection: ConnectionQueryMsg; + } | { token_default: TokenWarpDefaultQueryMsg; } @@ -159,6 +171,16 @@ export type RouterQueryForHexBinary = }; }; export type Order = 'asc' | 'desc'; +export type ConnectionQueryMsg = + | { + get_mailbox: {}; + } + | { + get_hook: {}; + } + | { + get_ism: {}; + }; export type TokenWarpDefaultQueryMsg = | { token_type: {}; @@ -172,6 +194,15 @@ export type IsmSpecifierQueryMsg = { export interface DomainsResponse { domains: number[]; } +export interface HookResponse { + hook?: string | null; +} +export interface IsmResponse { + ism?: string | null; +} +export interface MailboxResponse { + mailbox?: string | null; +} export type Addr = string; export interface OwnerResponse { owner: Addr; diff --git a/typescript/sdk/src/cw-types/WarpNative.types.ts b/typescript/sdk/src/cw-types/WarpNative.types.ts new file mode 100644 index 0000000000..073bee3398 --- /dev/null +++ b/typescript/sdk/src/cw-types/WarpNative.types.ts @@ -0,0 +1,232 @@ +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export type TokenModeMsgForNativeModeBrigedAndNativeModeCollateral = + | { + bridged: NativeModeBriged; + } + | { + collateral: NativeModeCollateral; + }; +export interface InstantiateMsg { + hrp: string; + mailbox: string; + owner: string; + token: TokenModeMsgForNativeModeBrigedAndNativeModeCollateral; +} +export interface NativeModeBriged { + denom: string; + metadata?: Metadata | null; +} +export interface Metadata { + base: string; + denom_units: DenomUnit[]; + description: string; + display: string; + name: string; + symbol: string; +} +export interface DenomUnit { + aliases: string[]; + denom: string; + exponent: number; +} +export interface NativeModeCollateral { + denom: string; +} +export type ExecuteMsg = + | { + ownable: OwnableMsg; + } + | { + router: RouterMsgForHexBinary; + } + | { + connection: ConnectionMsg; + } + | { + handle: HandleMsg; + } + | { + transfer_remote: { + amount: Uint128; + dest_domain: number; + recipient: HexBinary; + }; + }; +export type OwnableMsg = + | { + init_ownership_transfer: { + next_owner: string; + }; + } + | { + revoke_ownership_transfer: {}; + } + | { + claim_ownership: {}; + }; +export type RouterMsgForHexBinary = + | { + set_route: { + set: DomainRouteSetForHexBinary; + }; + } + | { + set_routes: { + set: DomainRouteSetForHexBinary[]; + }; + }; +export type HexBinary = string; +export type ConnectionMsg = + | { + set_mailbox: { + mailbox: string; + }; + } + | { + set_hook: { + hook: string; + }; + } + | { + set_ism: { + ism: string; + }; + }; +export type Uint128 = string; +export interface DomainRouteSetForHexBinary { + domain: number; + route?: HexBinary | null; +} +export interface HandleMsg { + body: HexBinary; + origin: number; + sender: HexBinary; +} +export type QueryMsg = + | { + ownable: OwnableQueryMsg; + } + | { + router: RouterQueryForHexBinary; + } + | { + connection: ConnectionQueryMsg; + } + | { + token_default: TokenWarpDefaultQueryMsg; + } + | { + ism_specifier: IsmSpecifierQueryMsg; + }; +export type OwnableQueryMsg = + | { + get_owner: {}; + } + | { + get_pending_owner: {}; + }; +export type RouterQueryForHexBinary = + | { + domains: {}; + } + | { + get_route: { + domain: number; + }; + } + | { + list_routes: { + limit?: number | null; + offset?: number | null; + order?: Order | null; + }; + }; +export type Order = 'asc' | 'desc'; +export type ConnectionQueryMsg = + | { + get_mailbox: {}; + } + | { + get_hook: {}; + } + | { + get_ism: {}; + }; +export type TokenWarpDefaultQueryMsg = + | { + token_type: {}; + } + | { + token_mode: {}; + }; +export type IsmSpecifierQueryMsg = { + interchain_security_module: []; +}; +export interface DomainsResponse { + domains: number[]; +} +export interface HookResponse { + hook?: string | null; +} +export interface IsmResponse { + ism?: string | null; +} +export interface MailboxResponse { + mailbox?: string | null; +} +export type Addr = string; +export interface OwnerResponse { + owner: Addr; +} +export interface PendingOwnerResponse { + pending_owner?: Addr | null; +} +export interface RouteResponseForHexBinary { + route: DomainRouteSetForHexBinary; +} +export interface InterchainSecurityModuleResponse { + ism?: Addr | null; +} +export interface RoutesResponseForHexBinary { + routes: DomainRouteSetForHexBinary[]; +} +export interface Empty { + [k: string]: unknown; +} +export type TokenMode = 'bridged' | 'collateral'; +export interface TokenModeResponse { + mode: TokenMode; +} +export type TokenType = + | { + native: TokenTypeNative; + } + | { + c_w20: { + contract: string; + }; + } + | { + c_w721: { + contract: string; + }; + }; +export type TokenTypeNative = + | { + fungible: { + denom: string; + }; + } + | { + non_fungible: { + class: string; + }; + }; +export interface TokenTypeResponse { + type: TokenType; +} diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts index 8ce9e7c9ee..c4ba932003 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts @@ -1,52 +1,215 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable no-console */ -import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; + +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + +/* eslint-disable no-console */ +import { + CosmWasmClient, + ExecuteInstruction, + SigningCosmWasmClient, +} from '@cosmjs/cosmwasm-stargate'; import { Secp256k1, keccak256 } from '@cosmjs/crypto'; import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; import { GasPrice, SigningStargateClient } from '@cosmjs/stargate'; import { Tendermint37Client } from '@cosmjs/tendermint-rpc'; -import { ProtocolType } from '@hyperlane-xyz/utils'; +import { + Address, + ProtocolType, + difference, + objMap, + promiseObjAll, +} from '@hyperlane-xyz/utils'; +import { BaseCosmWasmAdapter } from '../../app/MultiProtocolApp'; +import { CosmWasmCoreAdapter } from '../../core/adapters/CosmWasmCoreAdapter'; +import { + MailboxResponse, + QueryMsg as MerkleQuery, + OwnerResponse, +} from '../../cw-types/HookMerkle.types'; +import { + EnrolledValidatorsResponse, + ExecuteMsg as MultisigExecute, + QueryMsg as MultisigQuery, +} from '../../cw-types/IsmMultisig.types'; +import { MultisigConfig } from '../../ism/types'; import { ChainMetadata } from '../../metadata/chainMetadataTypes'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; +import { ChainMap, ChainName } from '../../types'; -import { CwHypNativeAdapter } from './CosmWasmTokenAdapter'; - -const router = - 'dual1nzkcccxw00u9egqfuuq2ue23hjj6kxmfvmc5y0r7wchk5e6nypns6768kk'; - -const dualitydevnet: ChainMetadata = { - name: 'dualitydevnet', - chainId: 'duality-devnet', - domainId: 33333, +const neutron: ChainMetadata = { protocol: ProtocolType.Cosmos, - bech32Prefix: 'dual', - slip44: 118, // what is this + name: 'neutron', + chainId: 'neutron-1', + displayName: 'Neutron', + domainId: 1853125230, + bech32Prefix: 'neutron', + slip44: 118, rpcUrls: [ - { - http: 'http://54.149.31.83:26657', - }, + { http: 'https://rpc-kralum.neutron-1.neutron.org' }, + { http: 'grpc-kralum.neutron-1.neutron.org:80' }, ], - isTestnet: true, + nativeToken: { + name: 'Neutron', + symbol: 'NTRN', + decimals: 6, + }, +}; + +const mantapacific: ChainMetadata = { + protocol: ProtocolType.Ethereum, + chainId: 169, + name: 'mantapacific', + displayName: 'Manta Pacific', + displayNameShort: 'Manta', + nativeToken: { + name: 'Ether', + symbol: 'ETH', + decimals: 18, + }, + blocks: { + confirmations: 1, + reorgPeriod: 0, + estimateBlockTime: 3, + }, + rpcUrls: [{ http: 'https://pacific-rpc.manta.network/http' }], +}; + +const neutronAddresses = { + mailbox: 'neutron1sjzzd4gwkggy6hrrs8kxxatexzcuz3jecsxm3wqgregkulzj8r7qlnuef4', + validatorAnnounce: + 'neutron17w4q6efzym3p4c6umyp4cjf2ustjtmwfqdhd7rt2fpcpk9fmjzsq0kj0f8', }; -const signer = ''; +const multiProtocolProvider = new MultiProtocolProvider({ + neutron, + mantapacific, +}); + +const adapter = new CosmWasmCoreAdapter( + neutron.name, + multiProtocolProvider, + neutronAddresses, +); + +type MultisigResponse = EnrolledValidatorsResponse; + +class CosmWasmMultisigAdapter extends BaseCosmWasmAdapter { + constructor( + public readonly chainName: ChainName, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: { multisig: Address }, + ) { + super(chainName, multiProvider, addresses); + } + + async queryMultisig( + msg: MultisigQuery, + ): Promise { + const provider = await this.getProvider(); + const response: R = await provider.queryContractSmart( + this.addresses.multisig, + msg, + ); + return response; + } + + async getConfig(chain: ChainName): Promise { + return this.queryMultisig({ + multisig_ism: { + enrolled_validators: { + domain: this.multiProvider.getDomainId(chain), + }, + }, + }); + } + + prepareMultisig(msg: MultisigExecute): ExecuteInstruction { + return { + contractAddress: this.addresses.multisig, + msg, + }; + } + + async configureMultisig( + configMap: ChainMap, + ): Promise { + const configuredMap = await promiseObjAll( + objMap(configMap, (origin, _) => this.getConfig(origin)), + ); + + const validatorInstructions = Object.entries(configMap).flatMap( + ([origin, config]) => { + const domain = this.multiProvider.getDomainId(origin); + const configuredSet = new Set(configuredMap[origin].validators); + const configSet = new Set(config.validators); + const unenrollList = Array.from( + difference(configuredSet, configSet).values(), + ); + const enrollList = Array.from( + difference(configSet, configuredSet).values(), + ); + return unenrollList + .map((validator) => + this.prepareMultisig({ + unenroll_validator: { + domain, + validator, + }, + }), + ) + .concat( + enrollList.map((validator) => + this.prepareMultisig({ + enroll_validator: { + set: { + domain, + validator, + }, + }, + }), + ), + ); + }, + ); + + const setThresholds = Object.entries(configMap) + .filter( + ([origin, { threshold }]) => + threshold !== configuredMap[origin].threshold, + ) + .map(([origin, config]) => ({ + domain: this.multiProvider.getDomainId(origin), + threshold: config.threshold, + })); + + if (setThresholds.length > 0) { + const thresholdInstruction = this.prepareMultisig({ + set_thresholds: { + set: setThresholds, + }, + }); + return [...validatorInstructions, thresholdInstruction]; + } -export async function getSigningClient() { + return validatorInstructions; + } +} + +export async function getSigningClient(pkey: string) { const wallet = await DirectSecp256k1Wallet.fromKey( - Buffer.from(signer, 'hex'), - dualitydevnet.bech32Prefix!, + Buffer.from(pkey, 'hex'), + neutron.bech32Prefix!, ); const [account] = await wallet.getAccounts(); - const clientBase = await Tendermint37Client.connect( - dualitydevnet.rpcUrls[0].http, - ); + const clientBase = await Tendermint37Client.connect(neutron.rpcUrls[0].http); - const gasPrice = GasPrice.fromString('0.025token'); + const gasPrice = GasPrice.fromString('0.1untrn'); const wasm = await SigningCosmWasmClient.createWithSigner( clientBase, @@ -75,37 +238,317 @@ export async function getSigningClient() { }; } -async function main() { - const multiProtocolProvider = new MultiProtocolProvider({ - dualitydevnet, - }); +const initTransferOwner = ( + address: Address, + newOwner: Address, + key = 'ownable', +): ExecuteInstruction => { + return { + contractAddress: address, + msg: { + [key]: { + init_ownership_transfer: { + next_owner: newOwner, + }, + }, + }, + }; +}; + +const claimTransferOwner = ( + address: Address, + key = 'ownable', +): ExecuteInstruction => { + return { + contractAddress: address, + msg: { + [key]: { + claim_ownership: {}, + }, + }, + }; +}; + +const getOwner = async ( + provider: CosmWasmClient, + address: Address, +): Promise
=> { + const ownableQuery = { + ownable: { get_owner: {} }, + }; + const ownerResponse: OwnerResponse = await provider.queryContractSmart( + address, + ownableQuery, + ); + return ownerResponse.owner; +}; + +export async function rotateHooks() { + const desiredDefault = + 'neutron1e5c2qqquc86rd3q77aj2wyht40z6z3q5pclaq040ue9f5f8yuf7qnpvkzk'; + + const desiredRequired = + 'neutron19qjplhq7jsmk7haneafqxyyhltgllvvag8c4g7jkmxw6mvd4h8sq7rqh02'; + + const safe = await getSigningClient( + '2ac7230628b8b4a587c4005798184735471b9240fc57fc75d97824e1fb6b5409', + ); - const gasDenom = 'token'; + const tx = await safe.wasm.executeMultiple( + safe.signer, + [ + adapter.setDefaultHook(desiredDefault), + adapter.setRequiredHook(desiredRequired), + ], + 'auto', + ); + + console.log(tx); +} + +export async function rotateAuth() { + const safe = await getSigningClient( + '2ac7230628b8b4a587c4005798184735471b9240fc57fc75d97824e1fb6b5409', + ); + + const desiredOwner = + 'neutron1fqf5mprg3f5hytvzp3t7spmsum6rjrw80mq8zgkc0h6rxga0dtzqws3uu7'; + + const addresses: string[] = [ + 'neutron1sjzzd4gwkggy6hrrs8kxxatexzcuz3jecsxm3wqgregkulzj8r7qlnuef4', // mailbox + 'neutron17w4q6efzym3p4c6umyp4cjf2ustjtmwfqdhd7rt2fpcpk9fmjzsq0kj0f8', // validator announce + 'neutron1q75ky8reksqzh0lkhk9k3csvjwv74jjquahrj233xc7dvzz5fv4qtvw0qg', // multisig ISM + 'neutron1e5c2qqquc86rd3q77aj2wyht40z6z3q5pclaq040ue9f5f8yuf7qnpvkzk', // merkle + 'neutron19qjplhq7jsmk7haneafqxyyhltgllvvag8c4g7jkmxw6mvd4h8sq7rqh02', // pausable + 'neutron1ch7x3xgpnj62weyes8vfada35zff6z59kt2psqhnx9gjnt2ttqdqtva3pa', // warp route + ]; + + let transferInstructions: ExecuteInstruction[] = []; + let claimInstructions: ExecuteInstruction[] = []; + + for (const address of addresses) { + const info = await safe.wasm.getContract(address); + console.log({ address, info }); + try { + await getOwner(safe.wasm, address); + + const transferInstruction = initTransferOwner(address, desiredOwner); + transferInstructions.push(transferInstruction); + + const claimInstruction = claimTransferOwner(address); + claimInstructions.push(claimInstruction); + } catch (e: any) { + if (e.message.includes('unknown variant `ownable`')) { + console.log( + `Skipping ${info.label} (${address}) because it is not ownable`, + ); + } else { + throw e; + } + } + // } - const adapter = new CwHypNativeAdapter( - dualitydevnet.name, + console.log(JSON.stringify({ transferInstructions, claimInstructions })); + + // const tx = await safe.wasm.executeMultiple( + // safe.signer, + // transferInstructions, + // 'auto', + // ); + // console.log(tx); + + // const claimTx = await safe.wasm.execute( + // safe.signer, + // address, + // claimInstruction.msg, + // 'auto', + // ); + // console.log(claimTx); + + const res = await safe.wasm.updateAdmin( + safe.signer, + address, + desiredOwner, + 'auto', + ); + console.log(res); + } +} + +export async function summary() { + let summary: any = {}; + + const provider = await adapter.getProvider(); + + const getOwner = async (address: Address): Promise
=> { + const ownableQuery = { + ownable: { get_owner: {} }, + }; + const ownerResponse: OwnerResponse = await provider.queryContractSmart( + address, + ownableQuery, + ); + return ownerResponse.owner; + }; + + const owner = await getOwner(neutronAddresses.mailbox); + const info = await provider.getContract(neutronAddresses.mailbox); + const defaultHook = await adapter.defaultHook(); + const requiredHook = await adapter.requiredHook(); + const defaultIsm = await adapter.defaultIsm(); + + summary.mailbox = { + owner, + ...info, + defaultHook, + requiredHook, + defaultIsm, + }; + + const router = + 'neutron1ch7x3xgpnj62weyes8vfada35zff6z59kt2psqhnx9gjnt2ttqdqtva3pa'; + summary.warproute = { + owner: await getOwner(router), + ...(await provider.getContract(router)), + }; + + summary.validatorAnnounce = { + // owner: await getOwner(neutronAddresses.validatorAnnounce), + ...(await provider.getContract(neutronAddresses.validatorAnnounce)), + }; + + const defaultIsmContract = await provider.getContract(defaultIsm); + + if (defaultIsmContract.label === 'hpl_ism_multisig') { + const multisigAdapter = new CosmWasmMultisigAdapter( + neutron.name, + multiProtocolProvider, + { multisig: defaultIsm }, + ); + const multisigConfig = await multisigAdapter.getConfig(mantapacific.name); + const owner = await getOwner(defaultIsm); + summary.defaultIsm = { + ...multisigConfig, + ...defaultIsmContract, + owner, + }; + } + + const getMailbox = async (hook: Address): Promise
=> { + const merkleMailboxQuery: MerkleQuery = { + hook: { + mailbox: {}, + }, + }; + const merkleMailboxResponse: MailboxResponse = + await provider.queryContractSmart(hook, merkleMailboxQuery); + return merkleMailboxResponse.mailbox; + }; + + const requiredHookContract = await provider.getContract(requiredHook); + if (requiredHookContract.label === 'hpl_hook_pausable') { + summary.requiredHook = { + ...requiredHookContract, + owner: await getOwner(requiredHook), + mailbox: await getMailbox(requiredHook), + }; + } + // else if (requiredHookContract.label === 'hpl_hook_aggregate') { + // const aggregateHookQuery: AggregateQuery = { + // aggregate_hook: { + // hooks: {}, + // }, + // }; + // const hooksResponse: HooksResponse = await provider.queryContractSmart( + // requiredHook, + // aggregateHookQuery, + // ); + // summary.requiredHook = { + // ...requiredHookContract, + // hooks: hooksResponse.hooks, + // owner: await getOwner(requiredHook), + // mailbox: await getMailbox(requiredHook), + // }; + + const defaultHookContract = await provider.getContract(defaultHook); + if (defaultHookContract.label === 'hpl_hook_merkle') { + summary.defaultHook = defaultHookContract; + } + + // for (const hook of hooksResponse.hooks) { + // const hook = defaultHook; + // const hookContract = await provider.getContract(hook); + // if (hookContract.label === 'hpl_hook_merkle') { + // // summary.requiredHook.merkleHook = { + // summary.merkleHook = { + // ...hookContract, + // mailbox: await getMailbox(hook), + // owner: await getOwner(hook), + // }; + // } + // } else if (hookContract.label === 'hpl_igp') { + // const igpAdapter = new CosmWasmIgpAdapter( + // neutron.name, + // multiProtocolProvider, + // { igp: hook }, + // ); + // const oracles = await igpAdapter.getOracles(); + // const defaultGas = await igpAdapter.defaultGas(); + // const beneficiary = await igpAdapter.beneficiary(); + + // const mantaData = await igpAdapter.getOracleData(mantapacific.name); + // const igpOracle = oracles[mantapacific.name]; + + // summary.requiredHook.igpHook = { + // oracles, + // mantaOracle: { + // ...mantaData, + // owner: await getOwner(igpOracle), + // ...(await provider.getContract(igpOracle)), + // }, + // defaultGas, + // beneficiary, + // mailbox: await getMailbox(hook), + // owner: await getOwner(hook), + // ...hookContract, + // }; + // } + // } + + console.log(JSON.stringify(summary)); +} + +export async function rotateValidators() { + const multisigAdapter = new CosmWasmMultisigAdapter( + neutron.name, multiProtocolProvider, - { warpRouter: router }, - gasDenom, + { + multisig: + 'neutron1q75ky8reksqzh0lkhk9k3csvjwv74jjquahrj233xc7dvzz5fv4qtvw0qg', + }, ); - const owner = await adapter.owner(); - const routers = await adapter.getAllRouters(); - const domains = await adapter.getDomains(); - const balance = await adapter.getBalance(owner); - - console.log({ owner, routers, domains, balance }); - - const msg = await adapter.populateTransferRemoteTx({ - destination: domains[0], - recipient: '0xE000fA4E466831dB288290Dd97e66560fb3d7d28', - weiAmountOrId: 10, - txValue: '2500000', + const instructions = await multisigAdapter.configureMultisig({ + [mantapacific.name]: { + threshold: 3, + validators: [ + '8e668c97ad76d0e28375275c41ece4972ab8a5bc', // hyperlane + '521a3e6bf8d24809fde1c1fd3494a859a16f132c', // cosmosstation + '25b9a0961c51e74fd83295293bc029131bf1e05a', // neutron (pablo) + '14025fe092f5f8a401dd9819704d9072196d2125', // p2p + 'a0ee95e280d46c14921e524b075d0c341e7ad1c8', // cosmos spaces + ], + }, }); - const client = await getSigningClient(); + console.log(JSON.stringify(instructions)); + + // const safe = await getSigningClient( + // '2ac7230628b8b4a587c4005798184735471b9240fc57fc75d97824e1fb6b5409', + // ); + + // const tx = await safe.wasm.executeMultiple(safe.signer, instructions, 'auto'); - const tx = await client.wasm.executeMultiple(client.signer, [msg], 'auto'); - console.log({ tx }); + // console.log(tx); } -main().catch(console.error); +rotateValidators().catch(console.error); From 72d7af37860403c969111a46ab89fa389d84dc5c Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 2 Nov 2023 17:56:23 -0400 Subject: [PATCH 23/30] Add naive cosmos native token adapter --- typescript/sdk/src/app/MultiProtocolApp.ts | 9 +++ .../token/adapters/CosmWasmTokenAdapter.ts | 64 ++++++++++++++++++- 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/typescript/sdk/src/app/MultiProtocolApp.ts b/typescript/sdk/src/app/MultiProtocolApp.ts index 10b98d56d3..daf841dcad 100644 --- a/typescript/sdk/src/app/MultiProtocolApp.ts +++ b/typescript/sdk/src/app/MultiProtocolApp.ts @@ -11,6 +11,7 @@ import { import { ChainMetadata } from '../metadata/chainMetadataTypes'; import { MultiProtocolProvider } from '../providers/MultiProtocolProvider'; import { + CosmJsProvider, CosmJsWasmProvider, EthersV5Provider, SolanaWeb3Provider, @@ -58,6 +59,14 @@ export class BaseCosmWasmAdapter extends BaseAppAdapter { } } +export class BaseCosmosAdapter extends BaseAppAdapter { + public readonly protocol: ProtocolType = ProtocolType.Cosmos; + + public getProvider(): CosmJsProvider['provider'] { + return this.multiProvider.getCosmJsProvider(this.chainName); + } +} + export class BaseSealevelAdapter extends BaseAppAdapter { public readonly protocol: ProtocolType = ProtocolType.Sealevel; diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts index 74d0a16d1a..44a968a84a 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts @@ -1,5 +1,6 @@ import { ExecuteInstruction } from '@cosmjs/cosmwasm-stargate'; -import { Coin } from '@cosmjs/stargate'; +import { Coin, MsgTransferEncodeObject } from '@cosmjs/stargate'; +import { MsgTransfer } from 'cosmjs-types/ibc/applications/transfer/v1/tx'; import { Address, @@ -8,7 +9,10 @@ import { strip0x, } from '@hyperlane-xyz/utils'; -import { BaseCosmWasmAdapter } from '../../app/MultiProtocolApp'; +import { + BaseCosmWasmAdapter, + BaseCosmosAdapter, +} from '../../app/MultiProtocolApp'; import { BalanceResponse, ExecuteMsg as Cw20Execute, @@ -28,7 +32,7 @@ import { } from '../../cw-types/WarpCw20.types'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; import { ChainName } from '../../types'; -import { ERC20Metadata } from '../config'; +import { ERC20Metadata, MinimalTokenMetadata } from '../config'; import { IHypTokenAdapter, @@ -38,6 +42,60 @@ import { } from './ITokenAdapter'; // Interacts with IBC denom tokens +export class NativeTokenAdapter + extends BaseCosmosAdapter + implements ITokenAdapter +{ + constructor( + public readonly chainName: string, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: Record, + public readonly ibcDenom: string = 'untrn', + ) { + super(chainName, multiProvider, addresses); + } + + async getBalance(address: string): Promise { + const provider = await this.getProvider(); + const coin = await provider.getBalance(address, this.ibcDenom); + return coin.amount; + } + + getMetadata(): Promise { + throw new Error('Metadata not available to native tokens'); + } + + populateApproveTx(_transferParams: TransferParams): unknown { + throw new Error('Approve not required for native tokens'); + } + + async populateTransferTx( + transferParams: TransferParams, + ): Promise { + const transfer: MsgTransfer = { + sourcePort: '', + sourceChannel: '', + token: { + denom: this.ibcDenom, + amount: transferParams.weiAmountOrId.toString(), + }, + sender: '', + receiver: '', + timeoutHeight: { + revisionNumber: 0n, + revisionHeight: 0n, + }, + timeoutTimestamp: 0n, + memo: '', + }; + return { + typeUrl: '/ibc.applications.transfer.v1.MsgTransfer', + value: transfer, + }; + } +} + +// Interacts with IBC denom tokens in CosmWasm export class CwNativeTokenAdapter extends BaseCosmWasmAdapter implements ITokenAdapter From 98ea2a0f1fc5a9222ff7d7368c6d64467cd4d0d3 Mon Sep 17 00:00:00 2001 From: Nam Chu Hoai Date: Fri, 3 Nov 2023 08:16:01 -0400 Subject: [PATCH 24/30] Fix merge --- typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts b/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts index 30418d9063..534267004f 100644 --- a/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts +++ b/typescript/infra/scripts/warp-routes/deploy-warp-monitor.ts @@ -3,7 +3,11 @@ import { HelmCommand } from '../../src/utils/helm'; import { runWarpRouteHelmCommand } from './helm'; async function main() { - await runWarpRouteHelmCommand(HelmCommand.InstallOrUpgrade, 'mainnet3'); + await runWarpRouteHelmCommand( + HelmCommand.InstallOrUpgrade, + 'mainnet3', + 'neutron', + ); } main() From b3b35f118eed15d4929ed0cce34e43ad9c4950a9 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 3 Nov 2023 13:39:05 -0400 Subject: [PATCH 25/30] Fix cw build --- .../core/adapters/CosmWasmIgpAdapter.test.ts | 104 ---------- .../sdk/src/cw-types/HookAggregate.types.ts | 92 +++++++++ .../sdk/src/cw-types/HookMerkle.types.ts | 180 ++++++++++++++++++ .../sdk/src/cw-types/HookPausable.types.ts | 97 ++++++++++ .../sdk/src/cw-types/HookRouting.types.ts | 127 ++++++++++++ .../src/cw-types/HookRoutingCustom.types.ts | 174 +++++++++++++++++ .../src/cw-types/HookRoutingFallback.types.ts | 132 +++++++++++++ .../sdk/src/cw-types/IsmAggregate.types.ts | 97 ++++++++++ .../sdk/src/cw-types/IsmMultisig.types.ts | 127 ++++++++++++ .../sdk/src/cw-types/IsmRouting.types.ts | 102 ++++++++++ .../src/cw-types/ValidatorAnnounce.types.ts | 33 ++++ .../ism/adapters/CosmWasmMultisigAdapter.ts | 122 ++++++++++++ .../adapters/CosmWasmTokenAdapter.test.ts | 178 +---------------- .../token/adapters/CosmWasmTokenAdapter.ts | 3 +- 14 files changed, 1295 insertions(+), 273 deletions(-) delete mode 100644 typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.test.ts create mode 100644 typescript/sdk/src/cw-types/HookAggregate.types.ts create mode 100644 typescript/sdk/src/cw-types/HookMerkle.types.ts create mode 100644 typescript/sdk/src/cw-types/HookPausable.types.ts create mode 100644 typescript/sdk/src/cw-types/HookRouting.types.ts create mode 100644 typescript/sdk/src/cw-types/HookRoutingCustom.types.ts create mode 100644 typescript/sdk/src/cw-types/HookRoutingFallback.types.ts create mode 100644 typescript/sdk/src/cw-types/IsmAggregate.types.ts create mode 100644 typescript/sdk/src/cw-types/IsmMultisig.types.ts create mode 100644 typescript/sdk/src/cw-types/IsmRouting.types.ts create mode 100644 typescript/sdk/src/cw-types/ValidatorAnnounce.types.ts create mode 100644 typescript/sdk/src/ism/adapters/CosmWasmMultisigAdapter.ts diff --git a/typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.test.ts b/typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.test.ts deleted file mode 100644 index 4b57b020d3..0000000000 --- a/typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ - -/* eslint-disable no-console */ -import { - ExecuteInstruction, - SigningCosmWasmClient, -} from '@cosmjs/cosmwasm-stargate'; -import { Secp256k1, keccak256 } from '@cosmjs/crypto'; -import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; -import { GasPrice, SigningStargateClient } from '@cosmjs/stargate'; -import { Tendermint37Client } from '@cosmjs/tendermint-rpc'; - -import { ProtocolType } from '@hyperlane-xyz/utils'; - -import { ChainMetadata } from '../../metadata/chainMetadataTypes'; -import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; - -import { CosmWasmIgpAdapter } from './CosmWasmIgpAdapter'; - -const neutron: ChainMetadata = { - protocol: ProtocolType.Cosmos, - name: 'neutron', - chainId: 'neutron-1', - displayName: 'Neutron', - domainId: 1853125230, - bech32Prefix: 'neutron', - slip44: 118, - rpcUrls: [ - { http: 'https://rpc-kralum.neutron-1.neutron.org' }, - { http: 'grpc-kralum.neutron-1.neutron.org:80' }, - ], - nativeToken: { - name: 'Neutron', - symbol: 'NTRN', - decimals: 6, - }, -}; - -const neutronAddresses = { - mailbox: 'neutron1sjzzd4gwkggy6hrrs8kxxatexzcuz3jecsxm3wqgregkulzj8r7qlnuef4', - igp: 'neutron12p8wntzra3vpfcqv05scdx5sa3ftaj6gjcmtm7ynkl0e6crtt4ns8cnrmx', -}; - -const signer = ''; - -export async function getSigningClient() { - const wallet = await DirectSecp256k1Wallet.fromKey( - Buffer.from(signer, 'hex'), - neutron.bech32Prefix!, - ); - - const [account] = await wallet.getAccounts(); - - const clientBase = await Tendermint37Client.connect(neutron.rpcUrls[0].http); - - const gasPrice = GasPrice.fromString('0.025token'); - - const wasm = await SigningCosmWasmClient.createWithSigner( - clientBase, - wallet, - { - gasPrice, - }, - ); - const stargate = await SigningStargateClient.createWithSigner( - clientBase, - wallet, - { - gasPrice, - }, - ); - - const pubkey = Secp256k1.uncompressPubkey(account.pubkey); - const ethaddr = keccak256(pubkey.slice(1)).slice(-20); - - return { - wasm, - stargate, - signer: account.address, - signer_addr: Buffer.from(ethaddr).toString('hex'), - signer_pubkey: Buffer.from(account.pubkey).toString('hex'), - }; -} - -async function main() { - const multiProtocolProvider = new MultiProtocolProvider({ - neutron, - }); - - const adapter = new CosmWasmIgpAdapter( - neutron.name, - multiProtocolProvider, - neutronAddresses, - ); - - const msg: ExecuteInstruction = adapter.prepareSetOracleMsg(); - - const client = await getSigningClient(); - - const tx = await client.wasm.executeMultiple(client.signer, [msg], 'auto'); - console.log({ tx }); -} - -main().catch(console.error); diff --git a/typescript/sdk/src/cw-types/HookAggregate.types.ts b/typescript/sdk/src/cw-types/HookAggregate.types.ts new file mode 100644 index 0000000000..e2aaea12e3 --- /dev/null +++ b/typescript/sdk/src/cw-types/HookAggregate.types.ts @@ -0,0 +1,92 @@ +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + hooks: string[]; + owner: string; +} +export type ExecuteMsg = + | { + ownable: OwnableMsg; + } + | { + post_dispatch: PostDispatchMsg; + } + | { + set_hooks: { + hooks: string[]; + }; + }; +export type OwnableMsg = + | { + init_ownership_transfer: { + next_owner: string; + }; + } + | { + revoke_ownership_transfer: {}; + } + | { + claim_ownership: {}; + }; +export type HexBinary = string; +export interface PostDispatchMsg { + message: HexBinary; + metadata: HexBinary; +} +export type QueryMsg = + | { + ownable: OwnableQueryMsg; + } + | { + hook: HookQueryMsg; + } + | { + aggregate_hook: AggregateHookQueryMsg; + }; +export type OwnableQueryMsg = + | { + get_owner: {}; + } + | { + get_pending_owner: {}; + }; +export type HookQueryMsg = + | { + quote_dispatch: QuoteDispatchMsg; + } + | { + mailbox: {}; + }; +export type AggregateHookQueryMsg = { + hooks: {}; +}; +export interface QuoteDispatchMsg { + message: HexBinary; + metadata: HexBinary; +} +export type Addr = string; +export interface OwnerResponse { + owner: Addr; +} +export interface PendingOwnerResponse { + pending_owner?: Addr | null; +} +export interface HooksResponse { + hooks: string[]; +} +export interface MailboxResponse { + mailbox: string; +} +export type Uint128 = string; +export interface QuoteDispatchResponse { + gas_amount?: Coin | null; +} +export interface Coin { + amount: Uint128; + denom: string; + [k: string]: unknown; +} diff --git a/typescript/sdk/src/cw-types/HookMerkle.types.ts b/typescript/sdk/src/cw-types/HookMerkle.types.ts new file mode 100644 index 0000000000..32765e3033 --- /dev/null +++ b/typescript/sdk/src/cw-types/HookMerkle.types.ts @@ -0,0 +1,180 @@ +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + mailbox: string; + owner: string; +} +export type ExecuteMsg = + | { + ownable: OwnableMsg; + } + | { + post_dispatch: PostDispatchMsg; + }; +export type OwnableMsg = + | { + init_ownership_transfer: { + next_owner: string; + }; + } + | { + revoke_ownership_transfer: {}; + } + | { + claim_ownership: {}; + }; +export type HexBinary = string; +export interface PostDispatchMsg { + message: HexBinary; + metadata: HexBinary; +} +export type QueryMsg = + | { + ownable: OwnableQueryMsg; + } + | { + hook: HookQueryMsg; + } + | { + merkle_hook: MerkleHookQueryMsg; + }; +export type OwnableQueryMsg = + | { + get_owner: {}; + } + | { + get_pending_owner: {}; + }; +export type HookQueryMsg = + | { + quote_dispatch: QuoteDispatchMsg; + } + | { + mailbox: {}; + }; +export type MerkleHookQueryMsg = + | { + count: {}; + } + | { + root: {}; + } + | { + branch: {}; + } + | { + tree: {}; + } + | { + check_point: {}; + }; +export interface QuoteDispatchMsg { + message: HexBinary; + metadata: HexBinary; +} +export interface BranchResponse { + branch: [ + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + ]; +} +export interface CheckPointResponse { + count: number; + root: HexBinary; +} +export interface CountResponse { + count: number; +} +export type Addr = string; +export interface OwnerResponse { + owner: Addr; +} +export interface PendingOwnerResponse { + pending_owner?: Addr | null; +} +export interface MailboxResponse { + mailbox: string; +} +export type Uint128 = string; +export interface QuoteDispatchResponse { + gas_amount?: Coin | null; +} +export interface Coin { + amount: Uint128; + denom: string; + [k: string]: unknown; +} +export interface RootResponse { + root: HexBinary; +} +export interface TreeResponse { + branch: [ + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + HexBinary, + ]; + count: number; +} diff --git a/typescript/sdk/src/cw-types/HookPausable.types.ts b/typescript/sdk/src/cw-types/HookPausable.types.ts new file mode 100644 index 0000000000..918a462f28 --- /dev/null +++ b/typescript/sdk/src/cw-types/HookPausable.types.ts @@ -0,0 +1,97 @@ +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + owner: string; + paused: boolean; +} +export type ExecuteMsg = + | { + ownable: OwnableMsg; + } + | { + pausable: PausableMsg; + } + | { + post_dispatch: PostDispatchMsg; + }; +export type OwnableMsg = + | { + init_ownership_transfer: { + next_owner: string; + }; + } + | { + revoke_ownership_transfer: {}; + } + | { + claim_ownership: {}; + }; +export type PausableMsg = + | { + pause: {}; + } + | { + release: {}; + }; +export type HexBinary = string; +export interface PostDispatchMsg { + message: HexBinary; + metadata: HexBinary; +} +export type QueryMsg = + | { + pausable: PausableQueryMsg; + } + | { + ownable: OwnableQueryMsg; + } + | { + hook: HookQueryMsg; + }; +export type PausableQueryMsg = { + pause_info: {}; +}; +export type OwnableQueryMsg = + | { + get_owner: {}; + } + | { + get_pending_owner: {}; + }; +export type HookQueryMsg = + | { + quote_dispatch: QuoteDispatchMsg; + } + | { + mailbox: {}; + }; +export interface QuoteDispatchMsg { + message: HexBinary; + metadata: HexBinary; +} +export type Addr = string; +export interface OwnerResponse { + owner: Addr; +} +export interface PendingOwnerResponse { + pending_owner?: Addr | null; +} +export interface MailboxResponse { + mailbox: string; +} +export interface PauseInfoResponse { + paused: boolean; +} +export type Uint128 = string; +export interface QuoteDispatchResponse { + gas_amount?: Coin | null; +} +export interface Coin { + amount: Uint128; + denom: string; + [k: string]: unknown; +} diff --git a/typescript/sdk/src/cw-types/HookRouting.types.ts b/typescript/sdk/src/cw-types/HookRouting.types.ts new file mode 100644 index 0000000000..564a01077b --- /dev/null +++ b/typescript/sdk/src/cw-types/HookRouting.types.ts @@ -0,0 +1,127 @@ +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + owner: string; +} +export type ExecuteMsg = + | { + ownable: OwnableMsg; + } + | { + post_dispatch: PostDispatchMsg; + } + | { + router: RouterMsgForAddr; + }; +export type OwnableMsg = + | { + init_ownership_transfer: { + next_owner: string; + }; + } + | { + revoke_ownership_transfer: {}; + } + | { + claim_ownership: {}; + }; +export type HexBinary = string; +export type RouterMsgForAddr = + | { + set_route: { + set: DomainRouteSetForAddr; + }; + } + | { + set_routes: { + set: DomainRouteSetForAddr[]; + }; + }; +export type Addr = string; +export interface PostDispatchMsg { + message: HexBinary; + metadata: HexBinary; +} +export interface DomainRouteSetForAddr { + domain: number; + route?: Addr | null; +} +export type QueryMsg = + | { + ownable: OwnableQueryMsg; + } + | { + router: RouterQueryForAddr; + } + | { + hook: HookQueryMsg; + }; +export type OwnableQueryMsg = + | { + get_owner: {}; + } + | { + get_pending_owner: {}; + }; +export type RouterQueryForAddr = + | { + domains: {}; + } + | { + get_route: { + domain: number; + }; + } + | { + list_routes: { + limit?: number | null; + offset?: number | null; + order?: Order | null; + }; + }; +export type Order = 'asc' | 'desc'; +export type HookQueryMsg = + | { + quote_dispatch: QuoteDispatchMsg; + } + | { + mailbox: {}; + }; +export interface QuoteDispatchMsg { + message: HexBinary; + metadata: HexBinary; +} +export interface DomainsResponse { + domains: number[]; +} +export interface OwnerResponse { + owner: Addr; +} +export interface PendingOwnerResponse { + pending_owner?: Addr | null; +} +export interface RouteResponseForAddr { + route: DomainRouteSetForAddr; +} +export interface RoutesResponseForAddr { + routes: DomainRouteSetForAddr[]; +} +export interface MailboxResponse { + mailbox: string; +} +export interface Empty { + [k: string]: unknown; +} +export type Uint128 = string; +export interface QuoteDispatchResponse { + gas_amount?: Coin | null; +} +export interface Coin { + amount: Uint128; + denom: string; + [k: string]: unknown; +} diff --git a/typescript/sdk/src/cw-types/HookRoutingCustom.types.ts b/typescript/sdk/src/cw-types/HookRoutingCustom.types.ts new file mode 100644 index 0000000000..ad8ecfdda3 --- /dev/null +++ b/typescript/sdk/src/cw-types/HookRoutingCustom.types.ts @@ -0,0 +1,174 @@ +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + owner: string; +} +export type ExecuteMsg = + | { + ownable: OwnableMsg; + } + | { + post_dispatch: PostDispatchMsg; + } + | { + router: RouterMsgForAddr; + } + | { + register_custom_hook: RegisterCustomHookMsg; + } + | { + register_custom_hooks: RegisterCustomHookMsg[]; + } + | { + clear_custom_hook: ClearCustomHookMsg; + } + | { + clear_custom_hooks: ClearCustomHookMsg[]; + }; +export type OwnableMsg = + | { + init_ownership_transfer: { + next_owner: string; + }; + } + | { + revoke_ownership_transfer: {}; + } + | { + claim_ownership: {}; + }; +export type HexBinary = string; +export type RouterMsgForAddr = + | { + set_route: { + set: DomainRouteSetForAddr; + }; + } + | { + set_routes: { + set: DomainRouteSetForAddr[]; + }; + }; +export type Addr = string; +export interface PostDispatchMsg { + message: HexBinary; + metadata: HexBinary; +} +export interface DomainRouteSetForAddr { + domain: number; + route?: Addr | null; +} +export interface RegisterCustomHookMsg { + dest_domain: number; + hook: string; + recipient: string; +} +export interface ClearCustomHookMsg { + dest_domain: number; + recipient: string; +} +export type QueryMsg = + | { + ownable: OwnableQueryMsg; + } + | { + router: RouterQueryForAddr; + } + | { + hook: HookQueryMsg; + } + | { + custom_routing_hook: CustomRoutingHookQueryMsg; + }; +export type OwnableQueryMsg = + | { + get_owner: {}; + } + | { + get_pending_owner: {}; + }; +export type RouterQueryForAddr = + | { + domains: {}; + } + | { + get_route: { + domain: number; + }; + } + | { + list_routes: { + limit?: number | null; + offset?: number | null; + order?: Order | null; + }; + }; +export type Order = 'asc' | 'desc'; +export type HookQueryMsg = + | { + quote_dispatch: QuoteDispatchMsg; + } + | { + mailbox: {}; + }; +export type CustomRoutingHookQueryMsg = + | { + custom_hook: { + dest_domain: number; + recipient: string; + }; + } + | { + custom_hooks: { + dest_domain: number; + limit?: number | null; + offset?: string | null; + order?: Order | null; + }; + }; +export interface QuoteDispatchMsg { + message: HexBinary; + metadata: HexBinary; +} +export interface CustomHookResponse { + dest_domain: number; + hook: string; + recipient: string; +} +export interface CustomHooksResponse { + custom_hooks: CustomHookResponse[]; +} +export interface DomainsResponse { + domains: number[]; +} +export interface OwnerResponse { + owner: Addr; +} +export interface PendingOwnerResponse { + pending_owner?: Addr | null; +} +export interface RouteResponseForAddr { + route: DomainRouteSetForAddr; +} +export interface RoutesResponseForAddr { + routes: DomainRouteSetForAddr[]; +} +export interface MailboxResponse { + mailbox: string; +} +export interface Empty { + [k: string]: unknown; +} +export type Uint128 = string; +export interface QuoteDispatchResponse { + gas_amount?: Coin | null; +} +export interface Coin { + amount: Uint128; + denom: string; + [k: string]: unknown; +} diff --git a/typescript/sdk/src/cw-types/HookRoutingFallback.types.ts b/typescript/sdk/src/cw-types/HookRoutingFallback.types.ts new file mode 100644 index 0000000000..93f9bd088e --- /dev/null +++ b/typescript/sdk/src/cw-types/HookRoutingFallback.types.ts @@ -0,0 +1,132 @@ +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + owner: string; +} +export type ExecuteMsg = + | { + ownable: OwnableMsg; + } + | { + post_dispatch: PostDispatchMsg; + } + | { + router: RouterMsgForAddr; + } + | { + set_fallback_hook: { + hook: string; + }; + }; +export type OwnableMsg = + | { + init_ownership_transfer: { + next_owner: string; + }; + } + | { + revoke_ownership_transfer: {}; + } + | { + claim_ownership: {}; + }; +export type HexBinary = string; +export type RouterMsgForAddr = + | { + set_route: { + set: DomainRouteSetForAddr; + }; + } + | { + set_routes: { + set: DomainRouteSetForAddr[]; + }; + }; +export type Addr = string; +export interface PostDispatchMsg { + message: HexBinary; + metadata: HexBinary; +} +export interface DomainRouteSetForAddr { + domain: number; + route?: Addr | null; +} +export type QueryMsg = + | { + ownable: OwnableQueryMsg; + } + | { + router: RouterQueryForAddr; + } + | { + hook: HookQueryMsg; + }; +export type OwnableQueryMsg = + | { + get_owner: {}; + } + | { + get_pending_owner: {}; + }; +export type RouterQueryForAddr = + | { + domains: {}; + } + | { + get_route: { + domain: number; + }; + } + | { + list_routes: { + limit?: number | null; + offset?: number | null; + order?: Order | null; + }; + }; +export type Order = 'asc' | 'desc'; +export type HookQueryMsg = + | { + quote_dispatch: QuoteDispatchMsg; + } + | { + mailbox: {}; + }; +export interface QuoteDispatchMsg { + message: HexBinary; + metadata: HexBinary; +} +export interface DomainsResponse { + domains: number[]; +} +export interface OwnerResponse { + owner: Addr; +} +export interface PendingOwnerResponse { + pending_owner?: Addr | null; +} +export interface RouteResponseForAddr { + route: DomainRouteSetForAddr; +} +export interface RoutesResponseForAddr { + routes: DomainRouteSetForAddr[]; +} +export interface MailboxResponse { + mailbox: string; +} +export interface Empty { + [k: string]: unknown; +} +export type Uint128 = string; +export interface QuoteDispatchResponse { + gas_amount?: Coin | null; +} +export interface Coin { + amount: Uint128; + denom: string; + [k: string]: unknown; +} diff --git a/typescript/sdk/src/cw-types/IsmAggregate.types.ts b/typescript/sdk/src/cw-types/IsmAggregate.types.ts new file mode 100644 index 0000000000..f57d20b546 --- /dev/null +++ b/typescript/sdk/src/cw-types/IsmAggregate.types.ts @@ -0,0 +1,97 @@ +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + isms: string[]; + owner: string; + threshold: number; +} +export type ExecuteMsg = + | { + ownable: OwnableMsg; + } + | { + set_isms: { + isms: string[]; + }; + }; +export type OwnableMsg = + | { + init_ownership_transfer: { + next_owner: string; + }; + } + | { + revoke_ownership_transfer: {}; + } + | { + claim_ownership: {}; + }; +export type QueryMsg = + | { + ownable: OwnableQueryMsg; + } + | { + ism: IsmQueryMsg; + } + | { + aggregate_ism: AggregateIsmQueryMsg; + }; +export type OwnableQueryMsg = + | { + get_owner: {}; + } + | { + get_pending_owner: {}; + }; +export type IsmQueryMsg = + | { + module_type: {}; + } + | { + verify: { + message: HexBinary; + metadata: HexBinary; + }; + } + | { + verify_info: { + message: HexBinary; + }; + }; +export type HexBinary = string; +export type AggregateIsmQueryMsg = { + isms: {}; +}; +export type Addr = string; +export interface OwnerResponse { + owner: Addr; +} +export interface PendingOwnerResponse { + pending_owner?: Addr | null; +} +export interface IsmsResponse { + isms: string[]; +} +export type IsmType = + | 'unused' + | 'routing' + | 'aggregation' + | 'legacy_multisig' + | 'merkle_root_multisig' + | 'message_id_multisig' + | 'null' + | 'ccip_read'; +export interface ModuleTypeResponse { + type: IsmType; +} +export interface VerifyResponse { + verified: boolean; +} +export interface VerifyInfoResponse { + threshold: number; + validators: HexBinary[]; +} diff --git a/typescript/sdk/src/cw-types/IsmMultisig.types.ts b/typescript/sdk/src/cw-types/IsmMultisig.types.ts new file mode 100644 index 0000000000..d214d92088 --- /dev/null +++ b/typescript/sdk/src/cw-types/IsmMultisig.types.ts @@ -0,0 +1,127 @@ +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + owner: string; +} +export type ExecuteMsg = + | { + ownable: OwnableMsg; + } + | { + enroll_validator: { + set: ValidatorSet; + }; + } + | { + enroll_validators: { + set: ValidatorSet[]; + }; + } + | { + unenroll_validator: { + domain: number; + validator: HexBinary; + }; + } + | { + set_threshold: { + set: ThresholdSet; + }; + } + | { + set_thresholds: { + set: ThresholdSet[]; + }; + }; +export type OwnableMsg = + | { + init_ownership_transfer: { + next_owner: string; + }; + } + | { + revoke_ownership_transfer: {}; + } + | { + claim_ownership: {}; + }; +export type HexBinary = string; +export interface ValidatorSet { + domain: number; + validator: HexBinary; +} +export interface ThresholdSet { + domain: number; + threshold: number; +} +export type QueryMsg = + | { + ownable: OwnableQueryMsg; + } + | { + ism: IsmQueryMsg; + } + | { + multisig_ism: MultisigIsmQueryMsg; + }; +export type OwnableQueryMsg = + | { + get_owner: {}; + } + | { + get_pending_owner: {}; + }; +export type IsmQueryMsg = + | { + module_type: {}; + } + | { + verify: { + message: HexBinary; + metadata: HexBinary; + }; + } + | { + verify_info: { + message: HexBinary; + }; + }; +export type MultisigIsmQueryMsg = { + enrolled_validators: { + domain: number; + }; +}; +export interface EnrolledValidatorsResponse { + threshold: number; + validators: HexBinary[]; +} +export type Addr = string; +export interface OwnerResponse { + owner: Addr; +} +export interface PendingOwnerResponse { + pending_owner?: Addr | null; +} +export type IsmType = + | 'unused' + | 'routing' + | 'aggregation' + | 'legacy_multisig' + | 'merkle_root_multisig' + | 'message_id_multisig' + | 'null' + | 'ccip_read'; +export interface ModuleTypeResponse { + type: IsmType; +} +export interface VerifyResponse { + verified: boolean; +} +export interface VerifyInfoResponse { + threshold: number; + validators: HexBinary[]; +} diff --git a/typescript/sdk/src/cw-types/IsmRouting.types.ts b/typescript/sdk/src/cw-types/IsmRouting.types.ts new file mode 100644 index 0000000000..41c8185f42 --- /dev/null +++ b/typescript/sdk/src/cw-types/IsmRouting.types.ts @@ -0,0 +1,102 @@ +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + isms: IsmSet[]; + owner: string; +} +export interface IsmSet { + address: string; + domain: number; +} +export type ExecuteMsg = + | { + ownable: OwnableMsg; + } + | { + set: { + ism: IsmSet; + }; + }; +export type OwnableMsg = + | { + init_ownership_transfer: { + next_owner: string; + }; + } + | { + revoke_ownership_transfer: {}; + } + | { + claim_ownership: {}; + }; +export type QueryMsg = + | { + ownable: OwnableQueryMsg; + } + | { + ism: IsmQueryMsg; + } + | { + routing_ism: RoutingIsmQueryMsg; + }; +export type OwnableQueryMsg = + | { + get_owner: {}; + } + | { + get_pending_owner: {}; + }; +export type IsmQueryMsg = + | { + module_type: {}; + } + | { + verify: { + message: HexBinary; + metadata: HexBinary; + }; + } + | { + verify_info: { + message: HexBinary; + }; + }; +export type HexBinary = string; +export type RoutingIsmQueryMsg = { + route: { + message: HexBinary; + }; +}; +export type Addr = string; +export interface OwnerResponse { + owner: Addr; +} +export interface PendingOwnerResponse { + pending_owner?: Addr | null; +} +export type IsmType = + | 'unused' + | 'routing' + | 'aggregation' + | 'legacy_multisig' + | 'merkle_root_multisig' + | 'message_id_multisig' + | 'null' + | 'ccip_read'; +export interface ModuleTypeResponse { + type: IsmType; +} +export interface RouteResponse { + ism: string; +} +export interface VerifyResponse { + verified: boolean; +} +export interface VerifyInfoResponse { + threshold: number; + validators: HexBinary[]; +} diff --git a/typescript/sdk/src/cw-types/ValidatorAnnounce.types.ts b/typescript/sdk/src/cw-types/ValidatorAnnounce.types.ts new file mode 100644 index 0000000000..0d5970682a --- /dev/null +++ b/typescript/sdk/src/cw-types/ValidatorAnnounce.types.ts @@ -0,0 +1,33 @@ +/** + * This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run the @cosmwasm/ts-codegen generate command to regenerate this file. + */ + +export interface InstantiateMsg { + hrp: string; + mailbox: string; +} +export type ExecuteMsg = { + announce: { + signature: HexBinary; + storage_location: string; + validator: HexBinary; + }; +}; +export type HexBinary = string; +export type QueryMsg = + | { + get_announce_storage_locations: { + validators: HexBinary[]; + }; + } + | { + get_announced_validators: {}; + }; +export interface GetAnnounceStorageLocationsResponse { + storage_locations: [string, string[]][]; +} +export interface GetAnnouncedValidatorsResponse { + validators: string[]; +} diff --git a/typescript/sdk/src/ism/adapters/CosmWasmMultisigAdapter.ts b/typescript/sdk/src/ism/adapters/CosmWasmMultisigAdapter.ts new file mode 100644 index 0000000000..8f2e7e768a --- /dev/null +++ b/typescript/sdk/src/ism/adapters/CosmWasmMultisigAdapter.ts @@ -0,0 +1,122 @@ +import { ExecuteInstruction } from '@cosmjs/cosmwasm-stargate'; + +import { + Address, + difference, + objMap, + promiseObjAll, +} from '@hyperlane-xyz/utils'; + +import { BaseCosmWasmAdapter } from '../../app/MultiProtocolApp'; +import { + EnrolledValidatorsResponse, + ExecuteMsg as MultisigExecute, + QueryMsg as MultisigQuery, +} from '../../cw-types/IsmMultisig.types'; +import { MultisigConfig } from '../../ism/types'; +import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; +import { ChainMap, ChainName } from '../../types'; + +type MultisigResponse = EnrolledValidatorsResponse; + +export class CosmWasmMultisigAdapter extends BaseCosmWasmAdapter { + constructor( + public readonly chainName: ChainName, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: { multisig: Address }, + ) { + super(chainName, multiProvider, addresses); + } + + async queryMultisig( + msg: MultisigQuery, + ): Promise { + const provider = await this.getProvider(); + const response: R = await provider.queryContractSmart( + this.addresses.multisig, + msg, + ); + return response; + } + + async getConfig(chain: ChainName): Promise { + return this.queryMultisig({ + multisig_ism: { + enrolled_validators: { + domain: this.multiProvider.getDomainId(chain), + }, + }, + }); + } + + prepareMultisig(msg: MultisigExecute): ExecuteInstruction { + return { + contractAddress: this.addresses.multisig, + msg, + }; + } + + async configureMultisig( + configMap: ChainMap, + ): Promise { + const configuredMap = await promiseObjAll( + objMap(configMap, (origin, _) => this.getConfig(origin)), + ); + + const validatorInstructions = Object.entries(configMap).flatMap( + ([origin, config]) => { + const domain = this.multiProvider.getDomainId(origin); + const configuredSet = new Set(configuredMap[origin].validators); + const configSet = new Set(config.validators); + const unenrollList = Array.from( + difference(configuredSet, configSet).values(), + ); + const enrollList = Array.from( + difference(configSet, configuredSet).values(), + ); + return unenrollList + .map((validator) => + this.prepareMultisig({ + unenroll_validator: { + domain, + validator, + }, + }), + ) + .concat( + enrollList.map((validator) => + this.prepareMultisig({ + enroll_validator: { + set: { + domain, + validator, + }, + }, + }), + ), + ); + }, + ); + + const setThresholds = Object.entries(configMap) + .filter( + ([origin, { threshold }]) => + threshold !== configuredMap[origin].threshold, + ) + .map(([origin, config]) => ({ + domain: this.multiProvider.getDomainId(origin), + threshold: config.threshold, + })); + + if (setThresholds.length > 0) { + const thresholdInstruction = this.prepareMultisig({ + set_thresholds: { + set: setThresholds, + }, + }); + return [...validatorInstructions, thresholdInstruction]; + } + + return validatorInstructions; + } +} diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts index c4ba932003..99ea389df1 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts @@ -1,9 +1,5 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -/* eslint-disable no-console */ - -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ - /* eslint-disable no-console */ import { CosmWasmClient, @@ -15,68 +11,18 @@ import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; import { GasPrice, SigningStargateClient } from '@cosmjs/stargate'; import { Tendermint37Client } from '@cosmjs/tendermint-rpc'; -import { - Address, - ProtocolType, - difference, - objMap, - promiseObjAll, -} from '@hyperlane-xyz/utils'; - -import { BaseCosmWasmAdapter } from '../../app/MultiProtocolApp'; +import { Address } from '@hyperlane-xyz/utils'; + +import { chainMetadata } from '../../consts/chainMetadata'; +import { Chains } from '../../consts/chains'; import { CosmWasmCoreAdapter } from '../../core/adapters/CosmWasmCoreAdapter'; import { MailboxResponse, QueryMsg as MerkleQuery, OwnerResponse, } from '../../cw-types/HookMerkle.types'; -import { - EnrolledValidatorsResponse, - ExecuteMsg as MultisigExecute, - QueryMsg as MultisigQuery, -} from '../../cw-types/IsmMultisig.types'; -import { MultisigConfig } from '../../ism/types'; -import { ChainMetadata } from '../../metadata/chainMetadataTypes'; +import { CosmWasmMultisigAdapter } from '../../ism/adapters/CosmWasmMultisigAdapter'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; -import { ChainMap, ChainName } from '../../types'; - -const neutron: ChainMetadata = { - protocol: ProtocolType.Cosmos, - name: 'neutron', - chainId: 'neutron-1', - displayName: 'Neutron', - domainId: 1853125230, - bech32Prefix: 'neutron', - slip44: 118, - rpcUrls: [ - { http: 'https://rpc-kralum.neutron-1.neutron.org' }, - { http: 'grpc-kralum.neutron-1.neutron.org:80' }, - ], - nativeToken: { - name: 'Neutron', - symbol: 'NTRN', - decimals: 6, - }, -}; - -const mantapacific: ChainMetadata = { - protocol: ProtocolType.Ethereum, - chainId: 169, - name: 'mantapacific', - displayName: 'Manta Pacific', - displayNameShort: 'Manta', - nativeToken: { - name: 'Ether', - symbol: 'ETH', - decimals: 18, - }, - blocks: { - confirmations: 1, - reorgPeriod: 0, - estimateBlockTime: 3, - }, - rpcUrls: [{ http: 'https://pacific-rpc.manta.network/http' }], -}; const neutronAddresses = { mailbox: 'neutron1sjzzd4gwkggy6hrrs8kxxatexzcuz3jecsxm3wqgregkulzj8r7qlnuef4', @@ -84,121 +30,17 @@ const neutronAddresses = { 'neutron17w4q6efzym3p4c6umyp4cjf2ustjtmwfqdhd7rt2fpcpk9fmjzsq0kj0f8', }; -const multiProtocolProvider = new MultiProtocolProvider({ - neutron, - mantapacific, -}); +const neutron = chainMetadata.neutron; +const mantapacific = chainMetadata.mantapacific; + +const multiProtocolProvider = new MultiProtocolProvider(); const adapter = new CosmWasmCoreAdapter( - neutron.name, + Chains.neutron, multiProtocolProvider, neutronAddresses, ); -type MultisigResponse = EnrolledValidatorsResponse; - -class CosmWasmMultisigAdapter extends BaseCosmWasmAdapter { - constructor( - public readonly chainName: ChainName, - public readonly multiProvider: MultiProtocolProvider, - public readonly addresses: { multisig: Address }, - ) { - super(chainName, multiProvider, addresses); - } - - async queryMultisig( - msg: MultisigQuery, - ): Promise { - const provider = await this.getProvider(); - const response: R = await provider.queryContractSmart( - this.addresses.multisig, - msg, - ); - return response; - } - - async getConfig(chain: ChainName): Promise { - return this.queryMultisig({ - multisig_ism: { - enrolled_validators: { - domain: this.multiProvider.getDomainId(chain), - }, - }, - }); - } - - prepareMultisig(msg: MultisigExecute): ExecuteInstruction { - return { - contractAddress: this.addresses.multisig, - msg, - }; - } - - async configureMultisig( - configMap: ChainMap, - ): Promise { - const configuredMap = await promiseObjAll( - objMap(configMap, (origin, _) => this.getConfig(origin)), - ); - - const validatorInstructions = Object.entries(configMap).flatMap( - ([origin, config]) => { - const domain = this.multiProvider.getDomainId(origin); - const configuredSet = new Set(configuredMap[origin].validators); - const configSet = new Set(config.validators); - const unenrollList = Array.from( - difference(configuredSet, configSet).values(), - ); - const enrollList = Array.from( - difference(configSet, configuredSet).values(), - ); - return unenrollList - .map((validator) => - this.prepareMultisig({ - unenroll_validator: { - domain, - validator, - }, - }), - ) - .concat( - enrollList.map((validator) => - this.prepareMultisig({ - enroll_validator: { - set: { - domain, - validator, - }, - }, - }), - ), - ); - }, - ); - - const setThresholds = Object.entries(configMap) - .filter( - ([origin, { threshold }]) => - threshold !== configuredMap[origin].threshold, - ) - .map(([origin, config]) => ({ - domain: this.multiProvider.getDomainId(origin), - threshold: config.threshold, - })); - - if (setThresholds.length > 0) { - const thresholdInstruction = this.prepareMultisig({ - set_thresholds: { - set: setThresholds, - }, - }); - return [...validatorInstructions, thresholdInstruction]; - } - - return validatorInstructions; - } -} - export async function getSigningClient(pkey: string) { const wallet = await DirectSecp256k1Wallet.fromKey( Buffer.from(pkey, 'hex'), diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts index 44a968a84a..5d94cf2f59 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts @@ -86,10 +86,11 @@ export class NativeTokenAdapter revisionHeight: 0n, }, timeoutTimestamp: 0n, - memo: '', + memo: '', // how to encode this? }; return { typeUrl: '/ibc.applications.transfer.v1.MsgTransfer', + // @ts-ignore value: transfer, }; } From 4fe8ed2b8b821f9fbaa699e63cd163d84ae021a7 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 3 Nov 2023 13:41:38 -0400 Subject: [PATCH 26/30] Move cosmos token adapter out --- .../token/adapters/CosmWasmTokenAdapter.ts | 65 +------------------ .../src/token/adapters/CosmosTokenAdapter.ts | 65 +++++++++++++++++++ 2 files changed, 68 insertions(+), 62 deletions(-) create mode 100644 typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts index 5d94cf2f59..594018cf99 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts @@ -1,6 +1,5 @@ import { ExecuteInstruction } from '@cosmjs/cosmwasm-stargate'; -import { Coin, MsgTransferEncodeObject } from '@cosmjs/stargate'; -import { MsgTransfer } from 'cosmjs-types/ibc/applications/transfer/v1/tx'; +import { Coin } from '@cosmjs/stargate'; import { Address, @@ -9,10 +8,7 @@ import { strip0x, } from '@hyperlane-xyz/utils'; -import { - BaseCosmWasmAdapter, - BaseCosmosAdapter, -} from '../../app/MultiProtocolApp'; +import { BaseCosmWasmAdapter } from '../../app/MultiProtocolApp'; import { BalanceResponse, ExecuteMsg as Cw20Execute, @@ -32,7 +28,7 @@ import { } from '../../cw-types/WarpCw20.types'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; import { ChainName } from '../../types'; -import { ERC20Metadata, MinimalTokenMetadata } from '../config'; +import { ERC20Metadata } from '../config'; import { IHypTokenAdapter, @@ -41,61 +37,6 @@ import { TransferRemoteParams, } from './ITokenAdapter'; -// Interacts with IBC denom tokens -export class NativeTokenAdapter - extends BaseCosmosAdapter - implements ITokenAdapter -{ - constructor( - public readonly chainName: string, - public readonly multiProvider: MultiProtocolProvider, - public readonly addresses: Record, - public readonly ibcDenom: string = 'untrn', - ) { - super(chainName, multiProvider, addresses); - } - - async getBalance(address: string): Promise { - const provider = await this.getProvider(); - const coin = await provider.getBalance(address, this.ibcDenom); - return coin.amount; - } - - getMetadata(): Promise { - throw new Error('Metadata not available to native tokens'); - } - - populateApproveTx(_transferParams: TransferParams): unknown { - throw new Error('Approve not required for native tokens'); - } - - async populateTransferTx( - transferParams: TransferParams, - ): Promise { - const transfer: MsgTransfer = { - sourcePort: '', - sourceChannel: '', - token: { - denom: this.ibcDenom, - amount: transferParams.weiAmountOrId.toString(), - }, - sender: '', - receiver: '', - timeoutHeight: { - revisionNumber: 0n, - revisionHeight: 0n, - }, - timeoutTimestamp: 0n, - memo: '', // how to encode this? - }; - return { - typeUrl: '/ibc.applications.transfer.v1.MsgTransfer', - // @ts-ignore - value: transfer, - }; - } -} - // Interacts with IBC denom tokens in CosmWasm export class CwNativeTokenAdapter extends BaseCosmWasmAdapter diff --git a/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts new file mode 100644 index 0000000000..9830265a24 --- /dev/null +++ b/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts @@ -0,0 +1,65 @@ +import { MsgTransferEncodeObject } from '@cosmjs/stargate'; +import { MsgTransfer } from 'cosmjs-types/ibc/applications/transfer/v1/tx'; + +import { Address } from '@hyperlane-xyz/utils'; + +import { BaseCosmosAdapter } from '../../app/MultiProtocolApp'; +import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; +import { MinimalTokenMetadata } from '../config'; + +import { ITokenAdapter, TransferParams } from './ITokenAdapter'; + +// Interacts with IBC denom tokens +export class NativeTokenAdapter + extends BaseCosmosAdapter + implements ITokenAdapter +{ + constructor( + public readonly chainName: string, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: Record, + public readonly ibcDenom: string = 'untrn', + ) { + super(chainName, multiProvider, addresses); + } + + async getBalance(address: string): Promise { + const provider = await this.getProvider(); + const coin = await provider.getBalance(address, this.ibcDenom); + return coin.amount; + } + + getMetadata(): Promise { + throw new Error('Metadata not available to native tokens'); + } + + populateApproveTx(_transferParams: TransferParams): unknown { + throw new Error('Approve not required for native tokens'); + } + + async populateTransferTx( + transferParams: TransferParams, + ): Promise { + const transfer: MsgTransfer = { + sourcePort: '', + sourceChannel: '', + token: { + denom: this.ibcDenom, + amount: transferParams.weiAmountOrId.toString(), + }, + sender: '', + receiver: '', + timeoutHeight: { + revisionNumber: 0n, + revisionHeight: 0n, + }, + timeoutTimestamp: 0n, + memo: '', // how to encode this? + }; + return { + typeUrl: '/ibc.applications.transfer.v1.MsgTransfer', + // @ts-ignore + value: transfer, + }; + } +} From dd92caa0d2da03e51773835a0961bc739f945591 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 3 Nov 2023 13:45:02 -0400 Subject: [PATCH 27/30] Fix lint --- typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.ts | 2 +- .../sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.ts b/typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.ts index 8a35731d77..1925e62aae 100644 --- a/typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.ts +++ b/typescript/sdk/src/core/adapters/CosmWasmIgpAdapter.ts @@ -79,7 +79,7 @@ export class CosmWasmIgpAdapter extends BaseCosmWasmAdapter { ); } - async defaultGas() { + async defaultGas(): Promise { const defaultGas = await this.queryIgp({ igp: { default_gas: {}, diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts index 99ea389df1..09cf202e21 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts @@ -165,8 +165,8 @@ export async function rotateAuth() { 'neutron1ch7x3xgpnj62weyes8vfada35zff6z59kt2psqhnx9gjnt2ttqdqtva3pa', // warp route ]; - let transferInstructions: ExecuteInstruction[] = []; - let claimInstructions: ExecuteInstruction[] = []; + const transferInstructions: ExecuteInstruction[] = []; + const claimInstructions: ExecuteInstruction[] = []; for (const address of addresses) { const info = await safe.wasm.getContract(address); @@ -218,7 +218,7 @@ export async function rotateAuth() { } export async function summary() { - let summary: any = {}; + const summary: any = {}; const provider = await adapter.getProvider(); From 23173b3ba9a632629764b309a7f48ca042853b58 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 3 Nov 2023 13:51:45 -0400 Subject: [PATCH 28/30] Resolve core adapter TODO --- typescript/sdk/src/core/MultiProtocolCore.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/typescript/sdk/src/core/MultiProtocolCore.ts b/typescript/sdk/src/core/MultiProtocolCore.ts index 517d2c9b4f..4f5cca13b4 100644 --- a/typescript/sdk/src/core/MultiProtocolCore.ts +++ b/typescript/sdk/src/core/MultiProtocolCore.ts @@ -11,6 +11,7 @@ import { MultiProtocolProvider } from '../providers/MultiProtocolProvider'; import { TypedTransactionReceipt } from '../providers/ProviderType'; import { ChainMap, ChainName } from '../types'; +import { CosmWasmCoreAdapter } from './adapters/CosmWasmCoreAdapter'; import { EvmCoreAdapter } from './adapters/EvmCoreAdapter'; import { SealevelCoreAdapter } from './adapters/SealevelCoreAdapter'; import { ICoreAdapter } from './adapters/types'; @@ -54,7 +55,7 @@ export class MultiProtocolCore extends MultiProtocolApp< ): AdapterClassType { if (protocol === ProtocolType.Ethereum) return EvmCoreAdapter; if (protocol === ProtocolType.Sealevel) return SealevelCoreAdapter; - // TODO cosmos core adapter here + if (protocol === ProtocolType.Cosmos) return CosmWasmCoreAdapter; throw new Error(`No adapter for protocol ${protocol}`); } From d017cacdd7dd21b58a143d0448a091b78abe2b04 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 3 Nov 2023 13:58:02 -0400 Subject: [PATCH 29/30] Move to readonly test to be safe --- typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts index 09cf202e21..984274b75b 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts @@ -393,4 +393,4 @@ export async function rotateValidators() { // console.log(tx); } -rotateValidators().catch(console.error); +summary().catch(console.error); From 9b4c832b91a05cf9dbf176ec9495fc8e455b3286 Mon Sep 17 00:00:00 2001 From: Nam Chu Hoai Date: Fri, 3 Nov 2023 21:25:17 -0400 Subject: [PATCH 30/30] Add dsrv/sg-1 --- .../sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts index 984274b75b..2c0da0b7f3 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts @@ -371,13 +371,15 @@ export async function rotateValidators() { ); const instructions = await multisigAdapter.configureMultisig({ [mantapacific.name]: { - threshold: 3, + threshold: 5, validators: [ '8e668c97ad76d0e28375275c41ece4972ab8a5bc', // hyperlane '521a3e6bf8d24809fde1c1fd3494a859a16f132c', // cosmosstation '25b9a0961c51e74fd83295293bc029131bf1e05a', // neutron (pablo) '14025fe092f5f8a401dd9819704d9072196d2125', // p2p 'a0ee95e280d46c14921e524b075d0c341e7ad1c8', // cosmos spaces + 'cc9a0b6de7fe314bd99223687d784730a75bb957', // dsrv + '42b6de2edbaa62c2ea2309ad85d20b3e37d38acf', // sg-1 ], }, });