@moneypot/hub 1.9.0-dev.9 → 1.10.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/cli/add-casino.js +24 -17
- package/dist/src/__generated__/gql.d.ts +2 -2
- package/dist/src/__generated__/gql.js +1 -1
- package/dist/src/__generated__/graphql.d.ts +23 -5
- package/dist/src/__generated__/graphql.js +3 -1
- package/dist/src/context.d.ts +12 -0
- package/dist/src/context.js +31 -0
- package/dist/src/db/index.d.ts +3 -6
- package/dist/src/db/index.js +5 -13
- package/dist/src/db/types.d.ts +1 -0
- package/dist/src/db/util.d.ts +4 -0
- package/dist/src/hash-chain/plugins/hub-create-hash-chain.js +3 -2
- package/dist/src/index.d.ts +7 -4
- package/dist/src/index.js +23 -12
- package/dist/src/pg-versions/010-take-request-refunded-at.sql +1 -0
- package/dist/src/plugins/hub-add-casino.js +4 -2
- package/dist/src/plugins/hub-authenticate.js +3 -2
- package/dist/src/plugins/hub-claim-faucet.js +5 -3
- package/dist/src/plugins/hub-make-outcome-bet.d.ts +1 -10
- package/dist/src/plugins/hub-make-outcome-bet.js +20 -55
- package/dist/src/plugins/hub-withdraw.js +3 -2
- package/dist/src/process-transfers/index.d.ts +5 -2
- package/dist/src/process-transfers/index.js +15 -11
- package/dist/src/process-transfers/polling-processor.d.ts +3 -1
- package/dist/src/process-transfers/polling-processor.js +17 -13
- package/dist/src/process-transfers/process-transfer.d.ts +3 -1
- package/dist/src/process-transfers/process-transfer.js +7 -8
- package/dist/src/process-transfers/websocket-processor.d.ts +3 -1
- package/dist/src/process-transfers/websocket-processor.js +3 -1
- package/dist/src/process-withdrawal-request.d.ts +3 -1
- package/dist/src/process-withdrawal-request.js +6 -5
- package/dist/src/risk-policy.d.ts +20 -0
- package/dist/src/risk-policy.js +61 -0
- package/dist/src/server/graphile.config.d.ts +3 -1
- package/dist/src/server/graphile.config.js +6 -5
- package/dist/src/server/index.d.ts +3 -1
- package/dist/src/server/index.js +5 -4
- package/dist/src/server/middleware/authentication.d.ts +2 -1
- package/dist/src/server/middleware/authentication.js +5 -5
- package/dist/src/take-request/process-take-request.d.ts +13 -2
- package/dist/src/take-request/process-take-request.js +70 -23
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import { GET_USER_FROM_USER_TOKEN } from "../graphql-queries.js";
|
|
|
5
5
|
import { exactlyOneRow, maybeOneRow } from "../db/util.js";
|
|
6
6
|
import { createGraphqlClient } from "../graphql-client.js";
|
|
7
7
|
import { constant, context, error, object, sideEffect, } from "postgraphile/grafast";
|
|
8
|
-
import {
|
|
8
|
+
import { withPgPoolTransaction, } from "../db/index.js";
|
|
9
9
|
import { logger } from "../logger.js";
|
|
10
10
|
import * as jwtService from "../services/jwt-service.js";
|
|
11
11
|
import { extractGraphQLErrorInfo, isGraphQLError } from "../GraphQLError.js";
|
|
@@ -51,7 +51,8 @@ export const HubAuthenticatePlugin = makeExtendSchemaPlugin(() => {
|
|
|
51
51
|
hubAuthenticate(_, { $input }) {
|
|
52
52
|
try {
|
|
53
53
|
const $context = context();
|
|
54
|
-
const $
|
|
54
|
+
const $superuserPool = $context.get("superuserPool");
|
|
55
|
+
const $success = sideEffect([$input, $superuserPool, $context], ([rawInput, superuserPool, context]) => {
|
|
55
56
|
return withPgPoolTransaction(superuserPool, async (pgClient) => {
|
|
56
57
|
let input;
|
|
57
58
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { object } from "grafast";
|
|
2
2
|
import { gql, makeExtendSchemaPlugin } from "postgraphile/utils";
|
|
3
|
-
import {
|
|
3
|
+
import { withPgPoolTransaction, } from "../db/index.js";
|
|
4
4
|
import { constant, context, sideEffect } from "postgraphile/grafast";
|
|
5
5
|
import { assert } from "tsafe";
|
|
6
6
|
const CLAIM_AMOUNT = 1000;
|
|
@@ -20,8 +20,10 @@ export const HubClaimFaucetPlugin = makeExtendSchemaPlugin(() => {
|
|
|
20
20
|
Mutation: {
|
|
21
21
|
plans: {
|
|
22
22
|
hubClaimFaucet() {
|
|
23
|
-
const $
|
|
24
|
-
const $
|
|
23
|
+
const $context = context();
|
|
24
|
+
const $identity = $context.get("identity");
|
|
25
|
+
const $superuserPool = $context.get("superuserPool");
|
|
26
|
+
const $result = sideEffect([$identity, $superuserPool], ([identity, superuserPool]) => {
|
|
25
27
|
if (identity?.kind !== "user") {
|
|
26
28
|
throw new Error("Must be logged in as user");
|
|
27
29
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
import { DbOutcome } from "../db/index.js";
|
|
3
3
|
import { Result } from "../util.js";
|
|
4
|
+
import { RiskPolicy } from "../risk-policy.js";
|
|
4
5
|
declare const InputSchema: z.ZodObject<{
|
|
5
6
|
kind: z.ZodString;
|
|
6
7
|
clientSeed: z.ZodString;
|
|
@@ -75,16 +76,6 @@ export type OutcomeBetConfig = {
|
|
|
75
76
|
export type OutcomeBetConfigMap<BetKind extends string> = {
|
|
76
77
|
[betKind in BetKind]: OutcomeBetConfig;
|
|
77
78
|
};
|
|
78
|
-
type AtLeastOneKey<T, Keys extends keyof T = keyof T> = Keys extends keyof T ? Required<Pick<T, Keys>> & Partial<Omit<T, Keys>> : never;
|
|
79
|
-
export type RiskLimits = AtLeastOneKey<{
|
|
80
|
-
maxWager?: number;
|
|
81
|
-
maxPayout?: number;
|
|
82
|
-
}>;
|
|
83
|
-
export type RiskPolicy = (args: {
|
|
84
|
-
currency: string;
|
|
85
|
-
wager: number;
|
|
86
|
-
bankroll: number;
|
|
87
|
-
}) => RiskLimits;
|
|
88
79
|
export declare function MakeOutcomeBetPlugin<BetKind extends string>({ betConfigs }: {
|
|
89
80
|
betConfigs: OutcomeBetConfigMap<BetKind>;
|
|
90
81
|
}): GraphileConfig.Plugin;
|
|
@@ -2,13 +2,13 @@ import { access, context, object, ObjectStep, sideEffect, } from "postgraphile/g
|
|
|
2
2
|
import { gql, makeExtendSchemaPlugin } from "postgraphile/utils";
|
|
3
3
|
import * as z from "zod";
|
|
4
4
|
import { GraphQLError } from "graphql";
|
|
5
|
-
import { DbHashKind, dbLockPlayerBalanceAndHouseBankroll, exactlyOneRow, maybeOneRow,
|
|
5
|
+
import { DbHashKind, dbLockPlayerBalanceAndHouseBankroll, 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, getPreimageHash, } from "../hash-chain/get-hash.js";
|
|
9
9
|
import { makeFinalHash, pickRandomOutcome } from "../hash-chain/util.js";
|
|
10
10
|
import { logger } from "../logger.js";
|
|
11
|
-
import {
|
|
11
|
+
import { validateRisk } from "../risk-policy.js";
|
|
12
12
|
const FLOAT_EPSILON = 1e-10;
|
|
13
13
|
function sum(ns) {
|
|
14
14
|
return ns.reduce((a, b) => a + b, 0);
|
|
@@ -65,15 +65,6 @@ const BetConfigsSchema = z.record(BetKindSchema, z.object({
|
|
|
65
65
|
.function()
|
|
66
66
|
.optional(),
|
|
67
67
|
}));
|
|
68
|
-
const RiskLimitsSchema = z
|
|
69
|
-
.object({
|
|
70
|
-
maxWager: z.number().int().positive().optional(),
|
|
71
|
-
maxPayout: z.number().int().positive().optional(),
|
|
72
|
-
})
|
|
73
|
-
.strict()
|
|
74
|
-
.refine((v) => v.maxWager !== undefined || v.maxPayout !== undefined, {
|
|
75
|
-
message: "Provide at least one of maxWager or maxPayout.",
|
|
76
|
-
});
|
|
77
68
|
export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
78
69
|
BetConfigsSchema.parse(betConfigs);
|
|
79
70
|
const betKinds = Object.keys(betConfigs);
|
|
@@ -115,7 +106,8 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
115
106
|
plans: {
|
|
116
107
|
hubMakeOutcomeBet: (_, { $input }) => {
|
|
117
108
|
const $identity = context().get("identity");
|
|
118
|
-
const $
|
|
109
|
+
const $superuserPool = context().get("superuserPool");
|
|
110
|
+
const $result = sideEffect([$identity, $superuserPool, $input], async ([identity, superuserPool, rawInput]) => {
|
|
119
111
|
if (identity?.kind !== "user") {
|
|
120
112
|
throw new GraphQLError("Unauthorized");
|
|
121
113
|
}
|
|
@@ -196,47 +188,19 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
196
188
|
throw new GraphQLError("You cannot afford the worst outcome");
|
|
197
189
|
}
|
|
198
190
|
const maxProfitMultiplier = Math.max(...input.outcomes.map((o) => o.profit));
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
wager: input.wager,
|
|
213
|
-
bankroll: dbHouseBankroll.amount,
|
|
214
|
-
})
|
|
215
|
-
: {});
|
|
216
|
-
if (!riskLimitsResult.success) {
|
|
217
|
-
logger.error(riskLimitsResult.error, "Invalid risk policy");
|
|
218
|
-
throw new GraphQLError("Invalid risk policy");
|
|
219
|
-
}
|
|
220
|
-
const riskLimits = riskLimitsResult.data;
|
|
221
|
-
if (riskLimits.maxWager != null &&
|
|
222
|
-
input.wager > riskLimits.maxWager) {
|
|
223
|
-
throw new GraphQLError(`Wager exceeds limit (${formatCurrency(riskLimits.maxWager, {
|
|
224
|
-
displayUnitName: dbCurrency.display_unit_name,
|
|
225
|
-
displayUnitScale: dbCurrency.display_unit_scale,
|
|
226
|
-
})}). Your wager: ${formatCurrency(input.wager, {
|
|
227
|
-
displayUnitName: dbCurrency.display_unit_name,
|
|
228
|
-
displayUnitScale: dbCurrency.display_unit_scale,
|
|
229
|
-
})}`);
|
|
230
|
-
}
|
|
231
|
-
if (riskLimits.maxPayout != null &&
|
|
232
|
-
maxPayout > riskLimits.maxPayout) {
|
|
233
|
-
throw new GraphQLError(`Payout exceeds limit (${formatCurrency(riskLimits.maxPayout, {
|
|
234
|
-
displayUnitName: dbCurrency.display_unit_name,
|
|
235
|
-
displayUnitScale: dbCurrency.display_unit_scale,
|
|
236
|
-
})}). Your payout: ${formatCurrency(maxPayout, {
|
|
237
|
-
displayUnitName: dbCurrency.display_unit_name,
|
|
238
|
-
displayUnitScale: dbCurrency.display_unit_scale,
|
|
239
|
-
})}`);
|
|
191
|
+
const maxPotentialPayout = input.wager * maxProfitMultiplier;
|
|
192
|
+
const riskResult = validateRisk({
|
|
193
|
+
currency: input.currency,
|
|
194
|
+
wager: input.wager,
|
|
195
|
+
bankroll: dbHouseBankroll.amount,
|
|
196
|
+
maxPotentialPayout,
|
|
197
|
+
riskPolicy: betConfig.riskPolicy,
|
|
198
|
+
displayUnitName: dbCurrency.display_unit_name,
|
|
199
|
+
displayUnitScale: dbCurrency.display_unit_scale,
|
|
200
|
+
outcomes: input.outcomes,
|
|
201
|
+
});
|
|
202
|
+
if (!riskResult.ok) {
|
|
203
|
+
throw new GraphQLError(riskResult.error);
|
|
240
204
|
}
|
|
241
205
|
const dbHashChain = await dbLockHubHashChain(pgClient, {
|
|
242
206
|
userId: session.user_id,
|
|
@@ -254,6 +218,7 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
254
218
|
if (dbHashChain.current_iteration === 1) {
|
|
255
219
|
finishHashChainInBackground({
|
|
256
220
|
hashChainId: input.hashChainId,
|
|
221
|
+
pool: superuserPool,
|
|
257
222
|
}).catch((e) => {
|
|
258
223
|
logger.error({ hashChainId: input.hashChainId, error: e }, "Error finishing hash chain in background");
|
|
259
224
|
});
|
|
@@ -440,7 +405,7 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
440
405
|
};
|
|
441
406
|
}, "HubMakeOutcomeBetPlugin");
|
|
442
407
|
}
|
|
443
|
-
async function finishHashChainInBackground({ hashChainId, }) {
|
|
408
|
+
async function finishHashChainInBackground({ hashChainId, pool, }) {
|
|
444
409
|
logger.debug({ hashChainId }, "Finishing hash chain in background");
|
|
445
410
|
const preimageHashResult = await getPreimageHash({
|
|
446
411
|
hashChainId,
|
|
@@ -453,7 +418,7 @@ async function finishHashChainInBackground({ hashChainId, }) {
|
|
|
453
418
|
digest: preimageHashResult.hash,
|
|
454
419
|
iteration: 0,
|
|
455
420
|
}, "Inserting preimage hash");
|
|
456
|
-
await withPgPoolTransaction(
|
|
421
|
+
await withPgPoolTransaction(pool, async (pgClient) => {
|
|
457
422
|
await dbInsertHubHash(pgClient, {
|
|
458
423
|
hashChainId,
|
|
459
424
|
kind: DbHashKind.PREIMAGE,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { constant, context, object, sideEffect } from "postgraphile/grafast";
|
|
2
2
|
import { gql, makeExtendSchemaPlugin } from "postgraphile/utils";
|
|
3
|
-
import {
|
|
3
|
+
import { withPgPoolTransaction } from "../db/index.js";
|
|
4
4
|
import { exactlyOneRow, maybeOneRow } from "../db/util.js";
|
|
5
5
|
import { GraphQLError } from "graphql";
|
|
6
6
|
export const HubWithdrawPlugin = makeExtendSchemaPlugin((build) => {
|
|
@@ -26,7 +26,8 @@ export const HubWithdrawPlugin = makeExtendSchemaPlugin((build) => {
|
|
|
26
26
|
plans: {
|
|
27
27
|
hubWithdraw(_, { $input }) {
|
|
28
28
|
const $identity = context().get("identity");
|
|
29
|
-
const $
|
|
29
|
+
const $superuserPool = context().get("superuserPool");
|
|
30
|
+
const $withdrawalRequestId = sideEffect([$input, $identity, $superuserPool], ([input, identity, superuserPool]) => {
|
|
30
31
|
if (identity?.kind !== "user") {
|
|
31
32
|
throw new GraphQLError("You must be logged in");
|
|
32
33
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { DbCasino, DbCasinoSecret } from "../db/index.js";
|
|
2
|
+
import * as pg from "pg";
|
|
2
3
|
export type CasinoWithSecret = DbCasino & DbCasinoSecret;
|
|
3
|
-
export declare function startCasinoTransferProcessor({ casinoId, signal, }: {
|
|
4
|
+
export declare function startCasinoTransferProcessor({ casinoId, signal, pool, }: {
|
|
4
5
|
casinoId: string;
|
|
5
6
|
signal: AbortSignal;
|
|
7
|
+
pool: pg.Pool;
|
|
6
8
|
}): Promise<void>;
|
|
7
|
-
export declare function initializeTransferProcessors({ signal, }: {
|
|
9
|
+
export declare function initializeTransferProcessors({ signal, pool, }: {
|
|
8
10
|
signal: AbortSignal;
|
|
11
|
+
pool: pg.Pool;
|
|
9
12
|
}): void;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { startPollingProcessor } from "./polling-processor.js";
|
|
2
2
|
import { startWebsocketProcessor } from "./websocket-processor.js";
|
|
3
|
-
import { superuserPool } from "../db/index.js";
|
|
4
3
|
import { dbGetCasinoById, dbGetCasinoSecretById } from "../db/internal.js";
|
|
5
4
|
import assert from "assert";
|
|
6
5
|
import { logger } from "../logger.js";
|
|
@@ -9,28 +8,29 @@ import * as db from "../db/index.js";
|
|
|
9
8
|
import * as pg from "pg";
|
|
10
9
|
import { z } from "zod";
|
|
11
10
|
const activeCasinos = new Set();
|
|
12
|
-
export async function startCasinoTransferProcessor({ casinoId, signal, }) {
|
|
11
|
+
export async function startCasinoTransferProcessor({ casinoId, signal, pool, }) {
|
|
13
12
|
if (activeCasinos.has(casinoId)) {
|
|
14
13
|
throw new Error(`processor already running for casino ${casinoId}`);
|
|
15
14
|
}
|
|
16
|
-
const casino = await dbGetCasinoById(
|
|
17
|
-
const secret = await dbGetCasinoSecretById(
|
|
15
|
+
const casino = await dbGetCasinoById(pool, casinoId);
|
|
16
|
+
const secret = await dbGetCasinoSecretById(pool, casinoId);
|
|
18
17
|
assert(casino, `Casino not found for casino id ${casinoId}`);
|
|
19
18
|
assert(secret, `Secret not found for casino id ${casinoId}`);
|
|
20
19
|
activeCasinos.add(casinoId);
|
|
21
|
-
startPollingProcessor({ casinoId, signal });
|
|
20
|
+
startPollingProcessor({ casinoId, signal, pool });
|
|
22
21
|
startWebsocketProcessor({
|
|
23
22
|
casinoId,
|
|
24
23
|
graphqlUrl: casino.graphql_url,
|
|
25
24
|
signal,
|
|
26
25
|
controllerId: secret.controller_id,
|
|
27
26
|
apiKey: secret.api_key,
|
|
27
|
+
pool,
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
|
-
export function initializeTransferProcessors({ signal, }) {
|
|
30
|
+
export function initializeTransferProcessors({ signal, pool, }) {
|
|
31
31
|
(async () => {
|
|
32
32
|
try {
|
|
33
|
-
const casinos = await db.listCasinos(
|
|
33
|
+
const casinos = await db.listCasinos(pool);
|
|
34
34
|
for (const casino of casinos) {
|
|
35
35
|
if (!URL.canParse(casino.graphql_url)) {
|
|
36
36
|
logger.warn(`Skipping casino ${casino.id} due to invalid graphql_url: "${casino.graphql_url}"`);
|
|
@@ -41,16 +41,16 @@ export function initializeTransferProcessors({ signal, }) {
|
|
|
41
41
|
logger.warn(`${casino.id} has localhost endpoint "${casino.graphql_url}" while NODE_ENV=production.`);
|
|
42
42
|
}
|
|
43
43
|
logger.info(`Starting casino processor for "${casino.name}" at "${casino.graphql_url}"`);
|
|
44
|
-
startCasinoTransferProcessor({ casinoId: casino.id, signal });
|
|
44
|
+
startCasinoTransferProcessor({ casinoId: casino.id, signal, pool });
|
|
45
45
|
}
|
|
46
|
-
await listenForNewCasinos({ signal });
|
|
46
|
+
await listenForNewCasinos({ signal, pool });
|
|
47
47
|
}
|
|
48
48
|
catch (e) {
|
|
49
49
|
logger.error(e, `Error initializing transfer processors`);
|
|
50
50
|
}
|
|
51
51
|
})();
|
|
52
52
|
}
|
|
53
|
-
async function listenForNewCasinos({ signal }) {
|
|
53
|
+
async function listenForNewCasinos({ signal, pool, }) {
|
|
54
54
|
const pgClient = new pg.Client(config.SUPERUSER_DATABASE_URL);
|
|
55
55
|
await pgClient.connect();
|
|
56
56
|
const NewCasinoPayload = z.object({
|
|
@@ -76,7 +76,11 @@ async function listenForNewCasinos({ signal }) {
|
|
|
76
76
|
logger.error(result.error, "Error parsing new casino notification");
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
|
-
startCasinoTransferProcessor({
|
|
79
|
+
startCasinoTransferProcessor({
|
|
80
|
+
casinoId: result.data.id,
|
|
81
|
+
signal,
|
|
82
|
+
pool,
|
|
83
|
+
});
|
|
80
84
|
break;
|
|
81
85
|
}
|
|
82
86
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import * as db from "../db/index.js";
|
|
2
|
+
import * as pg from "pg";
|
|
2
3
|
export declare const PAGINATE_TRANSFERS: import("@graphql-typed-document-node/core").TypedDocumentNode<import("../__generated__/graphql.js").PaginateTransfersQuery, import("../__generated__/graphql.js").Exact<{
|
|
3
4
|
controllerId: import("../__generated__/graphql.js").Scalars["UUID"]["input"];
|
|
4
5
|
after?: import("../__generated__/graphql.js").InputMaybe<import("../__generated__/graphql.js").Scalars["Cursor"]["input"]>;
|
|
5
6
|
limit?: import("../__generated__/graphql.js").InputMaybe<import("../__generated__/graphql.js").Scalars["Int"]["input"]>;
|
|
6
7
|
}>>;
|
|
7
|
-
export declare function startPollingProcessor({ casinoId, signal, }: {
|
|
8
|
+
export declare function startPollingProcessor({ casinoId, signal, pool, }: {
|
|
8
9
|
casinoId: db.DbCasino["id"];
|
|
9
10
|
signal: AbortSignal;
|
|
11
|
+
pool: pg.Pool;
|
|
10
12
|
}): void;
|
|
@@ -8,7 +8,6 @@ import { processWithdrawalRequests } from "../process-withdrawal-request.js";
|
|
|
8
8
|
import { processTakeRequests } from "../take-request/process-take-request.js";
|
|
9
9
|
import * as jwtService from "../services/jwt-service.js";
|
|
10
10
|
import { dbGetCasinoById, dbGetCasinoSecretById } from "../db/internal.js";
|
|
11
|
-
import { superuserPool } from "../db/index.js";
|
|
12
11
|
import { TransferStatusKind } from "../__generated__/graphql.js";
|
|
13
12
|
import { MP_COMPLETE_TRANSFER, processTransfer } from "./process-transfer.js";
|
|
14
13
|
import { gql } from "../__generated__/gql.js";
|
|
@@ -44,7 +43,7 @@ export const PAGINATE_TRANSFERS = gql(`
|
|
|
44
43
|
`);
|
|
45
44
|
const MIN_BACKOFF_TIME = 5000;
|
|
46
45
|
const MAX_BACKOFF_TIME = 30 * 1000;
|
|
47
|
-
export function startPollingProcessor({ casinoId, signal, }) {
|
|
46
|
+
export function startPollingProcessor({ casinoId, signal, pool, }) {
|
|
48
47
|
if (signal.aborted) {
|
|
49
48
|
logger.info(`[startTransferProcessor] AbortSignal aborted. Not starting processor for casino ${casinoId}`);
|
|
50
49
|
return;
|
|
@@ -55,7 +54,7 @@ export function startPollingProcessor({ casinoId, signal, }) {
|
|
|
55
54
|
lastAttempt: 0,
|
|
56
55
|
};
|
|
57
56
|
(async () => {
|
|
58
|
-
let cursor = await db.getTransferCursor(
|
|
57
|
+
let cursor = await db.getTransferCursor(pool, {
|
|
59
58
|
casinoId,
|
|
60
59
|
});
|
|
61
60
|
if (cursor && !isUuid(cursor)) {
|
|
@@ -80,13 +79,13 @@ export function startPollingProcessor({ casinoId, signal, }) {
|
|
|
80
79
|
await new Promise((resolve) => setTimeout(resolve, timeToWait));
|
|
81
80
|
}
|
|
82
81
|
processorState.lastAttempt = Date.now();
|
|
83
|
-
const casino = await dbGetCasinoById(
|
|
82
|
+
const casino = await dbGetCasinoById(pool, casinoId);
|
|
84
83
|
if (!casino) {
|
|
85
84
|
logger.warn(`casino ${casinoId} not found. Stopping processor...`);
|
|
86
85
|
shouldStop = true;
|
|
87
86
|
break;
|
|
88
87
|
}
|
|
89
|
-
const casinoSecret = await dbGetCasinoSecretById(
|
|
88
|
+
const casinoSecret = await dbGetCasinoSecretById(pool, casino.id);
|
|
90
89
|
if (!casinoSecret) {
|
|
91
90
|
logger.warn(`Casino secret hasn't been set for casino ${casinoId}. Skipping...`);
|
|
92
91
|
continue;
|
|
@@ -99,7 +98,7 @@ export function startPollingProcessor({ casinoId, signal, }) {
|
|
|
99
98
|
logger.info(`[startTransferProcessor] Aborted by graceful shutdown. (1)`);
|
|
100
99
|
break;
|
|
101
100
|
}
|
|
102
|
-
await jwtService.refreshCasinoJwksTask(
|
|
101
|
+
await jwtService.refreshCasinoJwksTask(pool, {
|
|
103
102
|
casinoId: casino.id,
|
|
104
103
|
graphqlClient,
|
|
105
104
|
});
|
|
@@ -114,7 +113,7 @@ export function startPollingProcessor({ casinoId, signal, }) {
|
|
|
114
113
|
return (res.allCurrencies?.nodes || []).flatMap((x) => x ? [x] : []);
|
|
115
114
|
});
|
|
116
115
|
if (currencies.length > 0) {
|
|
117
|
-
await db.upsertCurrencies(
|
|
116
|
+
await db.upsertCurrencies(pool, {
|
|
118
117
|
casinoId: casino.id,
|
|
119
118
|
currencies,
|
|
120
119
|
});
|
|
@@ -130,6 +129,7 @@ export function startPollingProcessor({ casinoId, signal, }) {
|
|
|
130
129
|
graphqlClient,
|
|
131
130
|
casinoInfo: { ...casino, ...casinoSecret },
|
|
132
131
|
signal,
|
|
132
|
+
pool,
|
|
133
133
|
});
|
|
134
134
|
if (signal.aborted) {
|
|
135
135
|
logger.info(`[startTransferProcessor] Aborted by graceful shutdown. (4)`);
|
|
@@ -139,6 +139,7 @@ export function startPollingProcessor({ casinoId, signal, }) {
|
|
|
139
139
|
abortSignal: signal,
|
|
140
140
|
casinoId: casino.id,
|
|
141
141
|
graphqlClient,
|
|
142
|
+
pool,
|
|
142
143
|
});
|
|
143
144
|
if (signal.aborted) {
|
|
144
145
|
logger.info(`[startTransferProcessor] Aborted by graceful shutdown. (5)`);
|
|
@@ -149,6 +150,7 @@ export function startPollingProcessor({ casinoId, signal, }) {
|
|
|
149
150
|
controllerId: casinoSecret.controller_id,
|
|
150
151
|
casinoId: casino.id,
|
|
151
152
|
graphqlClient,
|
|
153
|
+
pool,
|
|
152
154
|
});
|
|
153
155
|
processorState.backoffTime = MIN_BACKOFF_TIME;
|
|
154
156
|
}
|
|
@@ -161,11 +163,11 @@ export function startPollingProcessor({ casinoId, signal, }) {
|
|
|
161
163
|
logger.info(`processor stopped for casino ${casinoId}`);
|
|
162
164
|
})();
|
|
163
165
|
}
|
|
164
|
-
async function processWithdrawals({ abortSignal, casinoId, graphqlClient, }) {
|
|
166
|
+
async function processWithdrawals({ abortSignal, casinoId, graphqlClient, pool, }) {
|
|
165
167
|
if (abortSignal.aborted) {
|
|
166
168
|
return;
|
|
167
169
|
}
|
|
168
|
-
const withdrawals = await db.getPendingWithdrawals(
|
|
170
|
+
const withdrawals = await db.getPendingWithdrawals(pool, {
|
|
169
171
|
casinoId,
|
|
170
172
|
limit: 10,
|
|
171
173
|
});
|
|
@@ -185,7 +187,7 @@ async function processWithdrawals({ abortSignal, casinoId, graphqlClient, }) {
|
|
|
185
187
|
case undefined:
|
|
186
188
|
break;
|
|
187
189
|
case "CompleteTransferSuccess": {
|
|
188
|
-
await db.settleWithdrawal({
|
|
190
|
+
await db.settleWithdrawal(pool, {
|
|
189
191
|
withdrawalId: withdrawal.id,
|
|
190
192
|
newStatus: "COMPLETED",
|
|
191
193
|
});
|
|
@@ -198,7 +200,7 @@ async function processWithdrawals({ abortSignal, casinoId, graphqlClient, }) {
|
|
|
198
200
|
switch (currentState) {
|
|
199
201
|
case TransferStatusKind.Canceled:
|
|
200
202
|
case TransferStatusKind.Completed:
|
|
201
|
-
await db.settleWithdrawal({
|
|
203
|
+
await db.settleWithdrawal(pool, {
|
|
202
204
|
withdrawalId: withdrawal.id,
|
|
203
205
|
newStatus: currentState,
|
|
204
206
|
});
|
|
@@ -224,13 +226,14 @@ async function processWithdrawals({ abortSignal, casinoId, graphqlClient, }) {
|
|
|
224
226
|
}
|
|
225
227
|
}
|
|
226
228
|
}
|
|
227
|
-
async function processTransfersUntilEmpty({ afterCursor, graphqlClient, casinoInfo, signal, }) {
|
|
229
|
+
async function processTransfersUntilEmpty({ afterCursor, graphqlClient, casinoInfo, signal, pool, }) {
|
|
228
230
|
let hasNextPage = true;
|
|
229
231
|
const timeout = (ms) => new Promise((res) => setTimeout(res, ms));
|
|
230
232
|
while (hasNextPage && !signal.aborted) {
|
|
231
233
|
await processWithdrawalRequests({
|
|
232
234
|
casinoId: casinoInfo.id,
|
|
233
235
|
graphqlClient,
|
|
236
|
+
pool,
|
|
234
237
|
});
|
|
235
238
|
if (signal.aborted) {
|
|
236
239
|
logger.info(`[processTransfersUntilEmpty] Aborted by graceful shutdown.`);
|
|
@@ -266,8 +269,9 @@ async function processTransfersUntilEmpty({ afterCursor, graphqlClient, casinoIn
|
|
|
266
269
|
controllerId: casinoInfo.controller_id,
|
|
267
270
|
transfer,
|
|
268
271
|
graphqlClient,
|
|
272
|
+
pool,
|
|
269
273
|
});
|
|
270
|
-
await db.setTransferCursor(
|
|
274
|
+
await db.setTransferCursor(pool, {
|
|
271
275
|
cursor,
|
|
272
276
|
casinoId: casinoInfo.id,
|
|
273
277
|
});
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { TransferFieldsFragment } from "../__generated__/graphql.js";
|
|
2
2
|
import { GraphQLClient } from "graphql-request";
|
|
3
|
+
import * as pg from "pg";
|
|
3
4
|
export declare const MP_COMPLETE_TRANSFER: import("@graphql-typed-document-node/core").TypedDocumentNode<import("../__generated__/graphql.js").CompleteTransferMutation, import("../__generated__/graphql.js").Exact<{
|
|
4
5
|
mpTransferId: import("../__generated__/graphql.js").Scalars["UUID"]["input"];
|
|
5
6
|
}>>;
|
|
6
|
-
export declare function processTransfer({ casinoId, controllerId, transfer, graphqlClient, }: {
|
|
7
|
+
export declare function processTransfer({ casinoId, controllerId, transfer, graphqlClient, pool, }: {
|
|
7
8
|
casinoId: string;
|
|
8
9
|
controllerId: string;
|
|
9
10
|
transfer: Extract<TransferFieldsFragment, {
|
|
10
11
|
__typename: "ExperienceTransfer";
|
|
11
12
|
}>;
|
|
12
13
|
graphqlClient: GraphQLClient;
|
|
14
|
+
pool: pg.Pool;
|
|
13
15
|
}): Promise<void>;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as db from "../db/index.js";
|
|
2
2
|
import { TransferStatusKind, } from "../__generated__/graphql.js";
|
|
3
|
-
import { superuserPool } from "../db/index.js";
|
|
4
3
|
import { gql } from "../__generated__/gql.js";
|
|
5
4
|
import { logger } from "../logger.js";
|
|
6
5
|
import { assert } from "tsafe";
|
|
@@ -50,7 +49,7 @@ const MP_CLAIM_TRANSFER = gql(`
|
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
`);
|
|
53
|
-
export async function processTransfer({ casinoId, controllerId, transfer, graphqlClient, }) {
|
|
52
|
+
export async function processTransfer({ casinoId, controllerId, transfer, graphqlClient, pool, }) {
|
|
54
53
|
assert(transfer, "Expected transfer");
|
|
55
54
|
assert(transfer.__typename === "ExperienceTransfer", `Expected ExperienceTransfer but got ${transfer.__typename}`);
|
|
56
55
|
logger.debug(`processing transfer ${transfer.id} for casino ${casinoId}...`);
|
|
@@ -62,17 +61,17 @@ export async function processTransfer({ casinoId, controllerId, transfer, graphq
|
|
|
62
61
|
: transfer.holderByToHolderId;
|
|
63
62
|
assert(user?.__typename === "User", "Expected user transfer participant");
|
|
64
63
|
const currency = transfer.currencyByCurrency;
|
|
65
|
-
const dbSender = await db.upsertUser(
|
|
64
|
+
const dbSender = await db.upsertUser(pool, {
|
|
66
65
|
casinoId,
|
|
67
66
|
mpUserId: user.id,
|
|
68
67
|
uname: user.uname,
|
|
69
68
|
});
|
|
70
|
-
const dbExperience = await db.upsertExperience(
|
|
69
|
+
const dbExperience = await db.upsertExperience(pool, {
|
|
71
70
|
casinoId,
|
|
72
71
|
mpExperienceId: transfer.experienceByExperienceId.id,
|
|
73
72
|
name: transfer.experienceByExperienceId.name,
|
|
74
73
|
});
|
|
75
|
-
await db.upsertCurrencies(
|
|
74
|
+
await db.upsertCurrencies(pool, {
|
|
76
75
|
casinoId,
|
|
77
76
|
currencies: [currency],
|
|
78
77
|
});
|
|
@@ -85,7 +84,7 @@ export async function processTransfer({ casinoId, controllerId, transfer, graphq
|
|
|
85
84
|
case TransferStatusKind.Expired:
|
|
86
85
|
return;
|
|
87
86
|
case TransferStatusKind.Completed: {
|
|
88
|
-
await db.insertDeposit({
|
|
87
|
+
await db.insertDeposit(pool, {
|
|
89
88
|
casinoId,
|
|
90
89
|
mpTransferId: transfer.id,
|
|
91
90
|
userId: dbSender.id,
|
|
@@ -110,7 +109,7 @@ export async function processTransfer({ casinoId, controllerId, transfer, graphq
|
|
|
110
109
|
if (data.claimTransfer?.result.__typename !== "ClaimTransferSuccess") {
|
|
111
110
|
throw new Error(`Failed to claim transfer: ${JSON.stringify(data.claimTransfer)}`);
|
|
112
111
|
}
|
|
113
|
-
await db.insertDeposit({
|
|
112
|
+
await db.insertDeposit(pool, {
|
|
114
113
|
casinoId,
|
|
115
114
|
mpTransferId: transfer.id,
|
|
116
115
|
userId: dbSender.id,
|
|
@@ -130,7 +129,7 @@ export async function processTransfer({ casinoId, controllerId, transfer, graphq
|
|
|
130
129
|
logger.debug(`I sent ${user.uname} ${transfer.amount} base units of ${currency.id}`);
|
|
131
130
|
switch (transfer.status) {
|
|
132
131
|
case TransferStatusKind.Canceled:
|
|
133
|
-
await db.settleWithdrawal({
|
|
132
|
+
await db.settleWithdrawal(pool, {
|
|
134
133
|
withdrawalId: transfer.id,
|
|
135
134
|
newStatus: "CANCELED",
|
|
136
135
|
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import * as pg from "pg";
|
|
2
|
+
export declare function startWebsocketProcessor({ casinoId, graphqlUrl, signal, controllerId, apiKey, pool, }: {
|
|
2
3
|
casinoId: string;
|
|
3
4
|
graphqlUrl: string;
|
|
4
5
|
signal: AbortSignal;
|
|
5
6
|
controllerId: string;
|
|
6
7
|
apiKey: string;
|
|
8
|
+
pool: pg.Pool;
|
|
7
9
|
}): void;
|
|
@@ -49,7 +49,7 @@ async function mpGetExperienceTransfer(graphqlClient, id) {
|
|
|
49
49
|
const mpTransfer = useFragment(TRANSFER_FIELDS, x);
|
|
50
50
|
return mpTransfer;
|
|
51
51
|
}
|
|
52
|
-
export function startWebsocketProcessor({ casinoId, graphqlUrl, signal, controllerId, apiKey, }) {
|
|
52
|
+
export function startWebsocketProcessor({ casinoId, graphqlUrl, signal, controllerId, apiKey, pool, }) {
|
|
53
53
|
logger.info(`Starting websocket processor for ${graphqlUrl} using apiKey ${apiKey}`);
|
|
54
54
|
const client = createWebsocketClient({
|
|
55
55
|
url: httpToWs(new URL(graphqlUrl)).toString(),
|
|
@@ -89,6 +89,7 @@ export function startWebsocketProcessor({ casinoId, graphqlUrl, signal, controll
|
|
|
89
89
|
controllerId,
|
|
90
90
|
transfer: mpTransfer,
|
|
91
91
|
graphqlClient,
|
|
92
|
+
pool,
|
|
92
93
|
});
|
|
93
94
|
});
|
|
94
95
|
const dispose2 = createSubscription(client, MP_NEW_TAKE_REQUEST, async (result) => {
|
|
@@ -107,6 +108,7 @@ export function startWebsocketProcessor({ casinoId, graphqlUrl, signal, controll
|
|
|
107
108
|
mpTakeRequest,
|
|
108
109
|
casinoId,
|
|
109
110
|
graphqlClient,
|
|
111
|
+
pool,
|
|
110
112
|
});
|
|
111
113
|
});
|
|
112
114
|
signal.addEventListener("abort", () => {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import * as pg from "pg";
|
|
1
2
|
import { GraphQLClient } from "graphql-request";
|
|
2
|
-
export declare function processWithdrawalRequests({ casinoId, graphqlClient, }: {
|
|
3
|
+
export declare function processWithdrawalRequests({ casinoId, graphqlClient, pool, }: {
|
|
3
4
|
casinoId: string;
|
|
4
5
|
graphqlClient: GraphQLClient;
|
|
6
|
+
pool: pg.Pool;
|
|
5
7
|
}): Promise<void>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { assert } from "tsafe";
|
|
2
2
|
import { TransferStatusKind } from "./__generated__/graphql.js";
|
|
3
|
-
import { withPgPoolTransaction
|
|
3
|
+
import { withPgPoolTransaction } from "./db/index.js";
|
|
4
4
|
import { maybeOneRow, exactlyOneRow } from "./db/util.js";
|
|
5
5
|
import { START_PENDING_EXPERIENCE_TRANSFER } from "./graphql-queries.js";
|
|
6
6
|
import { logger } from "./logger.js";
|
|
7
|
-
async function processWithdrawalRequest({ requestId, graphqlClient, }) {
|
|
8
|
-
return withPgPoolTransaction(
|
|
7
|
+
async function processWithdrawalRequest({ requestId, graphqlClient, pool, }) {
|
|
8
|
+
return withPgPoolTransaction(pool, async (pgClient) => {
|
|
9
9
|
const dbWithdrawalRequest = await pgClient
|
|
10
10
|
.query({
|
|
11
11
|
text: `
|
|
@@ -120,8 +120,8 @@ async function processWithdrawalRequest({ requestId, graphqlClient, }) {
|
|
|
120
120
|
});
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
|
-
export async function processWithdrawalRequests({ casinoId, graphqlClient, }) {
|
|
124
|
-
const pendingRequests = await
|
|
123
|
+
export async function processWithdrawalRequests({ casinoId, graphqlClient, pool, }) {
|
|
124
|
+
const pendingRequests = await pool.query({
|
|
125
125
|
text: `
|
|
126
126
|
SELECT wr.*
|
|
127
127
|
FROM hub.withdrawal_request wr
|
|
@@ -136,6 +136,7 @@ export async function processWithdrawalRequests({ casinoId, graphqlClient, }) {
|
|
|
136
136
|
await processWithdrawalRequest({
|
|
137
137
|
requestId: request.id,
|
|
138
138
|
graphqlClient,
|
|
139
|
+
pool,
|
|
139
140
|
});
|
|
140
141
|
}
|
|
141
142
|
catch (error) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DbOutcome } from "./db/types.js";
|
|
2
|
+
import { Result } from "./util.js";
|
|
3
|
+
export type RiskPolicyArgs = {
|
|
4
|
+
currency: string;
|
|
5
|
+
wager: number;
|
|
6
|
+
bankroll: number;
|
|
7
|
+
maxPotentialPayout: number;
|
|
8
|
+
outcomes: DbOutcome[];
|
|
9
|
+
};
|
|
10
|
+
export type RiskLimits = {
|
|
11
|
+
maxWager?: number;
|
|
12
|
+
maxPayout: number;
|
|
13
|
+
};
|
|
14
|
+
export type RiskPolicy = (args: RiskPolicyArgs) => RiskLimits;
|
|
15
|
+
export declare function validateRisk(options: RiskPolicyArgs & {
|
|
16
|
+
riskPolicy: RiskPolicy;
|
|
17
|
+
} & {
|
|
18
|
+
displayUnitName: string;
|
|
19
|
+
displayUnitScale: number;
|
|
20
|
+
}): Result<void, string>;
|