Skip to content

Commit 1adaa66

Browse files
AIQnetLabclaude
andcommitted
feat(token): on-chain token transfers, canonical burn, sharded logs_root proofs
Success-gated QRC-20/721 transfer events are folded into the 2f+1 QC-certified Checkpoint.logs_root (active from genesis), giving trustless light-client receipts. - logs_root is SHARDED: the window root is a merkle over per-block sub-roots (stored at apply), so the macroblock seal folds ~90 roots and /logs/proof is a 2-level proof that touches ONE block, not O(window). Mobile QcLightClient verifies both levels. - Canonical burn address 0000000000000000000eon00000000000000036877022 (provably unspendable): QRC-20/721 transfer-to-it is a real burn (supply reduced / token destroyed + exact deposit refund); native QNC becomes non-circulating; mint-to-burn rejected. /public/stats reports burn_address + qnc_burned. - VM: deterministic tree-wide MAX_LOGS_PER_TX cap (traps -> reverts tx, shared CallState counter) + higher LOG_FUEL_BASE bound the per-window merkle against emit_log spam. - Node: reorg-consistent token-transfer index (cursor-paged, resume-watermark prune), decoded RPC feeds with oldest_available retention honesty, and a light contract-metadata read (no O(holders) account clone). - Mobile: trustless 2-level inclusion verify (a QC-root mismatch -> rejected, never confirmed), a QC-verified trust badge gated on wallet-trusted decimals, no node-supplied logo loaded as <Image>. - Explorer: token-transfer tab/pages, anchor-corroborated backward-height reconcile (no spurious DB wipe on node restart), composite SQL indexes, shared amount/logo utils. - External qnet-loadtest harness: confirmed-TPS + finality latency + 2-level proof sampling. Validated: qnet-consensus 91, qnet-state 131, qnet-vm 22, qnet-integration check clean, qnet-loadtest 5; mobile babel + explorer tsc clean. Consensus-structure change -> fresh genesis (logs_root_required gate=0, no mixed-binary). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 013761f commit 1adaa66

46 files changed

