0%
This tutorial will guide you through installing the necessary dependencies and running the stride-to-union.ts
script to transfer assets from Stride to Union Testnet using Bun.
Bun is a fast all-in-one JavaScript runtime. You can install it using one of the following methods:
curl
Run the following command in your terminal:
curl -fsSL https://bun.sh/install | bash
npm
If you already have npm installed, you can install Bun globally:
npm install -g bun
After installation, verify that Bun is installed correctly:
bun --version
After installing Bun, install the required dependencies for the script:
bun install @unionlabs/client viem @cosmjs/proto-signing @scure/base
Note: Make sure you are in the project directory before running this command.
Create a file named stride-to-union.ts
and copy the following code:
import { parseArgs } from "node:util";
import { fromHex, toHex } from "viem";
import {
http,
createUnionClient,
getChannelInfo,
getQuoteToken,
getRecommendedChannels,
bech32AddressToHex,
} from "@unionlabs/client";
import { bytes } from "@scure/base";
import { DirectSecp256k1Wallet } from "@cosmjs/proto-signing";
declare global {
interface BigInt {
toJSON: () => string;
}
}
if (!BigInt.prototype.toJSON) {
Object.defineProperty(BigInt.prototype, "toJSON", {
value: function () {
return this.toString();
},
writable: true,
configurable: true,
});
}
const { values } = parseArgs({
args: process.argv.slice(2),
options: {
"private-key": { type: "string", required: true },
"receiver": { type: "string", required: true },
"estimate-gas": { type: "boolean", default: false },
},
});
function hexToBytes(hexString: string): Uint8Array {
return bytes("hex", hexString.indexOf("0x") === 0 ? hexString.slice(2) : hexString);
}
const PRIVATE_KEY = values["private-key"];
const RECEIVER = values["receiver"];
if (!PRIVATE_KEY) throw new Error("Private key not found");
if (!RECEIVER) throw new Error("Receiver address not found");
const cosmosAccount = await DirectSecp256k1Wallet.fromKey(
Uint8Array.from(hexToBytes(PRIVATE_KEY)),
"stride"
);
const [account] = await cosmosAccount.getAccounts();
console.info("Wallet address:", account?.address);
const MUNO_DENOM = "ustrd";
const AMOUNT = 1n;
const SOURCE_CHAIN_ID = "stride-internal-1";
const DESTINATION_CHAIN_ID = "union-testnet-9";
async function main() {
try {
const baseToken = toHex(MUNO_DENOM);
const channels = await getRecommendedChannels();
const channel = getChannelInfo(SOURCE_CHAIN_ID, DESTINATION_CHAIN_ID, channels);
if (channel === null) {
console.info("No channel found");
process.exit(1);
}
console.info("Channel:", channel);
console.info("Base token:", baseToken);
const quoteToken = await getQuoteToken(SOURCE_CHAIN_ID, baseToken, channel);
if (quoteToken.isErr()) {
console.info("Could not get quote token");
console.error(quoteToken.error);
process.exit(1);
}
if (quoteToken.value.type === "NO_QUOTE_AVAILABLE") {
console.error("No quote token available");
process.exit(1);
}
console.info("Quote token:", quoteToken.value);
console.info(`Processing transfer for receiver: ${RECEIVER}`);
const unionClient = createUnionClient({
chainId: SOURCE_CHAIN_ID,
account: await DirectSecp256k1Wallet.fromKey(Uint8Array.from(hexToBytes(PRIVATE_KEY)), "stride"),
gasPrice: { amount: "0.0025", denom: "ustrd" },
transport: http("https://stride.testnet-1.stridenet.co"),
});
const transfer = await unionClient.transferAsset({
baseToken: MUNO_DENOM,
baseAmount: AMOUNT,
quoteToken: quoteToken.value.quote_token,
quoteAmount: AMOUNT,
receiver: bech32AddressToHex({ address: RECEIVER }),
sourceChannelId: channel.source_channel_id,
ucs03address: fromHex(`0x${channel.source_port_id}`, "string")
});
if (transfer.isErr()) {
console.error(`Transfer submission failed for receiver ${RECEIVER}`);
console.error(transfer.error);
return;
}
console.info("Transfer tx hash:", transfer.value);
} catch (error) {
console.error("Error executing transfer:", error);
}
}
main().catch((error) => {
console.error("Fatal error:", error);
});
Run the script using Bun:
bun stride-to-union.ts --private-key='YOUR_PRIVATE_KEY' --receiver='RECEIVER_ADDRESS'
Replace YOUR_PRIVATE_KEY
with your private key and RECEIVER_ADDRESS
with the recipient's address.
You might encounter the following error when running the script:
Could not get quote token
4105 | }
4106 | }
4107 | if (cosmosChainId.includes(channel.destination_chain_id)) {
4108 | let rpc = cosmosRpcs[channel.destination_chain_id];
4109 | let publicClient = await ResultAsync.fromPromise(CosmWasmClient.connect(rpc), (error2) => {
4110 | return new Error(`failed to create public cosmwasm client with rpc ${rpc}`, { cause: error2 });
^
error: failed to create public cosmwasm client with rpc https://rpc.testnet-9.union.build
at <anonymous> (/home/zixine/union/node_modules/@unionlabs/client/dist/index.mjs:4110:14)
at <anonymous> (/home/zixine/union/node_modules/neverthrow/dist/index.cjs.js:106:35)
90 | return {
91 | algorithm: "secp256k1",
92 | data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(data.value)),
93 | };
94 | default:
95 | throw new Error(`unknown pubkey type: ${data.type}`);
^
error: unknown pubkey type: cometbft/PubKeyBn254
at decodePubkey (/home/zixine/union/node_modules/@cosmjs/tendermint-rpc/build/tendermint37/adaptor/responses.js:95:23)
at decodeValidatorInfo (/home/zixine/union/node_modules/@cosmjs/tendermint-rpc/build/tendermint37/adaptor/responses.js:250:17)
at decodeStatus (/home/zixine/union/node_modules/@cosmjs/tendermint-rpc/build/tendermint37/adaptor/responses.js:296:24)
To fix this issue, follow these steps:
You can also find the original source of these patches at:https://github.com/unionlabs/union/tree/main/typescript-sdk/patches.
Create a folder named patches
in your project directory. Inside this folder, create two files:
@cosmjs+amino+0.33.0.patch
diff --git a/node_modules/@cosmjs/amino/build/pubkeys.js b/node_modules/@cosmjs/amino/build/pubkeys.js
index e9844ef..86101f8 100644
--- a/node_modules/@cosmjs/amino/build/pubkeys.js
+++ b/node_modules/@cosmjs/amino/build/pubkeys.js
@@ -9,6 +9,10 @@ function isSecp256k1Pubkey(pubkey) {
return pubkey.type === "tendermint/PubKeySecp256k1";
}
exports.isSecp256k1Pubkey = isSecp256k1Pubkey;
+function isBn254Pubkey(pubkey) {
+ return pubkey.type === "tendermint/PubKeyBn254";
+}
+exports.isBn254Pubkey = isBn254Pubkey;
exports.pubkeyType = {
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/ed25519/ed25519.go#L22 */
secp256k1: "tendermint/PubKeySecp256k1",
@@ -16,6 +20,7 @@ exports.pubkeyType = {
ed25519: "tendermint/PubKeyEd25519",
/** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */
sr25519: "tendermint/PubKeySr25519",
+ bn254: "tendermint/PubKeyBn254",
multisigThreshold: "tendermint/PubKeyMultisigThreshold",
};
function isSinglePubkey(pubkey) {
@cosmjs+tendermint-rpc+0.33.0.patch
diff --git a/node_modules/@cosmjs/tendermint-rpc/build/comet38/adaptor/responses.js b/node_modules/@cosmjs/tendermint-rpc/build/comet38/adaptor/responses.js
index 29ec063..28a5c02 100644
--- a/node_modules/@cosmjs/tendermint-rpc/build/comet38/adaptor/responses.js
+++ b/node_modules/@cosmjs/tendermint-rpc/build/comet38/adaptor/responses.js
@@ -72,7 +72,7 @@ function decodePubkey(data) {
if ("Sum" in data) {
// we don't need to check type because we're checking algorithm
const [[algorithm, value]] = Object.entries(data.Sum.value);
- (0, utils_1.assert)(algorithm === "ed25519" || algorithm === "secp256k1", `unknown pubkey type: ${algorithm}`);
+ (0, utils_1.assert)(algorithm === "ed25519" || algorithm === "secp256k1" || algorithm === "bn254", `unknown pubkey type: ${algorithm}`);
return {
algorithm,
data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(value)),
@@ -91,6 +91,16 @@ function decodePubkey(data) {
algorithm: "secp256k1",
data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(data.value)),
};
+ case "tendermint/PubKeyBn254":
+ return {
+ algorithm: "bn254",
+ data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(data.value)),
+ };
+ case "cometbft/PubKeyBn254":
+ return {
+ algorithm: "bn254",
+ data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(data.value)),
+ };
default:
throw new Error(`unknown pubkey type: ${data.type}`);
}
diff --git a/node_modules/@cosmjs/tendermint-rpc/build/tendermint37/adaptor/responses.js b/node_modules/@cosmjs/tendermint-rpc/build/tendermint37/adaptor/responses.js
index 19df9de..0015044 100644
--- a/node_modules/@cosmjs/tendermint-rpc/build/tendermint37/adaptor/responses.js
+++ b/node_modules/@cosmjs/tendermint-rpc/build/tendermint37/adaptor/responses.js
@@ -72,7 +72,7 @@ function decodePubkey(data) {
if ("Sum" in data) {
// we don't need to check type because we're checking algorithm
const [[algorithm, value]] = Object.entries(data.Sum.value);
- (0, utils_1.assert)(algorithm === "ed25519" || algorithm === "secp256k1", `unknown pubkey type: ${algorithm}`);
+ (0, utils_1.assert)(algorithm === "ed25519" || algorithm === "secp256k1" || algorithm === "bn254", `unknown pubkey type: ${algorithm}`);
return {
algorithm,
data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(value)),
@@ -91,6 +91,16 @@ function decodePubkey(data) {
algorithm: "secp256k1",
data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(data.value)),
};
+ case "tendermint/PubKeyBn254":
+ return {
+ algorithm: "bn254",
+ data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(data.value)),
+ };
+ case "cometbft/PubKeyBn254":
+ return {
+ algorithm: "bn254",
+ data: (0, encoding_1.fromBase64)((0, encodings_1.assertNotEmpty)(data.value)),
+ };
default:
throw new Error(`unknown pubkey type: ${data.type}`);
}
diff --git a/node_modules/@cosmjs/tendermint-rpc/build/tendermintclient.js b/node_modules/@cosmjs/tendermint-rpc/build/tendermintclient.js
index 257b104..dbf2240 100644
--- a/node_modules/@cosmjs/tendermint-rpc/build/tendermintclient.js
+++ b/node_modules/@cosmjs/tendermint-rpc/build/tendermintclient.js
@@ -28,7 +28,7 @@ async function connectComet(endpoint) {
if (version.startsWith("0.37.")) {
out = tm37Client;
}
- else if (version.startsWith("0.38.")) {
+ else if (version.startsWith("0.38.") || version.startsWith("1.0.")) {
tm37Client.disconnect();
out = await comet38_1.Comet38Client.connect(endpoint);
}
Install patch-package
and postinstall-postinstall
to ensure patches are applied after dependency installation:
bun install patch-package postinstall-postinstall
Edit your package.json
and add the following script:
{
"scripts": {
"postinstall": "patch-package"
}
}
Remove node_modules
and bun.lock
to simulate a fresh installation:
rm -rf node_modules bun.lock
Reinstall dependencies:
bun install
Verify that the patches are applied:
cat node_modules/@cosmjs/tendermint-rpc/build/tendermint37/adaptor/responses.js | grep 'PubKeyBn254'
This script is designed to transfer assets from the Stride network to the Union Testnet. Below is a detailed explanation of its key components and functionality.
The script uses the following modules:
parseArgs
β Reads arguments passed when running the script.fromHex, toHex
β Converts data to/from hexadecimal format.@unionlabs/client
β Main library for interacting with the Union network.@scure/base
β Used for data format conversion.@cosmjs/proto-signing
β Used to create and manage Cosmos-based wallets.In JavaScript, the BigInt
data type cannot be directly serialized to JSON. The following code adds a toJSON
method to BigInt
to enable conversion to a string when needed:
declare global {
interface BigInt {
toJSON: () => string;
}
}
if (!BigInt.prototype.toJSON) {
Object.defineProperty(BigInt.prototype, "toJSON", {
value: function () {
return this.toString();
},
writable: true,
configurable: true,
});
}
The script uses parseArgs
to read the following arguments:
--private-key
β User's private key for signing transactions.--receiver
β Recipient's address.--estimate-gas
β (Optional) If enabled, estimates gas usage.const { values } = parseArgs({
args: process.argv.slice(2),
options: {
"private-key": { type: "string", required: true },
"receiver": { type: "string", required: true },
"estimate-gas": { type: "boolean", default: false },
},
});
The script includes a utility function to convert hexadecimal strings to byte arrays:
function hexToBytes(hexString: string): Uint8Array {
return bytes("hex", hexString.indexOf("0x") === 0 ? hexString.slice(2) : hexString);
}
The script creates a Cosmos wallet using the provided private key:
const cosmosAccount = await DirectSecp256k1Wallet.fromKey(
Uint8Array.from(hexToBytes(PRIVATE_KEY)),
"stride"
);
const [account] = await cosmosAccount.getAccounts();
console.info("Wallet address:", account?.address);
The script defines the following constants for the transfer:
MUNO_DENOM
β Token to be transferred (e.g., ustrd
).AMOUNT
β Amount of tokens to transfer (in BigInt
).SOURCE_CHAIN_ID
β Source network ID (Stride).DESTINATION_CHAIN_ID
β Destination network ID (Union Testnet).The script performs the following steps to execute the transfer:
This script test the transfer of assets from Stride to Union Testnet by:
Official documentation and resources used in this script:
β **DO NOTβUNDER ANY CIRCUMSTANCESβTURN THIS SCRIPT INTO A BOT THAT SPAMS, EXPLOITS, OR ATTACKS THE NETWORK.** β
π¨ THIS IS YOUR LAST WARNING. IF YOU ABUSE THIS, YOU WILL BE PERMANENTLY BLACKLISTED. NO DISCUSSION. NO APPEALS. π¨
This Docs is meant for **educational use ONLY**. **If you modify it to flood transactions, overload RPCs, or intentionally harm the network,** YOU WILL BE BLACKLISTED IMMEDIATELY.
π¨ **Union is actively tracking misuse. If you try anything shady, expect an instant ban.**
π« **VIOLATORS WILL BE PERMANENTLY BANNED, BLACKLISTED ACROSS ALL SERVICES, AND MAY FACE LEGAL ACTION.** π«
**THIS IS NOT A JOKE.**
Haha, yeah, letβs be honestβmoving 1 million tokens per day is WILD that's why UNION its.