@openora/core 0.4.1-canary.91 → 0.4.1-canary.93

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 (157) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/admin-console/contract/index.d.ts +12 -0
  3. package/dist/admin-console/contract/index.d.ts.map +1 -1
  4. package/dist/admin-console/plugin.d.ts +18 -1
  5. package/dist/admin-console/plugin.d.ts.map +1 -1
  6. package/dist/admin-console/router/index.d.ts +6 -0
  7. package/dist/admin-console/router/index.d.ts.map +1 -1
  8. package/dist/analytics/plugin.d.ts +18 -1
  9. package/dist/analytics/plugin.d.ts.map +1 -1
  10. package/dist/audit/plugin.d.ts +18 -1
  11. package/dist/audit/plugin.d.ts.map +1 -1
  12. package/dist/casino/gaming/plugin.d.ts +18 -1
  13. package/dist/casino/gaming/plugin.d.ts.map +1 -1
  14. package/dist/casino/lobby/plugin.d.ts +18 -1
  15. package/dist/casino/lobby/plugin.d.ts.map +1 -1
  16. package/dist/cms/plugin.d.ts +18 -1
  17. package/dist/cms/plugin.d.ts.map +1 -1
  18. package/dist/compliance/plugin.d.ts +18 -1
  19. package/dist/compliance/plugin.d.ts.map +1 -1
  20. package/dist/contracts/adapters/audit.d.ts +1 -1
  21. package/dist/contracts/adapters/audit.d.ts.map +1 -1
  22. package/dist/contracts/adapters/audit.js.map +1 -1
  23. package/dist/contracts/adapters/index.d.ts +4 -2
  24. package/dist/contracts/adapters/index.d.ts.map +1 -1
  25. package/dist/contracts/adapters/index.js +2 -1
  26. package/dist/contracts/adapters/index.js.map +1 -1
  27. package/dist/contracts/adapters/payment.d.ts +99 -0
  28. package/dist/contracts/adapters/payment.d.ts.map +1 -1
  29. package/dist/contracts/adapters/payment.js +6 -0
  30. package/dist/contracts/adapters/payment.js.map +1 -1
  31. package/dist/contracts/adapters/rate-limit.d.ts +1 -0
  32. package/dist/contracts/adapters/rate-limit.d.ts.map +1 -1
  33. package/dist/contracts/adapters/rate-limit.js +1 -0
  34. package/dist/contracts/adapters/rate-limit.js.map +1 -1
  35. package/dist/contracts/adapters/wallet-asset-catalog.d.ts +34 -0
  36. package/dist/contracts/adapters/wallet-asset-catalog.d.ts.map +1 -0
  37. package/dist/contracts/adapters/wallet-asset-catalog.js +3 -0
  38. package/dist/contracts/adapters/wallet-asset-catalog.js.map +1 -0
  39. package/dist/contracts/schemas/__tests__/common-schemas.test.js +2 -1
  40. package/dist/contracts/schemas/__tests__/common-schemas.test.js.map +1 -1
  41. package/dist/contracts/schemas/common.d.ts.map +1 -1
  42. package/dist/contracts/schemas/common.js +7 -4
  43. package/dist/contracts/schemas/common.js.map +1 -1
  44. package/dist/contracts/schemas/events.d.ts +15 -0
  45. package/dist/contracts/schemas/events.d.ts.map +1 -1
  46. package/dist/contracts/schemas/events.js +16 -2
  47. package/dist/contracts/schemas/events.js.map +1 -1
  48. package/dist/contracts/schemas/platform-config.d.ts +45 -0
  49. package/dist/contracts/schemas/platform-config.d.ts.map +1 -1
  50. package/dist/contracts/schemas/platform-config.js +47 -0
  51. package/dist/contracts/schemas/platform-config.js.map +1 -1
  52. package/dist/contracts/schemas/wallet-tx.d.ts +38 -1
  53. package/dist/contracts/schemas/wallet-tx.d.ts.map +1 -1
  54. package/dist/contracts/schemas/wallet-tx.js +32 -3
  55. package/dist/contracts/schemas/wallet-tx.js.map +1 -1
  56. package/dist/engagement/chat/plugin.d.ts +18 -1
  57. package/dist/engagement/chat/plugin.d.ts.map +1 -1
  58. package/dist/engagement/chat-commands/plugin.d.ts +18 -1
  59. package/dist/engagement/chat-commands/plugin.d.ts.map +1 -1
  60. package/dist/engagement/notifications/plugin.d.ts +18 -1
  61. package/dist/engagement/notifications/plugin.d.ts.map +1 -1
  62. package/dist/engagement/social/plugin.d.ts +18 -1
  63. package/dist/engagement/social/plugin.d.ts.map +1 -1
  64. package/dist/engagement/social-transfers/plugin.d.ts +18 -1
  65. package/dist/engagement/social-transfers/plugin.d.ts.map +1 -1
  66. package/dist/iam/plugin.d.ts +18 -1
  67. package/dist/iam/plugin.d.ts.map +1 -1
  68. package/dist/iam/service/iam.service.d.ts +1 -1
  69. package/dist/pam/identity/plugin.d.ts +18 -1
  70. package/dist/pam/identity/plugin.d.ts.map +1 -1
  71. package/dist/pam/player-management/plugin.d.ts +18 -1
  72. package/dist/pam/player-management/plugin.d.ts.map +1 -1
  73. package/dist/pam/player-note/plugin.d.ts +18 -1
  74. package/dist/pam/player-note/plugin.d.ts.map +1 -1
  75. package/dist/pam/profile/plugin.d.ts +18 -1
  76. package/dist/pam/profile/plugin.d.ts.map +1 -1
  77. package/dist/pam/tag/plugin.d.ts +18 -1
  78. package/dist/pam/tag/plugin.d.ts.map +1 -1
  79. package/dist/server/auth/permissions.d.ts +36 -0
  80. package/dist/server/auth/permissions.d.ts.map +1 -1
  81. package/dist/server/auth/permissions.js +6 -0
  82. package/dist/server/auth/permissions.js.map +1 -1
  83. package/dist/server/db/__tests__/query-helpers.test.js +38 -1
  84. package/dist/server/db/__tests__/query-helpers.test.js.map +1 -1
  85. package/dist/server/db/index.d.ts +1 -1
  86. package/dist/server/db/index.d.ts.map +1 -1
  87. package/dist/server/db/index.js +1 -1
  88. package/dist/server/db/index.js.map +1 -1
  89. package/dist/server/db/query-helpers.d.ts +4 -0
  90. package/dist/server/db/query-helpers.d.ts.map +1 -1
  91. package/dist/server/db/query-helpers.js +31 -0
  92. package/dist/server/db/query-helpers.js.map +1 -1
  93. package/dist/server/runtime/core-token-catalog.d.ts +18 -1
  94. package/dist/server/runtime/core-token-catalog.d.ts.map +1 -1
  95. package/dist/server/runtime/core-token-catalog.js +3 -1
  96. package/dist/server/runtime/core-token-catalog.js.map +1 -1
  97. package/dist/testing/mock.d.ts +12 -1
  98. package/dist/testing/mock.d.ts.map +1 -1
  99. package/dist/testing/mock.js +16 -0
  100. package/dist/testing/mock.js.map +1 -1
  101. package/dist/wallet/__tests__/settlement-network.test.d.ts +2 -0
  102. package/dist/wallet/__tests__/settlement-network.test.d.ts.map +1 -0
  103. package/dist/wallet/__tests__/settlement-network.test.js +54 -0
  104. package/dist/wallet/__tests__/settlement-network.test.js.map +1 -0
  105. package/dist/wallet/__tests__/wallet-asset.router.int.test.d.ts +2 -0
  106. package/dist/wallet/__tests__/wallet-asset.router.int.test.d.ts.map +1 -0
  107. package/dist/wallet/__tests__/wallet-asset.router.int.test.js +234 -0
  108. package/dist/wallet/__tests__/wallet-asset.router.int.test.js.map +1 -0
  109. package/dist/wallet/__tests__/wallet-auto-rule.router.int.test.js +5 -3
  110. package/dist/wallet/__tests__/wallet-auto-rule.router.int.test.js.map +1 -1
  111. package/dist/wallet/__tests__/wallet-auto-withdrawal-config.router.int.test.js +4 -2
  112. package/dist/wallet/__tests__/wallet-auto-withdrawal-config.router.int.test.js.map +1 -1
  113. package/dist/wallet/__tests__/wallet-auto-withdrawal.int.test.js +6 -3
  114. package/dist/wallet/__tests__/wallet-auto-withdrawal.int.test.js.map +1 -1
  115. package/dist/wallet/__tests__/wallet-custody-stubs.router.test.d.ts +2 -0
  116. package/dist/wallet/__tests__/wallet-custody-stubs.router.test.d.ts.map +1 -0
  117. package/dist/wallet/__tests__/wallet-custody-stubs.router.test.js +80 -0
  118. package/dist/wallet/__tests__/wallet-custody-stubs.router.test.js.map +1 -0
  119. package/dist/wallet/__tests__/wallet-kyc-gate.int.test.js +2 -1
  120. package/dist/wallet/__tests__/wallet-kyc-gate.int.test.js.map +1 -1
  121. package/dist/wallet/__tests__/wallet-webhook.router.int.test.js +119 -4
  122. package/dist/wallet/__tests__/wallet-webhook.router.int.test.js.map +1 -1
  123. package/dist/wallet/__tests__/wallet.rate-limit.int.test.js +2 -1
  124. package/dist/wallet/__tests__/wallet.rate-limit.int.test.js.map +1 -1
  125. package/dist/wallet/__tests__/wallet.router.int.test.js +23 -3
  126. package/dist/wallet/__tests__/wallet.router.int.test.js.map +1 -1
  127. package/dist/wallet/__tests__/wallet.service.int.test.js +265 -12
  128. package/dist/wallet/__tests__/wallet.service.int.test.js.map +1 -1
  129. package/dist/wallet/adapters/wallet-asset-catalog.service.d.ts +14 -0
  130. package/dist/wallet/adapters/wallet-asset-catalog.service.d.ts.map +1 -0
  131. package/dist/wallet/adapters/wallet-asset-catalog.service.js +37 -0
  132. package/dist/wallet/adapters/wallet-asset-catalog.service.js.map +1 -0
  133. package/dist/wallet/admin-reporting.d.ts +2 -2
  134. package/dist/wallet/contract/index.d.ts +428 -4
  135. package/dist/wallet/contract/index.d.ts.map +1 -1
  136. package/dist/wallet/contract/index.js +203 -5
  137. package/dist/wallet/contract/index.js.map +1 -1
  138. package/dist/wallet/drizzle/migrations/0010_worthless_quasimodo.sql +84 -0
  139. package/dist/wallet/drizzle/migrations/meta/0010_snapshot.json +1565 -0
  140. package/dist/wallet/drizzle/migrations/meta/_journal.json +7 -0
  141. package/dist/wallet/plugin.d.ts +18 -1
  142. package/dist/wallet/plugin.d.ts.map +1 -1
  143. package/dist/wallet/plugin.js +19 -2
  144. package/dist/wallet/plugin.js.map +1 -1
  145. package/dist/wallet/router/index.d.ts +235 -3
  146. package/dist/wallet/router/index.d.ts.map +1 -1
  147. package/dist/wallet/router/index.js +117 -20
  148. package/dist/wallet/router/index.js.map +1 -1
  149. package/dist/wallet/schema/index.d.ts +984 -3
  150. package/dist/wallet/schema/index.d.ts.map +1 -1
  151. package/dist/wallet/schema/index.js +141 -2
  152. package/dist/wallet/schema/index.js.map +1 -1
  153. package/dist/wallet/service/wallet.service.d.ts +70 -6
  154. package/dist/wallet/service/wallet.service.d.ts.map +1 -1
  155. package/dist/wallet/service/wallet.service.js +379 -16
  156. package/dist/wallet/service/wallet.service.js.map +1 -1
  157. package/package.json +1 -1
