Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions docs/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@
"tables": [
"auto_withdrawal_rule",
"wallet",
"wallet_asset",
"wallet_auto_withdrawal_config",
"wallet_balance",
"wallet_deposit_address",
Expand All @@ -388,6 +389,8 @@
],
"routes": [
"wallet.approve",
"wallet.create",
"wallet.delete",
"wallet.delete",
"wallet.deposit",
"wallet.get",
Expand All @@ -396,12 +399,15 @@
"wallet.getBalance",
"wallet.getBalances",
"wallet.list",
"wallet.list",
"wallet.listAssets",
"wallet.listPlayerTransactions",
"wallet.listTransactions",
"wallet.reject",
"wallet.set",
"wallet.set",
"wallet.setActiveCurrency",
"wallet.update",
"wallet.webhook",
"wallet.withdraw"
]
Expand Down Expand Up @@ -802,6 +808,15 @@
"packages/core/src/wallet/plugin.ts"
]
},
{
"category": "wallet-asset-catalog",
"interface": "WalletAsset",
"token": "WALLET_ASSET_CATALOG",
"status": "wired",
"boundIn": [
"packages/core/src/wallet/plugin.ts"
]
},
{
"category": "wallet-commands",
"interface": "WalletDebitArgs",
Expand Down Expand Up @@ -1221,6 +1236,10 @@
"name": "createTagSchema",
"file": "packages/core/src/contracts/schemas/tag.ts"
},
{
"name": "CreateWalletAssetInputSchema",
"file": "packages/core/src/wallet/contract/index.ts"
},
{
"name": "CurrencyCodeSchema",
"file": "packages/core/src/contracts/schemas/igaming-config.ts"
Expand Down Expand Up @@ -1765,6 +1784,10 @@
"name": "ProviderSelectionSchema",
"file": "packages/core/src/contracts/schemas/igaming-config.ts"
},
{
"name": "PublicWalletAssetSchema",
"file": "packages/core/src/wallet/contract/index.ts"
},
{
"name": "RainCommandMetadataSchema",
"file": "packages/core/src/contracts/schemas/chat-command-metadata.ts"
Expand Down Expand Up @@ -1973,6 +1996,10 @@
"name": "updateTagSchema",
"file": "packages/core/src/contracts/schemas/tag.ts"
},
{
"name": "UpdateWalletAssetInputSchema",
"file": "packages/core/src/wallet/contract/index.ts"
},
{
"name": "UpsertLimitInputSchema",
"file": "packages/core/src/compliance/contract/limits.ts"
Expand Down Expand Up @@ -2009,6 +2036,14 @@
"name": "VerifyPasswordResetOtpInputSchema",
"file": "packages/core/src/contracts/schemas/identity.ts"
},
{
"name": "WalletAssetKeySchema",
"file": "packages/core/src/wallet/contract/index.ts"
},
{
"name": "WalletAssetSchema",
"file": "packages/core/src/wallet/contract/index.ts"
},
{
"name": "WalletAutoWithdrawalConfigSchema",
"file": "packages/core/src/wallet/contract/index.ts"
Expand Down
10 changes: 9 additions & 1 deletion packages/core/src/contracts/adapters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,17 @@ export {
KycCheckResultSchema,
} from './kyc.js';

export type { PaymentAdapter, PaymentWebhookEvent, PaymentWebhookVerifier } from './payment.js';
export type {
PaymentAdapter,
PaymentWebhookEvent,
PaymentWebhookVerifier,
CustodyBalance,
} from './payment.js';
export { PAYMENT_ADAPTER, PAYMENT_WEBHOOK_VERIFIER } from './payment.js';

export type { WalletAsset, WalletAssetCatalog } from './wallet-asset-catalog.js';
export { WALLET_ASSET_CATALOG } from './wallet-asset-catalog.js';

export type { GeoIpAdapter } from './geo-ip.js';
export { GEO_IP_ADAPTER } from './geo-ip.js';

Expand Down
57 changes: 57 additions & 0 deletions packages/core/src/contracts/adapters/payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ export type PaymentWebhookEvent =
txHash?: string;
};

/**
* A balance sitting in a per-player custody container that is not yet in the pooled
* account withdrawals are paid from. Produced by `PaymentAdapter.listSweepableBalances`
* and handed back to `sweepToPool` unchanged.
*/
export type CustodyBalance = {
userId: string;
currency: string;
network: string;
amount: string;
/** Current network cost to move it, same units as `amount`. */
estimatedFee: string;
};

