From f7c49541c680f50d4dfcf3ef63ba09f101e57002 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Fri, 27 Oct 2023 15:34:56 -0400 Subject: [PATCH] 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, + }, + ], + ); } }