Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,13 @@ Rules: `verifyQuoteSignature()` before using `depositAddress`; ids/decimals from
### 4.4 Worker loops
| Loop | Every | Does |
|---|---|---|
| `pollFunding` | 5 s | Non-terminal funding attempts → status |
| `scheduleBuys` | 60 s | Due vaults (`get_state`, balance) → quote → verify → plan match → cost check → `execute_buy` → submit tx |
| `pollBuys` | 10 s | In-flight buys → final status, destination tx |
| `expireQuotes` | 60 s | |
Alert when the keeper's NEAR balance is low.
| `pollFunding` | 5 s | Non-final funding attempts → 1Click status; PENDING past deadline → `expired`; alert on `underpaid`. One failing attempt never stops the others |
| `scheduleBuys` | 60 s | Per vault: skip if paused / not due / a buy is in flight / within the 15-min backoff after a skip or failure / vault empty. Amount = min(`amount_per_run`, balance). Real quote (fails closed on signature, K3.5) → cost check (`skipped_cost` + note) → plan match (`failed` "refused: …" + alert) → **insert row first** → `execute_buy` → `submitDepositTx` (best effort) |
| `pollBuys` | 10 s | In-flight buys → final status; on success the actual `amountOut` and destination tx from `swapDetails`; refunds noted with 1Click's reason |
| `expireQuotes` | 60 s | DB-only expiry of unpaid funding quotes (works while 1Click is down) |
| `keeperBalance` | 10 min | Alert below 1 NEAR — buys stop without gas |

Runner: a loop never overlaps itself, errors are logged and the loop continues, SIGINT/SIGTERM wait for running ticks. Logs are JSON lines with no secrets or personal data. Rows are timestamped with the worker clock (`deps.now`), never the DB's.

---

