@moneypot/hub 1.17.1 → 1.18.0-dev.1
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/dist/src/db/index.js +3 -1
- package/dist/src/db/public.d.ts +4 -1
- package/dist/src/db/public.js +20 -11
- package/dist/src/hash-chain/plugins/hub-bad-hash-chain-error.js +1 -1
- package/dist/src/plugins/chat/hub-chat-create-user-message.js +1 -1
- package/dist/src/plugins/chat/hub-chat-subscription.js +1 -1
- package/dist/src/plugins/hub-make-outcome-bet.js +95 -10
- package/dist/src/process-transfers/process-transfer.js +1 -2
- package/dist/src/risk-policy.d.ts +12 -3
- package/dist/src/risk-policy.js +28 -37
- package/package.json +3 -1
package/dist/src/db/index.js
CHANGED
|
@@ -82,7 +82,9 @@ export class DatabaseNotifier extends stream.EventEmitter {
|
|
|
82
82
|
}
|
|
83
83
|
export async function getTransferCursor(pgClient, { casinoId, }) {
|
|
84
84
|
const row = await pgClient
|
|
85
|
-
.query("select cursor from hub_hidden.transfer_cursor where casino_id = $1", [
|
|
85
|
+
.query("select cursor from hub_hidden.transfer_cursor where casino_id = $1", [
|
|
86
|
+
casinoId,
|
|
87
|
+
])
|
|
86
88
|
.then(maybeOneRow);
|
|
87
89
|
return row?.cursor;
|
|
88
90
|
}
|
package/dist/src/db/public.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { DbBalance, DbBankroll, DbCurrency, DbSession } from "./types.js";
|
|
1
|
+
import { DbBalance, DbBankroll, DbCasino, DbCurrency, DbSession } from "./types.js";
|
|
2
2
|
import { PgClientInTransaction, QueryExecutor } from "./index.js";
|
|
3
3
|
export declare function dbGetActiveSessionById(pgClient: QueryExecutor, sessionId: string): Promise<DbSession | undefined>;
|
|
4
4
|
export declare function dbGetCasinoCurrencyByKey(pgClient: QueryExecutor, { currencyKey, casinoId }: {
|
|
5
5
|
currencyKey: string;
|
|
6
6
|
casinoId: string;
|
|
7
7
|
}): Promise<DbCurrency | undefined>;
|
|
8
|
+
export declare function dbGetHouseBankrolls(pgClient: QueryExecutor, { casinoId }: {
|
|
9
|
+
casinoId: DbCasino["id"];
|
|
10
|
+
}): Promise<Pick<DbBankroll, "id" | "currency_key" | "amount">[]>;
|
|
8
11
|
export declare function dbLockPlayerBalance(pgClient: PgClientInTransaction, { userId, casinoId, experienceId, currencyKey, }: {
|
|
9
12
|
userId: string;
|
|
10
13
|
casinoId: string;
|
package/dist/src/db/public.js
CHANGED
|
@@ -13,20 +13,29 @@ export async function dbGetCasinoCurrencyByKey(pgClient, { currencyKey, casinoId
|
|
|
13
13
|
.query(`
|
|
14
14
|
SELECT *
|
|
15
15
|
FROM hub.currency
|
|
16
|
-
WHERE key = $1
|
|
16
|
+
WHERE key = $1
|
|
17
17
|
AND casino_id = $2
|
|
18
18
|
`, [currencyKey, casinoId])
|
|
19
19
|
.then(maybeOneRow);
|
|
20
20
|
}
|
|
21
|
+
export async function dbGetHouseBankrolls(pgClient, { casinoId }) {
|
|
22
|
+
return pgClient
|
|
23
|
+
.query(`
|
|
24
|
+
SELECT id, currency_key, amount
|
|
25
|
+
FROM hub.bankroll
|
|
26
|
+
WHERE casino_id = $1
|
|
27
|
+
`, [casinoId])
|
|
28
|
+
.then((res) => res.rows);
|
|
29
|
+
}
|
|
21
30
|
export async function dbLockPlayerBalance(pgClient, { userId, casinoId, experienceId, currencyKey, }) {
|
|
22
31
|
return pgClient
|
|
23
32
|
.query(`
|
|
24
|
-
SELECT *
|
|
25
|
-
FROM hub.balance
|
|
26
|
-
WHERE user_id = $1
|
|
27
|
-
AND casino_id = $2
|
|
28
|
-
AND experience_id = $3
|
|
29
|
-
AND currency_key = $4
|
|
33
|
+
SELECT *
|
|
34
|
+
FROM hub.balance
|
|
35
|
+
WHERE user_id = $1
|
|
36
|
+
AND casino_id = $2
|
|
37
|
+
AND experience_id = $3
|
|
38
|
+
AND currency_key = $4
|
|
30
39
|
FOR UPDATE
|
|
31
40
|
`, [userId, casinoId, experienceId, currencyKey])
|
|
32
41
|
.then(maybeOneRow)
|
|
@@ -35,10 +44,10 @@ export async function dbLockPlayerBalance(pgClient, { userId, casinoId, experien
|
|
|
35
44
|
export async function dbLockHouseBankroll(pgClient, { casinoId, currencyKey }) {
|
|
36
45
|
return pgClient
|
|
37
46
|
.query(`
|
|
38
|
-
SELECT *
|
|
39
|
-
FROM hub.bankroll
|
|
40
|
-
WHERE casino_id = $1
|
|
41
|
-
AND currency_key = $2
|
|
47
|
+
SELECT *
|
|
48
|
+
FROM hub.bankroll
|
|
49
|
+
WHERE casino_id = $1
|
|
50
|
+
AND currency_key = $2
|
|
42
51
|
FOR UPDATE
|
|
43
52
|
`, [casinoId, currencyKey])
|
|
44
53
|
.then(maybeOneRow)
|
|
@@ -2,7 +2,7 @@ import { access, context, object, ObjectStep, sideEffect, } from "postgraphile/g
|
|
|
2
2
|
import { gql, extendSchema } from "postgraphile/utils";
|
|
3
3
|
import * as z from "zod/v4";
|
|
4
4
|
import { GraphQLError } from "graphql";
|
|
5
|
-
import { DbHashKind, dbLockHouseBankroll, dbLockPlayerBalance, exactlyOneRow, maybeOneRow, withPgPoolTransaction, } from "../db/index.js";
|
|
5
|
+
import { DbHashKind, dbGetHouseBankrolls, dbLockHouseBankroll, dbLockPlayerBalance, exactlyOneRow, maybeOneRow, withPgPoolTransaction, } from "../db/index.js";
|
|
6
6
|
import { assert } from "tsafe";
|
|
7
7
|
import { dbInsertHubHash, dbLockHubHashChain, } from "../hash-chain/db-hash-chain.js";
|
|
8
8
|
import { getIntermediateHash } from "../hash-chain/get-hash.js";
|
|
@@ -92,7 +92,12 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
92
92
|
bet: HubOutcomeBet!
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
type HubRiskError {
|
|
96
|
+
message: String!
|
|
97
|
+
riskLimits: HubRiskLimit!
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
union HubMakeOutcomeBetResult = HubMakeOutcomeBetSuccess | HubBadHashChainError | HubRiskError
|
|
96
101
|
|
|
97
102
|
type HubMakeOutcomeBetPayload {
|
|
98
103
|
result: HubMakeOutcomeBetResult!
|
|
@@ -101,10 +106,58 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
101
106
|
extend type Mutation {
|
|
102
107
|
hubMakeOutcomeBet(input: HubMakeOutcomeBetInput!): HubMakeOutcomeBetPayload
|
|
103
108
|
}
|
|
109
|
+
|
|
110
|
+
type HubRiskLimit {
|
|
111
|
+
maxPayout: Float!
|
|
112
|
+
maxWager: Float
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
type HubRiskLimitWithCurrency {
|
|
116
|
+
currency: String!
|
|
117
|
+
maxPayout: Float!
|
|
118
|
+
maxWager: Float
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
extend type Query {
|
|
122
|
+
hubRiskLimits(betKind: BetKind!): [HubRiskLimitWithCurrency!]!
|
|
123
|
+
}
|
|
104
124
|
`;
|
|
105
125
|
return {
|
|
106
126
|
typeDefs,
|
|
107
127
|
objects: {
|
|
128
|
+
Query: {
|
|
129
|
+
plans: {
|
|
130
|
+
hubRiskLimits: (_, { $betKind }) => {
|
|
131
|
+
const $identity = context().get("identity");
|
|
132
|
+
const $superuserPool = context().get("superuserPool");
|
|
133
|
+
const $result = sideEffect([$identity, $superuserPool, $betKind], async ([identity, superuserPool, betKind]) => {
|
|
134
|
+
if (identity?.kind !== "user") {
|
|
135
|
+
throw new GraphQLError("Unauthorized");
|
|
136
|
+
}
|
|
137
|
+
const betConfig = betConfigs[betKind];
|
|
138
|
+
if (!betConfig) {
|
|
139
|
+
throw new GraphQLError(`Invalid bet kind`);
|
|
140
|
+
}
|
|
141
|
+
const dbHouseBankrolls = await dbGetHouseBankrolls(superuserPool, {
|
|
142
|
+
casinoId: identity.session.casino_id,
|
|
143
|
+
});
|
|
144
|
+
const limitsWithCurrency = dbHouseBankrolls.map((bankroll) => {
|
|
145
|
+
const limits = betConfig.riskPolicy({
|
|
146
|
+
type: "get-limits",
|
|
147
|
+
currency: bankroll.currency_key,
|
|
148
|
+
bankroll: bankroll.amount,
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
currency: bankroll.currency_key,
|
|
152
|
+
...limits,
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
return limitsWithCurrency;
|
|
156
|
+
});
|
|
157
|
+
return $result;
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
},
|
|
108
161
|
Mutation: {
|
|
109
162
|
plans: {
|
|
110
163
|
hubMakeOutcomeBet: (_, { $input }) => {
|
|
@@ -284,18 +337,28 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
284
337
|
}
|
|
285
338
|
const maxProfitMultiplier = Math.max(...input.outcomes.map((o) => o.profit));
|
|
286
339
|
const maxPotentialPayout = input.wager * maxProfitMultiplier;
|
|
340
|
+
const riskLimits = betConfig.riskPolicy({
|
|
341
|
+
type: "get-limits",
|
|
342
|
+
currency: input.currency,
|
|
343
|
+
bankroll: dbHouseBankroll.amount,
|
|
344
|
+
});
|
|
287
345
|
const riskResult = validateRisk({
|
|
346
|
+
type: "validate-bet",
|
|
288
347
|
currency: input.currency,
|
|
289
348
|
wager: input.wager,
|
|
290
349
|
bankroll: dbHouseBankroll.amount,
|
|
291
350
|
maxPotentialPayout,
|
|
292
|
-
|
|
351
|
+
riskLimits,
|
|
293
352
|
displayUnitName: dbCurrency.display_unit_name,
|
|
294
353
|
displayUnitScale: dbCurrency.display_unit_scale,
|
|
295
354
|
outcomes: input.outcomes,
|
|
296
355
|
});
|
|
297
356
|
if (!riskResult.ok) {
|
|
298
|
-
|
|
357
|
+
return {
|
|
358
|
+
__typename: "HubRiskError",
|
|
359
|
+
message: riskResult.error.message,
|
|
360
|
+
riskLimits: riskResult.error.riskLimits,
|
|
361
|
+
};
|
|
299
362
|
}
|
|
300
363
|
await pgClient.query({
|
|
301
364
|
text: `
|
|
@@ -354,14 +417,14 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
354
417
|
.query({
|
|
355
418
|
text: `
|
|
356
419
|
INSERT INTO hub.outcome_bet (
|
|
357
|
-
user_id,
|
|
358
|
-
casino_id,
|
|
359
|
-
experience_id,
|
|
420
|
+
user_id,
|
|
421
|
+
casino_id,
|
|
422
|
+
experience_id,
|
|
360
423
|
hash_id,
|
|
361
424
|
kind,
|
|
362
|
-
currency_key,
|
|
363
|
-
wager,
|
|
364
|
-
profit,
|
|
425
|
+
currency_key,
|
|
426
|
+
wager,
|
|
427
|
+
profit,
|
|
365
428
|
outcomes,
|
|
366
429
|
outcome_idx,
|
|
367
430
|
metadata
|
|
@@ -422,6 +485,28 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
422
485
|
},
|
|
423
486
|
},
|
|
424
487
|
},
|
|
488
|
+
HubRiskError: {
|
|
489
|
+
assertStep: ObjectStep,
|
|
490
|
+
plans: {
|
|
491
|
+
message($data) {
|
|
492
|
+
return access($data, "message");
|
|
493
|
+
},
|
|
494
|
+
riskLimits($data) {
|
|
495
|
+
return access($data, "riskLimits");
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
HubRiskLimit: {
|
|
500
|
+
assertStep: ObjectStep,
|
|
501
|
+
plans: {
|
|
502
|
+
maxWager($data) {
|
|
503
|
+
return access($data, "maxWager");
|
|
504
|
+
},
|
|
505
|
+
maxPayout($data) {
|
|
506
|
+
return access($data, "maxPayout");
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
},
|
|
425
510
|
HubMakeOutcomeBetPayload: {
|
|
426
511
|
assertStep: ObjectStep,
|
|
427
512
|
plans: {
|
|
@@ -107,8 +107,7 @@ export async function processTransfer({ casinoId, controllerId, transfer, graphq
|
|
|
107
107
|
}
|
|
108
108
|
logger.debug(data, "MP_CLAIM_TRANSFER response");
|
|
109
109
|
if (data.claimTransfer?.result.__typename !== "ClaimTransferSuccess") {
|
|
110
|
-
if (data.claimTransfer?.result.__typename ===
|
|
111
|
-
"InvalidTransferStatus" &&
|
|
110
|
+
if (data.claimTransfer?.result.__typename === "InvalidTransferStatus" &&
|
|
112
111
|
data.claimTransfer?.result.currentStatus ===
|
|
113
112
|
TransferStatusKind.Completed) {
|
|
114
113
|
logger.info(`Transfer ${transfer.id} already claimed (status: COMPLETED), skipping claim but attempting deposit insert`);
|
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import { DbOutcome } from "./db/types.js";
|
|
2
2
|
import { Result } from "./util.js";
|
|
3
3
|
export type RiskPolicyArgs = {
|
|
4
|
+
type: "validate-bet";
|
|
4
5
|
currency: string;
|
|
5
6
|
wager: number;
|
|
6
7
|
bankroll: number;
|
|
7
8
|
maxPotentialPayout: number;
|
|
8
9
|
outcomes: DbOutcome[];
|
|
9
10
|
};
|
|
11
|
+
export type BetLimitPolicyArgs = {
|
|
12
|
+
type: "get-limits";
|
|
13
|
+
currency: string;
|
|
14
|
+
bankroll: number;
|
|
15
|
+
};
|
|
10
16
|
export type RiskLimits = {
|
|
11
17
|
maxWager?: number;
|
|
12
18
|
maxPayout: number;
|
|
13
19
|
};
|
|
14
|
-
export type RiskPolicy = (args: RiskPolicyArgs) => RiskLimits;
|
|
20
|
+
export type RiskPolicy = (args: RiskPolicyArgs | BetLimitPolicyArgs) => RiskLimits;
|
|
15
21
|
export declare function validateRisk(options: RiskPolicyArgs & {
|
|
16
|
-
|
|
22
|
+
riskLimits: RiskLimits;
|
|
17
23
|
} & {
|
|
18
24
|
displayUnitName: string;
|
|
19
25
|
displayUnitScale: number;
|
|
20
|
-
}): Result<void,
|
|
26
|
+
}): Result<void, {
|
|
27
|
+
message: string;
|
|
28
|
+
riskLimits: RiskLimits;
|
|
29
|
+
}>;
|
package/dist/src/risk-policy.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { formatCurrency } from "./format-currency.js";
|
|
2
|
-
import { logger } from "./logger.js";
|
|
3
2
|
import { z } from "zod/v4";
|
|
4
3
|
const RiskLimitsSchema = z
|
|
5
4
|
.object({
|
|
@@ -8,53 +7,45 @@ const RiskLimitsSchema = z
|
|
|
8
7
|
})
|
|
9
8
|
.strict();
|
|
10
9
|
export function validateRisk(options) {
|
|
11
|
-
const { wager, bankroll, maxPotentialPayout
|
|
10
|
+
const { wager, bankroll, maxPotentialPayout } = options;
|
|
12
11
|
if (maxPotentialPayout > bankroll) {
|
|
12
|
+
const message = `House cannot cover potential payout (${formatCurrency(maxPotentialPayout, {
|
|
13
|
+
displayUnitName: options.displayUnitName,
|
|
14
|
+
displayUnitScale: options.displayUnitScale,
|
|
15
|
+
})}). Bankroll: ${formatCurrency(bankroll, {
|
|
16
|
+
displayUnitName: options.displayUnitName,
|
|
17
|
+
displayUnitScale: options.displayUnitScale,
|
|
18
|
+
})}`;
|
|
13
19
|
return {
|
|
14
20
|
ok: false,
|
|
15
|
-
error:
|
|
16
|
-
displayUnitName: options.displayUnitName,
|
|
17
|
-
displayUnitScale: options.displayUnitScale,
|
|
18
|
-
})}). Bankroll: ${formatCurrency(bankroll, {
|
|
19
|
-
displayUnitName: options.displayUnitName,
|
|
20
|
-
displayUnitScale: options.displayUnitScale,
|
|
21
|
-
})}`,
|
|
21
|
+
error: { message, riskLimits: options.riskLimits },
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
const limits = limitsResult.data;
|
|
36
|
-
if (limits.maxWager !== undefined && wager > limits.maxWager) {
|
|
24
|
+
if (options.riskLimits.maxWager !== undefined &&
|
|
25
|
+
wager > options.riskLimits.maxWager) {
|
|
26
|
+
const message = `Wager (${formatCurrency(wager, {
|
|
27
|
+
displayUnitName: options.displayUnitName,
|
|
28
|
+
displayUnitScale: options.displayUnitScale,
|
|
29
|
+
})}) exceeds limit (${formatCurrency(options.riskLimits.maxWager, {
|
|
30
|
+
displayUnitName: options.displayUnitName,
|
|
31
|
+
displayUnitScale: options.displayUnitScale,
|
|
32
|
+
})})`;
|
|
37
33
|
return {
|
|
38
34
|
ok: false,
|
|
39
|
-
error:
|
|
40
|
-
displayUnitName: options.displayUnitName,
|
|
41
|
-
displayUnitScale: options.displayUnitScale,
|
|
42
|
-
})}) exceeds limit (${formatCurrency(limits.maxWager, {
|
|
43
|
-
displayUnitName: options.displayUnitName,
|
|
44
|
-
displayUnitScale: options.displayUnitScale,
|
|
45
|
-
})})`,
|
|
35
|
+
error: { message, riskLimits: options.riskLimits },
|
|
46
36
|
};
|
|
47
37
|
}
|
|
48
|
-
if (maxPotentialPayout >
|
|
38
|
+
if (maxPotentialPayout > options.riskLimits.maxPayout) {
|
|
39
|
+
const message = `Payout (${formatCurrency(maxPotentialPayout, {
|
|
40
|
+
displayUnitName: options.displayUnitName,
|
|
41
|
+
displayUnitScale: options.displayUnitScale,
|
|
42
|
+
})}) exceeds limit (${formatCurrency(options.riskLimits.maxPayout, {
|
|
43
|
+
displayUnitName: options.displayUnitName,
|
|
44
|
+
displayUnitScale: options.displayUnitScale,
|
|
45
|
+
})})`;
|
|
49
46
|
return {
|
|
50
47
|
ok: false,
|
|
51
|
-
error:
|
|
52
|
-
displayUnitName: options.displayUnitName,
|
|
53
|
-
displayUnitScale: options.displayUnitScale,
|
|
54
|
-
})}) exceeds limit (${formatCurrency(limits.maxPayout, {
|
|
55
|
-
displayUnitName: options.displayUnitName,
|
|
56
|
-
displayUnitScale: options.displayUnitScale,
|
|
57
|
-
})})`,
|
|
48
|
+
error: { message, riskLimits: options.riskLimits },
|
|
58
49
|
};
|
|
59
50
|
}
|
|
60
51
|
return { ok: true, value: undefined };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moneypot/hub",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0-dev.1",
|
|
4
4
|
"author": "moneypot.com",
|
|
5
5
|
"homepage": "https://moneypot.com/hub",
|
|
6
6
|
"keywords": [
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"check": "tsc --noEmit && npm run check-dashboard",
|
|
38
38
|
"check-tests": "tsc -p tests/tsconfig.json --noEmit",
|
|
39
39
|
"check-dashboard": "cd ./dashboard && npm run check",
|
|
40
|
+
"format": "prettier --write \"**/*.ts\"",
|
|
40
41
|
"lint": "eslint --fix . && npm run lint-dashboard",
|
|
41
42
|
"lint-dashboard": "cd ./dashboard && npm run lint",
|
|
42
43
|
"prepublishOnly": "npm run build",
|
|
@@ -77,6 +78,7 @@
|
|
|
77
78
|
"eslint": "^9.8.0",
|
|
78
79
|
"globals": "^16.0.0",
|
|
79
80
|
"pino-pretty": "^13.0.0",
|
|
81
|
+
"prettier": "^3.6.2",
|
|
80
82
|
"supertest": "^7.0.0",
|
|
81
83
|
"tsx": "^4.20.3",
|
|
82
84
|
"typescript": "^5.4.5",
|