Note: Reference documents are maintained in English. A Turkish summary of this material is on the summary page.
This document is for developers building wallet clients (mobile, desktop, browser, hardware) that talk to an existing BitKuruş node over HTTPS. You do not need to run a federation node unless you also want to validate or replicate the ledger.
Related pages
| Resource | URL / path |
|---|---|
| Wallet docs (web) | /docs |
| Federation / node protocol | /run-a-node |
| Reference wallet UI | /wallet (see public/bitkurus/wallet.js) |
| Network profile | GET /api/network |
1. Wallet vs node
| Role | Responsibility |
|---|---|
| Wallet | Generate Ed25519 keys, read balances, plan UTXO spends, sign transactions, submit to POST /api/tx/submit |
| Node | Persist ledger, peer validation quorum, replication, issuance (apple tree, commission) |
A correct wallet never sends private keys to the API.
2. Identity and signing
- Address: 64-character lowercase hex Ed25519 public key (
senderon every transaction). - Signature: 128-character lowercase hex detached Ed25519 over canonical JSON of the payload without the
signaturefield.
Canonical JSON rules (must match verifiers byte-for-byte):
- Object keys sorted ascending (byte-wise UTF-8 order).
- Arrays keep submission order.
- No
\uXXXXescapes for Unicode — raw UTF-8. - Forward slashes not escaped.
- Money amounts are decimal strings with up to 18 fractional digits, e.g.
"1.000000000000000000"— never JSON numbers/floats.
Signing payload shape:
{
"tx_id": "wallet-tx-…",
"type": "transfer",
"sender": "<64-hex pubkey>",
"nonce": "wallet-tx-…-nonce",
"inputs": [{ "token_id": "…", "version": 1 }],
"outputs": [
{ "token_id": "wallet-out-…", "value": "10.000000000000000000", "owner": "<receiver>" }
]
}
Full rules: /run-a-node → Identity & canonical JSON.
3. Read APIs (before send)
GET /api/wallet/{public_key}
Returns balance, tokens[] with token_id, value, version, status, origin, origin_tx_id.
GET /api/network
Returns peer list, validator_commission_rate_ppm, capabilities. Use peers.length + 1 to estimate quorum size (floor(N/2)+1 on a typical federation).
GET /api/token/{token_id}
GET /api/tx/{tx_id}
GET /api/wallet/{public_key}/activity?limit=30
activity returns recent sends, receives, merge/split, commission, apple tree rows from the ledger (for history UI).
Optional presence probe after minting a new output:
GET /api/cluster/token/{token_id}?expect=active
Returns confirmed_count, node_count — how many federation members see the token as active.
4. UTXO transfer planning
BitKuruş uses UTXO-style tokens: each token_id is an unspent output. The wallet must select inputs whose sum ≥ amount (and handle change).
| Situation | type |
Steps |
|---|---|---|
| 1 input, exact amount | transfer |
1× submit |
| 1 input, pay less than value | split |
1× submit (receiver + change to self) |
| N inputs, exact total | merge |
1× submit (burn all inputs → one output to receiver) |
| N inputs, pay less than total | merge then split |
2× submit (see below) |
Value conservation: sum(inputs.value) = sum(outputs.value) (fee is implicit burn of the difference when you omit change; reference wallet sends explicit change output to sender).
Versions: Every input must include the current version from GET /api/wallet/{pubkey}. After any committed spend, versions change — always refresh before a new tx.
Reference implementation: planWalletTransfer() in public/bitkurus/wallet.js.
5. Multi-step flow: merge then pay
When the user has multiple tokens and needs change (most common failure scenario):
1. Plan merge tx:
- type: merge
- inputs: all selected UTXOs (with versions)
- outputs: one token to sender with value = sum(inputs)
2. POST /api/tx/submit (signed merge)
- Response 202 accepted — not final yet
3. Poll GET /api/tx/{merge_tx_id} until status === "committed"
4. Wait until merged output is spendable ON PEERS:
- GET /api/token/{merged_token_id} → status active
- GET /api/cluster/token/{merged_token_id}?expect=active
until confirmed_count >= floor(node_count/2)+1 (or retry policy below)
5. GET /api/wallet/{sender} again — refresh versions
6. Plan payment tx:
- type: split (or transfer if no change)
- inputs: [{ token_id: merged_token_id, version: <current> }]
- outputs: receiver + optional change to sender
7. POST /api/tx/submit (signed payment)
8. Poll until committed
Do not skip step 4. If you submit the payment immediately after local merge commit, peers may still return Peer validation failed because they have not replicated the merged token yet.
6. Peer validation (why submits fail)
Before any transaction is persisted, the receiving node broadcasts a transaction_validation envelope to configured peers. Each peer runs the same rules against its own database and answers validated or rejected with a reason.
Typical rejection reasons for wallet authors:
reason (examples) |
Meaning |
|---|---|
Input token does not exist. |
Peer has not replicated the input yet (common right after merge). |
Input token is not active. |
Already spent or locked. |
Input version mismatch. |
Stale version in your tx — refresh wallet state. |
Input token is locked. |
Another tx holds the lock — wait and retry. |
HTTP 409 response shape:
{
"result": "rejected",
"data": {
"error": "Peer validation failed.",
"peer_id": "node2",
"reason": "Input token does not exist."
}
}
Also possible:
{
"error": "Insufficient peer approvals.",
"required": 2,
"validated": 0
}
Recommended client behaviour
- Show
peer_idandreasonto the user. - On
Peer validation failedorInsufficient peer approvals, wait 1.5–3s, refresh wallet, retry (reference wallet retries up to 4 times). - After merge, poll
/api/cluster/token/{id}until quorum before the payment submit.
7. Submit and finality
POST /api/tx/submit
Content-Type: application/json
{ ... full signed transaction including signature ... }
| HTTP | Meaning |
|---|---|
202 + result: accepted |
Queued — poll GET /api/tx/{tx_id} |
409 + result: rejected |
Validation failed — see data.error, data.reason |
403 |
Observer node / read-only |
Poll until status is committed or rejected. Do not chain a second spend until the first tx is committed (or you risk version/lock errors).
8. Transaction types (summary)
type |
Inputs | Outputs | Use |
|---|---|---|---|
transfer |
1 | 1 | Simple send |
split |
1 | 2+ | Send + change |
merge |
2+ | 1 | Consolidate UTXOs |
merge + split |
— | — | Two transactions (merge, then split) |
sender must equal the 64-hex public key that owns all inputs.
9. Integration checklist
- Ed25519 keygen; private key never leaves device
- Canonical JSON signer tested against reference vectors (
/run-a-node) -
GET /api/wallet/{pubkey}drives input selection - Correct
versionon every input - UTXO planner: merge / split / merge-then-split
- Poll
GET /api/tx/{id}after each submit - After merge: wait for token
active+ peer quorum before next submit - Retry policy for peer validation failures
- Refresh wallet state after successful commit
- Optional:
GET /api/wallet/{pubkey}/activityfor history UI - Optional:
GET /api/cluster/token/{id}for replication indicator
10. What you do not need (wallet-only)
POST /api/peer/replicate(node operators only)- Validator commission wallet configuration
- Apple tree issuance (unless you build a faucet/tree client)
- Running queue workers or MySQL
11. Reference code map
| Concern | File |
|---|---|
| Transfer planning | public/bitkurus/wallet.js → planWalletTransfer |
| Merge-then-pay + peer wait | submitPaymentAfterMerge, waitForActiveToken, waitForTokenPeerQuorum |
| Submit + retry | submitSignedTransaction, submitSignedTransactionOnce |
| Canonical sign | signPayload |
| Server validation | app/Services/Transactions/TransactionService.php |
| Peer preflight | app/Http/Controllers/Api/TransactionController.php |
12. FAQ
Can I use one transfer with two inputs?
No. Use merge or two-step merge + split.
Is 202 accepted enough to show success?
No. Wait for committed.
Will a custom wallet “just work” if signing is correct?
Only if UTXO planning and post-merge peer sync are implemented. Signing alone is not sufficient.
Is BitKuruş mainnet-ready?
Prototype / trusted federation. See README trust model.