From e4ed2c3f863ba164bd86f5f9be50a09bc61dc808 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 9 Nov 2023 17:01:15 -0500 Subject: [PATCH] Cosmos adapter updates for gas and IBC (#2894) - Combine tx amounts in cw adapter when denoms match - Update CosmosTokenAdapters with latest from warp ui - Fix IBC-to-warp adapter for IGP support - Add logos for cosmos related chains --- solidity/package.json | 4 +- typescript/helloworld/package.json | 6 +- typescript/infra/package.json | 8 +- typescript/sdk/logos/black/cosmos.svg | 1 + typescript/sdk/logos/black/cosmwasm.svg | 1 + typescript/sdk/logos/black/manta.svg | 1 + typescript/sdk/logos/black/neutron.svg | 1 + typescript/sdk/logos/color/cosmos.svg | 1 + typescript/sdk/logos/color/cosmwasm.svg | 1 + typescript/sdk/logos/color/manta.svg | 1 + typescript/sdk/logos/color/neutron.svg | 1 + typescript/sdk/package.json | 7 +- typescript/sdk/src/consts/chainMetadata.ts | 1 + typescript/sdk/src/index.ts | 5 + .../token/adapters/CosmWasmTokenAdapter.ts | 33 ++-- .../src/token/adapters/CosmosTokenAdapter.ts | 171 +++++++++++++++--- typescript/utils/package.json | 2 +- yarn.lock | 25 +-- 18 files changed, 211 insertions(+), 59 deletions(-) create mode 100644 typescript/sdk/logos/black/cosmos.svg create mode 100644 typescript/sdk/logos/black/cosmwasm.svg create mode 100644 typescript/sdk/logos/black/manta.svg create mode 100644 typescript/sdk/logos/black/neutron.svg create mode 100644 typescript/sdk/logos/color/cosmos.svg create mode 100644 typescript/sdk/logos/color/cosmwasm.svg create mode 100644 typescript/sdk/logos/color/manta.svg create mode 100644 typescript/sdk/logos/color/neutron.svg diff --git a/solidity/package.json b/solidity/package.json index cc1ed89cc4..80367ab22e 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,10 +1,10 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "3.1.0-beta0", + "version": "3.1.0-beta3", "dependencies": { "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "3.1.0-beta0", + "@hyperlane-xyz/utils": "3.1.0-beta3", "@openzeppelin/contracts": "^4.8.0", "@openzeppelin/contracts-upgradeable": "^4.8.0" }, diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index 5b95890f78..99a231521d 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": "3.1.0-beta0", + "version": "3.1.0-beta3", "dependencies": { - "@hyperlane-xyz/core": "3.1.0-beta0", - "@hyperlane-xyz/sdk": "3.1.0-beta0", + "@hyperlane-xyz/core": "3.1.0-beta3", + "@hyperlane-xyz/sdk": "3.1.0-beta3", "@openzeppelin/contracts-upgradeable": "^4.8.0", "ethers": "^5.7.2" }, diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 1cdba73ed7..d0dc0a7c71 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "1.5.1", + "version": "3.1.0-beta3", "dependencies": { "@arbitrum/sdk": "^3.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -11,9 +11,9 @@ "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@hyperlane-xyz/helloworld": "3.1.0-beta0", - "@hyperlane-xyz/sdk": "3.1.0-beta0", - "@hyperlane-xyz/utils": "3.1.0-beta0", + "@hyperlane-xyz/helloworld": "3.1.0-beta3", + "@hyperlane-xyz/sdk": "3.1.0-beta3", + "@hyperlane-xyz/utils": "3.1.0-beta3", "@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/logos/black/cosmos.svg b/typescript/sdk/logos/black/cosmos.svg new file mode 100644 index 0000000000..5f8ab7b33d --- /dev/null +++ b/typescript/sdk/logos/black/cosmos.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript/sdk/logos/black/cosmwasm.svg b/typescript/sdk/logos/black/cosmwasm.svg new file mode 100644 index 0000000000..bef563f624 --- /dev/null +++ b/typescript/sdk/logos/black/cosmwasm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript/sdk/logos/black/manta.svg b/typescript/sdk/logos/black/manta.svg new file mode 100644 index 0000000000..43e1691fc6 --- /dev/null +++ b/typescript/sdk/logos/black/manta.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript/sdk/logos/black/neutron.svg b/typescript/sdk/logos/black/neutron.svg new file mode 100644 index 0000000000..5f541609e1 --- /dev/null +++ b/typescript/sdk/logos/black/neutron.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript/sdk/logos/color/cosmos.svg b/typescript/sdk/logos/color/cosmos.svg new file mode 100644 index 0000000000..6023d57e6a --- /dev/null +++ b/typescript/sdk/logos/color/cosmos.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript/sdk/logos/color/cosmwasm.svg b/typescript/sdk/logos/color/cosmwasm.svg new file mode 100644 index 0000000000..21df94390a --- /dev/null +++ b/typescript/sdk/logos/color/cosmwasm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript/sdk/logos/color/manta.svg b/typescript/sdk/logos/color/manta.svg new file mode 100644 index 0000000000..65ea376986 --- /dev/null +++ b/typescript/sdk/logos/color/manta.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript/sdk/logos/color/neutron.svg b/typescript/sdk/logos/color/neutron.svg new file mode 100644 index 0000000000..5f541609e1 --- /dev/null +++ b/typescript/sdk/logos/color/neutron.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 18f4514dc2..2ca61790a5 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,17 +1,18 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "3.1.0-beta0", + "version": "3.1.0-beta3", "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.31.3", "@cosmjs/stargate": "^0.31.3", - "@hyperlane-xyz/core": "3.1.0-beta0", - "@hyperlane-xyz/utils": "3.1.0-beta0", + "@hyperlane-xyz/core": "3.1.0-beta3", + "@hyperlane-xyz/utils": "3.1.0-beta3", "@solana/spl-token": "^0.3.8", "@solana/web3.js": "^1.78.0", "@types/coingecko-api": "^1.0.10", "@types/debug": "^4.1.7", "@wagmi/chains": "^0.2.6", + "bignumber.js": "^9.1.1", "coingecko-api": "^1.0.10", "cosmjs-types": "^0.9.0", "cross-fetch": "^3.1.5", diff --git a/typescript/sdk/src/consts/chainMetadata.ts b/typescript/sdk/src/consts/chainMetadata.ts index 1b7d753852..d95960b0b6 100644 --- a/typescript/sdk/src/consts/chainMetadata.ts +++ b/typescript/sdk/src/consts/chainMetadata.ts @@ -814,6 +814,7 @@ export const neutron: ChainMetadata = { { name: 'Mintscan', url: 'https://www.mintscan.io/neutron', + // TODO API not actually supported, using url to meet validation requirements apiUrl: 'https://www.mintscan.io/neutron', family: ExplorerFamily.Other, }, diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 23a3efdaf4..2eff41891a 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -294,6 +294,11 @@ export { CwNativeTokenAdapter, CwTokenAdapter, } from './token/adapters/CosmWasmTokenAdapter'; +export { + CosmIbcToWarpTokenAdapter, + CosmIbcTokenAdapter, + CosmNativeTokenAdapter, +} from './token/adapters/CosmosTokenAdapter'; export { EvmHypCollateralAdapter, EvmHypSyntheticAdapter, diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.ts index 594018cf99..10249bca11 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 BigNumber from 'bignumber.js'; import { Address, @@ -370,8 +371,27 @@ export class CwHypNativeAdapter if (!txValue) { throw new Error('txValue is required for native tokens'); } - const collateralDenom = await this.denom(); + + const funds: Coin[] = + collateralDenom === this.gasDenom + ? [ + { + amount: new BigNumber(weiAmountOrId).plus(txValue).toFixed(0), + denom: collateralDenom, + }, + ] + : [ + { + amount: weiAmountOrId.toString(), + denom: collateralDenom, + }, + { + amount: txValue.toString(), + denom: this.gasDenom, + }, + ]; + return this.cw20adapter.prepareRouter( { transfer_remote: { @@ -380,16 +400,7 @@ export class CwHypNativeAdapter amount: weiAmountOrId.toString(), }, }, - [ - { - amount: weiAmountOrId.toString(), - denom: collateralDenom, - }, - { - amount: txValue.toString(), - denom: this.gasDenom, - }, - ], + funds, ); } } diff --git a/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts index 9830265a24..ab482fdbfa 100644 --- a/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts @@ -1,31 +1,44 @@ import { MsgTransferEncodeObject } from '@cosmjs/stargate'; -import { MsgTransfer } from 'cosmjs-types/ibc/applications/transfer/v1/tx'; +import Long from 'long'; -import { Address } from '@hyperlane-xyz/utils'; +import { Address, Domain } from '@hyperlane-xyz/utils'; import { BaseCosmosAdapter } from '../../app/MultiProtocolApp'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider'; +import { ChainName } from '../../types'; import { MinimalTokenMetadata } from '../config'; -import { ITokenAdapter, TransferParams } from './ITokenAdapter'; +import { CwHypCollateralAdapter } from './CosmWasmTokenAdapter'; +import { + IHypTokenAdapter, + ITokenAdapter, + TransferParams, + TransferRemoteParams, +} from './ITokenAdapter'; -// Interacts with IBC denom tokens -export class NativeTokenAdapter +const COSMOS_IBC_TRANSFER_TIMEOUT = 600_000; // 10 minutes + +// Interacts with native tokens on a Cosmos chain (e.g TIA on Celestia) +export class CosmNativeTokenAdapter extends BaseCosmosAdapter implements ITokenAdapter { constructor( - public readonly chainName: string, + public readonly chainName: ChainName, public readonly multiProvider: MultiProtocolProvider, public readonly addresses: Record, - public readonly ibcDenom: string = 'untrn', + public readonly properties: { + ibcDenom: string; + }, ) { + if (!properties.ibcDenom) + throw new Error('Missing properties for CosmNativeTokenAdapter'); super(chainName, multiProvider, addresses); } async getBalance(address: string): Promise { const provider = await this.getProvider(); - const coin = await provider.getBalance(address, this.ibcDenom); + const coin = await provider.getBalance(address, this.properties.ibcDenom); return coin.amount; } @@ -38,28 +51,140 @@ export class NativeTokenAdapter } async populateTransferTx( - transferParams: TransferParams, + _transferParams: TransferParams, + ): Promise { + throw new Error('TODO not yet implemented'); + } +} + +// Interacts with native tokens on a Cosmos chain and adds support for IBC transfers +// This implements the IHypTokenAdapter interface but it's an imperfect fit as some +// methods don't apply to IBC transfers the way they do for Warp transfers +export class CosmIbcTokenAdapter + extends CosmNativeTokenAdapter + implements IHypTokenAdapter +{ + constructor( + public readonly chainName: ChainName, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: Record, + public readonly properties: { + ibcDenom: string; + sourcePort: string; + sourceChannel: string; + }, + ) { + if ( + !properties.ibcDenom || + !properties.sourcePort || + !properties.sourceChannel + ) + throw new Error('Missing properties for CosmNativeIbcTokenAdapter'); + super(chainName, multiProvider, addresses, properties); + } + + getDomains(): Promise { + throw new Error('Method not applicable to IBC adapters'); + } + getRouterAddress(_domain: Domain): Promise { + throw new Error('Method not applicable to IBC adapters'); + } + getAllRouters(): Promise< + Array<{ + domain: Domain; + address: Buffer; + }> + > { + throw new Error('Method not applicable to IBC adapters'); + } + quoteGasPayment(_destination: Domain): Promise { + throw new Error('Method not applicable to IBC adapters'); + } + + async populateTransferRemoteTx( + transferParams: TransferRemoteParams, + memo = '', ): Promise { - const transfer: MsgTransfer = { - sourcePort: '', - sourceChannel: '', + if (!transferParams.fromAccountOwner) + throw new Error('fromAccountOwner is required for ibc transfers'); + + const value = { + sourcePort: this.properties.sourcePort, + sourceChannel: this.properties.sourceChannel, token: { - denom: this.ibcDenom, + denom: this.properties.ibcDenom, amount: transferParams.weiAmountOrId.toString(), }, - sender: '', - receiver: '', - timeoutHeight: { - revisionNumber: 0n, - revisionHeight: 0n, - }, - timeoutTimestamp: 0n, - memo: '', // how to encode this? + sender: transferParams.fromAccountOwner, + receiver: transferParams.recipient, + // Represented as nano-seconds + timeoutTimestamp: Long.fromNumber( + new Date().getTime() + COSMOS_IBC_TRANSFER_TIMEOUT, + ).multiply(1_000_000), + memo, }; return { typeUrl: '/ibc.applications.transfer.v1.MsgTransfer', - // @ts-ignore - value: transfer, + value, + }; + } +} + +// A wrapper for the CosmIbcTokenAdapter that adds support auto-initiated warp transfers +// A.k.a. 'One-Click' cosmos to evm transfers +export class CosmIbcToWarpTokenAdapter + extends CosmIbcTokenAdapter + implements IHypTokenAdapter +{ + constructor( + public readonly chainName: ChainName, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: { + intermediateRouterAddress: Address; + destinationRouterAddress: Address; + }, + public readonly properties: CosmIbcTokenAdapter['properties'] & { + derivedIbcDenom: string; + intermediateChainName: ChainName; + }, + ) { + super(chainName, multiProvider, addresses, properties); + } + + async populateTransferRemoteTx( + transferParams: TransferRemoteParams, + ): Promise { + const cwAdapter = new CwHypCollateralAdapter( + this.properties.intermediateChainName, + this.multiProvider, + { + token: this.properties.derivedIbcDenom, + warpRouter: this.addresses.intermediateRouterAddress, + }, + this.properties.derivedIbcDenom, + ); + const transfer = await cwAdapter.populateTransferRemoteTx(transferParams); + const cwMemo = { + wasm: { + contract: transfer.contractAddress, + msg: transfer.msg, + funds: transfer.funds, + }, }; + const memo = JSON.stringify(cwMemo); + if (transfer.funds?.length !== 1) { + // Only transfers where the interchain gas denom matches the token are currently supported + throw new Error('Expected exactly one denom for IBC to Warp transfer'); + } + // Grab amount from the funds details which accounts for interchain gas + const weiAmountOrId = transfer.funds[0].amount; + return super.populateTransferRemoteTx( + { + ...transferParams, + weiAmountOrId, + recipient: this.addresses.intermediateRouterAddress, + }, + memo, + ); } } diff --git a/typescript/utils/package.json b/typescript/utils/package.json index f2ce16c190..a9904075eb 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": "3.1.0-beta0", + "version": "3.1.0-beta3", "dependencies": { "@cosmjs/encoding": "^0.31.3", "@solana/web3.js": "^1.78.0", diff --git a/yarn.lock b/yarn.lock index b2444c031c..87f5743fe6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4097,12 +4097,12 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/core@3.1.0-beta0, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@3.1.0-beta3, @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": 3.1.0-beta0 + "@hyperlane-xyz/utils": 3.1.0-beta3 "@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@3.1.0-beta0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@3.1.0-beta3, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: - "@hyperlane-xyz/core": 3.1.0-beta0 - "@hyperlane-xyz/sdk": 3.1.0-beta0 + "@hyperlane-xyz/core": 3.1.0-beta3 + "@hyperlane-xyz/sdk": 3.1.0-beta3 "@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": 3.1.0-beta0 - "@hyperlane-xyz/sdk": 3.1.0-beta0 - "@hyperlane-xyz/utils": 3.1.0-beta0 + "@hyperlane-xyz/helloworld": 3.1.0-beta3 + "@hyperlane-xyz/sdk": 3.1.0-beta3 + "@hyperlane-xyz/utils": 3.1.0-beta3 "@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@3.1.0-beta0, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@3.1.0-beta3, @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": 3.1.0-beta0 - "@hyperlane-xyz/utils": 3.1.0-beta0 + "@hyperlane-xyz/core": 3.1.0-beta3 + "@hyperlane-xyz/utils": 3.1.0-beta3 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@solana/spl-token": ^0.3.8 @@ -4233,6 +4233,7 @@ __metadata: "@types/node": ^16.9.1 "@types/ws": ^8.5.5 "@wagmi/chains": ^0.2.6 + bignumber.js: ^9.1.1 chai: ^4.3.6 coingecko-api: ^1.0.10 cosmjs-types: ^0.9.0 @@ -4253,7 +4254,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@3.1.0-beta0, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@3.1.0-beta3, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: