LapseCoin Transactions
Transaction format

A transaction is a plain, visible JSON object. Nothing in it is encrypted, the memo included.

FieldRequiredDescription
fromyesSender address (twelve dot-separated words)
pubkeyyesSender's FALCON-512 public key, hex. Its derived address must equal from.
outputsyesNon-empty list of {"to": address, "amount": ticks}, at most 500. Amounts are positive integers. You cannot pay your own address.
nonceyesInteger, exactly the sender's last used nonce plus one (the first transaction uses 1)
feeyesNon-negative integer in ticks, chosen by the sender
memonoPlaintext string, at most 200 bytes, no null bytes. Omit the key entirely when there is no memo.
signatureyesFALCON-512 signature over the canonical JSON of every other field, hex

Unknown fields are rejected. The sender's balance must cover the sum of all outputs plus the fee. 1 LAPSE = 100,000,000 ticks.

Signing
  1. Build the transaction object without signature.
  2. Serialize it as canonical JSON: keys sorted, no whitespace. The node uses orjson with sorted keys. For integers and strings, Python's json.dumps(tx, sort_keys=True, separators=(",", ":"), ensure_ascii=False) produces the same bytes.
  3. Sign those bytes with FALCON-512 (liboqs, algorithm name "Falcon-512").
  4. Add the signature as a hex string in signature.
  5. POST the whole object to /api/tx/send.

FALCON signing uses fresh randomness, so re-signing the same content gives a different valid signature. The transaction hash excludes the signature, so the hash stays the same.

Fees

The fee is an absolute amount in ticks. Builders prefer higher fee per byte, where the size is the canonical JSON length of the transaction excluding the signature (and including the fee field itself). GET /api/fees reports the current next_block rate. Board posts (memo starting with [board] ) have an additional protocol-enforced fee floor.

Nonce

Nonces are sequential per sender, starting from 0 for an address that has never sent. A transaction is valid only if its nonce is exactly the last used nonce plus one, counting the sender's pending mempool transactions. GET /api/state?addr=... reports the highest nonce used, confirmed or pending; the next transaction uses that plus one. See the API page.

Board memos

The board is made of ordinary transactions whose memo follows a few conventions. A ref is the first six hex characters of a post's transaction hash.

MemoMeaning
[board] + textA post. May begin with a profile header [p:icon:nick] and then a reply header [r:ref]. Pays the board fee floor and burns 1 tick.
[vote+] ref, [vote-] refA vote on a post. Not a board post: no fee floor, 1 tick burned.
[board] [e:ref:pos:ndel]textAn edit. In the sender's post with that ref, replace ndel characters at character position pos with text. It is a board post, so it pays the fee floor, burns 1 tick and counts toward the floor's staircase.
[del] refHides the sender's post with that ref. Not a board post: no fee floor, 1 tick burned.

Edits and deletes change nothing on the chain, and nothing could: the original text stays in its block. They are rules that readers apply the same way. An edit or delete acts only on a post by its own sender that came before it, and an edit is applied to the post as earlier edits left it, in chain order. The result must be non-empty and fit in a post (200 bytes of memo less the [board] tag). An edit that cannot be applied is not hidden: it shows as the ordinary post it also is. The node's validation does not check any of this, so a node will relay an edit or delete of someone else's post, and readers will ignore it.

Address derivation

Addresses are a one-way hash of the public key, so any syntactically valid word sequence passes validation. Validity does not prove that anyone holds the key.

Python example

Requires liboqs-python (module oqs) and requests. Keys come from oqs.Signature("Falcon-512").generate_keypair() and export_secret_key().

import hashlib, json, requests, oqs

NODE = "https://lapsenode.vicnas.me"
WORDLIST = open("bip39_english.txt").read().split()   # src/bip39_english.txt in the repo

def address_from_pubkey(pk: bytes) -> str:
    bits = int.from_bytes(hashlib.sha256(pk).digest()[:17], "big") >> 4   # top 132 bits
    return ".".join(WORDLIST[(bits >> (11 * i)) & 0x7FF] for i in range(11, -1, -1))

def canonical(obj) -> bytes:
    return json.dumps(obj, sort_keys=True, separators=(",", ":"),
                      ensure_ascii=False).encode()

def next_nonce(addr: str) -> int:
    hist = requests.get(f"{NODE}/api/address/{addr}/history").json()
    sent = sum(1 for r in hist if r["direction"] == "sent")
    pool = requests.get(f"{NODE}/api/mempool").json()["transactions"]
    pending = sum(1 for t in pool if t["from"] == addr)
    return sent + pending + 1

def send(secret_key: bytes, public_key: bytes, to_addr: str,
         amount_ticks: int, fee_ticks: int, memo: str = ""):
    addr = address_from_pubkey(public_key)
    tx = {
        "from": addr,
        "pubkey": public_key.hex(),
        "outputs": [{"to": to_addr, "amount": amount_ticks}],
        "nonce": next_nonce(addr),
        "fee": fee_ticks,
    }
    if memo:
        tx["memo"] = memo
    signature = oqs.Signature("Falcon-512", secret_key).sign(canonical(tx))
    tx["signature"] = signature.hex()
    return requests.post(f"{NODE}/api/tx/send", json=tx).json()
    # {"ok": true, "tx_hash": "..."} or {"ok": false, "error": "..."}
Deposit detection
import requests

def confirmed_deposits(node, addr):
    rows = requests.get(f"{node}/api/address/{addr}/history").json()
    for r in rows:
        if r["direction"] != "received":
            continue
        tx = r["tx"]
        ticks = sum(o["amount"] for o in tx["outputs"] if o["to"] == addr)
        yield r["tx_hash"], r["height"], ticks, tx.get("memo", "")

def confirmations(node, tx_hash):
    t = requests.get(f"{node}/api/tx/{tx_hash}").json()
    return t.get("confirmations", 0)

Deduplicate by tx_hash, sum every output paying your address (one transaction can pay you more than once), and wait for the confirmation depth you are comfortable with before crediting.

Notes