@@ -1,9 +1,9 @@
1
- import { describe, it, expect, vi, beforeAll, afterAll, beforeEach } from 'vitest';
1
+ import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
2
2
  import { findOneOrThrow } from '@openora/core/server';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { call, ORPCError } from '@orpc/server';
5
5
  import { createTestDb } from '@openora/core/testing';
6
- import { mock, makeEventBus, testContext, makeAuditWriter, makeAdminGuard, makeIdentityReader, } from '../../testing/mock.js';
6
+ import { mock, makeEventBus, testContext, makeAuditWriter, makeAdminGuard, makeIdentityReader, makePaymentProviderRegistry, } from '../../testing/mock.js';
7
7
  import { migrate } from '../migrate.js';
8
8
  import { wallet, walletTransaction } from '../schema/index.js';
9
9
  import { createWalletRouter } from '../router/index.js';
@@ -26,15 +26,20 @@ function realWalletService() {
26
26
  drizzle: db.drizzle,
27
27
  events: makeEventBus(),
28
28
  payment: mock({}),
29
+ paymentProviders: makePaymentProviderRegistry(),
29
30
  audit: makeAuditWriter(),
30
31
  identityReader: makeIdentityReader(),
31
32
  });
32
33
  }
33
34
  function routerWith(adminGuard) {
34
- return createWalletRouter(realWalletService(), adminGuard, makeAuditWriter(), mock({}), mock({ verify: vi.fn().mockReturnValue(false) }));
35
+ return createWalletRouter(realWalletService(), adminGuard, makeAuditWriter(), makePaymentProviderRegistry());
35
36
  }