export type PaymentAdapter = {
processDeposit(
amount: string,
Expand Down Expand Up @@ -80,6 +94,49 @@ export type PaymentAdapter = {
rawBody: string,
headers: Record<string, string | string[] | undefined>,
): PaymentWebhookEvent | null;

/**
* Whether this adapter can actually serve the given asset. The asset catalog is
* operator-editable at runtime, so an admin can name a (currency, network) pair the
* bound vendor has never heard of; the catalog's write path calls this first and
* rejects rather than letting the pair reach a player's deposit screen. Optional -
* an adapter that omits it is assumed to accept anything the operator configures.
*/
supportsAsset?(currency: string, network: string): boolean;

/**
* Per-player balances the vendor holds that are not yet in the pooled account.
* Only meaningful for a custody vendor whose per-player deposit containers are
* distinct from the account withdrawals are paid out of - a synchronous PSP, which
* never holds a per-player balance, omits it.
*/
listSweepableBalances?(): Promise<CustodyBalance[]>;

/**
* Move one player's balance into the pooled account. Implemented alongside
* `listSweepableBalances`; the caller owns the policy (dust floor, fee thresholds)
* and this only performs the transfer it is handed.
*/
sweepToPool?(balance: CustodyBalance): Promise<{ externalId: string }>;

/**
* Vendor transactions in a window, normalized into the same events `parseWebhook`
* produces - reconciliation is that same normalization, polled instead of pushed.
* Implemented by a vendor whose ledger can be listed after the fact; a PSP that only
* pushes webhooks omits it.
*/
listTransactions?(range: { since: Date; until: Date }): Promise<PaymentWebhookEvent[]>;

/**
* Targeted status lookup for a single withdrawal, or null when the vendor has no
* record of it. Not redundant with `listTransactions`: a withdrawal stuck in
* `processing` for days falls outside any sane reconciliation window, so finalizing
* it needs a direct lookup by `externalId`.
*/
getWithdrawalStatus?(externalId: string): Promise<{
status: 'processing' | 'completed' | 'failed';
txHash?: string;
} | null>;
};

export const PAYMENT_ADAPTER: Token<PaymentAdapter> = createToken('PAYMENT_ADAPTER');
Expand Down
37 changes: 37 additions & 0 deletions packages/core/src/contracts/adapters/wallet-asset-catalog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { createToken } from './token.js';

/**
* One operator-configured (currency, network) pair the platform accepts deposits for or
* pays withdrawals out of. Rows are editable at runtime from the admin surface, so this
* is config rather than code: adding a currency is not a deploy.
*/
export type WalletAsset = {
currency: string;
network: string;
/**
* The bound payment vendor's own identifier for this asset (eg a custody vendor's
* `USDT_ERC20`). Opaque - the wallet module stores and returns it, never parses it,
* so a different vendor's identifier scheme needs no core change.
*/
providerAssetId: string;
minDeposit: string;
minWithdrawal: string;
withdrawalFee: string;
depositEnabled: boolean;
withdrawalEnabled: boolean;
};

/**
* Read side of the asset catalog, for an adapter that needs the operator's asset table
* without importing wallet internals.
*
* Unlike most ports here, the wallet module binds a DB-backed default implementation to
* this token itself (as with `CACHE`/`RATE_LIMITER`), so an operator gets a working
* catalog with no wiring; rebinding it is possible but not required.
*/
export type WalletAssetCatalog = {
list(): Promise<WalletAsset[]>;
get(currency: string, network: string): Promise<WalletAsset | null>;
};

export const WALLET_ASSET_CATALOG = createToken<WalletAssetCatalog>('WALLET_ASSET_CATALOG');
2 changes: 2 additions & 0 deletions packages/core/src/server/auth/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const statement = {
tag: ['view', 'create', 'delete'] as const,
'chat-room': ['view', 'create', 'update', 'delete'] as const,
'auto-withdrawal-config': ['view', 'update'] as const,
'wallet-asset': ['view', 'create', 'update', 'delete'] as const,
'chat-command': ['view', 'update'] as const,
'chat-moderation': ['view', 'moderate'] as const,
} as const;
Expand Down Expand Up @@ -48,6 +49,7 @@ export const adminRole = ac.newRole({
tag: ['view', 'create', 'delete'],
'chat-room': ['view', 'create', 'update', 'delete'],
'auto-withdrawal-config': ['view', 'update'],
'wallet-asset': ['view', 'create', 'update', 'delete'],
'chat-command': ['view', 'update'],
'chat-moderation': ['view', 'moderate'],
});
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/server/runtime/core-token-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
SMS_ADAPTER,
SOCIAL_COMMANDS,
TAG_EVALUATION_COMMANDS,
WALLET_ASSET_CATALOG,
WALLET_COMMANDS,
WALLET_READER,
} from '@openora/core/contracts';
Expand Down Expand Up @@ -104,6 +105,7 @@ const coreTokenCatalog = {
SMS_ADAPTER,
SOCIAL_COMMANDS,
TAG_EVALUATION_COMMANDS,
WALLET_ASSET_CATALOG,
WALLET_COMMANDS,
WALLET_READER,
} satisfies TokenCatalog;
Expand Down
Loading
Loading