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
frommatchespublicKey(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
| Response | Meaning |
|---|---|
400 nonce stale | use chain.getNonce(address) for the current nonce |
400 balanță insuficientă | the from account doesn't have funds/capacity |
400 Hash incorect | the payload was modified after signing (wrong field order) |
400 ... post FORK_HEIGHT_* | that tx type isn't active yet on this network |