Skip to content

Commit 9c60fed

Browse files
authored
cosmossdk cw20 quote token (#3671)
- **fix(typescript-sdk): quote token check** - **feat(typescript-sdk): cosmos quote token prediction** - **fix(typescript-sdk): fix incorrect dest chain id**
2 parents 939e640 + 4895923 commit 9c60fed

File tree

11 files changed

+340
-370
lines changed

11 files changed

+340
-370
lines changed

Cargo.lock

Lines changed: 0 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ members = [
8888
"cosmwasm/ibc-union/light-clients/berachain",
8989
"cosmwasm/ibc-union/light-clients/cometbls",
9090
"cosmwasm/ibc-union/light-clients/ethereum",
91-
"cosmwasm/ibc-union/light-clients/ethermint",
91+
# "cosmwasm/ibc-union/light-clients/ethermint",
9292
"cosmwasm/ibc-union/light-clients/tendermint",
9393
"cosmwasm/ibc-union/light-clients/state-lens-ics23-mpt",
9494

typescript-sdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@unionlabs/client",
3-
"version": "0.0.53",
3+
"version": "0.0.54",
44
"homepage": "https://union.build",
55
"description": "Union Labs cross-chain transfers client",
66
"type": "module",
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { fromHex, http } from "viem"
2+
import { parseArgs } from "node:util"
3+
import { consola } from "scripts/logger"
4+
import { createUnionClient, hexToBytes } from "#mod.ts"
5+
import {
6+
getChannelInfo,
7+
getQuoteToken,
8+
getRecommendedChannels
9+
} from "#query/offchain/ucs03-channels"
10+
import { DirectSecp256k1Wallet } from "@cosmjs/proto-signing"
11+
12+
// hack to encode bigints to json
13+
declare global {
14+
interface BigInt {
15+
toJSON: () => string
16+
}
17+
}
18+
19+
if (!BigInt.prototype.toJSON) {
20+
Object.defineProperty(BigInt.prototype, "toJSON", {
21+
value: function () {
22+
return this.toString()
23+
},
24+
writable: true,
25+
configurable: true
26+
})
27+
}
28+
// end hack
29+
30+
const cliArgs = parseArgs({
31+
args: process.argv.slice(2),
32+
options: {
33+
"private-key": { type: "string" },
34+
"estimate-gas": { type: "boolean", default: false }
35+
}
36+
})
37+
38+
const PRIVATE_KEY = cliArgs.values["private-key"]
39+
const STARS_DENOM = "ubbn"
40+
const AMOUNT = 1n
41+
const RECEIVER = "0xE6831e169d77a861A0E71326AFA6d80bCC8Bc6aA"
42+
const SOURCE_CHAIN_ID = "bbn-test-5"
43+
const DESTINATION_CHAIN_ID = "union-testnet-9"
44+
45+
const channels = await getRecommendedChannels()
46+
47+
const channel = getChannelInfo(SOURCE_CHAIN_ID, DESTINATION_CHAIN_ID, channels)
48+
if (channel === null) {
49+
consola.info("no channel found")
50+
process.exit(1)
51+
}
52+
53+
consola.info("channel", channel)
54+
55+
const quoteToken = await getQuoteToken(SOURCE_CHAIN_ID, STARS_DENOM, channel)
56+
if (quoteToken.isErr()) {
57+
consola.info("could not get quote token")
58+
consola.error(quoteToken.error)
59+
process.exit(1)
60+
}
61+
62+
if (quoteToken.value.type === "NO_QUOTE_AVAILABLE") {
63+
consola.error("No quote token available")
64+
process.exit(1)
65+
}
66+
consola.info("quote token", quoteToken.value)
67+
68+
if (!PRIVATE_KEY) {
69+
consola.error("no private key provided")
70+
process.exit(1)
71+
}
72+
73+
const unionClient = createUnionClient({
74+
chainId: SOURCE_CHAIN_ID,
75+
account: await DirectSecp256k1Wallet.fromKey(Uint8Array.from(hexToBytes(PRIVATE_KEY)), "bbn"),
76+
gasPrice: { amount: "0.025", denom: "ubbn" },
77+
transport: http("https://rpc.bbn-test-5.babylon.chain.kitchen")
78+
})
79+
80+
const transfer = await unionClient.transferAsset({
81+
baseToken: STARS_DENOM,
82+
baseAmount: AMOUNT,
83+
quoteToken: quoteToken.value.quote_token,
84+
quoteAmount: AMOUNT,
85+
receiver: RECEIVER,
86+
sourceChannelId: channel.source_channel_id,
87+
ucs03address: fromHex(`0x${channel.source_port_id}`, "string")
88+
})
89+
90+
if (transfer.isErr()) {
91+
consola.error("transfer submission failed:", transfer.error)
92+
process.exit(1)
93+
}
94+
95+
consola.info("transfer tx hash", transfer.value)

typescript-sdk/playground/holesky-to-sepolia.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ if (quoteToken.isErr()) {
4444

4545
consola.info("quote token", quoteToken.value)
4646

47+
if (quoteToken.value.type === "NO_QUOTE_AVAILABLE") {
48+
consola.error("No quote token available")
49+
process.exit(1)
50+
}
51+
4752
const transferArgs = {
4853
baseToken: BASE_TOKEN,
4954
baseAmount: AMOUNT,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate"
2+
import { consola } from "scripts/logger"
3+
import { ResultAsync } from "neverthrow"
4+
import { toHex } from "viem"
5+
6+
let publicClient = await ResultAsync.fromPromise(
7+
CosmWasmClient.connect("https://rpc.bbn-test-5.babylon.chain.kitchen"),
8+
error => {
9+
return new Error("failed to create public cosmos client", { cause: error })
10+
}
11+
)
12+
13+
if (publicClient.isErr()) {
14+
consola.error(publicClient.error)
15+
process.exit(1)
16+
}
17+
18+
let client = publicClient.value
19+
20+
// TODO: don't haredcode this either.
21+
let quoteToken = await ResultAsync.fromPromise(
22+
client.queryContractSmart("bbn1mvvl3jvyn8dh9whfzkdzgedk56cjge7stsfwdjrcsvczns5waqzs6023q4", {
23+
predict_wrapped_denom: { path: "0", channel: 6, token: toHex("ubbn") }
24+
}),
25+
error => {
26+
return new Error("failed to query predict wrapped denom", { cause: error })
27+
}
28+
)
29+
30+
consola.info(quoteToken)
31+
32+
if (quoteToken.isOk()) {
33+
consola.info(toHex(quoteToken.value))
34+
}

typescript-sdk/playground/stargaze-to-holesky.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ if (quoteToken.isErr()) {
5959
process.exit(1)
6060
}
6161

62+
if (quoteToken.value.type === "NO_QUOTE_AVAILABLE") {
63+
consola.error("No quote token available")
64+
process.exit(1)
65+
}
66+
6267
consola.info("quote token", quoteToken.value)
6368

6469
if (!PRIVATE_KEY) {
@@ -84,8 +89,7 @@ const transfer = await stargazeClient.transferAsset({
8489
})
8590

8691
if (transfer.isErr()) {
87-
consola.info("transfer submission failed")
88-
consola.error(transfer.error)
92+
consola.error("transfer submission failed:", transfer.error)
8993
process.exit(1)
9094
}
9195

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { fromHex, http } from "viem"
2+
import { parseArgs } from "node:util"
3+
import { consola } from "scripts/logger"
4+
import { createUnionClient, hexToBytes } from "#mod.ts"
5+
import {
6+
getChannelInfo,
7+
getQuoteToken,
8+
getRecommendedChannels
9+
} from "#query/offchain/ucs03-channels"
10+
import { DirectSecp256k1Wallet } from "@cosmjs/proto-signing"
11+
12+
// hack to encode bigints to json
13+
declare global {
14+
interface BigInt {
15+
toJSON: () => string
16+
}
17+
}
18+
19+
if (!BigInt.prototype.toJSON) {
20+
Object.defineProperty(BigInt.prototype, "toJSON", {
21+
value: function () {
22+
return this.toString()
23+
},
24+
writable: true,
25+
configurable: true
26+
})
27+
}
28+
// end hack
29+
30+
const cliArgs = parseArgs({
31+
args: process.argv.slice(2),
32+
options: {
33+
"private-key": { type: "string" },
34+
"estimate-gas": { type: "boolean", default: false }
35+
}
36+
})
37+
38+
const PRIVATE_KEY = cliArgs.values["private-key"]
39+
const STARS_DENOM = "muno"
40+
const AMOUNT = 1n
41+
const RECEIVER = "0xE6831e169d77a861A0E71326AFA6d80bCC8Bc6aA"
42+
const SOURCE_CHAIN_ID = "union-testnet-9"
43+
const DESTINATION_CHAIN_ID = "bbn-test-5"
44+
45+
const channels = await getRecommendedChannels()
46+
47+
const channel = getChannelInfo(SOURCE_CHAIN_ID, DESTINATION_CHAIN_ID, channels)
48+
if (channel === null) {
49+
consola.info("no channel found")
50+
process.exit(1)
51+
}
52+
53+
consola.info("channel", channel)
54+
55+
const quoteToken = await getQuoteToken(SOURCE_CHAIN_ID, STARS_DENOM, channel)
56+
if (quoteToken.isErr()) {
57+
consola.info("could not get quote token")
58+
consola.error(quoteToken.error)
59+
process.exit(1)
60+
}
61+
62+
if (quoteToken.value.type === "NO_QUOTE_AVAILABLE") {
63+
consola.error("No quote token available")
64+
process.exit(1)
65+
}
66+
consola.info("quote token", quoteToken.value)
67+
68+
if (!PRIVATE_KEY) {
69+
consola.error("no private key provided")
70+
process.exit(1)
71+
}
72+
73+
const unionClient = createUnionClient({
74+
chainId: SOURCE_CHAIN_ID,
75+
account: await DirectSecp256k1Wallet.fromKey(Uint8Array.from(hexToBytes(PRIVATE_KEY)), "union"),
76+
gasPrice: { amount: "0.025", denom: "muno" },
77+
transport: http("https://rpc.testnet-9.union.build")
78+
})
79+
80+
const transfer = await unionClient.transferAsset({
81+
baseToken: STARS_DENOM,
82+
baseAmount: AMOUNT,
83+
quoteToken: quoteToken.value.quote_token,
84+
quoteAmount: AMOUNT,
85+
receiver: RECEIVER,
86+
sourceChannelId: channel.source_channel_id,
87+
ucs03address: fromHex(`0x${channel.source_port_id}`, "string")
88+
})
89+
90+
if (transfer.isErr()) {
91+
consola.error("transfer submission failed:", transfer.error)
92+
process.exit(1)
93+
}
94+
95+
consola.info("transfer tx hash", transfer.value)

typescript-sdk/src/cosmos/client.ts

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
cosmwasmTransfer,
3-
ibcTransferSimulate,
43
cosmwasmTransferSimulate,
54
cosmosSameChainTransferSimulate
65
} from "./transfer.ts"
@@ -16,15 +15,21 @@ import type {
1615
} from "../types.ts"
1716

1817
export const cosmosChainId = [
19-
"mocha-4",
2018
"elgafar-1",
2119
"osmo-test-5",
22-
"union-testnet-8",
2320
"union-testnet-9",
2421
"stride-internal-1",
2522
"bbn-test-5"
2623
] as const
2724

25+
export const cosmosRpcs: Record<CosmosChainId, string> = {
26+
"elgafar-1": "https://rpc.elgafar-1.stargaze.chain.kitchen",
27+
"osmo-test-5": "https://rpc.osmo-test-5.osmosis.chain.kitchen",
28+
"union-testnet-9": "https://rpc.union-testnet-9.union.chain.kitchen",
29+
"stride-internal-1": "https://rpc.stride-internal-1.stride.chain.kitchen",
30+
"bbn-test-5": "https://rpc.bbn-test-5.babylon.chain.kitchen"
31+
}
32+
2833
export type CosmosChainId = `${(typeof cosmosChainId)[number]}`
2934

3035
export interface CosmosClientParameters {
@@ -222,32 +227,6 @@ export const createCosmosClient = (parameters: CosmosClientParameters) =>
222227
]
223228
})
224229
}
225-
226-
if (destinationChainId === "union-testnet-8") {
227-
if (!sourceChannel) return err(new Error("Source channel not found"))
228-
const [account_] = await account.getAccounts()
229-
if (!account) return err(new Error("No account found"))
230-
231-
const stamp = timestamp()
232-
233-
return await ibcTransferSimulate({
234-
gasPrice,
235-
account,
236-
rpcUrl,
237-
messageTransfers: [
238-
{
239-
sourceChannel: sourceChannel.toString(),
240-
sourcePort: "transfer",
241-
sender: account_?.address,
242-
token: { denom: denomAddress, amount: amount.toString() },
243-
timeoutHeight: { revisionHeight: 888_888_888n, revisionNumber: 8n },
244-
receiver: receiver.startsWith("0x") ? receiver.slice(2) : receiver,
245-
memo: memo ?? `${stamp} Sending ${amount} ${denomAddress} to ${receiver}`
246-
}
247-
]
248-
})
249-
}
250-
251230
return err(new Error("Unsupported network"))
252231
}
253232
}))

typescript-sdk/src/generated/graphql-env.d.ts

Lines changed: 21 additions & 277 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)