Getting started
Signing transactions
How to sign RawEvmTransaction and RawSorobanTransaction with the wallet library you already use.
The SDK returns unsigned RawTransaction objects. Signing and broadcasting are yours. Below are drop in helpers for the common wallet libraries.
RawTransaction is a discriminated union.
type RawTransaction = RawEvmTransaction | RawSorobanTransaction;
interface RawEvmTransaction {
family: "evm";
chainId: number;
from: `0x${string}`;
to: `0x${string}`;
data: `0x${string}`;
value?: bigint;
}
interface RawSorobanTransaction {
family: "stellar";
networkPassphrase: string;
from: string;
xdr: string; // already prepared, ready to sign
}Use the exported isRawEvm and isRawSoroban type guards to route.
EVM with viem
import { createWalletClient, custom } from "viem";
import type { RawEvmTransaction } from "@zethub/bridge";
const wallet = createWalletClient({
transport: custom(window.ethereum),
account,
});
async function sendEvm(tx: RawEvmTransaction) {
return wallet.sendTransaction({
to: tx.to,
data: tx.data,
value: tx.value,
chain: null, // wallet handles the switch, or check chainId first
});
}EVM with wagmi
import { sendTransaction, switchChain } from "@wagmi/core";
import type { RawEvmTransaction } from "@zethub/bridge";
async function sendEvm(tx: RawEvmTransaction) {
await switchChain(wagmiConfig, { chainId: tx.chainId });
return sendTransaction(wagmiConfig, {
to: tx.to,
data: tx.data,
value: tx.value,
});
}EVM with ethers v6
import type { RawEvmTransaction } from "@zethub/bridge";
async function sendEvm(tx: RawEvmTransaction, signer: ethers.Signer) {
return signer.sendTransaction({
to: tx.to,
data: tx.data,
value: tx.value,
chainId: tx.chainId,
});
}Stellar with Freighter
import { signTransaction } from "@stellar/freighter-api";
import { TransactionBuilder } from "@stellar/stellar-sdk";
import type { RawSorobanTransaction } from "@zethub/bridge";
async function sendSoroban(tx: RawSorobanTransaction, server: rpc.Server) {
const signedXdr = await signTransaction(tx.xdr, {
networkPassphrase: tx.networkPassphrase,
});
const signedTx = TransactionBuilder.fromXDR(signedXdr, tx.networkPassphrase);
const sent = await server.sendTransaction(signedTx);
return sent.hash;
}Stellar with Stellar Wallets Kit
import { StellarWalletsKit } from "@creit.tech/stellar-wallets-kit";
import { TransactionBuilder } from "@stellar/stellar-sdk";
import type { RawSorobanTransaction } from "@zethub/bridge";
async function sendSoroban(
tx: RawSorobanTransaction,
kit: StellarWalletsKit,
server: rpc.Server,
) {
const { signedTxXdr } = await kit.signTransaction(tx.xdr, {
networkPassphrase: tx.networkPassphrase,
});
const signedTx = TransactionBuilder.fromXDR(signedTxXdr, tx.networkPassphrase);
const sent = await server.sendTransaction(signedTx);
return sent.hash;
}Waiting for confirmation
The SDK raw txs are already prepared and simulated. Once you broadcast, waiting for confirmation is your job. Most wallet libraries have a helper.
| Library | Helper |
|---|---|
| viem | publicClient.waitForTransactionReceipt({ hash }) |
| wagmi | waitForTransactionReceipt(config, { hash }) |
| ethers | tx.wait() on the returned response |
| Stellar | poll server.getTransaction(hash) until status === "SUCCESS" |
Routing raw txs
If your code has to accept either kind:
import { isRawEvm, isRawSoroban } from "@zethub/bridge";
async function broadcast(tx: RawTransaction) {
if (isRawEvm(tx)) return sendEvm(tx);
if (isRawSoroban(tx)) return sendSoroban(tx);
throw new Error("Unknown tx family");
}