@waiaas/daemon 2.6.0-rc.1 → 2.6.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/api/routes/admin.d.ts +5 -1
  2. package/dist/api/routes/admin.d.ts.map +1 -1
  3. package/dist/api/routes/admin.js +298 -4
  4. package/dist/api/routes/admin.js.map +1 -1
  5. package/dist/api/routes/openapi-schemas.d.ts +21 -0
  6. package/dist/api/routes/openapi-schemas.d.ts.map +1 -1
  7. package/dist/api/routes/openapi-schemas.js +3 -0
  8. package/dist/api/routes/openapi-schemas.js.map +1 -1
  9. package/dist/api/routes/policies.d.ts.map +1 -1
  10. package/dist/api/routes/policies.js +18 -4
  11. package/dist/api/routes/policies.js.map +1 -1
  12. package/dist/api/routes/tokens.d.ts +5 -1
  13. package/dist/api/routes/tokens.d.ts.map +1 -1
  14. package/dist/api/routes/tokens.js +70 -1
  15. package/dist/api/routes/tokens.js.map +1 -1
  16. package/dist/api/routes/wallets.d.ts.map +1 -1
  17. package/dist/api/routes/wallets.js +3 -0
  18. package/dist/api/routes/wallets.js.map +1 -1
  19. package/dist/api/server.d.ts.map +1 -1
  20. package/dist/api/server.js +6 -1
  21. package/dist/api/server.js.map +1 -1
  22. package/dist/pipeline/database-policy-engine.d.ts +29 -4
  23. package/dist/pipeline/database-policy-engine.d.ts.map +1 -1
  24. package/dist/pipeline/database-policy-engine.js +147 -27
  25. package/dist/pipeline/database-policy-engine.js.map +1 -1
  26. package/dist/pipeline/sign-only.d.ts +4 -0
  27. package/dist/pipeline/sign-only.d.ts.map +1 -1
  28. package/dist/pipeline/sign-only.js +1 -0
  29. package/dist/pipeline/sign-only.js.map +1 -1
  30. package/dist/pipeline/stages.d.ts.map +1 -1
  31. package/dist/pipeline/stages.js +2 -0
  32. package/dist/pipeline/stages.js.map +1 -1
  33. package/package.json +4 -4
  34. package/public/admin/assets/index-C608OrkU.js +1 -0
  35. package/public/admin/index.html +1 -1
  36. package/public/admin/assets/index-D06O_cSo.js +0 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Admin route handlers: 22 daemon administration endpoints.
2
+ * Admin route handlers: 24 daemon administration endpoints.
3
3
  *
4
4
  * GET /admin/status - Daemon health/uptime/version (masterAuth)
5
5
  * POST /admin/kill-switch - Activate kill switch (masterAuth)
@@ -21,6 +21,8 @@
21
21
  * GET /admin/telegram-users - List Telegram bot users (masterAuth)
22
22
  * PUT /admin/telegram-users/:chatId - Update Telegram user role (masterAuth)
23
23
  * DELETE /admin/telegram-users/:chatId - Delete Telegram user (masterAuth)
24
+ * GET /admin/transactions - Cross-wallet transaction list with filters (masterAuth)
25
+ * GET /admin/incoming - Cross-wallet incoming transaction list with filters (masterAuth)
24
26
  *
25
27
  * @see docs/37-rest-api-complete-spec.md
26
28
  * @see docs/36-killswitch-evm-freeze.md
