diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0af4361..fbb4a7c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,6 +54,9 @@ importers: server: dependencies: + '@cashu/cashu-ts': + specifier: ^3.5.0 + version: 3.5.0 '@hono/node-server': specifier: ^1.14.1 version: 1.19.11(hono@4.12.5) @@ -72,6 +75,9 @@ importers: nanoid: specifier: ^5.1.5 version: 5.1.6 + nostr-tools: + specifier: ^2.23.3 + version: 2.23.3(typescript@5.9.3) devDependencies: '@types/better-sqlite3': specifier: ^7.6.13 @@ -592,6 +598,10 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@cashu/cashu-ts@3.5.0': + resolution: {integrity: sha512-LVOfCY1ZjQ+/f/CwekCeH+QRjN9SaHp8rTUjf8f4feHxT9Bqn/Bw+qYfNhaBKLWrqS19DMK/q/ern3AQ2MCq9w==} + engines: {node: '>=22.4.0'} + '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} @@ -1058,6 +1068,18 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@noble/ciphers@2.1.1': + resolution: {integrity: sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==} + engines: {node: '>= 20.19.0'} + + '@noble/curves@2.0.1': + resolution: {integrity: sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==} + engines: {node: '>= 20.19.0'} + + '@noble/hashes@2.0.1': + resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} + engines: {node: '>= 20.19.0'} + '@petamoriken/float16@3.9.3': resolution: {integrity: sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==} @@ -1248,6 +1270,15 @@ packages: cpu: [x64] os: [win32] + '@scure/base@2.0.0': + resolution: {integrity: sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==} + + '@scure/bip32@2.0.1': + resolution: {integrity: sha512-4Md1NI5BzoVP+bhyJaY3K6yMesEFzNS1sE/cP+9nuvE7p/b0kx9XbpDHHFl8dHtufcbdHRUUQdRqLIPHN/s7yA==} + + '@scure/bip39@2.0.1': + resolution: {integrity: sha512-PsxdFj/d2AcJcZDX1FXN3dDgitDDTmwf78rKZq1a6c1P1Nan1X/Sxc7667zU3U+AN60g7SxxP0YCVw2H/hBycg==} + '@surma/rollup-plugin-off-main-thread@2.2.3': resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} @@ -2367,6 +2398,17 @@ packages: node-releases@2.0.36: resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} + nostr-tools@2.23.3: + resolution: {integrity: sha512-AALyt9k8xPdF4UV2mlLJ2mgCn4kpTB0DZ8t2r6wjdUh6anfx2cTVBsHUlo9U0EY/cKC5wcNyiMAmRJV5OVEalA==} + peerDependencies: + typescript: '>=5.0.0' + peerDependenciesMeta: + typescript: + optional: true + + nostr-wasm@0.1.0: + resolution: {integrity: sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==} + object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} @@ -3698,6 +3740,13 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@cashu/cashu-ts@3.5.0': + dependencies: + '@noble/curves': 2.0.1 + '@noble/hashes': 2.0.1 + '@scure/base': 2.0.0 + '@scure/bip32': 2.0.1 + '@drizzle-team/brocli@0.10.2': {} '@esbuild-kit/core-utils@3.3.2': @@ -3953,6 +4002,14 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@noble/ciphers@2.1.1': {} + + '@noble/curves@2.0.1': + dependencies: + '@noble/hashes': 2.0.1 + + '@noble/hashes@2.0.1': {} + '@petamoriken/float16@3.9.3': {} '@rollup/plugin-babel@5.3.1(@babel/core@7.29.0)(rollup@2.80.0)': @@ -4078,6 +4135,19 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.59.0': optional: true + '@scure/base@2.0.0': {} + + '@scure/bip32@2.0.1': + dependencies: + '@noble/curves': 2.0.1 + '@noble/hashes': 2.0.1 + '@scure/base': 2.0.0 + + '@scure/bip39@2.0.1': + dependencies: + '@noble/hashes': 2.0.1 + '@scure/base': 2.0.0 + '@surma/rollup-plugin-off-main-thread@2.2.3': dependencies: ejs: 3.1.10 @@ -5196,6 +5266,20 @@ snapshots: node-releases@2.0.36: {} + nostr-tools@2.23.3(typescript@5.9.3): + dependencies: + '@noble/ciphers': 2.1.1 + '@noble/curves': 2.0.1 + '@noble/hashes': 2.0.1 + '@scure/base': 2.0.0 + '@scure/bip32': 2.0.1 + '@scure/bip39': 2.0.1 + nostr-wasm: 0.1.0 + optionalDependencies: + typescript: 5.9.3 + + nostr-wasm@0.1.0: {} + object-inspect@1.13.4: {} object-keys@1.1.1: {} diff --git a/server/package.json b/server/package.json index 96c71f2..6b2c2e4 100644 --- a/server/package.json +++ b/server/package.json @@ -11,12 +11,14 @@ "migrate": "tsx src/db/migrate.ts" }, "dependencies": { + "@cashu/cashu-ts": "^3.5.0", "@hono/node-server": "^1.14.1", "better-sqlite3": "^11.9.1", "chalk": "^5.6.2", "drizzle-orm": "^0.40.1", "hono": "^4.7.6", - "nanoid": "^5.1.5" + "nanoid": "^5.1.5", + "nostr-tools": "^2.23.3" }, "devDependencies": { "@types/better-sqlite3": "^7.6.13", diff --git a/server/src/db/schema.ts b/server/src/db/schema.ts index a8fbd7b..4d7979a 100644 --- a/server/src/db/schema.ts +++ b/server/src/db/schema.ts @@ -20,6 +20,9 @@ export const bots = sqliteTable('bots', { consecutiveErrors: integer('consecutive_errors').notNull().default(0), lastErrorAt: text('last_error_at'), customization: text('customization'), + satsWon: integer('sats_won').notNull().default(0), + satsWagered: integer('sats_wagered').notNull().default(0), + hasWallet: integer('has_wallet', { mode: 'boolean' }).notNull().default(false), createdAt: text('created_at').notNull(), }) @@ -36,6 +39,9 @@ export const fights = sqliteTable('fights', { scheduledAt: text('scheduled_at'), startedAt: text('started_at'), endedAt: text('ended_at'), + mode: text('mode', { enum: ['free', 'ranked'] }).notNull().default('free'), + potSats: integer('pot_sats').notNull().default(0), + payoutStatus: text('payout_status', { enum: ['pending', 'paid', 'failed'] }), createdAt: text('created_at').notNull(), }) @@ -56,6 +62,34 @@ export const rounds = sqliteTable('rounds', { createdAt: text('created_at').notNull(), }) +export const payments = sqliteTable('payments', { + id: text('id').primaryKey(), + fightId: text('fight_id').references(() => fights.id), + botId: text('bot_id').notNull().references(() => bots.id), + direction: text('direction', { enum: ['in', 'out'] }).notNull(), + amountSats: integer('amount_sats').notNull(), + method: text('method', { enum: ['lightning', 'cashu'] }).notNull(), + status: text('status', { + enum: ['pending', 'confirmed', 'failed', 'refunded'], + }).notNull().default('pending'), + invoice: text('invoice'), + preimage: text('preimage'), + cashuToken: text('cashu_token'), + errorReason: text('error_reason'), + createdAt: text('created_at').notNull(), + confirmedAt: text('confirmed_at'), + refundedAt: text('refunded_at'), +}) + +export const walletConnections = sqliteTable('wallet_connections', { + id: text('id').primaryKey(), + botId: text('bot_id').notNull().unique().references(() => bots.id), + method: text('method', { enum: ['nwc', 'lnaddress', 'cashu_mint'] }).notNull(), + connectionData: text('connection_data').notNull(), + createdAt: text('created_at').notNull(), + lastUsedAt: text('last_used_at'), +}) + export const bets = sqliteTable('bets', { id: text('id').primaryKey(), fightId: text('fight_id').notNull().references(() => fights.id), diff --git a/server/src/db/startup.ts b/server/src/db/startup.ts index 657d1a4..d7e6348 100644 --- a/server/src/db/startup.ts +++ b/server/src/db/startup.ts @@ -58,6 +58,34 @@ export function runMigrations() { ); `) + sqlite.exec(` + CREATE TABLE IF NOT EXISTS payments ( + id TEXT PRIMARY KEY, + fight_id TEXT REFERENCES fights(id), + bot_id TEXT NOT NULL REFERENCES bots(id), + direction TEXT NOT NULL, + amount_sats INTEGER NOT NULL, + method TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending', + invoice TEXT, + preimage TEXT, + cashu_token TEXT, + error_reason TEXT, + created_at TEXT NOT NULL, + confirmed_at TEXT, + refunded_at TEXT + ); + + CREATE TABLE IF NOT EXISTS wallet_connections ( + id TEXT PRIMARY KEY, + bot_id TEXT NOT NULL UNIQUE REFERENCES bots(id), + method TEXT NOT NULL, + connection_data TEXT NOT NULL, + created_at TEXT NOT NULL, + last_used_at TEXT + ); + `) + // Column migrations for existing databases const migrations = [ "ALTER TABLE bots ADD COLUMN archetype TEXT NOT NULL DEFAULT 'standard'", @@ -66,6 +94,14 @@ export function runMigrations() { "ALTER TABLE bots ADD COLUMN consecutive_errors INTEGER NOT NULL DEFAULT 0", "ALTER TABLE bots ADD COLUMN last_error_at TEXT", "ALTER TABLE bots ADD COLUMN customization TEXT", + // Ranked fights columns + "ALTER TABLE fights ADD COLUMN mode TEXT NOT NULL DEFAULT 'free'", + "ALTER TABLE fights ADD COLUMN pot_sats INTEGER NOT NULL DEFAULT 0", + "ALTER TABLE fights ADD COLUMN payout_status TEXT", + // Bot sats tracking + "ALTER TABLE bots ADD COLUMN sats_won INTEGER NOT NULL DEFAULT 0", + "ALTER TABLE bots ADD COLUMN sats_wagered INTEGER NOT NULL DEFAULT 0", + "ALTER TABLE bots ADD COLUMN has_wallet INTEGER NOT NULL DEFAULT 0", ] for (const sql of migrations) {