36
37
  const transactionDenyingGuard = () => makeAdminGuard({ deny: ['transaction'], caller: { userId: 'caller-1', role: 'support' } });
37
38
  const allowingGuard = () => makeAdminGuard({ caller: { userId: 'caller-1' } });
39
+ const adjustmentDenyingGuard = () => makeAdminGuard({
40
+ deny: ['player:adjust-balance'],
41
+ caller: { userId: 'caller-1', role: 'admin' },
42
+ });
38
43
  async function seedLedger(userId, amounts) {
39
44
  const row = findOneOrThrow(await db.drizzle.db.insert(wallet).values({ userId, currency: 'USD' }).returning(), new Error('seedLedger: query returned no row'));
40
45
  for (const amount of amounts) {
@@ -70,4 +75,19 @@ describe('wallet router listPlayerTransactions authz', () => {
70
75
  expect(result.items[0]?.amount).toBe('10.000000000000000000');
71
76
  });
72
77
  });
78
+ describe('wallet router manualAdjustment authz', () => {
79
+ it('requires the player:adjust-balance grant and changes nothing on denial', async () => {
80
+ const userId = randomUUID();
81
+ await expect(call(routerWith(adjustmentDenyingGuard()).manualAdjustment, {
82
+ userId,
83
+ direction: 'credit',
84
+ amount: '5',
85
+ currency: 'USD',
86
+ reason: 'compensation',
87
+ idempotencyKey: randomUUID(),
88
+ }, { context: CTX })).rejects.toMatchObject({ code: 'FORBIDDEN' });
89
+ expect(await db.drizzle.db.select().from(wallet)).toHaveLength(0);
90
+ expect(await db.drizzle.db.select().from(walletTransaction)).toHaveLength(0);
91
+ });
92
+ });
73
93
  //# sourceMappingURL=wallet.router.int.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"wallet.router.int.test.js","sourceRoot":"","sources":["../../../src/wallet/__tests__/wallet.router.int.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG/C,OAAO,EAAE,YAAY,EAAe,MAAM,uBAAuB,CAAC;AAClE,OAAO,EACL,IAAI,EACJ,YAAY,EACZ,WAAW,EACX,eAAe,EACf,cAAc,EACd,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE7D,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC;AAC1B,MAAM,OAAO,GAAG,sCAAsC,CAAC;AAEvD,IAAI,EAAU,CAAC;AAEf,SAAS,CAAC,KAAK,IAAI,EAAE;IACnB,EAAE,GAAG,MAAM,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;AACrC,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,KAAK,IAAI,EAAE;IAClB,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,UAAU,CAAC,KAAK,IAAI,EAAE;IACpB,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC9C,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC,CAAC,CAAC;AAEH,SAAS,iBAAiB;IACxB,OAAO,IAAI,aAAa,CAAC;QACvB,OAAO,EAAE,EAAE,CAAC,OAAO;QACnB,MAAM,EAAE,YAAY,EAAE;QACtB,OAAO,EAAE,IAAI,CAAiB,EAAE,CAAC;QACjC,KAAK,EAAE,eAAe,EAAE;QACxB,cAAc,EAAE,kBAAkB,EAAE;KACrC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,UAAU,CAAC,UAAsB;IACxC,OAAO,kBAAkB,CACvB,iBAAiB,EAAE,EACnB,UAAU,EACV,eAAe,EAAE,EACjB,IAAI,CAAiB,EAAE,CAAC,EACxB,IAAI,CAAyB,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,CACzE,CAAC;AACJ,CAAC;AAED,MAAM,uBAAuB,GAAG,GAAG,EAAE,CACnC,cAAc,CAAC,EAAE,IAAI,EAAE,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;AAE7F,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC,cAAc,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;AAE/E,KAAK,UAAU,UAAU,CAAC,MAAc,EAAE,OAAiB;IACzD,MAAM,GAAG,GAAG,cAAc,CACxB,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,SAAS,EAAE,EAClF,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAC/C,CAAC;IACF,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC;YACnD,QAAQ,EAAE,GAAG,CAAC,EAAE;YAChB,IAAI,EAAE,SAAS;YACf,MAAM;YACN,QAAQ,EAAE,KAAK;YACf,MAAM,EAAE,WAAW;YACnB,IAAI,EAAE,MAAM;SACb,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,QAAQ,CAAC,4CAA4C,EAAE,GAAG,EAAE;IAC1D,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QAErC,MAAM,MAAM,CACV,IAAI,CACF,UAAU,CAAC,uBAAuB,EAAE,CAAC,CAAC,sBAAsB,EAC5D,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EACvC,EAAE,OAAO,EAAE,GAAG,EAAE,CACjB,CACF,CAAC,OAAO,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;QAC/E,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QAE9C,MAAM,MAAM,GAAG,MAAM,IAAI,CACvB,UAAU,CAAC,aAAa,EAAE,CAAC,CAAC,sBAAsB,EAClD,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EACvC,EAAE,OAAO,EAAE,GAAG,EAAE,CACjB,CAAC;QAEF,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC;YACvD,uBAAuB;YACvB,uBAAuB;SACxB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QACrC,MAAM,UAAU,CAAC,UAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QAE3C,MAAM,MAAM,GAAG,MAAM,IAAI,CACvB,UAAU,CAAC,aAAa,EAAE,CAAC,CAAC,sBAAsB,EAClD,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EACvC,EAAE,OAAO,EAAE,GAAG,EAAE,CACjB,CAAC;QAEF,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"wallet.router.int.test.js","sourceRoot":"","sources":["../../../src/wallet/__tests__/wallet.router.int.test.ts"],"names":[],"mappings":"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"}
