Skip to main content

Sign & broadcast a transaction

The SDK's client.send() is the easy path. This guide shows the manual path — useful for custom tx types, offline signing, or building your own wallet.

Manual build + sign

import { buildTransferTx, ChainRest } from "@ombrachain/sdk";

const chain = new ChainRest("https://api.ombra-net.com");
const nonce = await chain.getNonce(wallet.address);

const tx = buildTransferTx(
wallet.address,
"ox0000000000000000000000000000000000000001",
1_500_000n, // amount (micro-OMBRA)
1_000n, // fee
nonce,
wallet.privateKey,
);

// tx now has { type, from, to, amount, fee, nonce, timestamp, publicKey, hash, signature }
await fetch("https://api.ombra-net.com/api/chain/tx", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(tx, (_k, v) => (typeof v === "bigint" ? v.toString() : v)),
});

The canonical hash

Every builder ends with the same recipe (signAndFinalize):

withPub = { ...orderedFields, publicKey }
hash = sha256hex( JSON.stringify(withPub, bigint→string) )
sig = ed25519( utf8(hash), privateKey )
final = { ...withPub, hash, signature }
  • Field order is part of the hash — use the builders, don't hand-roll the object.
  • BigInt fields (amount, fee, bytes, …) are serialized as strings.
  • The server re-derives the hash and verifies from matches publicKey (anti-impersonation).

Playground

transaction playground

Build a transaction, sign it in-browser (byte-exact canonical hash + Ed25519 signature), and optionally broadcast it to mainnet. Broadcasting needs a funded key with the right nonce — expect a clear error otherwise.

Common errors

ResponseMeaning
400 nonce staleuse chain.getNonce(address) for the current nonce
400 balanță insuficientăthe from account doesn't have funds/capacity
400 Hash incorectthe payload was modified after signing (wrong field order)
400 ... post FORK_HEIGHT_*that tx type isn't active yet on this network