Lines changed: 4353 additions & 441 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,3 +687,9 @@ dev-scratch/
687687
# Build artifacts (never track)
688688
*.rlib
689689
*.tsbuildinfo
690+
691+
# Explorer app TypeScript source libs are real code, not Python build artifacts (override the generic `lib/` ignore above)
692+
!applications/qnet-explorer/frontend/lib/
693+
!applications/qnet-explorer/frontend/lib/**
694+
!applications/qnet-explorer/frontend/src/lib/
695+
!applications/qnet-explorer/frontend/src/lib/**

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ members = [
77
"core/qnet-sharding",
88
"core/qnet-vm",
99
"development/qnet-integration",
10+
"development/qnet-loadtest",
1011
"audit"
1112
]
1213
resolver = "2"

QNet_Whitepaper.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,17 @@ pub struct SimpleMempool {
13071307
- **Pool #3**: Activation QNC redistributed to all nodes
13081308
- **Decimals**: 9 (1 QNC = 10^9 nanoQNC)
13091309

1310+
**Canonical Burn Address (all QNet tokens):**
1311+
1312+
```
1313+
0000000000000000000eon00000000000000036877022
1314+
```
1315+
1316+
- **Nothing-up-my-sleeve**: the address body is all zeros with a valid checksum; addresses derive from SHA512(pubkey), so no key can ever map to it — provably unspendable forever
1317+
- **QRC-20**: transferring to it is a REAL burn recognized by the protocol — holder debited, `total_supply` reduced, `total_burned` increased 1:1, a `burn` event emitted. Works for ANY token, including non-burnable ones (the opt-in `burnable` flag only gates the token's own `burn` method)
1318+
- **QRC-721 (NFT)**: transferring to it destroys the token — ownership record removed, storage deposits refunded, a `burn` event emitted
1319+
- **Native QNC**: accumulates unspendably at the address and is excluded from circulating supply (`circulating = total_supply − qnc_burned`, reported by `/api/v1/public/stats`). Burned QNC still counts against the 2^32 emission cap — burning never re-opens emission headroom
1320+
13101321
### 7.2 Sharp Drop Halving Innovation
13111322

13121323
**Revolutionary Emission Schedule:**

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1905,6 +1905,17 @@ curl http://localhost:8003/api/v1/transactions/{hash}
19051905
- `GET /api/v1/token/{address}` - Get token info (name, symbol, decimals, supply)
19061906
- `GET /api/v1/token/{address}/balance/{holder}` - Get token balance for holder
19071907

1908+
**Token Transfers (decoded, effect-sourced events):**
1909+
- `GET /api/v1/account/{address}/token-transfers?limit=&before=` - QRC-20/721 transfers for an address (cursor-paged)
1910+
- `GET /api/v1/token/{contract}/transfers?limit=&before=` - Transfers for one token (cursor-paged)
1911+
- `GET /api/v1/token-transfers?from=&to=&limit=&after=` - Height-range feed for indexers (`truncated` + `next_cursor`)
1912+
- `GET /api/v1/logs/proof?tx_hash=&log_index=` - Light-client merkle inclusion proof against the QC-certified `logs_root`
1913+
1914+
**Canonical Burn Address:**
1915+
- `0000000000000000000eon00000000000000036877022` - well-known, provably unspendable (all-zeros body, no key can derive it)
1916+
- Sending ANY QRC-20 (even non-burnable) or QRC-721 here is a REAL on-chain burn: supply reduced / token destroyed, `burn` event emitted
1917+
- Sending native QNC here removes it from circulation permanently; `GET /api/v1/public/stats` reports `burn_address` + `qnc_burned` (circulating = total_supply − qnc_burned)
1918+
19081919
**Blockchain Data:**
19091920
- `GET /api/v1/block/latest` - Get latest block
19101921
- `GET /api/v1/block/{height}` - Get block by height

applications/qnet-explorer/frontend/instrumentation.ts

Lines changed: 0 additions & 129 deletions
This file was deleted.

applications/qnet-explorer/frontend/lib/db.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,61 @@ export async function searchQrc20DeploysByText(needle: string, limit: number = 2
612612
return result.rows;
613613
}
614614

615+
// ============================================================================
616+
// TOKEN TRANSFERS (decoded, effect-sourced index — populated by sync-service)
617+
// ============================================================================
618+
619+
// One decoded transfer log. `amount` is a u64 base-unit value; PostgreSQL returns
620+
// NUMERIC as a string, so it stays exact past 2^53 — scale it with the token's
621+
// decimals via formatTokenAmount, never Number(). from==''⇒mint, to==''⇒burn.
622+
export interface TokenTransferRow {
623+
tx_hash: string;
624+
log_index: number;
625+
contract: string;
626+
from_address: string;
627+
to_address: string;
628+
amount: string;
629+
kind: string;
630+
std: string;
631+
token_id: string;
632+
block: number;
633+
timestamp: number;
634+
}
635+
636+
// All transfers touching an address (as sender or recipient), newest first. A UNION of two single-column
637+
// branches — not `from = $1 OR to = $1` — so each side is an index-ordered, LIMIT-bounded scan on its
638+
// (address, block DESC, log_index DESC) composite index (an OR across two columns cannot use either index
639+
// for ordering and forces a full BitmapOr fetch + sort). UNION dedupes a self-transfer (from == to == $1).
640+
export async function getAddressTokenTransfers(address: string, limit: number = 50): Promise<TokenTransferRow[]> {
641+
validateAddress(address);
642+
if (!Number.isInteger(limit) || limit < 1 || limit > 500) limit = 50;
643+
const result = await query<TokenTransferRow>(
644+
`SELECT * FROM (
645+
(SELECT * FROM token_transfers WHERE from_address = $1 ORDER BY block DESC, log_index DESC LIMIT $2)
646+
UNION
647+
(SELECT * FROM token_transfers WHERE to_address = $1 ORDER BY block DESC, log_index DESC LIMIT $2)
648+
) u
649+
ORDER BY block DESC, log_index DESC
650+
LIMIT $2`,
651+
[address, limit]
652+
);
653+
return result.rows;
654+
}
655+
656+
// All transfers of one token contract, newest first.
657+
export async function getContractTokenTransfers(contract: string, limit: number = 50): Promise<TokenTransferRow[]> {
658+
validateAddress(contract);
659+
if (!Number.isInteger(limit) || limit < 1 || limit > 500) limit = 50;
660+
const result = await query<TokenTransferRow>(
661+
`SELECT * FROM token_transfers
662+
WHERE contract = $1
663+
ORDER BY block DESC, log_index DESC
664+
LIMIT $2`,
665+
[contract, limit]
666+
);
667+
return result.rows;
668+
}
669+
615670
export async function closePool(): Promise<void> {
616671
if (pool) {
617672
await pool.end();

0 commit comments

Comments
 (0)