@@ -2,12 +2,13 @@ import { describe, it, expect, vi, beforeAll, afterAll, beforeEach } from 'vites
2
2
  import { findOneOrThrow } from '@openora/core/server';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { and, eq, sql } from 'drizzle-orm';
5
+ import { DEFAULT_PAYMENT_PROVIDER, } from '@openora/core/contracts';
5
6
  import { createTestDb } from '@openora/core/testing';
6
7
  import { migrate as migrateProfile } from '@openora/core/pam/migrate/profile';
7
- import { mock, makeEventBus, makeIdentityReader, NO_CLIENT_META, makeAuditWriter, } from '../../testing/mock.js';
8
+ import { mock, makeEventBus, makeIdentityReader, NO_CLIENT_META, makeAuditWriter, makePaymentProviderRegistry, } from '../../testing/mock.js';
8
9
  import { migrate } from '../migrate.js';
9
- import { wallet, walletBalance, walletTransaction, walletDepositAddress } from '../schema/index.js';
10
- import { WalletService, WalletNotFoundError, WithdrawalNotFoundError, WithdrawalNotPendingError, InsufficientBalanceError, AmbiguousDepositAddressError, CurrencyMismatchError, IdempotencyKeyReuseError, DepositAddressUnsupportedError, DestinationAddressRequiredError, } from '../service/wallet.service.js';
10
+ import { wallet, walletBalance, walletTransaction, walletDepositAddress, walletAsset, } from '../schema/index.js';
11
+ import { WalletService, WalletNotFoundError, WithdrawalNotFoundError, WithdrawalNotPendingError, InsufficientBalanceError, AmbiguousDepositAddressError, CurrencyMismatchError, IdempotencyKeyReuseError, DepositAddressUnsupportedError, DestinationAddressRequiredError, AmbiguousNetworkError, BelowMinimumWithdrawalError, WithdrawalDisabledError, } from '../service/wallet.service.js';
11
12
  import { WithdrawalQueueItemSchema } from '../contract/index.js';
