Skip to content

Commit

Permalink
Initiate UCS03 CosmWasm transfers from TypeScript SDK (#3511)
Browse files Browse the repository at this point in the history
- **feat(ts-sdk): configure union-testnet-9**
- **feat(ts-sdk): configure elgafar-1**
- **feat(ts-sdk): transfer from stargaze to holesky**
- **feat(ts-sdk): randomize salt for cosmos transfers**
- **fix(ts-sdk): note that quote_token is hardcoded for now**
- **chore(ts-sdk): publish v0.0.45**
  • Loading branch information
cor authored Jan 14, 2025
2 parents c80823d + bfc1eef commit bc450f1
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 79 deletions.
2 changes: 1 addition & 1 deletion typescript-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@unionlabs/client",
"version": "0.0.44",
"version": "0.0.45",
"homepage": "https://union.build",
"description": "Union Labs cross-chain transfers client",
"type": "module",
Expand Down
74 changes: 74 additions & 0 deletions typescript-sdk/playground/stargaze-to-holesky.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env bun
import "scripts/patch.ts"
import { http } from "viem"
import { holesky } from "viem/chains"
import { parseArgs } from "node:util"
import { raise } from "#utilities/index.ts"
import { consola } from "../scripts/logger.ts"
import { hexToBytes } from "#convert.ts"
import { privateKeyToAccount } from "viem/accounts"
import { DirectSecp256k1Wallet } from "@cosmjs/proto-signing"
import { createUnionClient, type TransferAssetsParameters } from "#mod.ts"

/* `bun playground/union-to-holesky.ts --private-key "..."` --estimate-gas */

const { values } = parseArgs({
args: process.argv.slice(2),
options: {
"private-key": { type: "string" },
"estimate-gas": { type: "boolean", default: false }
}
})

const PRIVATE_KEY = values["private-key"]
if (!PRIVATE_KEY) raise("Private key not found")
const ONLY_ESTIMATE_GAS = values["estimate-gas"] ?? false

const evmAccount = privateKeyToAccount(`0x${PRIVATE_KEY}`)

const cosmosAccount = await DirectSecp256k1Wallet.fromKey(
Uint8Array.from(hexToBytes(PRIVATE_KEY)),
"stars"
)

try {
const client = createUnionClient({
account: cosmosAccount,
chainId: "elgafar-1",
gasPrice: { amount: "0.025", denom: "ustars" },
transport: http("https://rpc.elgafar-1.stargaze.chain.kitchen")
})

const transferPayload = {
amount: 1n,
denomAddress: "ustars",
destinationChainId: `${holesky.id}`,
receiver: "0x8478B37E983F520dBCB5d7D3aAD8276B82631aBd"
} satisfies TransferAssetsParameters<"elgafar-1">

// const gasEstimationResponse = await client.simulateTransaction(transferPayload)

// consola.box("Union to holesky gas cost:", gasEstimationResponse)

// if (ONLY_ESTIMATE_GAS) process.exit(0)

// if (!gasEstimationResponse.isOk()) {
// console.info("Transaction simulation failed")
// process.exit(1)
// }

const transfer = await client.transferAsset(transferPayload)

if (transfer.isErr()) {
console.error(transfer.error)
process.exit(1)
}

consola.info(transfer.value)
process.exit(0)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : error
console.error(errorMessage)
} finally {
process.exit(0)
}
74 changes: 74 additions & 0 deletions typescript-sdk/playground/union-to-holesky.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env bun
import "scripts/patch.ts"
import { http } from "viem"
import { holesky } from "viem/chains"
import { parseArgs } from "node:util"
import { raise } from "#utilities/index.ts"
import { consola } from "../scripts/logger.ts"
import { hexToBytes } from "#convert.ts"
import { privateKeyToAccount } from "viem/accounts"
import { DirectSecp256k1Wallet } from "@cosmjs/proto-signing"
import { createUnionClient, type TransferAssetsParameters } from "#mod.ts"

/* `bun playground/union-to-holesky.ts --private-key "..."` --estimate-gas */

const { values } = parseArgs({
args: process.argv.slice(2),
options: {
"private-key": { type: "string" },
"estimate-gas": { type: "boolean", default: false }
}
})

const PRIVATE_KEY = values["private-key"]
if (!PRIVATE_KEY) raise("Private key not found")
const ONLY_ESTIMATE_GAS = values["estimate-gas"] ?? false

const evmAccount = privateKeyToAccount(`0x${PRIVATE_KEY}`)

const cosmosAccount = await DirectSecp256k1Wallet.fromKey(
Uint8Array.from(hexToBytes(PRIVATE_KEY)),
"union"
)

try {
const client = createUnionClient({
account: cosmosAccount,
chainId: "union-testnet-9",
gasPrice: { amount: "0.0025", denom: "muno" },
transport: http("https://rpc.testnet-9.union.build")
})

const transferPayload = {
amount: 1n,
denomAddress: "muno",
destinationChainId: `${holesky.id}`,
receiver: "0x8478B37E983F520dBCB5d7D3aAD8276B82631aBd"
} satisfies TransferAssetsParameters<"union-testnet-8">

// const gasEstimationResponse = await client.simulateTransaction(transferPayload)

// consola.box("Union to holesky gas cost:", gasEstimationResponse)

// if (ONLY_ESTIMATE_GAS) process.exit(0)

// if (!gasEstimationResponse.isOk()) {
// console.info("Transaction simulation failed")
// process.exit(1)
// }

const transfer = await client.transferAsset(transferPayload)

if (transfer.isErr()) {
console.error(transfer.error)
process.exit(1)
}

consola.info(transfer.value)
process.exit(0)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : error
console.error(errorMessage)
} finally {
process.exit(0)
}
154 changes: 77 additions & 77 deletions typescript-sdk/src/cosmos/client.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import {
ibcTransfer,
cosmwasmTransfer,
ibcTransferSimulate,
cosmosSameChainTransfer,
cosmwasmTransferSimulate,
cosmosSameChainTransferSimulate
} from "./transfer.ts"
import { err, type Result } from "neverthrow"
import { timestamp } from "../utilities/index.ts"
import { bech32AddressToHex } from "../convert.ts"
import { createPfmMemo, getHubbleChainDetails } from "../pfm.ts"
import { fallback, createClient, type HttpTransport } from "viem"
import { fallback, createClient, type HttpTransport, toHex } from "viem"
import type { OfflineSigner, TransferAssetsParameters } from "../types.ts"

export const cosmosChainId = [
"mocha-4",
"elgafar-1",
"osmo-test-5",
"union-testnet-8",
"stride-internal-1"
"union-testnet-9",
"stride-internal-1",
"bbn-test-5"
] as const

export type CosmosChainId = `${(typeof cosmosChainId)[number]}`
Expand Down Expand Up @@ -52,16 +52,16 @@ export const createCosmosClient = (parameters: CosmosClientParameters) =>
if (!account) return err(new Error("No cosmos signer found"))
if (!gasPrice) return err(new Error("No gas price found"))

if (sourceChainId === destinationChainId) {
const transfer = await cosmosSameChainTransfer({
rpcUrl,
account,
gasPrice,
receiver,
asset: { denom: denomAddress, amount: amount.toString() }
})
return transfer
}
// if (sourceChainId === destinationChainId) {
// const transfer = await cosmosSameChainTransfer({
// rpcUrl,
// account,
// gasPrice,
// receiver,
// asset: { denom: denomAddress, amount: amount.toString() }
// })
// return transfer
// }

const stamp = timestamp()
const chainDetails = await getHubbleChainDetails({
Expand All @@ -71,71 +71,71 @@ export const createCosmosClient = (parameters: CosmosClientParameters) =>

if (chainDetails.isErr()) return err(chainDetails.error)

if (chainDetails.value.transferType === "pfm") {
if (!chainDetails.value.port) return err(new Error("Port not found in hubble"))
const pfmMemo = createPfmMemo({
port: chainDetails.value.port,
channel: chainDetails.value.destinationChannel.toString(),
receiver: cosmosChainId.includes(destinationChainId)
? bech32AddressToHex({ address: receiver })
: receiver
})
if (pfmMemo.isErr()) return err(pfmMemo.error)
memo = pfmMemo.value
}

const sourceChannel = chainDetails.value.sourceChannel
relayContractAddress ??= chainDetails.value.relayContractAddress

if (sourceChainId === "union-testnet-8") {
if (!sourceChannel) return err(new Error("Source channel not found"))
if (!relayContractAddress) return err(new Error("Relay contract address not found"))

const transfer = await cosmwasmTransfer({
account,
rpcUrl,
gasPrice,
instructions: [
{
contractAddress: relayContractAddress,
msg: {
transfer: {
channel: sourceChannel,
receiver: receiver.startsWith("0x") ? receiver.slice(2) : receiver,
memo: memo ?? `${stamp} Sending ${amount} ${denomAddress} to ${receiver}`
}
},
funds: [{ amount: amount.toString(), denom: denomAddress }]
}
]
})
return transfer
}

if (destinationChainId === "union-testnet-8") {
if (!sourceChannel) return err(new Error("Source channel not found"))

const [account_] = await account.getAccounts()
if (!account) return err(new Error("No account found"))

const transfer = await ibcTransfer({
account,
rpcUrl,
gasPrice,
messageTransfers: [
{
sourceChannel: sourceChannel.toString(),
sourcePort: "transfer",
sender: account_?.address,
token: { denom: denomAddress, amount: amount.toString() },
timeoutHeight: { revisionHeight: 888_888_888n, revisionNumber: 8n },
receiver: receiver.startsWith("0x") ? receiver.slice(2) : receiver,
memo: memo ?? `${stamp} Sending ${amount} ${denomAddress} to ${receiver}`
}
]
})
return transfer
}
// if (sourceChainId === "union-testnet-9") {
if (!sourceChannel) return err(new Error("Source channel not found"))
if (!relayContractAddress) return err(new Error("Relay contract address not found"))

// add a salt to each transfer to prevent hash collisions
// important because ibc-union does not use sequence numbers
// such that intents are possible based on deterministic packet hashes
const rawSalt = new Uint8Array(32)
crypto.getRandomValues(rawSalt)
const salt = toHex(rawSalt)

const transfer = await cosmwasmTransfer({
account,
rpcUrl,
gasPrice,
instructions: [
{
contractAddress: relayContractAddress,
msg: {
transfer: {
channel_id: sourceChannel,
receiver: receiver,
base_token: denomAddress,
base_amount: amount,
quote_token: "0x9EC5e8b3509162D12209A882e42A6A4Fd1751A84", // TODO: don't hardcode
quote_amount: amount,
timeout_height: 1000000000,
timeout_timestamp: 0,
salt
}
},
funds: [{ amount: amount.toString(), denom: denomAddress }]
}
]
})
return transfer
// }

// if (destinationChainId === "union-testnet-8") {
// if (!sourceChannel) return err(new Error("Source channel not found"))

// const [account_] = await account.getAccounts()
// if (!account) return err(new Error("No account found"))

// const transfer = await ibcTransfer({
// account,
// rpcUrl,
// gasPrice,
// messageTransfers: [
// {
// sourceChannel: sourceChannel.toString(),
// sourcePort: "transfer",
// sender: account_?.address,
// token: { denom: denomAddress, amount: amount.toString() },
// timeoutHeight: { revisionHeight: 888_888_888n, revisionNumber: 8n },
// receiver: receiver.startsWith("0x") ? receiver.slice(2) : receiver,
// memo: memo ?? `${stamp} Sending ${amount} ${denomAddress} to ${receiver}`
// }
// ]
// })
// return transfer
// }

return err(new Error("Unsupported network"))
},
Expand Down Expand Up @@ -189,7 +189,7 @@ export const createCosmosClient = (parameters: CosmosClientParameters) =>
// destinationChainId = chainDetails.value.destinationChainId
relayContractAddress ??= chainDetails.value.relayContractAddress

if (sourceChainId === "union-testnet-8") {
if (sourceChainId === "union-testnet-9") {
if (!relayContractAddress) return err(new Error("Relay contract address not found"))

const stamp = timestamp()
Expand Down
30 changes: 29 additions & 1 deletion typescript-sdk/src/pfm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,35 @@ export async function getHubbleChainDetails({
ucs3_config: {
address: "0x7b7872fec715c787a1be3f062adedc82b3b06144",
channels: {
[sepolia.id.toString()]: 9
[sepolia.id.toString()]: 9,
"union-testnet-9": 8,
"elgafar-1": 11
}
}
},
{
testnet: true,
chain_id: "union-testnet-9",
rpc_type: "cosmos",
addr_prefix: "union",
display_name: "Union Testnet 9",
ucs3_config: {
address: "union19hspxmypfxsdsnxttma8rxvp7dtcmzhl9my0ee64avg358vlpawsdvucqa",
channels: {
[holesky.id.toString()]: 7
}
}
},
{
testnet: true,
chain_id: "elgafar-1",
rpc_type: "cosmos",
addr_prefix: "stars",
display_name: "Stargaze Testnet",
ucs3_config: {
address: "stars1vv5v4sk4tzxs9a0685j4shdqazj44dla8rfu6np40h9tneuruq0s3rs6kq",
channels: {
[holesky.id.toString()]: 15
}
}
}
Expand Down

0 comments on commit bc450f1

Please sign in to comment.