Expand All @@ -211,6 +213,7 @@ buys (id uuid pk, user_id uuid fk, quote_ref text unique not null, amount_in num
amount_in_usd numeric, amount_out numeric(78,0), deposit_address text unique,
quote_signature text, quote_raw jsonb, near_tx_hash text, oneclick_status text,
status text not null, -- skipped_cost|no_quote|sent|processing|success|refunded|failed|expired
note text, -- why it was skipped, refused, failed or refunded (migration 0001)
destination_tx_hash text, created_at timestamptz default now(), completed_at timestamptz)
```
On-chain vault = truth for plan and balance. DB = history and metadata.
Expand Down
5 changes: 4 additions & 1 deletion ARCHITECTURE_ESSENTIALS.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ On-chain vault = truth for plan + balance.
- Funding status refreshes from 1Click on read; plan preview refunds to the user's own account before a vault exists

## Worker
`pollFunding` 5 s · `scheduleBuys` 60 s · `pollBuys` 10 s · `expireQuotes` 60 s · alert on low keeper gas
`pollFunding` 5 s · `scheduleBuys` 60 s · `pollBuys` 10 s · `expireQuotes` 60 s · keeper gas alert 10 min
- Every loop is `(deps) => Promise` (worker/deps.ts) — test one tick with fakes
- scheduleBuys order: due? → not in flight / not in 15-min backoff → amount = min(per-run, balance) → quote → cost → plan match → **insert row** → execute_buy → submit
- Every skip/refusal/failure writes `buys.note`; refusals and failures `log.alert`

## Demo & dry-run
`pnpm demo` = zero secrets, PGlite, seeded vault + history, fast-forward 30 days with one `skipped_cost`. Never moves funds.
Expand Down
15 changes: 8 additions & 7 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
| 2. Core logic (`lib/core`) | Nov 6–7 | 100% | ✅ done |
| 3. Integrations (1Click, NEAR, DB) | Nov 7–8 | 80% | in progress (K3.5 blocked on K0.8) |
| 4. API routes | Nov 8–9 | 100% | ✅ done |
| 5. Worker (scheduler) | Nov 9–10 | 0% | not started |
| 5. Worker (scheduler) | Nov 9–10 | 100% | ✅ done |
| 6. UI | Nov 9–11 | 0% | not started |
| 7. Demo mode & dry-run | Nov 11 | 0% | not started |
| 8. Hardening & edge cases | Nov 11–12 | 0% | not started |
| 9. Deploy & real run | Nov 12 | 0% | not started |
| 10. README & public repo | Nov 12–13 | 14% | in progress |
| 11. Launch: video, post, submission | Nov 13–14 | 0% | not started |
| 12. After submission | Nov 14–25 | 0% | not started |
| **Overall (MVP)** | | **~40%** | |
| **Overall (MVP)** | | **~48%** | |

Phase % = done tasks ÷ non-dropped tasks in that phase. Overall = average of all 14 rows (D + phases 0–12). (Earlier entries over-estimated the overall %; recalculated 2026-11-04.)

Expand Down Expand Up @@ -129,11 +129,11 @@ Format: `- [task id] what blocks it · who can unblock · since date`

## Phase 5 — Worker (Nov 9–10)

- [ ] **K5.1** [#50](https://github.com/MFrancis-dev/stack/issues/50) `worker/index.ts`, graceful shutdown
- [ ] **K5.2** [#51](https://github.com/MFrancis-dev/stack/issues/51) `pollFunding` · tests
- [ ] **K5.3** [#52](https://github.com/MFrancis-dev/stack/issues/52) `scheduleBuys`: due check → quote → verify → plan match → cost check → `execute_buy` → submit · tests for skip, no_quote, mismatch, success
- [ ] **K5.4** [#53](https://github.com/MFrancis-dev/stack/issues/53) `pollBuys` · tests
- [ ] **K5.5** [#54](https://github.com/MFrancis-dev/stack/issues/54) `expireQuotes`; low keeper-gas alert
- [x] **K5.1** [#50](https://github.com/MFrancis-dev/stack/issues/50) `worker/index.ts`, graceful shutdown · 2026-11-04: entry + `startLoops` runner (no overlap, errors logged, SIGINT/SIGTERM wait for ticks); smoke-run OK with no env
- [x] **K5.2** [#51](https://github.com/MFrancis-dev/stack/issues/51) `pollFunding` · tests · 2026-11-04: status mapping, expiry past deadline, underpaid alert, per-attempt error isolation
- [x] **K5.3** [#52](https://github.com/MFrancis-dev/stack/issues/52) `scheduleBuys`: due check → quote → verify → plan match → cost check → `execute_buy` → submit · tests for skip, no_quote, mismatch, success · 2026-11-04: due/in-flight/backoff/empty checks, min(per-run, balance), cost → plan match → insert-then-execute → submit; every skip/refusal/failure recorded with a note (new `buys.note`, migration 0001)
- [x] **K5.4** [#53](https://github.com/MFrancis-dev/stack/issues/53) `pollBuys` · tests · 2026-11-04: final status, actual amountOut + destination tx from swapDetails, refund reason
- [x] **K5.5** [#54](https://github.com/MFrancis-dev/stack/issues/54) `expireQuotes`; low keeper-gas alert · 2026-11-04: DB-only quote expiry; keeper balance alert below 1 NEAR

## Phase 6 — UI (Nov 9–11)

Expand Down Expand Up @@ -216,6 +216,7 @@ Format: `- [task id] what blocks it · who can unblock · since date`
## Update log
One line per contribution, newest first: `YYYY-MM-DD · who/agent · what changed · tasks`

- 2026-11-04 · agent · Phase 5 worker: pollFunding, scheduleBuys (full safety chain, backoff, insert-before-execute), pollBuys, expireQuotes, keeper gas alert, no-overlap runner; `buys.note` migration; 100 TS tests · K5.1–K5.5
- 2026-11-04 · agent · Phase 4 API: NEP-413 sessions, onboard, vault, tokens, funding (dry/real/status/submit), plan preview, buys, CSV; services seam for tests/demo; 84 TS tests · K4.1–K4.7, K8.7
- 2026-11-04 · agent · Phase 3 integrations: 1Click layer (fail-closed signatures), NEAR bindings + NEP-413 sign-in, first DB migration + queries; 66 TS tests; new K3.5 (signature verification) blocked on K0.8 · K3.1–K3.5
- 2026-11-04 · agent · Phase 2 core logic: money, cost, planMatch, address (checksums), assets, schemas; 33 TS tests · K2.1–K2.6
Expand Down
1 change: 1 addition & 0 deletions drizzle/0001_buy_note.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "buys" ADD COLUMN "note" text;
Loading
Loading