# Q-Fund Crowdfund ATs — Technical Specification (v1 refund, v2 non-refund) **Scope:** This document specifies two Qortal Automated Transactions (ATs) that implement a crowdfund with two settlement behaviors: **v1 (refund-if-goal-missed)** and **v2 (pay-awardee-even-if-missed)**. It describes their immutable **code bytes**, **codeHash** calculation, **data bytes ABI**, **state machine**, **creation & deploy** flow, and verification rules so future developers can reproduce identical ATs (same codeHash) and build compatible apps. --- ## 1) Invariants & Golden Rules - **Code identity = `codeHash` = `Base58(SHA-256(codeBytes))`.** Never include user parameters in `codeBytes`. - **ABI uses big‑endian u64** unless noted. Address bytes are 32 bytes. - **Creation bytes = header + codeBytes + dataBytes**. Only `dataBytes` vary per deployment. - **Determinism**: avoid compiling time-dependent constants into `codeBytes`; compute cutoffs from `GET_BLOCK_TIMESTAMP` at runtime. --- ## 2) Code Hashes (Pinned) - **v1 (refund)** - `codeHash` = `9gS2L74FdaG3zuEeYv815xVyHkhvLguq7ZGD6pf24i8F` - `codeBytes.length` = 167 bytes - **v2 (pay-anyway)** - `codeHash` = `HaqJBVVr9gZqgARZ5UZd7EU9ybyvVK2fCo9sx3gMMFsr` - `codeBytes.length` = 104 bytes > These hashes were recomputed by `Base58(SHA-256(codeBytes))` from deployed creation bytes and from the app’s embedded Base64 (v2). --- ## 2a) Premade codeBytes (reference) These are the canonical, premade **codeBytes** for each variant. They are immutable; apps should embed these exact bytes (Base64) to reproduce the same `codeHash`. ### v1 — refund-if-goal-missed - codeHash: `9gS2L74FdaG3zuEeYv815xVyHkhvLguq7ZGD6pf24i8F` - codeBytes.length: `167` - SHA-256(codeBytes) hex: `80f770c2b6e6475208da0e9daa6b632b1b18293a606c88a213142929e2e7e9f0` **Base64 (exact):** ``` NQMBAAAABTUDAAAAAAI3BAYAAAACAAAAAgAAAAACAAAAAwAAAAJLAAAAAwAAAAAAAAAgJQAAAAM1BAAAAAAEIAAAAAQAAAABGTgBHwAAAAAAAAAKMgQDKDAzAwQAAAAFNQElAAAABhsAAAAGByg1AwcAAAAFIAAAAAUAAAACCyg1AwUAAAAHJAAAAAcAAAAI0jUDBgAAAAkyAwozBAIAAAAJGgAAAFk= ``` ### v2 — pay-awardee-even-if-missed - codeHash: `HaqJBVVr9gZqgARZ5UZd7EU9ybyvVK2fCo9sx3gMMFsr` - codeBytes.length: `104` - SHA-256(codeBytes) hex: `f665c427e2a59187aa4ee1c6325c226cd57814b02d007071439fb103f0badf3d` **Base64 (exact):** ``` NQMBAAAABTUDAAAAAAI3BAYAAAACAAAAAgAAAAACAAAAAwAAAAJLAAAAAwAAAAAAAAAgJQAAAAM1BAAAAAAEIAAAAAQAAAABGTgBHwAAAAAAAAAGMgQDKDA4AR8AAAAAAAAABjIEAyg= ``` --- ## 3) Data Bytes ABI (public interface) ### Common fields - **[0..7] (u64)** — *sleepMinutes/blocks window*. Number of minutes/blocks until the campaign decision point. Stored as u64 (big-endian). Used to compute a **timestamp cutoff** and a **sleep-until-height**. - **[8..15] (u64)** — *goalAmountAtoms*. Goal amount in QORT atoms (QORT×1e8). ### Variant‑specific fields - **v1 (refund)** — extended ABI to support refund scanning - **[16..23] (u64)** — *lastTxnTimestamp* (internal; initialized 0). Tracks scan cursor for inbound tx iteration. - **[24..31] (u64)** — *cutoffTimestamp* (internal; computed). - **[32..39] (u64)** — *finalAmount* (internal; computed at decision time). - **[40..47] (u64)** — *tmpAmount* (scratch). - **[48..55] (u64)** — *tmpType* (scratch; holds tx type). - **[56..63] (u64)** — *tmpHeight* (scratch; holds height from timestamp). - **[64..71] (u64)** — *paymentTypeId* (= `PAYMENT` = 2). Constant used to filter only payment transactions. - **[72..79] (u64)** — *reserved*. - **[80..111] (32 bytes)** — *awardeeAddress*. - **v2 (pay-anyway)** — minimal ABI - **[16..47] (4×u64)** — reserved/scratch (implementation-specific). - **[48..79] (32 bytes)** — *awardeeAddress*. > **Note:** All unspecified fields are zero-initialized. The layouts above reflect the offsets used by the app (v2) and by the v1 Java program. Keep exact sizes to preserve codeHash parity across builds. --- ## 4) State Machine (high level) ### v1 — Refund if goal missed 1. **Init**: Read `sleepMinutes` & `goalAtoms`; awardee set in `dataBytes`. 2. **Compute cutoff**: `GET_BLOCK_TIMESTAMP` → `ADD_MINUTES_TO_TIMESTAMP(sleepMinutes)` → store `cutoffTimestamp`. 3. **Sleep**: Convert cutoff timestamp to height; `SLP_DAT(height)` to pause until decision block. 4. **Decision**: - `GET_CURRENT_BALANCE` → store `finalAmount`. - If `finalAmount >= goalAtoms`: `SET_B_DAT(awardee)` → `PAY_ALL_TO_ADDRESS_IN_B` → `FIN_IMD`. - Else: enter **Refund Loop**. 5. **Refund Loop**: - `PUT_TX_AFTER_TIMESTAMP_INTO_A(lastTxnTimestamp)`; if A is zero → `FIN_IMD`. - `GET_TIMESTAMP_FROM_TX_IN_A` → update `lastTxnTimestamp`; if `>= cutoffTimestamp` → `FIN_IMD`. - `GET_TYPE_FROM_TX_IN_A` → if `!= paymentTypeId` → loop (skip non-payments). - `GET_AMOUNT_FROM_TX_IN_A` & `PUT_ADDRESS_FROM_TX_IN_A_INTO_B` → `PAY_TO_ADDRESS_IN_B(amount)` → loop. ### v2 — Pay awardee even if goal missed 1. **Init**: Read `sleepMinutes` & `goalAtoms`; awardee set in `dataBytes`. 2. **Compute cutoff** as in v1; **Sleep** until decision block. 3. **Decision**: Regardless of whether `GET_CURRENT_BALANCE >= goalAtoms`, **pay all**: `SET_B_DAT(awardee)` → `PAY_ALL_TO_ADDRESS_IN_B` → `FIN_IMD`. --- ## 5) Build → Create → Deploy 1. **Build `dataBytes`** (big-endian): - Write u64 at offset 0: `sleepMinutes` (or block window). - Write u64 at offset 8: `goalAtoms`. - Write 32‑byte awardee at offset **v1: 80** / **v2: 48**. - (v1 only) Write u64 at offset 64: `paymentTypeId = 2`. 2. **Use fixed `codeBytesBase64`** per variant (see §2). 3. **POST `/at/create`** with: ```json { "ciyamAtVersion": 2, "codeBytesBase64": "...", "dataBytesBase64": "...", "numCallStackPages": 0, "numUserStackPages": 0, "minActivationAmount": 0 } ``` 4. **Verify** the response by decoding `creationBytes` and re-hashing `codeBytes`. 5. **Deploy** via Hub: `qortalRequest({ action: 'DEPLOY_AT', creationBytes, name, description, tags:'q-fund', amount:0.2, assetId:0, type:'crowdfund' })`. --- ## 6) Creation Bytes Decoder (sanity tool) Given `creationBytes` (Base58): - Decode → parse header → extract `codeLen` → slice `codeBytes` → slice `dataBytes`. - Compute `codeHash = Base58(SHA-256(codeBytes))`. - Assert expected lengths and non-absurd values (e.g., data ≥ 112 for v1; ≥ 80 for v2). --- ## 7) Parity Tests (Definition of Done) - **Code parity:** The provided `codeBytesBase64` must hash to the pinned `codeHash` for each variant. - **Creation parity:** Changing only `dataBytes` must not alter `codeHash`. - **Behavior parity:** - v1: when goal is missed, donors are refunded; when met, awardee receives all. - v2: awardee receives all regardless of goal. - **ABI parity:** Offsets and sizes match §3 (BE u64, 32‑byte address). --- ## 8) Known Offsets & Helpers (app reference) - **v2 (repo)** uses - `setLongValue(data, 0, blocks)` - `setLongValue(data, 8, goalAtoms)` - `replaceArraySlice(data, 48, bs58.decode(awardee))` - **v1** must use - `setLongValue(data, 0, blocks)` - `setLongValue(data, 8, goalAtoms)` - `setLongValue(data, 64, 2)` // PAYMENT constant - `replaceArraySlice(data, 80, bs58.decode(awardee))` --- ## 9) Hash Recipe (exact) ```text codeHash = Base58( SHA-256( codeBytes ) ) ``` --- ## 10) Appendix — Validation Artifacts - v1 `codeBytes.length` = 167; v2 `codeBytes.length` = 104.