Linked positions (Qualified Start): extra wallets on one account, credits pooled, buy-from picker

- accounts/db: positions table + JSON store (add/list/owner/remove; main-wallet and cross-account guards)
- auth verify: asPosition links a second wallet without touching the session; position wallets can't mint a session
- /api/my/positions (chain-refreshed member ids, buyer counts, credits) + remove
- credits pooled across main + positions on /api/me, dashboard, campaigns; campaign charged to the best-funded position
- My line: own positions listed on level 1 as 'You · position N'
- Buy pane: Qualified Start card + Add a position flow + Buy-from picker with connected-wallet guard
- Wallet pane: Your positions card; chatbot answer updated

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-09 17:18:47 -05:00
parent b86dbfd9e9
commit 046464b83e
9 changed files with 327 additions and 14 deletions
+9
View File
@@ -120,6 +120,15 @@ async function bootstrap() {
await alterSafe('ALTER TABLE campaigns ADD COLUMN expires BIGINT NULL'); // featured rotation end time
await alterSafe('ALTER TABLE campaigns ADD COLUMN starts BIGINT NULL'); // featured run start (booked day)
await alterSafe('ALTER TABLE campaigns ADD COLUMN house TINYINT NOT NULL DEFAULT 0'); // admin house ad: free, never charged
// linked positions: extra wallets owned by one email account (Qualified Start).
// Each is its own on-chain member sponsored by the account's main member.
await q(`CREATE TABLE IF NOT EXISTS positions (
address VARCHAR(64) PRIMARY KEY,
email VARCHAR(190) NOT NULL,
member_id INT NOT NULL DEFAULT 0,
created BIGINT NOT NULL,
INDEX (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
await q(`CREATE TABLE IF NOT EXISTS visit_seen (
campaign_id INT NOT NULL,
email VARCHAR(190) NOT NULL,