@openora/core 0.4.0 → 0.4.1-canary.11
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.
- package/LICENSE-COMMERCIAL.md +2 -2
- package/README.md +8 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/admin-console/__tests__/backoffice.service.test.js +1 -1
- package/dist/admin-console/__tests__/backoffice.service.test.js.map +1 -1
- package/dist/admin-console/contract/index.d.ts +14 -14
- package/dist/admin-console/contract/index.d.ts.map +1 -1
- package/dist/admin-console/contract/index.js +8 -8
- package/dist/admin-console/contract/index.js.map +1 -1
- package/dist/admin-console/router/index.d.ts +7 -7
- package/dist/admin-console/service/backoffice.service.d.ts +22 -49
- package/dist/admin-console/service/backoffice.service.d.ts.map +1 -1
- package/dist/admin-console/service/backoffice.service.js +16 -31
- package/dist/admin-console/service/backoffice.service.js.map +1 -1
- package/dist/audit/__tests__/audit.service.test.js +29 -2
- package/dist/audit/__tests__/audit.service.test.js.map +1 -1
- package/dist/audit/contract/index.d.ts.map +1 -1
- package/dist/audit/contract/index.js +2 -0
- package/dist/audit/contract/index.js.map +1 -1
- package/dist/audit/plugin.d.ts.map +1 -1
- package/dist/audit/plugin.js +54 -4
- package/dist/audit/plugin.js.map +1 -1
- package/dist/audit/service/audit.service.d.ts +1 -0
- package/dist/audit/service/audit.service.d.ts.map +1 -1
- package/dist/audit/service/audit.service.js +53 -25
- package/dist/audit/service/audit.service.js.map +1 -1
- package/dist/casino/gaming/contract/index.d.ts.map +1 -1
- package/dist/casino/gaming/contract/index.js +3 -3
- package/dist/casino/gaming/contract/index.js.map +1 -1
- package/dist/casino/gaming/drizzle/migrations/0001_easy_ben_grimm.sql +4 -0
- package/dist/casino/gaming/drizzle/migrations/meta/0001_snapshot.json +188 -0
- package/dist/casino/gaming/drizzle/migrations/meta/_journal.json +7 -0
- package/dist/casino/gaming/schema/index.js +2 -2
- package/dist/casino/gaming/schema/index.js.map +1 -1
- package/dist/casino/gaming/service/gaming.service.d.ts +11 -11
- package/dist/casino/gaming/service/gaming.service.d.ts.map +1 -1
- package/dist/casino/gaming/service/gaming.service.js +4 -14
- package/dist/casino/gaming/service/gaming.service.js.map +1 -1
- package/dist/cms/__tests__/cms.cache.test.js +1 -1
- package/dist/cms/__tests__/cms.cache.test.js.map +1 -1
- package/dist/cms/__tests__/cms.router.test.d.ts +2 -0
- package/dist/cms/__tests__/cms.router.test.d.ts.map +1 -0
- package/dist/cms/__tests__/cms.router.test.js +51 -0
- package/dist/cms/__tests__/cms.router.test.js.map +1 -0
- package/dist/cms/router/index.d.ts.map +1 -1
- package/dist/cms/router/index.js +12 -12
- package/dist/cms/router/index.js.map +1 -1
- package/dist/cms/service/cms.service.d.ts +7 -7
- package/dist/cms/service/cms.service.d.ts.map +1 -1
- package/dist/cms/service/cms.service.js +39 -23
- package/dist/cms/service/cms.service.js.map +1 -1
- package/dist/compliance/__tests__/compliance.service.test.js +2 -1
- package/dist/compliance/__tests__/compliance.service.test.js.map +1 -1
- package/dist/compliance/__tests__/kyc.service.test.js +12 -10
- package/dist/compliance/__tests__/kyc.service.test.js.map +1 -1
- package/dist/compliance/__tests__/rg-monitoring.service.test.js +17 -6
- package/dist/compliance/__tests__/rg-monitoring.service.test.js.map +1 -1
- package/dist/compliance/__tests__/rg.contract.test.js +18 -10
- package/dist/compliance/__tests__/rg.contract.test.js.map +1 -1
- package/dist/compliance/__tests__/rg.router.test.js +5 -4
- package/dist/compliance/__tests__/rg.router.test.js.map +1 -1
- package/dist/compliance/__tests__/rg.service.test.js +18 -12
- package/dist/compliance/__tests__/rg.service.test.js.map +1 -1
- package/dist/compliance/contract/index.d.ts +20 -14
- package/dist/compliance/contract/index.d.ts.map +1 -1
- package/dist/compliance/contract/limits.d.ts +9 -2
- package/dist/compliance/contract/limits.d.ts.map +1 -1
- package/dist/compliance/contract/limits.js +21 -3
- package/dist/compliance/contract/limits.js.map +1 -1
- package/dist/compliance/contract/rg.d.ts +28 -23
- package/dist/compliance/contract/rg.d.ts.map +1 -1
- package/dist/compliance/contract/rg.js +14 -9
- package/dist/compliance/contract/rg.js.map +1 -1
- package/dist/compliance/drizzle/migrations/0001_funny_vance_astro.sql +5 -0
- package/dist/compliance/drizzle/migrations/meta/0001_snapshot.json +660 -0
- package/dist/compliance/drizzle/migrations/meta/_journal.json +7 -0
- package/dist/compliance/plugin.d.ts.map +1 -1
- package/dist/compliance/plugin.js +26 -12
- package/dist/compliance/plugin.js.map +1 -1
- package/dist/compliance/router/index.d.ts +20 -14
- package/dist/compliance/router/index.d.ts.map +1 -1
- package/dist/compliance/router/index.js +2 -1
- package/dist/compliance/router/index.js.map +1 -1
- package/dist/compliance/schema/index.d.ts +31 -14
- package/dist/compliance/schema/index.d.ts.map +1 -1
- package/dist/compliance/schema/index.js +10 -7
- package/dist/compliance/schema/index.js.map +1 -1
- package/dist/compliance/service/compliance.service.d.ts +27 -22
- package/dist/compliance/service/compliance.service.d.ts.map +1 -1
- package/dist/compliance/service/compliance.service.js +9 -22
- package/dist/compliance/service/compliance.service.js.map +1 -1
- package/dist/compliance/service/kyc.service.d.ts +22 -19
- package/dist/compliance/service/kyc.service.d.ts.map +1 -1
- package/dist/compliance/service/kyc.service.js +19 -24
- package/dist/compliance/service/kyc.service.js.map +1 -1
- package/dist/compliance/service/re-kyc-trigger.d.ts +4 -4
- package/dist/compliance/service/re-kyc-trigger.d.ts.map +1 -1
- package/dist/compliance/service/re-kyc-trigger.js +10 -6
- package/dist/compliance/service/re-kyc-trigger.js.map +1 -1
- package/dist/compliance/service/rg-eval.d.ts.map +1 -1
- package/dist/compliance/service/rg-eval.js +4 -2
- package/dist/compliance/service/rg-eval.js.map +1 -1
- package/dist/compliance/service/rg-monitoring.service.d.ts +6 -5
- package/dist/compliance/service/rg-monitoring.service.d.ts.map +1 -1
- package/dist/compliance/service/rg-monitoring.service.js +62 -31
- package/dist/compliance/service/rg-monitoring.service.js.map +1 -1
- package/dist/compliance/service/rg.service.d.ts +28 -6
- package/dist/compliance/service/rg.service.d.ts.map +1 -1
- package/dist/compliance/service/rg.service.js +60 -34
- package/dist/compliance/service/rg.service.js.map +1 -1
- package/dist/contracts/adapters/admin-wallet-reporting.d.ts +5 -5
- package/dist/contracts/adapters/payment.d.ts +2 -2
- package/dist/contracts/adapters/wallet-commands.d.ts +5 -5
- package/dist/contracts/adapters/wallet-reader.d.ts +2 -2
- package/dist/contracts/adapters/wallet-reader.d.ts.map +1 -1
- package/dist/contracts/schemas/common.d.ts +2 -5
- package/dist/contracts/schemas/common.d.ts.map +1 -1
- package/dist/contracts/schemas/common.js +3 -4
- package/dist/contracts/schemas/common.js.map +1 -1
- package/dist/contracts/schemas/events.d.ts +35 -9
- package/dist/contracts/schemas/events.d.ts.map +1 -1
- package/dist/contracts/schemas/events.js +32 -7
- package/dist/contracts/schemas/events.js.map +1 -1
- package/dist/contracts/schemas/platform-config.d.ts +9 -9
- package/dist/contracts/schemas/platform-config.d.ts.map +1 -1
- package/dist/contracts/schemas/platform-config.js +6 -5
- package/dist/contracts/schemas/platform-config.js.map +1 -1
- package/dist/contracts/schemas/player.d.ts +2 -2
- package/dist/contracts/schemas/player.js +3 -3
- package/dist/contracts/schemas/player.js.map +1 -1
- package/dist/contracts/schemas/tag.d.ts +2 -2
- package/dist/contracts/schemas/tag.js +3 -3
- package/dist/contracts/schemas/tag.js.map +1 -1
- package/dist/engagement/chat/moderation/moderate.d.ts.map +1 -1
- package/dist/engagement/chat/moderation/moderate.js +2 -1
- package/dist/engagement/chat/moderation/moderate.js.map +1 -1
- package/dist/engagement/chat/react/use-chat-stream.d.ts +2 -3
- package/dist/engagement/chat/react/use-chat-stream.d.ts.map +1 -1
- package/dist/engagement/chat/react/use-chat-stream.js +4 -2
- package/dist/engagement/chat/react/use-chat-stream.js.map +1 -1
- package/dist/engagement/chat/router/index.d.ts.map +1 -1
- package/dist/engagement/chat/router/index.js +2 -1
- package/dist/engagement/chat/router/index.js.map +1 -1
- package/dist/engagement/chat/service/chat.service.d.ts.map +1 -1
- package/dist/engagement/chat/service/chat.service.js +22 -11
- package/dist/engagement/chat/service/chat.service.js.map +1 -1
- package/dist/engagement/notifications/__tests__/notifications.service.test.js +4 -3
- package/dist/engagement/notifications/__tests__/notifications.service.test.js.map +1 -1
- package/dist/engagement/notifications/contract/index.d.ts +24 -2
- package/dist/engagement/notifications/contract/index.d.ts.map +1 -1
- package/dist/engagement/notifications/contract/index.js +10 -1
- package/dist/engagement/notifications/contract/index.js.map +1 -1
- package/dist/engagement/notifications/index.d.ts +1 -1
- package/dist/engagement/notifications/index.d.ts.map +1 -1
- package/dist/engagement/notifications/plugin.d.ts.map +1 -1
- package/dist/engagement/notifications/plugin.js +6 -3
- package/dist/engagement/notifications/plugin.js.map +1 -1
- package/dist/engagement/notifications/router/index.d.ts +4 -1
- package/dist/engagement/notifications/router/index.d.ts.map +1 -1
- package/dist/engagement/notifications/router/index.js +15 -6
- package/dist/engagement/notifications/router/index.js.map +1 -1
- package/dist/engagement/notifications/service/notifications.service.d.ts +6 -13
- package/dist/engagement/notifications/service/notifications.service.d.ts.map +1 -1
- package/dist/engagement/notifications/service/notifications.service.js +6 -14
- package/dist/engagement/notifications/service/notifications.service.js.map +1 -1
- package/dist/iam/__tests__/iam.service.test.js +14 -9
- package/dist/iam/__tests__/iam.service.test.js.map +1 -1
- package/dist/iam/plugin.d.ts.map +1 -1
- package/dist/iam/plugin.js +4 -2
- package/dist/iam/plugin.js.map +1 -1
- package/dist/iam/seed/seed-default-roles.d.ts.map +1 -1
- package/dist/iam/seed/seed-default-roles.js +2 -1
- package/dist/iam/seed/seed-default-roles.js.map +1 -1
- package/dist/iam/service/iam.service.d.ts +19 -17
- package/dist/iam/service/iam.service.d.ts.map +1 -1
- package/dist/iam/service/iam.service.js +68 -34
- package/dist/iam/service/iam.service.js.map +1 -1
- package/dist/pam/identity/admin-user-directory.d.ts.map +1 -1
- package/dist/pam/identity/admin-user-directory.js +18 -9
- package/dist/pam/identity/admin-user-directory.js.map +1 -1
- package/dist/pam/identity/router/index.d.ts.map +1 -1
- package/dist/pam/identity/router/index.js +4 -2
- package/dist/pam/identity/router/index.js.map +1 -1
- package/dist/pam/identity/service/identity.service.d.ts +24 -2
- package/dist/pam/identity/service/identity.service.d.ts.map +1 -1
- package/dist/pam/identity/service/identity.service.js +51 -19
- package/dist/pam/identity/service/identity.service.js.map +1 -1
- package/dist/pam/identity/service/login-enforcement.service.d.ts +3 -3
- package/dist/pam/identity/service/login-enforcement.service.d.ts.map +1 -1
- package/dist/pam/identity/service/login-enforcement.service.js.map +1 -1
- package/dist/pam/identity/service/session.service.d.ts +5 -3
- package/dist/pam/identity/service/session.service.d.ts.map +1 -1
- package/dist/pam/identity/service/session.service.js +3 -3
- package/dist/pam/identity/service/session.service.js.map +1 -1
- package/dist/pam/player-management/__tests__/kyc-status-writer.test.js +2 -1
- package/dist/pam/player-management/__tests__/kyc-status-writer.test.js.map +1 -1
- package/dist/pam/player-management/__tests__/player.router.test.js +2 -2
- package/dist/pam/player-management/__tests__/player.router.test.js.map +1 -1
- package/dist/pam/player-management/__tests__/player.service.test.js +2 -1
- package/dist/pam/player-management/__tests__/player.service.test.js.map +1 -1
- package/dist/pam/player-management/contract/index.d.ts +10 -10
- package/dist/pam/player-management/router/index.d.ts +8 -8
- package/dist/pam/player-management/service/kyc-status-writer.d.ts.map +1 -1
- package/dist/pam/player-management/service/kyc-status-writer.js +2 -1
- package/dist/pam/player-management/service/kyc-status-writer.js.map +1 -1
- package/dist/pam/player-management/service/player.service.d.ts +15 -15
- package/dist/pam/player-management/service/player.service.d.ts.map +1 -1
- package/dist/pam/player-management/service/player.service.js +15 -12
- package/dist/pam/player-management/service/player.service.js.map +1 -1
- package/dist/pam/player-note/schema/index.d.ts +4 -4
- package/dist/pam/player-note/schema/index.js +6 -6
- package/dist/pam/player-note/schema/index.js.map +1 -1
- package/dist/pam/player-note/service/player-note.service.d.ts +11 -3
- package/dist/pam/player-note/service/player-note.service.d.ts.map +1 -1
- package/dist/pam/player-note/service/player-note.service.js +1 -1
- package/dist/pam/player-note/service/player-note.service.js.map +1 -1
- package/dist/pam/profile/__tests__/profile.service.test.js +2 -1
- package/dist/pam/profile/__tests__/profile.service.test.js.map +1 -1
- package/dist/pam/profile/contract/index.d.ts +4 -4
- package/dist/pam/profile/react/profile.d.ts +6 -7
- package/dist/pam/profile/react/profile.d.ts.map +1 -1
- package/dist/pam/profile/react/profile.js.map +1 -1
- package/dist/pam/profile/router/index.d.ts +4 -4
- package/dist/pam/profile/service/profile.service.d.ts +4 -4
- package/dist/pam/profile/service/profile.service.d.ts.map +1 -1
- package/dist/pam/profile/service/profile.service.js +2 -1
- package/dist/pam/profile/service/profile.service.js.map +1 -1
- package/dist/pam/shared/headers-mapper.d.ts.map +1 -1
- package/dist/pam/shared/headers-mapper.js +2 -1
- package/dist/pam/shared/headers-mapper.js.map +1 -1
- package/dist/pam/shared/player-mapper.d.ts +17 -2
- package/dist/pam/shared/player-mapper.d.ts.map +1 -1
- package/dist/pam/shared/player-mapper.js +2 -2
- package/dist/pam/shared/player-mapper.js.map +1 -1
- package/dist/pam/tag/__tests__/get-active-tag-keys.test.js +2 -1
- package/dist/pam/tag/__tests__/get-active-tag-keys.test.js.map +1 -1
- package/dist/pam/tag/__tests__/tag-evaluation.service.test.js +19 -18
- package/dist/pam/tag/__tests__/tag-evaluation.service.test.js.map +1 -1
- package/dist/pam/tag/__tests__/tag-mappers.test.js +6 -6
- package/dist/pam/tag/__tests__/tag-mappers.test.js.map +1 -1
- package/dist/pam/tag/__tests__/tag-rule.service.test.js +14 -14
- package/dist/pam/tag/__tests__/tag-rule.service.test.js.map +1 -1
- package/dist/pam/tag/__tests__/tag.router.test.d.ts +2 -0
- package/dist/pam/tag/__tests__/tag.router.test.d.ts.map +1 -0
- package/dist/pam/tag/__tests__/tag.router.test.js +38 -0
- package/dist/pam/tag/__tests__/tag.router.test.js.map +1 -0
- package/dist/pam/tag/contract/index.d.ts +3 -3
- package/dist/pam/tag/drizzle/migrations/0001_neat_iron_monger.sql +2 -0
- package/dist/pam/tag/drizzle/migrations/meta/0001_snapshot.json +295 -0
- package/dist/pam/tag/drizzle/migrations/meta/_journal.json +7 -0
- package/dist/pam/tag/router/index.d.ts +3 -3
- package/dist/pam/tag/schema/index.d.ts +16 -16
- package/dist/pam/tag/schema/index.d.ts.map +1 -1
- package/dist/pam/tag/schema/index.js +19 -19
- package/dist/pam/tag/schema/index.js.map +1 -1
- package/dist/pam/tag/seed/data/default-tag-rules.js +6 -6
- package/dist/pam/tag/seed/data/default-tag-rules.js.map +1 -1
- package/dist/pam/tag/seed/seed-default-tag-rules.d.ts.map +1 -1
- package/dist/pam/tag/seed/seed-default-tag-rules.js +5 -3
- package/dist/pam/tag/seed/seed-default-tag-rules.js.map +1 -1
- package/dist/pam/tag/service/tag-evaluation.service.d.ts.map +1 -1
- package/dist/pam/tag/service/tag-evaluation.service.js +32 -18
- package/dist/pam/tag/service/tag-evaluation.service.js.map +1 -1
- package/dist/pam/tag/service/tag-mappers.d.ts +8 -2
- package/dist/pam/tag/service/tag-mappers.d.ts.map +1 -1
- package/dist/pam/tag/service/tag-mappers.js +3 -8
- package/dist/pam/tag/service/tag-mappers.js.map +1 -1
- package/dist/pam/tag/service/tag-rule.service.d.ts +34 -4
- package/dist/pam/tag/service/tag-rule.service.d.ts.map +1 -1
- package/dist/pam/tag/service/tag-rule.service.js +9 -15
- package/dist/pam/tag/service/tag-rule.service.js.map +1 -1
- package/dist/pam/tag/service/tag.service.d.ts +51 -8
- package/dist/pam/tag/service/tag.service.d.ts.map +1 -1
- package/dist/pam/tag/service/tag.service.js +6 -11
- package/dist/pam/tag/service/tag.service.js.map +1 -1
- package/dist/react/context/api-client.d.ts.map +1 -1
- package/dist/react/context/api-client.js +14 -7
- package/dist/react/context/api-client.js.map +1 -1
- package/dist/react/hooks/use-event-stream.d.ts.map +1 -1
- package/dist/react/hooks/use-event-stream.js +6 -3
- package/dist/react/hooks/use-event-stream.js.map +1 -1
- package/dist/react/hooks/use-paginated-list.js +3 -3
- package/dist/react/hooks/use-paginated-list.js.map +1 -1
- package/dist/server/auth/admin-guard.d.ts +12 -0
- package/dist/server/auth/admin-guard.d.ts.map +1 -1
- package/dist/server/auth/admin-guard.js +14 -1
- package/dist/server/auth/admin-guard.js.map +1 -1
- package/dist/server/auth/auth.d.ts +9 -0
- package/dist/server/auth/auth.d.ts.map +1 -1
- package/dist/server/auth/auth.js +9 -1
- package/dist/server/auth/auth.js.map +1 -1
- package/dist/server/auth/permission-levels.d.ts.map +1 -1
- package/dist/server/auth/permission-levels.js +12 -6
- package/dist/server/auth/permission-levels.js.map +1 -1
- package/dist/server/db/drizzle.d.ts.map +1 -1
- package/dist/server/db/drizzle.js +2 -1
- package/dist/server/db/drizzle.js.map +1 -1
- package/dist/server/db/drizzle.service.d.ts.map +1 -1
- package/dist/server/db/drizzle.service.js +2 -1
- package/dist/server/db/drizzle.service.js.map +1 -1
- package/dist/server/db/index.d.ts +1 -1
- package/dist/server/db/index.d.ts.map +1 -1
- package/dist/server/db/index.js +1 -1
- package/dist/server/db/index.js.map +1 -1
- package/dist/server/db/migrate.d.ts.map +1 -1
- package/dist/server/db/migrate.js +2 -1
- package/dist/server/db/migrate.js.map +1 -1
- package/dist/server/db/outbox/__tests__/relay.test.js +4 -2
- package/dist/server/db/outbox/__tests__/relay.test.js.map +1 -1
- package/dist/server/db/outbox/relay.d.ts +15 -0
- package/dist/server/db/outbox/relay.d.ts.map +1 -1
- package/dist/server/db/outbox/relay.js +21 -14
- package/dist/server/db/outbox/relay.js.map +1 -1
- package/dist/server/db/query-helpers.d.ts +2 -0
- package/dist/server/db/query-helpers.d.ts.map +1 -1
- package/dist/server/db/query-helpers.js +28 -1
- package/dist/server/db/query-helpers.js.map +1 -1
- package/dist/server/kernel/__tests__/event-bus.test.js +10 -7
- package/dist/server/kernel/__tests__/event-bus.test.js.map +1 -1
- package/dist/server/kernel/__tests__/job-queue.test.js +5 -2
- package/dist/server/kernel/__tests__/job-queue.test.js.map +1 -1
- package/dist/server/kernel/__tests__/rate-limiter.test.js +10 -5
- package/dist/server/kernel/__tests__/rate-limiter.test.js.map +1 -1
- package/dist/server/kernel/__tests__/redis-cache.test.js +2 -1
- package/dist/server/kernel/__tests__/redis-cache.test.js.map +1 -1
- package/dist/server/kernel/bullmq-job-queue.d.ts +12 -0
- package/dist/server/kernel/bullmq-job-queue.d.ts.map +1 -1
- package/dist/server/kernel/bullmq-job-queue.js +21 -4
- package/dist/server/kernel/bullmq-job-queue.js.map +1 -1
- package/dist/server/kernel/cache.d.ts.map +1 -1
- package/dist/server/kernel/cache.js +14 -7
- package/dist/server/kernel/cache.js.map +1 -1
- package/dist/server/kernel/container.d.ts +10 -0
- package/dist/server/kernel/container.d.ts.map +1 -1
- package/dist/server/kernel/container.js +12 -1
- package/dist/server/kernel/container.js.map +1 -1
- package/dist/server/kernel/event-bus.d.ts +9 -0
- package/dist/server/kernel/event-bus.d.ts.map +1 -1
- package/dist/server/kernel/event-bus.js +9 -1
- package/dist/server/kernel/event-bus.js.map +1 -1
- package/dist/server/kernel/event-stream.d.ts.map +1 -1
- package/dist/server/kernel/event-stream.js +2 -1
- package/dist/server/kernel/event-stream.js.map +1 -1
- package/dist/server/kernel/job-queue.d.ts +12 -0
- package/dist/server/kernel/job-queue.d.ts.map +1 -1
- package/dist/server/kernel/job-queue.js +38 -15
- package/dist/server/kernel/job-queue.js.map +1 -1
- package/dist/server/kernel/ownership.d.ts.map +1 -1
- package/dist/server/kernel/ownership.js +2 -1
- package/dist/server/kernel/ownership.js.map +1 -1
- package/dist/server/kernel/platform-config-loader.d.ts.map +1 -1
- package/dist/server/kernel/platform-config-loader.js +34 -16
- package/dist/server/kernel/platform-config-loader.js.map +1 -1
- package/dist/server/kernel/rate-limiter.d.ts.map +1 -1
- package/dist/server/kernel/rate-limiter.js +6 -3
- package/dist/server/kernel/rate-limiter.js.map +1 -1
- package/dist/server/kernel/realtime-transport.d.ts.map +1 -1
- package/dist/server/kernel/realtime-transport.js +10 -5
- package/dist/server/kernel/realtime-transport.js.map +1 -1
- package/dist/server/kernel/redis-cache.d.ts.map +1 -1
- package/dist/server/kernel/redis-cache.js +10 -5
- package/dist/server/kernel/redis-cache.js.map +1 -1
- package/dist/server/kernel/redis-rate-limiter.d.ts +11 -0
- package/dist/server/kernel/redis-rate-limiter.d.ts.map +1 -1
- package/dist/server/kernel/redis-rate-limiter.js +13 -1
- package/dist/server/kernel/redis-rate-limiter.js.map +1 -1
- package/dist/server/kernel/serialize-row.d.ts.map +1 -1
- package/dist/server/kernel/serialize-row.js +4 -2
- package/dist/server/kernel/serialize-row.js.map +1 -1
- package/dist/server/plugin-host/load-extensions.js +2 -1
- package/dist/server/plugin-host/load-extensions.js.map +1 -1
- package/dist/server/plugin-host/load-plugins.d.ts.map +1 -1
- package/dist/server/plugin-host/load-plugins.js +4 -2
- package/dist/server/plugin-host/load-plugins.js.map +1 -1
- package/dist/server/plugin-host/service-manifest.d.ts.map +1 -1
- package/dist/server/plugin-host/service-manifest.js +4 -2
- package/dist/server/plugin-host/service-manifest.js.map +1 -1
- package/dist/server/runtime/create-app.d.ts +12 -0
- package/dist/server/runtime/create-app.d.ts.map +1 -1
- package/dist/server/runtime/create-app.js +27 -6
- package/dist/server/runtime/create-app.js.map +1 -1
- package/dist/wallet/__tests__/admin-reporting.test.js +2 -2
- package/dist/wallet/__tests__/admin-reporting.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet-auto-rule.router.test.js +5 -5
- package/dist/wallet/__tests__/wallet-auto-rule.router.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet-auto-withdrawal.test.js +45 -34
- package/dist/wallet/__tests__/wallet-auto-withdrawal.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet-commands.service.test.js +54 -37
- package/dist/wallet/__tests__/wallet-commands.service.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet-kyc-gate.test.js +5 -5
- package/dist/wallet/__tests__/wallet-kyc-gate.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet.rate-limit.test.js +2 -2
- package/dist/wallet/__tests__/wallet.rate-limit.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet.service.test.js +62 -34
- package/dist/wallet/__tests__/wallet.service.test.js.map +1 -1
- package/dist/wallet/adapters/mock/mock-payment-adapter.d.ts +2 -2
- package/dist/wallet/adapters/wallet-reader.service.d.ts +1 -1
- package/dist/wallet/adapters/wallet-reader.service.js +1 -1
- package/dist/wallet/adapters/wallet-reader.service.js.map +1 -1
- package/dist/wallet/admin-reporting.d.ts +4 -4
- package/dist/wallet/admin-reporting.d.ts.map +1 -1
- package/dist/wallet/admin-reporting.js +8 -8
- package/dist/wallet/admin-reporting.js.map +1 -1
- package/dist/wallet/contract/index.d.ts +20 -20
- package/dist/wallet/contract/index.d.ts.map +1 -1
- package/dist/wallet/contract/index.js +14 -10
- package/dist/wallet/contract/index.js.map +1 -1
- package/dist/wallet/drizzle/migrations/0001_redundant_micromacro.sql +4 -0
- package/dist/wallet/drizzle/migrations/meta/0001_snapshot.json +397 -0
- package/dist/wallet/drizzle/migrations/meta/_journal.json +7 -0
- package/dist/wallet/plugin.d.ts.map +1 -1
- package/dist/wallet/plugin.js +5 -1
- package/dist/wallet/plugin.js.map +1 -1
- package/dist/wallet/router/index.d.ts +11 -11
- package/dist/wallet/schema/index.js +3 -3
- package/dist/wallet/schema/index.js.map +1 -1
- package/dist/wallet/service/wallet-commands.service.d.ts +1 -0
- package/dist/wallet/service/wallet-commands.service.d.ts.map +1 -1
- package/dist/wallet/service/wallet-commands.service.js +32 -24
- package/dist/wallet/service/wallet-commands.service.js.map +1 -1
- package/dist/wallet/service/wallet.service.d.ts +46 -18
- package/dist/wallet/service/wallet.service.d.ts.map +1 -1
- package/dist/wallet/service/wallet.service.js +152 -74
- package/dist/wallet/service/wallet.service.js.map +1 -1
- package/oxlint/oxlintrc.json +19 -3
- package/package.json +1 -1
- package/turbo-generators/src/config.ts +18 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { makeNotFoundError, makeConflictError, createDomainError, findOneOrThrow, pageToOffset, withAdvisoryXactLock, assertRateLimit, createLogger, } from '@openora/core/server';
|
|
1
|
+
import { makeNotFoundError, makeConflictError, createDomainError, findOneOrThrow, pageToOffset, withAdvisoryXactLock, assertRateLimit, createLogger, moneyToNumber, } from '@openora/core/server';
|
|
2
2
|
import { eq, desc, sql, and, gte, lte, count, inArray } from 'drizzle-orm';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { wallet, walletTransaction, autoWithdrawalRule, } from '../schema/index.js';
|
|
@@ -39,7 +39,8 @@ function namespacedIdempotencyKey(namespace, rawKey) {
|
|
|
39
39
|
}
|
|
40
40
|
// ponytail: a flat, currency-agnostic threshold - a starter heuristic for the review
|
|
41
41
|
// queue, not a compliance risk engine. Revisit with a real risk service if/when one exists.
|
|
42
|
-
|
|
42
|
+
// moneyToNumber is the sanctioned JS conversion point for this heuristic comparison only.
|
|
43
|
+
const LARGE_WITHDRAWAL_THRESHOLD = '5000';
|
|
43
44
|
// ponytail: >=3 withdrawals in a 24h window flags velocity; a flat count, not a per-tier rule.
|
|
44
45
|
const HIGH_FREQUENCY_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
45
46
|
const HIGH_FREQUENCY_MIN_COUNT = 3;
|
|
@@ -50,7 +51,7 @@ function toAutoWithdrawalRuleDto(row) {
|
|
|
50
51
|
return {
|
|
51
52
|
id: row.id,
|
|
52
53
|
userId: row.userId,
|
|
53
|
-
threshold:
|
|
54
|
+
threshold: row.threshold,
|
|
54
55
|
reason: row.reason,
|
|
55
56
|
createdBy: row.createdBy,
|
|
56
57
|
createdAt: row.createdAt.toISOString(),
|
|
@@ -62,6 +63,14 @@ function toAutoWithdrawalRuleDto(row) {
|
|
|
62
63
|
function providerNameFor(rail) {
|
|
63
64
|
return rail === 'crypto' ? 'fireblocks' : 'psp';
|
|
64
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Owns all wallet balance mutations and the withdrawal review/auto-approval
|
|
68
|
+
* pipeline. Every balance change is a transactional, idempotent-keyed ledger
|
|
69
|
+
* write (never a bare balance update) - `amount` is always a decimal string,
|
|
70
|
+
* mutated via SQL numeric arithmetic, never a JS float. Domain events emit only
|
|
71
|
+
* AFTER the owning transaction commits, and only for the non-replay branch of
|
|
72
|
+
* an idempotent call.
|
|
73
|
+
*/
|
|
65
74
|
export class WalletService {
|
|
66
75
|
drizzle;
|
|
67
76
|
events;
|
|
@@ -88,8 +97,9 @@ export class WalletService {
|
|
|
88
97
|
}
|
|
89
98
|
/** Fail-closed KYC gate: when enabled, a withdrawal needs a passing status (verified|manually_overridden). */
|
|
90
99
|
async assertKycForWithdrawal(userId) {
|
|
91
|
-
if (!this.platformConfig?.kyc?.gateWithdrawals)
|
|
100
|
+
if (!this.platformConfig?.kyc?.gateWithdrawals) {
|
|
92
101
|
return;
|
|
102
|
+
}
|
|
93
103
|
const status = await this.autoApprovalKycStatus(userId);
|
|
94
104
|
if (!status || !KYC_PASS_STATUSES.has(status)) {
|
|
95
105
|
throw new KycRequiredError();
|
|
@@ -98,13 +108,23 @@ export class WalletService {
|
|
|
98
108
|
async getBalance(userId) {
|
|
99
109
|
const [record] = await this.drizzle.db.select().from(wallet).where(eq(wallet.userId, userId));
|
|
100
110
|
if (!record) {
|
|
101
|
-
return { balance: 0, currency: 'USD' };
|
|
111
|
+
return { balance: '0', currency: 'USD' };
|
|
102
112
|
}
|
|
103
113
|
return {
|
|
104
|
-
balance:
|
|
114
|
+
balance: record.balance,
|
|
105
115
|
currency: record.currency,
|
|
106
116
|
};
|
|
107
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* `idempotencyKey` (when supplied) makes a retried deposit a no-op: a replay
|
|
120
|
+
* returns the original committed result WITHOUT calling the PSP again or
|
|
121
|
+
* crediting the balance twice, and rejects if the same key is reused with a
|
|
122
|
+
* different amount/currency (`IdempotencyKeyReuseError`). A GENUINE
|
|
123
|
+
* concurrent race (two first-attempts, same key, landing at once) can both
|
|
124
|
+
* reach the PSP, but the ledger insert's unique-index conflict still
|
|
125
|
+
* guarantees only one balance credit - the loser's write becomes a replay
|
|
126
|
+
* read of the winner's row.
|
|
127
|
+
*/
|
|
108
128
|
async deposit({ userId, amount, currency, provider, idempotencyKey, }) {
|
|
109
129
|
await this.rateLimit(userId);
|
|
110
130
|
// Short-circuit a replay BEFORE the PSP call - the common retry case (client
|
|
@@ -114,8 +134,9 @@ export class WalletService {
|
|
|
114
134
|
let preResolvedWallet;
|
|
115
135
|
if (idempotencyKey) {
|
|
116
136
|
const found = await this.findDepositReplay({ userId, idempotencyKey, amount, currency });
|
|
117
|
-
if (found.replay)
|
|
137
|
+
if (found.replay) {
|
|
118
138
|
return found.replay;
|
|
139
|
+
}
|
|
119
140
|
preResolvedWallet = found.walletRecord;
|
|
120
141
|
}
|
|
121
142
|
const psp = await this.payment.processDeposit(amount, currency, { userId, provider });
|
|
@@ -125,10 +146,7 @@ export class WalletService {
|
|
|
125
146
|
[walletRecord] = await txn.select().from(wallet).where(eq(wallet.userId, userId));
|
|
126
147
|
}
|
|
127
148
|
if (!walletRecord) {
|
|
128
|
-
|
|
129
|
-
.insert(wallet)
|
|
130
|
-
.values({ userId, balance: '0', currency })
|
|
131
|
-
.returning();
|
|
149
|
+
walletRecord = findOneOrThrow(await txn.insert(wallet).values({ userId, balance: '0', currency }).returning(), new WalletNotFoundError(userId));
|
|
132
150
|
}
|
|
133
151
|
const { row, replayed } = await this.insertIdempotentTransaction(txn, {
|
|
134
152
|
namespace: DEPOSIT_IDEMPOTENCY_NAMESPACE,
|
|
@@ -139,7 +157,7 @@ export class WalletService {
|
|
|
139
157
|
values: {
|
|
140
158
|
walletId: walletRecord.id,
|
|
141
159
|
type: 'deposit',
|
|
142
|
-
amount
|
|
160
|
+
amount,
|
|
143
161
|
currency,
|
|
144
162
|
status: 'completed',
|
|
145
163
|
rail: railFor(currency),
|
|
@@ -150,13 +168,18 @@ export class WalletService {
|
|
|
150
168
|
if (!replayed) {
|
|
151
169
|
await txn
|
|
152
170
|
.update(wallet)
|
|
153
|
-
.set({ balance: sql `${wallet.balance} + ${amount}` })
|
|
171
|
+
.set({ balance: sql `${wallet.balance} + ${amount}::numeric` })
|
|
154
172
|
.where(eq(wallet.id, walletRecord.id));
|
|
155
173
|
}
|
|
156
174
|
return { transactionId: row.id, replayed };
|
|
157
175
|
});
|
|
158
176
|
if (!replayed) {
|
|
159
|
-
this.events.emit('wallet.deposit.completed', {
|
|
177
|
+
this.events.emit('wallet.deposit.completed', {
|
|
178
|
+
userId,
|
|
179
|
+
amount,
|
|
180
|
+
currency,
|
|
181
|
+
transactionId,
|
|
182
|
+
});
|
|
160
183
|
}
|
|
161
184
|
return { transactionId, status: 'completed' };
|
|
162
185
|
}
|
|
@@ -167,16 +190,20 @@ export class WalletService {
|
|
|
167
190
|
.select()
|
|
168
191
|
.from(wallet)
|
|
169
192
|
.where(eq(wallet.userId, userId));
|
|
170
|
-
if (!walletRecord)
|
|
193
|
+
if (!walletRecord) {
|
|
171
194
|
return { walletRecord: undefined, replay: undefined };
|
|
195
|
+
}
|
|
172
196
|
const existing = await this.findByIdempotencyKey(this.drizzle.db, walletRecord.id, namespacedIdempotencyKey(DEPOSIT_IDEMPOTENCY_NAMESPACE, idempotencyKey));
|
|
173
|
-
if (!existing)
|
|
197
|
+
if (!existing) {
|
|
174
198
|
return { walletRecord, replay: undefined };
|
|
199
|
+
}
|
|
175
200
|
this.assertReplayMatches(existing, amount, currency);
|
|
176
201
|
return { walletRecord, replay: { transactionId: existing.id, status: existing.status } };
|
|
177
202
|
}
|
|
203
|
+
// Replay comparison only (not a ledger write) - moneyToNumber avoids a false mismatch
|
|
204
|
+
// between an unnormalized caller string (eg "10") and the DB's fixed-scale value ("10.00").
|
|
178
205
|
assertReplayMatches(existing, amount, currency) {
|
|
179
|
-
if (
|
|
206
|
+
if (moneyToNumber(existing.amount) !== moneyToNumber(amount) ||
|
|
180
207
|
existing.currency.toUpperCase() !== currency.toUpperCase()) {
|
|
181
208
|
throw new IdempotencyKeyReuseError();
|
|
182
209
|
}
|
|
@@ -194,8 +221,9 @@ export class WalletService {
|
|
|
194
221
|
const [row] = idempotencyKey
|
|
195
222
|
? await insertQuery.onConflictDoNothing().returning()
|
|
196
223
|
: await insertQuery.returning();
|
|
197
|
-
if (row)
|
|
224
|
+
if (row) {
|
|
198
225
|
return { row, replayed: false };
|
|
226
|
+
}
|
|
199
227
|
// Only the keyed (onConflictDoNothing) branch above can return an empty `returning()`
|
|
200
228
|
// - the plain-insert branch always yields exactly one row.
|
|
201
229
|
if (!idempotencyKey) {
|
|
@@ -208,7 +236,16 @@ export class WalletService {
|
|
|
208
236
|
this.assertReplayMatches(winner, amount, currency);
|
|
209
237
|
return { row: winner, replayed: true };
|
|
210
238
|
}
|
|
211
|
-
/**
|
|
239
|
+
/**
|
|
240
|
+
* Creates a PENDING withdrawal and HOLDS the funds immediately (balance
|
|
241
|
+
* debited at request time, not on approval) - funds are returned on
|
|
242
|
+
* reject/PSP-failure. The debit is a conditional `UPDATE ... WHERE balance
|
|
243
|
+
* >= amount`, atomic with the row lock (`FOR UPDATE`) taken on the wallet,
|
|
244
|
+
* so two concurrent withdrawals can't both pass a balance check and
|
|
245
|
+
* double-spend. `idempotencyKey` replay semantics mirror `deposit()`. May
|
|
246
|
+
* return an already-`completed`/`processing` result if the request
|
|
247
|
+
* qualifies for (and passes) auto-approval - see `maybeAutoApprove`.
|
|
248
|
+
*/
|
|
212
249
|
async withdraw({ userId, amount, currency, idempotencyKey, }) {
|
|
213
250
|
await this.rateLimit(userId);
|
|
214
251
|
await this.assertKycForWithdrawal(userId);
|
|
@@ -242,7 +279,7 @@ export class WalletService {
|
|
|
242
279
|
values: {
|
|
243
280
|
walletId: current.id,
|
|
244
281
|
type: 'withdrawal',
|
|
245
|
-
amount
|
|
282
|
+
amount,
|
|
246
283
|
currency,
|
|
247
284
|
status: 'pending',
|
|
248
285
|
rail: railFor(currency),
|
|
@@ -260,11 +297,11 @@ export class WalletService {
|
|
|
260
297
|
// Guarded conditional debit: the WHERE makes balance>=amount atomic with the write; 0 rows back rolls back the whole tx.
|
|
261
298
|
const debited = await txn
|
|
262
299
|
.update(wallet)
|
|
263
|
-
.set({ balance: sql `${wallet.balance} - ${amount}` })
|
|
264
|
-
.where(and(eq(wallet.id, current.id), gte(wallet.balance, amount
|
|
300
|
+
.set({ balance: sql `${wallet.balance} - ${amount}::numeric` })
|
|
301
|
+
.where(and(eq(wallet.id, current.id), gte(wallet.balance, amount)))
|
|
265
302
|
.returning({ id: wallet.id });
|
|
266
303
|
if (debited.length !== 1) {
|
|
267
|
-
throw new InsufficientBalanceError(
|
|
304
|
+
throw new InsufficientBalanceError(current.balance, amount);
|
|
268
305
|
}
|
|
269
306
|
return {
|
|
270
307
|
transactionId: row.id,
|
|
@@ -274,9 +311,15 @@ export class WalletService {
|
|
|
274
311
|
rail: railFor(currency),
|
|
275
312
|
};
|
|
276
313
|
});
|
|
277
|
-
if (replayed)
|
|
314
|
+
if (replayed) {
|
|
278
315
|
return { transactionId, status };
|
|
279
|
-
|
|
316
|
+
}
|
|
317
|
+
this.events.emit('wallet.withdrawal.requested', {
|
|
318
|
+
userId,
|
|
319
|
+
amount,
|
|
320
|
+
currency,
|
|
321
|
+
transactionId,
|
|
322
|
+
});
|
|
280
323
|
// Fail-closed post-step: decides who approves (system vs manual queue), never throws out of withdraw() - failure leaves the row pending.
|
|
281
324
|
const auto = await this.maybeAutoApprove({
|
|
282
325
|
userId,
|
|
@@ -292,17 +335,20 @@ export class WalletService {
|
|
|
292
335
|
const db = this.drizzle.db;
|
|
293
336
|
const { page, limit } = filters;
|
|
294
337
|
const conditions = [eq(walletTransaction.type, 'withdrawal')];
|
|
295
|
-
if (filters.status)
|
|
338
|
+
if (filters.status) {
|
|
296
339
|
conditions.push(eq(walletTransaction.status, filters.status));
|
|
297
|
-
|
|
340
|
+
}
|
|
341
|
+
if (filters.currency) {
|
|
298
342
|
conditions.push(eq(walletTransaction.currency, filters.currency));
|
|
299
|
-
|
|
343
|
+
}
|
|
344
|
+
if (filters.rail) {
|
|
300
345
|
conditions.push(eq(walletTransaction.rail, filters.rail));
|
|
346
|
+
}
|
|
301
347
|
if (filters.minAmount !== undefined) {
|
|
302
|
-
conditions.push(gte(walletTransaction.amount, filters.minAmount
|
|
348
|
+
conditions.push(gte(walletTransaction.amount, filters.minAmount));
|
|
303
349
|
}
|
|
304
350
|
if (filters.maxAmount !== undefined) {
|
|
305
|
-
conditions.push(lte(walletTransaction.amount, filters.maxAmount
|
|
351
|
+
conditions.push(lte(walletTransaction.amount, filters.maxAmount));
|
|
306
352
|
}
|
|
307
353
|
if (filters.dateFrom) {
|
|
308
354
|
conditions.push(gte(walletTransaction.createdAt, new Date(filters.dateFrom)));
|
|
@@ -324,7 +370,7 @@ export class WalletService {
|
|
|
324
370
|
const matching = filters.kycStatus
|
|
325
371
|
? rows.filter((r) => byUserId.get(r.userId)?.kycStatus === filters.kycStatus)
|
|
326
372
|
: rows;
|
|
327
|
-
const start = (page
|
|
373
|
+
const start = pageToOffset(page, limit);
|
|
328
374
|
const pageRows = matching.slice(start, start + limit);
|
|
329
375
|
// One batched velocity query for the page (no N+1); shares the window + threshold + query the auto-approval evaluator uses.
|
|
330
376
|
const pageWalletIds = [...new Set(pageRows.map((r) => r.tx.walletId))];
|
|
@@ -332,15 +378,17 @@ export class WalletService {
|
|
|
332
378
|
const items = pageRows.map((r) => {
|
|
333
379
|
const summary = byUserId.get(r.userId);
|
|
334
380
|
const riskTags = [];
|
|
335
|
-
if (
|
|
381
|
+
if (moneyToNumber(r.tx.amount) >= moneyToNumber(LARGE_WITHDRAWAL_THRESHOLD)) {
|
|
336
382
|
riskTags.push('large_amount');
|
|
337
|
-
|
|
383
|
+
}
|
|
384
|
+
if (frequentWalletIds.has(r.tx.walletId)) {
|
|
338
385
|
riskTags.push('high_frequency');
|
|
386
|
+
}
|
|
339
387
|
return {
|
|
340
388
|
transactionId: r.tx.id,
|
|
341
389
|
userId: r.userId,
|
|
342
390
|
username: summary?.username ?? '',
|
|
343
|
-
amount:
|
|
391
|
+
amount: r.tx.amount,
|
|
344
392
|
currency: r.tx.currency,
|
|
345
393
|
rail: r.tx.rail ?? null,
|
|
346
394
|
status: r.tx.status,
|
|
@@ -374,7 +422,7 @@ export class WalletService {
|
|
|
374
422
|
if (current.status !== 'pending' || current.type !== 'withdrawal') {
|
|
375
423
|
throw new WithdrawalNotPendingError();
|
|
376
424
|
}
|
|
377
|
-
|
|
425
|
+
return findOneOrThrow(await txn
|
|
378
426
|
.update(walletTransaction)
|
|
379
427
|
.set({
|
|
380
428
|
status: 'processing',
|
|
@@ -383,14 +431,13 @@ export class WalletService {
|
|
|
383
431
|
...(reviewReason !== undefined ? { reviewReason } : {}),
|
|
384
432
|
})
|
|
385
433
|
.where(eq(walletTransaction.id, withdrawalId))
|
|
386
|
-
.returning();
|
|
387
|
-
return updated;
|
|
434
|
+
.returning(), new WithdrawalNotFoundError(withdrawalId));
|
|
388
435
|
}
|
|
389
436
|
// Phase two: settle a `processing` withdrawal via the PSP (Completed on success, Failed + refund on error).
|
|
390
437
|
// Runs OUTSIDE the hold/flip transaction so the external PSP call never holds a DB lock.
|
|
391
438
|
async settleApproved(tx, adminId) {
|
|
392
439
|
const userId = await this.userIdForWallet(tx.walletId);
|
|
393
|
-
const amount =
|
|
440
|
+
const amount = tx.amount;
|
|
394
441
|
// approved/failed are admin-attributed events (schema requires a uuid adminId); the system auto path
|
|
395
442
|
// skips them - its trail is the AUDIT_WRITER entry plus the shared `completed` event below.
|
|
396
443
|
if (adminId) {
|
|
@@ -420,7 +467,7 @@ export class WalletService {
|
|
|
420
467
|
.where(eq(walletTransaction.id, tx.id));
|
|
421
468
|
await txn
|
|
422
469
|
.update(wallet)
|
|
423
|
-
.set({ balance: sql `${wallet.balance} + ${amount}` })
|
|
470
|
+
.set({ balance: sql `${wallet.balance} + ${amount}::numeric` })
|
|
424
471
|
.where(eq(wallet.id, tx.walletId));
|
|
425
472
|
});
|
|
426
473
|
if (adminId) {
|
|
@@ -454,22 +501,25 @@ export class WalletService {
|
|
|
454
501
|
// leave the row pending for the manual queue. NEVER throws: any error fails closed to manual.
|
|
455
502
|
async maybeAutoApprove(args) {
|
|
456
503
|
// Cheap gates first (auto-approval off, or crypto - never auto-approved): skip opening a tx/lock.
|
|
457
|
-
if (!this.platformConfig?.autoWithdrawal?.enabled || args.rail !== 'fiat')
|
|
504
|
+
if (!this.platformConfig?.autoWithdrawal?.enabled || args.rail !== 'fiat') {
|
|
458
505
|
return undefined;
|
|
506
|
+
}
|
|
459
507
|
try {
|
|
460
508
|
const cfg = this.platformConfig.autoWithdrawal;
|
|
461
509
|
// Threshold/KYC/risk/velocity gates run outside any lock; only the cap check below needs serializing.
|
|
462
510
|
const gates = await this.evaluateAutoApproval(args);
|
|
463
|
-
if (!gates)
|
|
511
|
+
if (!gates) {
|
|
464
512
|
return undefined;
|
|
513
|
+
}
|
|
465
514
|
// Serialize the cap-check-and-flip per user with an advisory lock: without it two concurrent
|
|
466
515
|
// withdrawals read the same daily-cap usage and both auto-approve, bypassing the cap. The cap read
|
|
467
516
|
// and the flip that writes the marker commit together, so a blocked caller re-reads current usage.
|
|
468
517
|
// PSP settlement runs AFTER, outside the lock.
|
|
469
518
|
const decided = await this.drizzle.db.transaction((txn) => withAdvisoryXactLock(txn, args.userId, async () => {
|
|
470
519
|
const caps = await this.autoApprovalCaps({ walletId: args.walletId, amount: args.amount, cfg }, txn);
|
|
471
|
-
if (caps.exceeded)
|
|
520
|
+
if (caps.exceeded) {
|
|
472
521
|
return null;
|
|
522
|
+
}
|
|
473
523
|
const tx = await this.flipToProcessing({
|
|
474
524
|
txn,
|
|
475
525
|
withdrawalId: args.transactionId,
|
|
@@ -478,8 +528,9 @@ export class WalletService {
|
|
|
478
528
|
});
|
|
479
529
|
return { tx, caps };
|
|
480
530
|
}));
|
|
481
|
-
if (!decided)
|
|
531
|
+
if (!decided) {
|
|
482
532
|
return undefined;
|
|
533
|
+
}
|
|
483
534
|
const decision = {
|
|
484
535
|
...gates,
|
|
485
536
|
dailyCapAmount: cfg.dailyCapAmount ?? null,
|
|
@@ -494,7 +545,12 @@ export class WalletService {
|
|
|
494
545
|
action: 'wallet.withdrawal.auto_approved',
|
|
495
546
|
resourceType: 'wallet_transaction',
|
|
496
547
|
resourceId: args.transactionId,
|
|
497
|
-
after: {
|
|
548
|
+
after: {
|
|
549
|
+
userId: args.userId,
|
|
550
|
+
amount: args.amount,
|
|
551
|
+
currency: args.currency,
|
|
552
|
+
...decision,
|
|
553
|
+
},
|
|
498
554
|
});
|
|
499
555
|
}
|
|
500
556
|
catch (err) {
|
|
@@ -518,29 +574,37 @@ export class WalletService {
|
|
|
518
574
|
// route to manual. The daily-cap check is NOT here - it runs under the advisory lock (see maybeAutoApprove).
|
|
519
575
|
async evaluateAutoApproval({ userId, amount, walletId, rail, }) {
|
|
520
576
|
const cfg = this.platformConfig?.autoWithdrawal;
|
|
521
|
-
if (!cfg?.enabled)
|
|
577
|
+
if (!cfg?.enabled) {
|
|
522
578
|
return null;
|
|
579
|
+
}
|
|
523
580
|
// Hard stop: crypto is irreversible and AML-sensitive - never auto-approved.
|
|
524
|
-
if (rail !== 'fiat')
|
|
581
|
+
if (rail !== 'fiat') {
|
|
525
582
|
return null;
|
|
583
|
+
}
|
|
526
584
|
const threshold = await this.resolveAutoThreshold(userId);
|
|
527
|
-
if (!threshold || threshold.value <= 0)
|
|
585
|
+
if (!threshold || moneyToNumber(threshold.value) <= 0) {
|
|
528
586
|
return null;
|
|
529
|
-
|
|
587
|
+
}
|
|
588
|
+
if (moneyToNumber(amount) > moneyToNumber(threshold.value)) {
|
|
530
589
|
return null;
|
|
590
|
+
}
|
|
531
591
|
// Independent of kyc.gateWithdrawals: auto-approval always demands a passing status; anything else fails closed.
|
|
532
592
|
const kycStatus = await this.autoApprovalKycStatus(userId);
|
|
533
|
-
if (!kycStatus || !KYC_PASS_STATUSES.has(kycStatus))
|
|
593
|
+
if (!kycStatus || !KYC_PASS_STATUSES.has(kycStatus)) {
|
|
534
594
|
return null;
|
|
595
|
+
}
|
|
535
596
|
const riskTags = await this.autoApprovalRiskTags(userId, cfg.excludeRiskFlags);
|
|
536
597
|
// null = exclusions configured but the lookup port is unavailable => fail closed.
|
|
537
|
-
if (riskTags === null)
|
|
598
|
+
if (riskTags === null) {
|
|
538
599
|
return null;
|
|
539
|
-
|
|
600
|
+
}
|
|
601
|
+
if (riskTags.some((t) => cfg.excludeRiskFlags.includes(t))) {
|
|
540
602
|
return null;
|
|
603
|
+
}
|
|
541
604
|
const heuristics = await this.autoApprovalHeuristics({ walletId, amount });
|
|
542
|
-
if (heuristics.largeAmount || heuristics.highFrequency)
|
|
605
|
+
if (heuristics.largeAmount || heuristics.highFrequency) {
|
|
543
606
|
return null;
|
|
607
|
+
}
|
|
544
608
|
return {
|
|
545
609
|
threshold: threshold.value,
|
|
546
610
|
thresholdSource: threshold.source,
|
|
@@ -554,33 +618,38 @@ export class WalletService {
|
|
|
554
618
|
.select()
|
|
555
619
|
.from(autoWithdrawalRule)
|
|
556
620
|
.where(eq(autoWithdrawalRule.userId, userId));
|
|
557
|
-
if (rule)
|
|
558
|
-
return { value:
|
|
621
|
+
if (rule) {
|
|
622
|
+
return { value: rule.threshold, source: 'per-player' };
|
|
623
|
+
}
|
|
559
624
|
const global = this.platformConfig?.autoWithdrawal?.fiatThreshold;
|
|
560
|
-
if (global !== undefined)
|
|
625
|
+
if (global !== undefined) {
|
|
561
626
|
return { value: global, source: 'global' };
|
|
627
|
+
}
|
|
562
628
|
return null;
|
|
563
629
|
}
|
|
564
630
|
async autoApprovalKycStatus(userId) {
|
|
565
631
|
// No directory bound => cannot verify KYC => fail closed.
|
|
566
|
-
if (!this.directory)
|
|
632
|
+
if (!this.directory) {
|
|
567
633
|
return null;
|
|
634
|
+
}
|
|
568
635
|
const [summary] = await this.directory.lookupPlayers([userId]);
|
|
569
636
|
return summary?.kycStatus ?? null;
|
|
570
637
|
}
|
|
571
638
|
// Active tag keys; [] when no exclusions configured or none carried; null when configured but the port is unbound (fail closed).
|
|
572
639
|
async autoApprovalRiskTags(userId, excludeRiskFlags) {
|
|
573
|
-
if (excludeRiskFlags.length === 0)
|
|
640
|
+
if (excludeRiskFlags.length === 0) {
|
|
574
641
|
return [];
|
|
575
|
-
|
|
642
|
+
}
|
|
643
|
+
if (!this.riskTags) {
|
|
576
644
|
return null;
|
|
645
|
+
}
|
|
577
646
|
const byUser = await this.riskTags.getActiveTagKeys([userId]);
|
|
578
647
|
return byUser.get(userId) ?? [];
|
|
579
648
|
}
|
|
580
649
|
async autoApprovalHeuristics({ walletId, amount, }) {
|
|
581
650
|
const frequent = await this.frequentWithdrawalWalletIds(this.drizzle.db, [walletId]);
|
|
582
651
|
return {
|
|
583
|
-
largeAmount: amount >= LARGE_WITHDRAWAL_THRESHOLD,
|
|
652
|
+
largeAmount: moneyToNumber(amount) >= moneyToNumber(LARGE_WITHDRAWAL_THRESHOLD),
|
|
584
653
|
highFrequency: frequent.has(walletId),
|
|
585
654
|
};
|
|
586
655
|
}
|
|
@@ -588,8 +657,9 @@ export class WalletService {
|
|
|
588
657
|
// Single source of the velocity check, shared by the review queue tag and the auto-approval heuristic.
|
|
589
658
|
async frequentWithdrawalWalletIds(db, walletIds) {
|
|
590
659
|
const frequent = new Set();
|
|
591
|
-
if (walletIds.length === 0)
|
|
660
|
+
if (walletIds.length === 0) {
|
|
592
661
|
return frequent;
|
|
662
|
+
}
|
|
593
663
|
const since = new Date(Date.now() - HIGH_FREQUENCY_WINDOW_MS);
|
|
594
664
|
const counts = await db
|
|
595
665
|
.select({ walletId: walletTransaction.walletId, n: count() })
|
|
@@ -597,8 +667,9 @@ export class WalletService {
|
|
|
597
667
|
.where(and(eq(walletTransaction.type, 'withdrawal'), gte(walletTransaction.createdAt, since), inArray(walletTransaction.walletId, walletIds)))
|
|
598
668
|
.groupBy(walletTransaction.walletId);
|
|
599
669
|
for (const row of counts) {
|
|
600
|
-
if (Number(row.n) >= HIGH_FREQUENCY_MIN_COUNT)
|
|
670
|
+
if (Number(row.n) >= HIGH_FREQUENCY_MIN_COUNT) {
|
|
601
671
|
frequent.add(row.walletId);
|
|
672
|
+
}
|
|
602
673
|
}
|
|
603
674
|
return frequent;
|
|
604
675
|
}
|
|
@@ -607,24 +678,30 @@ export class WalletService {
|
|
|
607
678
|
async autoApprovalCaps({ walletId, amount, cfg, }, db) {
|
|
608
679
|
const since = new Date(Date.now() - DAILY_CAP_WINDOW_MS);
|
|
609
680
|
const [row] = await db
|
|
610
|
-
.select({
|
|
681
|
+
.select({
|
|
682
|
+
total: sql `coalesce(sum(${walletTransaction.amount}), 0)`,
|
|
683
|
+
n: count(),
|
|
684
|
+
})
|
|
611
685
|
.from(walletTransaction)
|
|
612
686
|
.where(and(eq(walletTransaction.walletId, walletId), eq(walletTransaction.type, 'withdrawal'), eq(walletTransaction.reviewReason, AUTO_APPROVED_REASON), gte(walletTransaction.createdAt, since)));
|
|
613
|
-
const amountUsed =
|
|
687
|
+
const amountUsed = row?.total ?? '0';
|
|
614
688
|
const countUsed = Number(row?.n ?? 0);
|
|
615
|
-
|
|
689
|
+
// Cap comparison is a review-queue decision, not a ledger write - moneyToNumber is the
|
|
690
|
+
// documented single conversion point (see the helper's own doc comment).
|
|
691
|
+
const amountExceeded = cfg.dailyCapAmount !== undefined &&
|
|
692
|
+
moneyToNumber(amountUsed) + moneyToNumber(amount) > moneyToNumber(cfg.dailyCapAmount);
|
|
616
693
|
const countExceeded = cfg.dailyCapCount !== undefined && countUsed + 1 > cfg.dailyCapCount;
|
|
617
694
|
return { exceeded: amountExceeded || countExceeded, amountUsed, countUsed };
|
|
618
695
|
}
|
|
619
696
|
async setAutoWithdrawalRule({ userId, threshold, reason, createdBy, }) {
|
|
620
|
-
const
|
|
697
|
+
const row = findOneOrThrow(await this.drizzle.db
|
|
621
698
|
.insert(autoWithdrawalRule)
|
|
622
|
-
.values({ userId, threshold
|
|
699
|
+
.values({ userId, threshold, reason, createdBy })
|
|
623
700
|
.onConflictDoUpdate({
|
|
624
701
|
target: autoWithdrawalRule.userId,
|
|
625
|
-
set: { threshold
|
|
702
|
+
set: { threshold, reason, createdBy },
|
|
626
703
|
})
|
|
627
|
-
.returning();
|
|
704
|
+
.returning(), new WalletNotFoundError(userId));
|
|
628
705
|
return toAutoWithdrawalRuleDto(row);
|
|
629
706
|
}
|
|
630
707
|
async getAutoWithdrawalRule(userId) {
|
|
@@ -652,21 +729,21 @@ export class WalletService {
|
|
|
652
729
|
if (current.status !== 'pending' || current.type !== 'withdrawal') {
|
|
653
730
|
throw new WithdrawalNotPendingError();
|
|
654
731
|
}
|
|
655
|
-
const
|
|
732
|
+
const updated = findOneOrThrow(await txn
|
|
656
733
|
.update(walletTransaction)
|
|
657
734
|
.set({ status: 'rejected', reviewedBy: adminId, reviewedAt, reviewReason: reason })
|
|
658
735
|
.where(eq(walletTransaction.id, withdrawalId))
|
|
659
|
-
.returning();
|
|
736
|
+
.returning(), new WithdrawalNotFoundError(withdrawalId));
|
|
660
737
|
await txn
|
|
661
738
|
.update(wallet)
|
|
662
|
-
.set({ balance: sql `${wallet.balance} + ${updated.amount}` })
|
|
739
|
+
.set({ balance: sql `${wallet.balance} + ${updated.amount}::numeric` })
|
|
663
740
|
.where(eq(wallet.id, updated.walletId));
|
|
664
741
|
return updated;
|
|
665
742
|
});
|
|
666
743
|
const userId = await this.userIdForWallet(tx.walletId);
|
|
667
744
|
this.events.emit('wallet.withdrawal.rejected', {
|
|
668
745
|
userId,
|
|
669
|
-
amount:
|
|
746
|
+
amount: tx.amount,
|
|
670
747
|
currency: tx.currency,
|
|
671
748
|
transactionId: tx.id,
|
|
672
749
|
adminId,
|
|
@@ -677,8 +754,9 @@ export class WalletService {
|
|
|
677
754
|
async getTransactions(userId, page, limit) {
|
|
678
755
|
const db = this.drizzle.db;
|
|
679
756
|
const [walletRecord] = await db.select().from(wallet).where(eq(wallet.userId, userId));
|
|
680
|
-
if (!walletRecord)
|
|
757
|
+
if (!walletRecord) {
|
|
681
758
|
return { items: [], total: 0, page, limit };
|
|
759
|
+
}
|
|
682
760
|
const where = eq(walletTransaction.walletId, walletRecord.id);
|
|
683
761
|
const [txs, [{ n }]] = await Promise.all([
|
|
684
762
|
db
|
|
@@ -694,7 +772,7 @@ export class WalletService {
|
|
|
694
772
|
items: txs.map((tx) => ({
|
|
695
773
|
id: tx.id,
|
|
696
774
|
type: tx.type,
|
|
697
|
-
amount:
|
|
775
|
+
amount: tx.amount,
|
|
698
776
|
currency: tx.currency,
|
|
699
777
|
status: tx.status,
|
|
700
778
|
createdAt: tx.createdAt.toISOString(),
|