@@ -97,6 +99,8 @@ export interface AdminRouteDeps {
97
99
  * GET /admin/telegram-users - List Telegram bot users (masterAuth)
98
100
  * PUT /admin/telegram-users/:chatId - Update Telegram user role (masterAuth)
99
101
  * DELETE /admin/telegram-users/:chatId - Delete Telegram user (masterAuth)
102
+ * GET /admin/transactions - Cross-wallet transaction list (masterAuth)
103
+ * GET /admin/incoming - Cross-wallet incoming tx list (masterAuth)
100
104
  */
101
105
  export declare function adminRoutes(deps: AdminRouteDeps): OpenAPIHono;
102
106
  //# sourceMappingURL=admin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../../src/api/routes/admin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAEhE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGjE,OAAO,KAAK,EAAyE,YAAY,EAAE,iBAAiB,EAAgB,MAAM,cAAc,CAAC;AAEzJ,OAAO,KAAK,EAAE,gBAAgB,EAAc,MAAM,gDAAgD,CAAC;AAInG,OAAO,KAAK,KAAK,MAAM,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6CAA6C,CAAC;AACvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAE9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAExE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8CAA8C,CAAC;AAChF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yDAAyD,CAAC;AACtG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uDAAuD,CAAC;AA+BjG,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,qBAAqB,CAAC,OAAO,MAAM,CAAC,CAAC;IACzC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,kBAAkB,EAAE,MAAM,eAAe,CAAC;IAC1C,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAClE,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,kBAAkB,CAAC,EAAE,YAAY,CAAC,eAAe,CAAC,CAAC;IACnD,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACpD,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,YAAY,CAAC,EAAE;QAAE,yBAAyB,EAAE,OAAO,CAAC;QAAC,wBAAwB,EAAE,MAAM,CAAA;KAAE,CAAC;IACxF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IAChD,gBAAgB,CAAC,EAAE,iBAAiB,CAAC;IACrC,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mBAAmB,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAClD;AAkhBD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,cAAc,GAAG,WAAW,CA2vC7D"}
1
+ {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../../src/api/routes/admin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAEhE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,KAAK,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGjE,OAAO,KAAK,EAAyE,YAAY,EAAE,iBAAiB,EAAgB,MAAM,cAAc,CAAC;AAEzJ,OAAO,KAAK,EAAE,gBAAgB,EAAc,MAAM,gDAAgD,CAAC;AAInG,OAAO,KAAK,KAAK,MAAM,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6CAA6C,CAAC;AACvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAE9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAExE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8CAA8C,CAAC;AAChF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yDAAyD,CAAC;AACtG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uDAAuD,CAAC;AA+BjG,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,qBAAqB,CAAC,OAAO,MAAM,CAAC,CAAC;IACzC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,kBAAkB,EAAE,MAAM,eAAe,CAAC;IAC1C,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAClE,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,kBAAkB,CAAC,EAAE,YAAY,CAAC,eAAe,CAAC,CAAC;IACnD,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACpD,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,YAAY,CAAC,EAAE;QAAE,yBAAyB,EAAE,OAAO,CAAC;QAAC,wBAAwB,EAAE,MAAM,CAAA;KAAE,CAAC;IACxF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IAChD,gBAAgB,CAAC,EAAE,iBAAiB,CAAC;IACrC,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mBAAmB,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAClD;AAmoBD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,cAAc,GAAG,WAAW,CAq8C7D"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Admin route handlers: 22 daemon administration endpoints.
2
+ * Admin route handlers: 24 daemon administration endpoints.
3
3
  *
4
4
  * GET /admin/status - Daemon health/uptime/version (masterAuth)
5
5
  * POST /admin/kill-switch - Activate kill switch (masterAuth)
@@ -21,16 +21,18 @@
21
21
  * GET /admin/telegram-users - List Telegram bot users (masterAuth)
22
22
  * PUT /admin/telegram-users/:chatId - Update Telegram user role (masterAuth)
23
23
  * DELETE /admin/telegram-users/:chatId - Delete Telegram user (masterAuth)
24
+ * GET /admin/transactions - Cross-wallet transaction list with filters (masterAuth)
25
+ * GET /admin/incoming - Cross-wallet incoming transaction list with filters (masterAuth)
24
26
  *
25
27
  * @see docs/37-rest-api-complete-spec.md
26
28
  * @see docs/36-killswitch-evm-freeze.md
27
29
  */
28
30
  import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi';
29
- import { sql, desc, eq, and, isNull, gt, count as drizzleCount } from 'drizzle-orm';
31
+ import { sql, desc, eq, and, isNull, gt, gte, lte, count as drizzleCount } from 'drizzle-orm';
30
32
  import { createHash } from 'node:crypto';
31
33
  import { WAIaaSError, getDefaultNetwork, getNetworksForEnvironment } from '@waiaas/core';
32
34
  import { CurrencyCodeSchema, formatRatePreview } from '@waiaas/core';
33
- import { wallets, sessions, sessionWallets, notificationLogs, policies, transactions } from '../../infrastructure/database/schema.js';
35
+ import { wallets, sessions, sessionWallets, notificationLogs, policies, transactions, incomingTransactions } from '../../infrastructure/database/schema.js';
34
36
  import { generateId } from '../../infrastructure/database/id.js';
35
37
  import { buildConnectInfoPrompt } from './connect-info.js';
36
38
  import { getSettingDefinition } from '../../infrastructure/settings/index.js';
@@ -168,6 +170,9 @@ const notificationLogQuerySchema = z.object({
168
170
  pageSize: z.string().optional().default('20'),
169
171
  channel: z.string().optional(),
170
172
  status: z.string().optional(),
173
+ eventType: z.string().optional(),
174
+ since: z.string().optional(),
175
+ until: z.string().optional(),
171
176
  });
172
177
  const notificationsLogRoute = createRoute({
173
178
  method: 'get',
@@ -369,6 +374,7 @@ const adminWalletTransactionsRoute = createRoute({
369
374
  params: z.object({ id: z.string().uuid() }),
370
375
  query: z.object({
371
376
  limit: z.coerce.number().int().min(1).max(100).default(20).optional(),
377
+ offset: z.coerce.number().int().min(0).default(0).optional(),
372
378
  }),
373
379
  },
374
380
  responses: {
@@ -416,6 +422,7 @@ const adminWalletBalanceRoute = createRoute({
416
422
  .object({
417
423
  balance: z.string(),
418
424
  symbol: z.string(),
425
+ usd: z.number().nullable().optional(),
419
426
  })
420
427
  .nullable(),
421
428
  tokens: z.array(z.object({
@@ -516,6 +523,105 @@ const telegramUserDeleteRoute = createRoute({
516
523
  },
517
524
  });
518
525
  // ---------------------------------------------------------------------------
526
+ // Cross-wallet admin transaction route definitions
527
+ // ---------------------------------------------------------------------------
528
+ const adminTransactionsQuerySchema = z.object({
529
+ offset: z.coerce.number().int().min(0).default(0).optional(),
530
+ limit: z.coerce.number().int().min(1).max(100).default(20).optional(),
531
+ wallet_id: z.string().uuid().optional(),
532
+ type: z.string().optional(),
533
+ status: z.string().optional(),
534
+ network: z.string().optional(),
535
+ since: z.coerce.number().optional(),
536
+ until: z.coerce.number().optional(),
537
+ search: z.string().optional(),
538
+ });
539
+ const adminTransactionsRoute = createRoute({
540
+ method: 'get',
541
+ path: '/admin/transactions',
542
+ tags: ['Admin'],
543
+ summary: 'List cross-wallet transactions with filters and pagination',
544
+ request: {
545
+ query: adminTransactionsQuerySchema,
546
+ },
547
+ responses: {
548
+ 200: {
549
+ description: 'Paginated cross-wallet transaction list',
550
+ content: {
551
+ 'application/json': {
552
+ schema: z.object({
553
+ items: z.array(z.object({
554
+ id: z.string(),
555
+ walletId: z.string(),
556
+ walletName: z.string().nullable(),
557
+ type: z.string(),
558
+ status: z.string(),
559
+ tier: z.string().nullable(),
560
+ toAddress: z.string().nullable(),
561
+ amount: z.string().nullable(),
562
+ amountUsd: z.number().nullable(),
563
+ network: z.string().nullable(),
564
+ txHash: z.string().nullable(),
565
+ chain: z.string(),
566
+ createdAt: z.number().nullable(),
567
+ })),
568
+ total: z.number().int(),
569
+ offset: z.number().int(),
570
+ limit: z.number().int(),
571
+ }),
572
+ },
573
+ },
574
+ },
575
+ },
576
+ });
577
+ const adminIncomingQuerySchema = z.object({
578
+ offset: z.coerce.number().int().min(0).default(0).optional(),
579
+ limit: z.coerce.number().int().min(1).max(100).default(20).optional(),
580
+ wallet_id: z.string().uuid().optional(),
581
+ chain: z.string().optional(),
582
+ status: z.string().optional(),
583
+ suspicious: z.enum(['true', 'false']).optional(),
584
+ });
585
+ const adminIncomingRoute = createRoute({
586
+ method: 'get',
587
+ path: '/admin/incoming',
588
+ tags: ['Admin'],
589
+ summary: 'List cross-wallet incoming transactions with filters and pagination',
590
+ request: {
591
+ query: adminIncomingQuerySchema,
592
+ },
593
+ responses: {
594
+ 200: {
595
+ description: 'Paginated cross-wallet incoming transaction list',
596
+ content: {
597
+ 'application/json': {
598
+ schema: z.object({
599
+ items: z.array(z.object({
600
+ id: z.string(),
601
+ txHash: z.string(),
602
+ walletId: z.string(),
603
+ walletName: z.string().nullable(),
604
+ fromAddress: z.string(),
605
+ amount: z.string(),
606
+ tokenAddress: z.string().nullable(),
607
+ chain: z.string(),
608
+ network: z.string(),
609
+ status: z.string(),
610
+ blockNumber: z.number().nullable(),
611
+ detectedAt: z.number().nullable(),
612
+ confirmedAt: z.number().nullable(),
613
+ suspicious: z.boolean(),
614
+ })),
615
+ total: z.number().int(),
616
+ offset: z.number().int(),
617
+ limit: z.number().int(),
618
+ }),
619
+ },
620
+ },
621
+ },
622
+ },
623
+ });
624
+ // ---------------------------------------------------------------------------
519
625
  // Route factory
520
626
  // ---------------------------------------------------------------------------
521
627
  /**
@@ -543,6 +649,8 @@ const telegramUserDeleteRoute = createRoute({
543
649
  * GET /admin/telegram-users - List Telegram bot users (masterAuth)
544
650
  * PUT /admin/telegram-users/:chatId - Update Telegram user role (masterAuth)
545
651
  * DELETE /admin/telegram-users/:chatId - Delete Telegram user (masterAuth)
652
+ * GET /admin/transactions - Cross-wallet transaction list (masterAuth)
653
+ * GET /admin/incoming - Cross-wallet incoming tx list (masterAuth)
546
654
  */
547
655
  export function adminRoutes(deps) {
548
656
  const router = new OpenAPIHono({ defaultHook: openApiValidationHook });
@@ -599,6 +707,7 @@ export function adminRoutes(deps) {
599
707
  amount: transactions.amount,
600
708
  amountUsd: transactions.amountUsd,
601
709
  network: transactions.network,
710
+ txHash: transactions.txHash,
602
711
  createdAt: transactions.createdAt,
603
712
  })
604
713
  .from(transactions)
@@ -616,6 +725,7 @@ export function adminRoutes(deps) {
616
725
  amount: tx.amount ?? null,
617
726
  amountUsd: tx.amountUsd ?? null,
618
727
  network: tx.network ?? null,
728
+ txHash: tx.txHash ?? null,
619
729
  createdAt: tx.createdAt instanceof Date
620
730
  ? Math.floor(tx.createdAt.getTime() / 1000)
621
731
  : (typeof tx.createdAt === 'number' ? tx.createdAt : null),
@@ -890,6 +1000,21 @@ export function adminRoutes(deps) {
890
1000
  if (query.status) {
891
1001
  conditions.push(eq(notificationLogs.status, query.status));
892
1002
  }
1003
+ if (query.eventType) {
1004
+ conditions.push(eq(notificationLogs.eventType, query.eventType));
1005
+ }
1006
+ if (query.since) {
1007
+ const sinceTs = parseInt(query.since, 10);
1008
+ if (!isNaN(sinceTs)) {
1009
+ conditions.push(gte(notificationLogs.createdAt, new Date(sinceTs * 1000)));
1010
+ }
1011
+ }
1012
+ if (query.until) {
1013
+ const untilTs = parseInt(query.until, 10);
1014
+ if (!isNaN(untilTs)) {
1015
+ conditions.push(lte(notificationLogs.createdAt, new Date(untilTs * 1000)));
1016
+ }
1017
+ }
893
1018
  const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
894
1019
  // Query total count
895
1020
  const totalResult = deps.db
@@ -1149,6 +1274,7 @@ export function adminRoutes(deps) {
1149
1274
  const { id } = c.req.valid('param');
1150
1275
  const query = c.req.valid('query');
1151
1276
  const limit = query.limit ?? 20;
1277
+ const offset = query.offset ?? 0;
1152
1278
  // Verify wallet exists
1153
1279
  const wallet = deps.db.select().from(wallets).where(eq(wallets.id, id)).get();
1154
1280
  if (!wallet) {
@@ -1161,6 +1287,7 @@ export function adminRoutes(deps) {
1161
1287
  .where(eq(transactions.walletId, id))
1162
1288
  .orderBy(desc(transactions.createdAt))
1163
1289
  .limit(limit)
1290
+ .offset(offset)
1164
1291
  .all();
1165
1292
  // Total count
1166
1293
  const totalResult = deps.db
@@ -1211,6 +1338,15 @@ export function adminRoutes(deps) {
1211
1338
  const adapter = await deps.adapterPool.resolve(chain, network, rpcUrl);
1212
1339
  const balanceInfo = await adapter.getBalance(wallet.publicKey);
1213
1340
  const nativeBalance = (Number(balanceInfo.balance) / 10 ** balanceInfo.decimals).toString();
1341
+ // Resolve USD price for native token if price oracle is available
1342
+ let nativeUsd = null;
1343
+ if (deps.priceOracle) {
1344
+ try {
1345
+ const priceInfo = await deps.priceOracle.getNativePrice(chain);
1346
+ nativeUsd = Number(nativeBalance) * priceInfo.usdPrice;
1347
+ }
1348
+ catch { /* non-critical: USD price unavailable */ }
1349
+ }
1214
1350
  const assets = await adapter.getAssets(wallet.publicKey);
1215
1351
  const tokens = assets
1216
1352
  .filter((a) => !a.isNative)
@@ -1222,7 +1358,7 @@ export function adminRoutes(deps) {
1222
1358
  return {
1223
1359
  network,
1224
1360
  isDefault: network === defaultNetwork,
1225
- native: { balance: nativeBalance, symbol: balanceInfo.symbol },
1361
+ native: { balance: nativeBalance, symbol: balanceInfo.symbol, usd: nativeUsd },
1226
1362
  tokens,
1227
1363
  };
1228
1364
  }));
@@ -1291,6 +1427,164 @@ export function adminRoutes(deps) {
1291
1427
  return c.json({ success: true }, 200);
1292
1428
  });
1293
1429
  // ---------------------------------------------------------------------------
1430
+ // GET /admin/transactions (cross-wallet transaction list)
1431
+ // ---------------------------------------------------------------------------
1432
+ router.openapi(adminTransactionsRoute, async (c) => {
1433
+ const query = c.req.valid('query');
1434
+ const offset = query.offset ?? 0;
1435
+ const limit = query.limit ?? 20;
1436
+ // Build WHERE conditions
1437
+ const conditions = [];
1438
+ if (query.wallet_id) {
1439
+ conditions.push(eq(transactions.walletId, query.wallet_id));
1440
+ }
1441
+ if (query.type) {
1442
+ conditions.push(eq(transactions.type, query.type));
1443
+ }
1444
+ if (query.status) {
1445
+ conditions.push(eq(transactions.status, query.status));
1446
+ }
1447
+ if (query.network) {
1448
+ conditions.push(eq(transactions.network, query.network));
1449
+ }
1450
+ if (query.since !== undefined) {
1451
+ conditions.push(sql `${transactions.createdAt} >= ${query.since}`);
1452
+ }
1453
+ if (query.until !== undefined) {
1454
+ conditions.push(sql `${transactions.createdAt} <= ${query.until}`);
1455
+ }
1456
+ if (query.search) {
1457
+ const pattern = `%${query.search}%`;
1458
+ conditions.push(sql `(${transactions.txHash} LIKE ${pattern} OR ${transactions.toAddress} LIKE ${pattern})`);
1459
+ }
1460
+ const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
1461
+ // Count total
1462
+ const totalResult = deps.db
1463
+ .select({ count: drizzleCount() })
1464
+ .from(transactions)
1465
+ .where(whereClause)
1466
+ .get();
1467
+ const total = totalResult?.count ?? 0;
1468
+ // Query with JOIN for walletName
1469
+ const rows = deps.db
1470
+ .select({
1471
+ id: transactions.id,
1472
+ walletId: transactions.walletId,
1473
+ walletName: wallets.name,
1474
+ type: transactions.type,
1475
+ status: transactions.status,
1476
+ tier: transactions.tier,
1477
+ toAddress: transactions.toAddress,
1478
+ amount: transactions.amount,
1479
+ amountUsd: transactions.amountUsd,
1480
+ network: transactions.network,
1481
+ txHash: transactions.txHash,
1482
+ chain: transactions.chain,
1483
+ createdAt: transactions.createdAt,
1484
+ })
1485
+ .from(transactions)
1486
+ .leftJoin(wallets, eq(transactions.walletId, wallets.id))
1487
+ .where(whereClause)
1488
+ .orderBy(desc(transactions.createdAt))
1489
+ .offset(offset)
1490
+ .limit(limit)
1491
+ .all();
1492
+ const items = rows.map((row) => ({
1493
+ id: row.id,
1494
+ walletId: row.walletId,
1495
+ walletName: row.walletName ?? null,
1496
+ type: row.type,
1497
+ status: row.status,
1498
+ tier: row.tier ?? null,
1499
+ toAddress: row.toAddress ?? null,
1500
+ amount: row.amount ?? null,
1501
+ amountUsd: row.amountUsd ?? null,
1502
+ network: row.network ?? null,
1503
+ txHash: row.txHash ?? null,
1504
+ chain: row.chain,
1505
+ createdAt: row.createdAt instanceof Date
1506
+ ? Math.floor(row.createdAt.getTime() / 1000)
1507
+ : (typeof row.createdAt === 'number' ? row.createdAt : null),
1508
+ }));
1509
+ return c.json({ items, total, offset, limit }, 200);
1510
+ });
1511
+ // ---------------------------------------------------------------------------
1512
+ // GET /admin/incoming (cross-wallet incoming transaction list)
1513
+ // ---------------------------------------------------------------------------
1514
+ router.openapi(adminIncomingRoute, async (c) => {
1515
+ const query = c.req.valid('query');
1516
+ const offset = query.offset ?? 0;
1517
+ const limit = query.limit ?? 20;
1518
+ // Build WHERE conditions (no default status filter -- admin sees all)
1519
+ const conditions = [];
1520
+ if (query.wallet_id) {
1521
+ conditions.push(eq(incomingTransactions.walletId, query.wallet_id));
1522
+ }
1523
+ if (query.chain) {
1524
+ conditions.push(eq(incomingTransactions.chain, query.chain));
1525
+ }
1526
+ if (query.status) {
1527
+ conditions.push(eq(incomingTransactions.status, query.status));
1528
+ }
1529
+ if (query.suspicious !== undefined) {
1530
+ conditions.push(eq(incomingTransactions.isSuspicious, query.suspicious === 'true'));
1531
+ }
1532
+ const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
1533
+ // Count total
1534
+ const totalResult = deps.db
1535
+ .select({ count: drizzleCount() })
1536
+ .from(incomingTransactions)
1537
+ .where(whereClause)
1538
+ .get();
1539
+ const total = totalResult?.count ?? 0;
1540
+ // Query with JOIN for walletName
1541
+ const rows = deps.db
1542
+ .select({
1543
+ id: incomingTransactions.id,
1544
+ txHash: incomingTransactions.txHash,
1545
+ walletId: incomingTransactions.walletId,
1546
+ walletName: wallets.name,
1547
+ fromAddress: incomingTransactions.fromAddress,
1548
+ amount: incomingTransactions.amount,
1549
+ tokenAddress: incomingTransactions.tokenAddress,
1550
+ chain: incomingTransactions.chain,
1551
+ network: incomingTransactions.network,
1552
+ status: incomingTransactions.status,
1553
+ blockNumber: incomingTransactions.blockNumber,
1554
+ detectedAt: incomingTransactions.detectedAt,
1555
+ confirmedAt: incomingTransactions.confirmedAt,
1556
+ isSuspicious: incomingTransactions.isSuspicious,
1557
+ })
1558
+ .from(incomingTransactions)
1559
+ .leftJoin(wallets, eq(incomingTransactions.walletId, wallets.id))
1560
+ .where(whereClause)
1561
+ .orderBy(desc(incomingTransactions.detectedAt))
1562
+ .offset(offset)
1563
+ .limit(limit)
1564
+ .all();
1565
+ const items = rows.map((row) => ({
1566
+ id: row.id,
1567
+ txHash: row.txHash,
1568
+ walletId: row.walletId,
1569
+ walletName: row.walletName ?? null,
1570
+ fromAddress: row.fromAddress,
1571
+ amount: row.amount,
1572
+ tokenAddress: row.tokenAddress ?? null,
1573
+ chain: row.chain,
1574
+ network: row.network,
1575
+ status: row.status,
1576
+ blockNumber: row.blockNumber ?? null,
1577
+ detectedAt: row.detectedAt instanceof Date
1578
+ ? Math.floor(row.detectedAt.getTime() / 1000)
1579
+ : (typeof row.detectedAt === 'number' ? row.detectedAt : null),
1580
+ confirmedAt: row.confirmedAt instanceof Date
1581
+ ? Math.floor(row.confirmedAt.getTime() / 1000)
1582
+ : (typeof row.confirmedAt === 'number' ? row.confirmedAt : null),
1583
+ suspicious: row.isSuspicious ?? false,
1584
+ }));
1585
+ return c.json({ items, total, offset, limit }, 200);
1586
+ });
1587
+ // ---------------------------------------------------------------------------
1294
1588
  // POST /admin/agent-prompt — Generate agent connection prompt
1295
1589
  // ---------------------------------------------------------------------------
1296
1590
  const agentPromptRoute = createRoute({