12
13
  let db;
13
14
  function makePsp() {
@@ -25,12 +26,18 @@ function makeService(overrides = {}) {
25
26
  drizzle: db.drizzle,
26
27
  events: events,
27
28
  payment: mock(psp),
29
+ paymentProviders: makePaymentProviderRegistry(),
28
30
  audit,
29
31
  identityReader: makeIdentityReader(),
30
32
  ...overrides,
31
33
  });
32
34
  return { svc, events, psp, audit };
33
35
  }
36
+ function playerIdentityReader() {
37
+ const identityReader = makeIdentityReader();
38
+ vi.mocked(identityReader.getPlayerIdByUserId).mockResolvedValue(randomUUID());
39
+ return identityReader;
40
+ }
34
41
  const queueService = () => makeService().svc;
35
42
  function makeDirectory(summaries) {
36
43
  return mock({
@@ -90,7 +97,7 @@ afterAll(async () => {
90
97
  await db.drop();
91
98
  });
92
99
  beforeEach(async () => {
93
- await db.drizzle.db.execute(sql `TRUNCATE ${walletTransaction}, ${walletDepositAddress}, ${wallet} RESTART IDENTITY CASCADE`);
100
+ await db.drizzle.db.execute(sql `TRUNCATE ${walletTransaction}, ${walletDepositAddress}, ${walletAsset}, ${wallet} RESTART IDENTITY CASCADE`);
94
101
  });
95
102
  describe('WalletService.deposit (real PG)', () => {
96
103
  it('creates the wallet on a first deposit, credits it, and writes one completed fiat ledger row', async () => {
@@ -121,7 +128,7 @@ describe('WalletService.deposit (real PG)', () => {
121
128
  it('records the crypto rail for a crypto currency', async () => {
122
129
  const { svc } = makeService();
123
130
  const w = await seedWallet({ currency: 'BTC' });
124
- await svc.deposit({ userId: w.userId, amount: '1', currency: 'BTC', provider: 'fireblocks' });
131
+ await svc.deposit({ userId: w.userId, amount: '1', currency: 'BTC', provider: 'custody' });
125
132
  const rows = await txRows(w.id);
126
133
  expect(rows[0]).toMatchObject({ type: 'deposit', rail: 'crypto' });
127
134
  });
@@ -151,6 +158,96 @@ describe('WalletService.deposit (real PG)', () => {
151
158
  expect(await balanceOf(w.userId, 'EUR')).toBe(10);
152
159
  });
153
160
  });
161
+ describe('WalletService.manualAdjust (real PG)', () => {
162
+ it('credits, debits, writes an immutable ledger row and replays exactly once', async () => {
163
+ const { svc, audit, psp } = makeService({ identityReader: playerIdentityReader() });
164
+ const player = await seedWallet({ balance: '10' });
165
+ const adminId = randomUUID();
166
+ const idempotencyKey = randomUUID();
167
+ const credit = await svc.manualAdjust({
168
+ adminId,
169
+ userId: player.userId,
170
+ direction: 'credit',
171
+ amount: '5',
172
+ currency: 'USD',
173
+ reason: 'settlement correction',
174
+ idempotencyKey,
175
+ ...NO_CLIENT_META,
176
+ });
177
+ const replay = await svc.manualAdjust({
178
+ adminId,
179
+ userId: player.userId,
180
+ direction: 'credit',
181
+ amount: '5',
182
+ currency: 'USD',
183
+ reason: 'settlement correction',
184
+ idempotencyKey,
185
+ ...NO_CLIENT_META,
186
+ });
187
+ const debit = await svc.manualAdjust({
188
+ adminId,
189
+ userId: player.userId,
190
+ direction: 'debit',
191
+ amount: '3',
192
+ currency: 'USD',
193
+ reason: 'duplicate credit reversal',
194
+ idempotencyKey: randomUUID(),
195
+ ...NO_CLIENT_META,
196
+ });
197
+ expect(replay).toEqual(credit);
198
+ expect(await balanceOf(player.userId)).toBe(12);
199
+ expect(await txById(credit.transactionId)).toMatchObject({
200
+ type: 'manual_credit',
201
+ status: 'completed',
202
+ reviewedBy: adminId,
203
+ reviewReason: 'settlement correction',
204
+ });
205
+ expect(await txById(debit.transactionId)).toMatchObject({ type: 'manual_debit' });
206
+ expect(audit.recordInTransaction).toHaveBeenCalledTimes(2);
207
+ expect(audit.recordInTransaction).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({
208
+ action: 'wallet.manual_adjustment.created',
209
+ actorId: adminId,
210
+ resourceId: credit.transactionId,
211
+ }));
212
+ expect(psp.processDeposit).not.toHaveBeenCalled();
213
+ expect(psp.processWithdrawal).not.toHaveBeenCalled();
214
+ });
215
+ it('rejects an overdraw without a ledger or audit entry', async () => {
216
+ const { svc, audit } = makeService({ identityReader: playerIdentityReader() });
217
+ const player = await seedWallet({ balance: '10' });
218
+ await expect(svc.manualAdjust({
219
+ adminId: randomUUID(),
220
+ userId: player.userId,
221
+ direction: 'debit',
222
+ amount: '11',
223
+ currency: 'USD',
224
+ reason: 'correction',
225
+ idempotencyKey: randomUUID(),
226
+ ...NO_CLIENT_META,
227
+ })).rejects.toBeInstanceOf(InsufficientBalanceError);
228
+ expect(await balanceOf(player.userId)).toBe(10);
229
+ expect(await txRows(player.id)).toHaveLength(0);
230
+ expect(audit.recordInTransaction).not.toHaveBeenCalled();
231
+ });
232
+ it('creates a selected-currency balance for a player without a wallet', async () => {
233
+ const { svc } = makeService({ identityReader: playerIdentityReader() });
234
+ const userId = randomUUID();
235
+ await svc.manualAdjust({
236
+ adminId: randomUUID(),
237
+ userId,
238
+ direction: 'credit',
239
+ amount: '7',
240
+ currency: 'EUR',
241
+ reason: 'goodwill',
242
+ idempotencyKey: randomUUID(),
243
+ ...NO_CLIENT_META,
244
+ });
245
+ expect(await svc.getBalances(userId)).toEqual({
246
+ activeCurrency: 'EUR',
247
+ balances: [{ currency: 'EUR', balance: '7.000000000000000000' }],
248
+ });
249
+ });
250
+ });
154
251
  describe('WalletService.getBalances / setActiveCurrency (real PG)', () => {
155
252
  it('returns every held currency ordered by code alongside the active one', async () => {
156
253
  const { svc } = makeService();
@@ -216,6 +313,99 @@ describe('WalletService.withdraw (real PG)', () => {
216
313
  destinationAddress: 'bc1qexample',
217
314
  });
218
315
  });
316
+ it('pins the only payable network on the transaction', async () => {
317
+ const { svc } = makeService();
318
+ const w = await seedWallet({ balance: '2', currency: 'BTC' });
319
+ await db.drizzle.db.insert(walletAsset).values({
320
+ currency: 'BTC',
321
+ network: 'BITCOIN',
322
+ providerAssetId: 'BTC',
323
+ minDeposit: '0',
324
+ minWithdrawal: '0',
325
+ withdrawalFee: '0',
326
+ });
327
+ const result = await svc.withdraw({
328
+ userId: w.userId,
329
+ amount: '1',
330
+ currency: 'BTC',
331
+ destinationAddress: 'bc1qexample',
332
+ ...NO_CLIENT_META,
333
+ });
334
+ expect(await txById(result.transactionId)).toMatchObject({ network: 'BITCOIN' });
335
+ });
336
+ it('demands a network when the currency is payable on several, holding no funds', async () => {
337
+ const { svc } = makeService();
338
+ const w = await seedWallet({ balance: '100', currency: 'USDT' });
339
+ await db.drizzle.db.insert(walletAsset).values(['ERC20', 'TRC20'].map((network) => ({
340
+ currency: 'USDT',
341
+ network,
342
+ providerAssetId: `USDT_${network}`,
343
+ minDeposit: '0',
344
+ minWithdrawal: '0',
345
+ withdrawalFee: '0',
346
+ })));
347
+ await expect(svc.withdraw({
348
+ userId: w.userId,
349
+ amount: '10',
350
+ currency: 'USDT',
351
+ destinationAddress: '0xexample',
352
+ ...NO_CLIENT_META,
353
+ })).rejects.toBeInstanceOf(AmbiguousNetworkError);
354
+ expect(await balanceOf(w.userId)).toBe(100);
355
+ });
356
+ it('enforces the minimum of the chosen chain, not the currency', async () => {
357
+ const { svc } = makeService();
358
+ const w = await seedWallet({ balance: '100', currency: 'USDT' });
359
+ await db.drizzle.db.insert(walletAsset).values([
360
+ {
361
+ currency: 'USDT',
362
+ network: 'ERC20',
363
+ providerAssetId: 'USDT_ERC20',
364
+ minDeposit: '0',
365
+ minWithdrawal: '20',
366
+ withdrawalFee: '0',
367
+ },
368
+ {
369
+ currency: 'USDT',
370
+ network: 'BEP20',
371
+ providerAssetId: 'USDT_BSC',
372
+ minDeposit: '0',
373
+ minWithdrawal: '1',
374
+ withdrawalFee: '0',
375
+ },
376
+ ]);
377
+ const args = {
378
+ userId: w.userId,
379
+ amount: '5',
380
+ currency: 'USDT',
381
+ destinationAddress: '0xexample',
382
+ ...NO_CLIENT_META,
383
+ };
384
+ await expect(svc.withdraw({ ...args, network: 'ERC20' })).rejects.toBeInstanceOf(BelowMinimumWithdrawalError);
385
+ const ok = await svc.withdraw({ ...args, network: 'BEP20' });
386
+ expect(await txById(ok.transactionId)).toMatchObject({ network: 'BEP20' });
387
+ });
388
+ it('fails closed when the currency is configured but payable nowhere', async () => {
389
+ const { svc } = makeService();
390
+ const w = await seedWallet({ balance: '100', currency: 'USDT' });
391
+ await db.drizzle.db.insert(walletAsset).values({
392
+ currency: 'USDT',
393
+ network: 'ERC20',
394
+ providerAssetId: 'USDT_ERC20',
395
+ minDeposit: '0',
396
+ minWithdrawal: '0',
397
+ withdrawalFee: '0',
398
+ withdrawalEnabled: false,
399
+ });
400
+ await expect(svc.withdraw({
401
+ userId: w.userId,
402
+ amount: '10',
403
+ currency: 'USDT',
404
+ destinationAddress: '0xexample',
405
+ ...NO_CLIENT_META,
406
+ })).rejects.toBeInstanceOf(WithdrawalDisabledError);
407
+ expect(await balanceOf(w.userId)).toBe(100);
408
+ });
219
409
  it('throws DestinationAddressRequiredError for a crypto withdrawal with no address', async () => {
220
410
  const { svc } = makeService();
221
411
  const w = await seedWallet({ balance: '2', currency: 'BTC' });
@@ -478,7 +668,7 @@ describe('WalletService idempotency - withdraw (real PG)', () => {
478
668
  });
479
669
  });
480
670
  describe('WalletService.approveWithdrawal (real PG)', () => {
481
- it('moves pending -> completed, records the provider, and emits approved then completed', async () => {
671
+ it('moves pending -> completed and emits approved then completed', async () => {
482
672
  const { svc, events, psp } = makeService();
483
673
  const w = await seedWallet({ balance: '60' });
484
674
  const adminId = randomUUID();
@@ -489,7 +679,7 @@ describe('WalletService.approveWithdrawal (real PG)', () => {
489
679
  expect(await txById(pending.id)).toMatchObject({
490
680
  status: 'completed',
491
681
  reviewedBy: adminId,
492
- providerName: 'psp',
682
+ providerName: DEFAULT_PAYMENT_PROVIDER,
493
683
  });
494
684
  expect(await balanceOf(w.userId)).toBe(60);
495
685
  expect(emittedTopics(events)).toEqual([
@@ -497,12 +687,53 @@ describe('WalletService.approveWithdrawal (real PG)', () => {
497
687
  'wallet.withdrawal.completed',
498
688
  ]);
499
689
  });
500
- it('records the fireblocks provider for a crypto-rail withdrawal', async () => {
690
+ it('resolves providerName from the default binding when the pair has no catalog row', async () => {
501
691
  const { svc } = makeService();
502
692
  const w = await seedWallet({ currency: 'BTC' });
503
693
  const pending = await seedTx(w.id, { currency: 'BTC', rail: 'crypto', amount: '1' });
504
694
  await svc.approveWithdrawal(randomUUID(), pending.id);
505
- expect(await txById(pending.id)).toMatchObject({ providerName: 'fireblocks' });
695
+ expect(await txById(pending.id)).toMatchObject({ providerName: DEFAULT_PAYMENT_PROVIDER });
696
+ });
697
+ it("resolves providerName from the (currency, network) catalog row's own providerName", async () => {
698
+ const { svc } = makeService();
699
+ await db.drizzle.db.insert(walletAsset).values({
700
+ currency: 'USDT',
701
+ network: 'ERC20',
702
+ providerAssetId: 'USDT_ERC20',
703
+ minDeposit: '1',
704
+ minWithdrawal: '1',
705
+ withdrawalFee: '1',
706
+ providerName: 'vendor-a',
707
+ });
708
+ const w = await seedWallet({ currency: 'USDT' });
709
+ const pending = await seedTx(w.id, {
710
+ currency: 'USDT',
711
+ network: 'ERC20',
712
+ rail: 'crypto',
713
+ amount: '5',
714
+ });
715
+ await svc.approveWithdrawal(randomUUID(), pending.id);
716
+ expect(await txById(pending.id)).toMatchObject({ providerName: 'vendor-a' });
717
+ });
718
+ it('falls back to the default binding when the catalog row has a null providerName', async () => {
719
+ const { svc } = makeService();
720
+ await db.drizzle.db.insert(walletAsset).values({
721
+ currency: 'USDT',
722
+ network: 'TRC20',
723
+ providerAssetId: 'USDT_TRC20',
724
+ minDeposit: '1',
725
+ minWithdrawal: '1',
726
+ withdrawalFee: '1',
727
+ });
728
+ const w = await seedWallet({ currency: 'USDT' });
729
+ const pending = await seedTx(w.id, {
730
+ currency: 'USDT',
731
+ network: 'TRC20',
732
+ rail: 'crypto',
733
+ amount: '5',
734
+ });
735
+ await svc.approveWithdrawal(randomUUID(), pending.id);
736
+ expect(await txById(pending.id)).toMatchObject({ providerName: DEFAULT_PAYMENT_PROVIDER });
506
737
  });
507
738
  it('marks failed, refunds the hold, emits failed, and rethrows when the PSP throws', async () => {
508
739
  const { svc, events, psp } = makeService();
@@ -763,7 +994,7 @@ describe('WalletService.getOrCreateDepositAddress (real PG)', () => {
763
994
  userId,
764
995
  currency: 'BTC',
765
996
  address: 'bc1qexisting',
766
- providerName: 'fireblocks',
997
+ providerName: 'custody',
767
998
  });
768
999
  expect(await svc.getOrCreateDepositAddress(userId, 'BTC')).toEqual({
769
1000
  address: 'bc1qexisting',
@@ -783,7 +1014,29 @@ describe('WalletService.getOrCreateDepositAddress (real PG)', () => {
783
1014
  .select()
784
1015
  .from(walletDepositAddress)
785
1016
  .where(eq(walletDepositAddress.userId, userId));
786
- expect(stored).toMatchObject({ address: 'bc1qnew', providerName: 'fireblocks' });
1017
+ expect(stored).toMatchObject({ address: 'bc1qnew', providerName: DEFAULT_PAYMENT_PROVIDER });
1018
+ });
1019
+ it("persists the (currency, network) catalog row's own providerName", async () => {
1020
+ await db.drizzle.db.insert(walletAsset).values({
1021
+ currency: 'USDT',
1022
+ network: 'ERC20',
1023
+ providerAssetId: 'USDT_ERC20',
1024
+ minDeposit: '1',
1025
+ minWithdrawal: '1',
1026
+ withdrawalFee: '1',
1027
+ providerName: 'vendor-a',
1028
+ });
1029
+ const userId = randomUUID();
1030
+ const issueDepositAddress = vi.fn(async () => ({ address: '0xnew' }));
1031
+ const { svc } = makeService({
1032
+ payment: mock({ ...makePsp(), issueDepositAddress }),
1033
+ });
1034
+ await svc.getOrCreateDepositAddress(userId, 'USDT', 'ERC20');
1035
+ const [stored] = await db.drizzle.db
1036
+ .select()
1037
+ .from(walletDepositAddress)
1038
+ .where(eq(walletDepositAddress.userId, userId));
1039
+ expect(stored).toMatchObject({ address: '0xnew', providerName: 'vendor-a' });
787
1040
  });
788
1041
  it('issues one address when two calls race on the same user and currency', async () => {
789
1042
  const userId = randomUUID();
@@ -811,7 +1064,7 @@ describe('WalletService.creditDepositByAddress (real PG)', () => {
811
1064
  async function seedAddress(userId, address, currency = 'BTC', network) {
812
1065
  await db.drizzle.db
813
1066
  .insert(walletDepositAddress)
814
- .values({ userId, currency, address, network, providerName: 'fireblocks' });
1067
+ .values({ userId, currency, address, network, providerName: 'custody' });
815
1068
  }
816
1069
  it('resolves the address, credits the wallet, and emits deposit.completed', async () => {
817
1070
  const { svc, events } = makeService();