The core operation of any write integration: turning an intent into signed bytes and putting them on chain — without reimplementing a single consensus rule.
This guide uses the Rust SDK. The same flow works over plain HTTP, but then signing is your problem.
Four facts define the format. Getting any of them wrong produces a transaction the node refuses without much explanation.
Six steps. The first three happen entirely on your machine.
A client without a wallet reads but does not sign: write methods fail before touching the network. With a wallet, the address comes for free.
use eav7_sdk::{Eav7Client, ProductionWallet};
use eav7::transaction::TxSpec;
use std::time::Duration;
const UNIT: u128 = 1_000_000; // 1 EAV7 = 1 000 000 e7
let carteira = ProductionWallet::from_file("carteira.json")?;
let cliente = Eav7Client::com_carteira("https://eavscan.com", Box::new(carteira));
// o endereço NÃO é informado: sai das duas chaves públicas
let de = cliente.endereco().expect("cliente com carteira");Use nextNonce, not nonce. The first already counts this sender's mempool entries; the second only counts what has landed in a block.
// nextNonce já considera o que este remetente tem no mempool let nonce = cliente.proximo_nonce(&de)?;
montar() assembles the envelope, signs with both keys and verifies the transaction through the same path the node would use. No packet leaves the machine.
let spec = TxSpec::nova("TRANSFER", 5 * UNIT, nonce, agora_ms())
.para("E7DEST…9A02");
// monta, assina (secp256k1 + ML-DSA-44) e VERIFICA localmente.
// Um erro aqui é o mesmo erro que o nó daria — sem gastar uma ida à rede.
let tx = cliente.montar(spec)?;It is worth looking once to know what is going over the wire. The fields above the blank line are the signed ones; below it are the signatures, the keys and the id.
{
"protocol": "eav20",
"scheme": "eav7-hybrid-1",
"type": "TRANSFER",
"from": "E7A4B2…9F21",
"to": "E7DEST…9A02",
"amount": "5000000",
"fee": "10000",
"nonce": 42,
"timestamp": 1770000000000,
"data": null,
"publicKey": "-----BEGIN PUBLIC KEY-----…",
"pqPublicKey": "-----BEGIN PUBLIC KEY-----…",
"signature": "MEQCIF…",
"pqSignature": "hQ8xR2…",
"id": "0x8c1f…"
}The response is 200 even on rejection: the verdict is in the body. Check accepted before anything else.
let recibo = cliente.enviar(&tx)?;
if !recibo.accepted {
eprintln!("recusada: {}", recibo.reason.unwrap_or_default());
return Ok(());
}
println!("no mempool: {}", recibo.id);Submitting only proves the mempool took it. aguardar_confirmacao polls /tx/:id until a blockHeight appears — and a 404 along the way does not abort the wait, because the transaction may not have propagated to that node yet.
let bloco = cliente.aguardar_confirmacao(&recibo.id, Duration::from_secs(30))?;
println!("confirmada no bloco {}", bloco.block_height);
// TempoEsgotado NÃO é veredito: a transação pode entrar depois do prazo.
// Reconsulte /tx/{id} antes de reenviar — reenviar cria um segundo nonce.Two families: the admission verdict, which arrives as 200, and transport or shape failures.
| Response | What it means |
|---|---|
200 · accepted: true | This node's mempool took the transaction. It has not executed yet. |
200 · accepted: false | Admission refused; the reason field explains. Do not resend without fixing the cause. |
reason: already known | This transaction is already in the mempool or already in a block. Not an error — stop resending. |
400 · { error } | Malformed body, invalid signature, out-of-range nonce, or a full mempool. |
429 · Retry-After | Rate limit or an abuse block. Honour the header before trying again. |
montar() makes no HTTP call, so nothing forces the machine that signs to be the machine that broadcasts.
// MÁQUINA FRIA — a carteira vive aqui, e montar() não toca a rede.
// O nonce precisa ter sido lido antes, na máquina conectada.
let tx = cliente.montar(
TxSpec::nova("TRANSFER", 5 * UNIT, nonce, agora_ms()).para("E7DEST…9A02"),
)?;
// A transação assinada é um valor comum: leve-a por arquivo, QR ou fila.
// MÁQUINA QUENTE — cliente SEM carteira, só transporte.
let publicador = Eav7Client::novo("https://eavscan.com");
let recibo = publicador.enviar(&tx)?;A Remetente reserves the nonce locally: the second transaction uses n+1 without asking the node. It is the path to stake + vote + claim in one go.
// Duas escritas seguidas: a segunda leria o MESMO nextNonce se
// perguntasse ao nó antes de a primeira entrar no mempool.
let mut remetente = cliente.remetente()?;
let a = remetente.stake(1_000 * UNIT)?;
let b = remetente.votar(vec![("E7VAL…77A1".into(), 500 * UNIT)])?;
for tx in [a, b] {
let id = tx.id.expect("assinada");
cliente.aguardar_confirmacao(&id, Duration::from_secs(30))?;
}64Cap on nonces ahead of the confirmed one. A burst longer than this is refused.Every one of them has cost somebody time. None produces an obvious error message.