# Sign blocks from a server safely: implementation spec

Follow these steps in order. Each step ends with a check. Stop and ask the site owner when a step says "ask".

- Guide: https://nano133.com/guides/sign-blocks-safely · as Markdown: https://nano133.com/guides/sign-blocks-safely.md
- Stage: **Alpha**. We ran its code and its checks pass. It read a real node (read only), or, if it needs no node, it ran on real signatures or real files. Where it copies code that nano133.com runs, a test shows both act the same. Last checked 2026-10-25.
- Use at your own risk, for education. Real Nano moves: keep amounts small.
- Code files: https://nano133.com/guides/sign-blocks-safely/code/safe-send.mjs

## Rules that must hold

1. Never change the secret or the label of a seed once money has come in. Never print, log, store or return a seed or a private key.
2. Every piece of code that signs a block for a wallet takes that wallet's lock first. Every write that records money, and every publish, proves the lock in the same transaction. A run that lost its lock stops at once.
3. Receive only a block that the node reports as a confirmed send to this wallet, one block at a time. Never build a block from a frontier or a balance that you did not just read.
4. Sign, then record the block and its hash, then publish. Never publish a block that is not recorded. One job id means one send: refuse a job id that comes back with another wallet, address or amount.
5. After a stop, never sign a new block for a job that has a recorded block until the ledger answers that the recorded block is not there and cannot land. An answer that is not "not found" means wait.
6. The destination of an automatic send comes from your code or your deploy settings, never from a request or from a row that someone else can write. A test never sends real Nano.

## Steps

The guide has the how for each step. This list has what must be true at its end.

1. **Many wallets from one secret.** Check: run `node safe-send.mjs address 0` twice: the same address. Run it with index `1`: another address. Run it with another secret: another address. An index of `1000000`, or a secret shorter than 32 characters, is refused.
2. **One run at a time for each wallet.** Check: start a send that waits while it holds the lock, and start a second send for the same wallet: the second answers `busy` (exit code 4) and does nothing. Start a send whose lock runs out while it waits, and let another run take the wallet: the slow run answers `the wallet lock was lost` (exit code 5), publishes nothing, and the wallet does not fork.
3. **Read the wallet, then build the block.** Check: run `NANO_RPC=… node safe-send.mjs state <address>`: it prints the frontier, the balance in raw and the representative that your node reports. A block that is not confirmed is refused: `not confirmed yet`. A block that was received is not received twice.
4. **Record the block before you publish it.** Check: send for a job: `sent: block <hash>`, and the hash is the one that was recorded. Send for the same job again: `already sent: block <hash>`, and the ledger holds one send.
5. **Run it again after a stop.** Check: stop a send after it recorded the block and before it published: the ledger holds nothing, and the next run publishes the same block (the recorded hash). Stop one after the node took the block: the next run says `already sent` and the ledger still holds one send. Stop one, let another job move the wallet, and run the first again: it makes a new block, and each job sent once.
6. **Send only what your code says.** Check: `send` refuses an address that is not valid, the wallet itself, an amount above the balance and an amount that is not whole raw. A job id that comes back with another amount is refused.

## Done when

- [ ] Step 1 check passes.
- [ ] Step 2 check passes.
- [ ] Step 3 check passes.
- [ ] Step 4 check passes.
- [ ] Step 5 check passes.
- [ ] Step 6 check passes.
- [ ] `node test.mjs` passes in mock mode (put the code files next to it). It never sends money.
- [ ] You told the owner the stage of this guide and of the track you used.

## What breaks, and what it costs

| Failure | Loss in Nano | Check |
| --- | --- | --- |
| The secret or the label changes after money came in, or the secret is lost | The money stays in the older wallets. Only the old secret and label sign for them | Step 1: never change either |
| A secret or a key is logged, printed or sent to a browser | Everything in every wallet it can sign for | Steps 1 and 6: a key lives inside the signing function only; keep little in a wallet |
| Two runs sign on one frontier (a fork) | The block that loses, and every block built on it, is rolled back: a send in it does not arrive | Step 2: one lock for each wallet, used by all code that signs |
| A run outlives its lock and publishes | A fork, or a second send of the same payment | Step 2: the fence proves the lock before every record and every publish |
| A send is published, the answer is lost, and the job runs again | The same payment goes out twice | Steps 4 and 5: record the block first, and ask the ledger before you send anew |
| The ledger is asked and the node gives an error, and the run sends a new block anyway | The same payment goes out twice, if the first block was on the ledger | Step 5: only "not found" lets you go on |
| A block that is not confirmed is received | The wallet's chain is rolled back with every send after it | Step 3: receive only confirmed blocks |
| A block is built from an old frontier or balance | None: the node refuses it, and the job waits | Step 3: read the wallet's state after you hold the lock |
| A job id comes back with another address or amount | The wrong payment goes out | Step 4: one job id names one send; refuse the rest |
| The destination is read from a request or a row | The money goes where a visitor chose | Step 6: the destination is in your code |

## Notes for agents

Ask the site owner for these five things before you start. Never guess them:

1. The RPC address of their own Nano node, and the confirmation that it allows `account_info`, `account_history`, `block_info`, `work_generate` and `process`. Never use a node that you do not trust to decide whether money moves.
2. Where the secret lives (their secret manager) and the label for this purpose. Write the label once, in one file marked as frozen. It never changes once money has come in.
3. The database for the locks, the jobs and the index counter, and the confirmation that it has transactions and a unique key.
4. The destination addresses of every automatic send. They go in code or in deploy settings. They are never read from a request or from a row that a visitor can write.
5. Every piece of code that signs for each wallet (jobs, sweeps, returns). All of them must use that wallet's one lock.

Never print, log or store a seed or a key. Never send on a real network without the owner. Test with the throwaway secret that `node test.mjs` makes, and send no Nano. After a stop, never sign a new block for a job that has a recorded block until the ledger says that the block is not there.
