@moneypot/hub 1.3.0-dev.14 → 1.3.0-dev.3
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/config.d.ts +13 -8
- package/dist/src/config.js +52 -78
- package/dist/src/db/index.d.ts +0 -1
- package/dist/src/db/index.js +1 -2
- package/dist/src/db/types.d.ts +0 -28
- package/dist/src/db/types.js +1 -5
- package/dist/src/index.js +1 -1
- package/dist/src/pg-advisory-lock.d.ts +4 -9
- package/dist/src/pg-advisory-lock.js +1 -8
- package/dist/src/pg-versions/{006-outcome-bet.sql → 005-outcome-bet.sql} +1 -8
- package/dist/src/plugins/hub-make-outcome-bet.d.ts +9 -36
- package/dist/src/plugins/hub-make-outcome-bet.js +23 -187
- package/dist/src/process-transfers.js +1 -1
- package/dist/src/server/graphile.config.js +1 -9
- package/dist/src/server/handle-errors.js +1 -1
- package/dist/src/server/index.js +1 -1
- package/dist/src/take-request/process-take-request.js +3 -3
- package/package.json +1 -1
- package/dist/src/hash-chain/db-hash-chain.d.ts +0 -15
- package/dist/src/hash-chain/db-hash-chain.js +0 -35
- package/dist/src/hash-chain/get-hash.d.ts +0 -17
- package/dist/src/hash-chain/get-hash.js +0 -57
- package/dist/src/hash-chain/plugins/hub-bad-hash-chain-error.d.ts +0 -1
- package/dist/src/hash-chain/plugins/hub-bad-hash-chain-error.js +0 -20
- package/dist/src/hash-chain/plugins/hub-create-hash-chain.d.ts +0 -1
- package/dist/src/hash-chain/plugins/hub-create-hash-chain.js +0 -111
- package/dist/src/hash-chain/plugins/hub-user-active-hash-chain.d.ts +0 -1
- package/dist/src/hash-chain/plugins/hub-user-active-hash-chain.js +0 -46
- package/dist/src/pg-versions/005-hash-chain.sql +0 -84
- package/dist/src/plugins/hub-outcome-input-non-null-fields.d.ts +0 -18
- package/dist/src/plugins/hub-outcome-input-non-null-fields.js +0 -20
package/dist/src/config.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import "dotenv/config";
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
export interface Config {
|
|
3
|
+
NODE_ENV: string;
|
|
4
|
+
PORT: number;
|
|
5
|
+
DATABASE_URL: string;
|
|
6
|
+
SUPERUSER_DATABASE_URL: string;
|
|
7
|
+
MP_GRAPHQL_URL: string;
|
|
8
|
+
HASHCHAINSERVER_OPTS: {
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
applicationSecret: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
declare const config: Config;
|
|
14
|
+
export default config;
|
package/dist/src/config.js
CHANGED
|
@@ -1,83 +1,57 @@
|
|
|
1
1
|
import "dotenv/config";
|
|
2
2
|
import pgConnectionString from "pg-connection-string";
|
|
3
3
|
import { logger } from "./logger.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
let NODE_ENV = process.env.NODE_ENV || "";
|
|
5
|
+
if (!NODE_ENV) {
|
|
6
|
+
NODE_ENV = "development";
|
|
7
|
+
logger.warn("Missing NODE_ENV env var. Defaulting to 'development'");
|
|
7
8
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!value) {
|
|
39
|
-
throw new Error(`Missing DATABASE_URL env var.`);
|
|
40
|
-
}
|
|
41
|
-
if (!URL.parse(value)) {
|
|
42
|
-
logger.warn("DATABASE_URL is not a valid URL.");
|
|
43
|
-
}
|
|
44
|
-
const databaseUrlUsername = pgConnectionString.parse(value).user;
|
|
45
|
-
if (databaseUrlUsername !== "app_postgraphile") {
|
|
46
|
-
logger.warn(`DATABASE_URL username is ${databaseUrlUsername}, expected app_postgraphile`);
|
|
47
|
-
}
|
|
48
|
-
return value;
|
|
49
|
-
});
|
|
50
|
-
export const SUPERUSER_DATABASE_URL = getEnvVariable("SUPERUSER_DATABASE_URL", (value) => {
|
|
51
|
-
if (!value) {
|
|
52
|
-
throw new Error("SUPERUSER_DATABASE_URL env var is required");
|
|
53
|
-
}
|
|
54
|
-
if (!URL.parse(value)) {
|
|
55
|
-
logger.warn("SUPERUSER_DATABASE_URL is not a valid URL.");
|
|
56
|
-
}
|
|
57
|
-
return value;
|
|
58
|
-
});
|
|
59
|
-
export const HASHCHAINSERVER_URL = getEnvVariable("HASHCHAINSERVER_URL", (value) => {
|
|
60
|
-
value = value || "mock-server";
|
|
61
|
-
if (value === "mock-server") {
|
|
62
|
-
logger.warn("Using mock-server for HASHCHAINSERVER_URL. This is only allowed in development.");
|
|
63
|
-
}
|
|
64
|
-
else if (!URL.parse(value)) {
|
|
65
|
-
logger.warn("HASHCHAINSERVER_URL is not a valid URL. It can either be empty, 'mock-server', or URL but it was: " +
|
|
66
|
-
value);
|
|
67
|
-
}
|
|
68
|
-
if (NODE_ENV !== "development" && value === "mock-server") {
|
|
69
|
-
logger.warn("Using mock-server for HASHCHAINSERVER_URL. This is only allowed in development.");
|
|
70
|
-
}
|
|
71
|
-
return value;
|
|
72
|
-
});
|
|
73
|
-
export const HASHCHAINSERVER_MAX_ITERATIONS = getEnvVariable("HASHCHAINSERVER_MAX_ITERATIONS", (value) => {
|
|
74
|
-
const iterations = Number.parseInt(value, 10) || 1_000;
|
|
75
|
-
assert(iterations >= 10, "HASHCHAINSERVER_MAX_ITERATIONS must be >= 10");
|
|
76
|
-
return iterations;
|
|
77
|
-
});
|
|
78
|
-
export const HASHCHAINSERVER_APPLICATION_SECRET = getEnvVariable("HASHCHAINSERVER_APPLICATION_SECRET", (value) => {
|
|
79
|
-
if (!value && NODE_ENV !== "development") {
|
|
80
|
-
logger.warn("Missing HASHCHAINSERVER_APPLICATION_SECRET and NODE_ENV != 'development': To use the hashchain server you must pick a random (but stable) HASHCHAINSERVER_APPLICATION_SECRET for secure communciation with it. (If you aren't using the hashchain server, you can ignore this.)");
|
|
9
|
+
let PORT = Number.parseInt(process.env.PORT || "", 10);
|
|
10
|
+
if (!PORT || PORT <= 0 || !Number.isSafeInteger(PORT)) {
|
|
11
|
+
PORT = 4000;
|
|
12
|
+
logger.warn("Warning: PORT missing or invalid, defaulting to ", PORT);
|
|
13
|
+
}
|
|
14
|
+
let MP_GRAPHQL_URL = process.env.MP_GRAPHQL_URL || "";
|
|
15
|
+
if (!MP_GRAPHQL_URL) {
|
|
16
|
+
MP_GRAPHQL_URL = "http://localhost:3000/graphql";
|
|
17
|
+
logger.warn(`Missing MP_GRAPHQL_URL env var. Defaulting to ${MP_GRAPHQL_URL}`);
|
|
18
|
+
}
|
|
19
|
+
if (!MP_GRAPHQL_URL.includes("/graphql")) {
|
|
20
|
+
logger.warn(`MP_GRAPHQL_URL didn't include '/graphql'. Are you sure it points to a graphql endpoint?`);
|
|
21
|
+
}
|
|
22
|
+
const DATABASE_URL = process.env.DATABASE_URL || "";
|
|
23
|
+
if (!DATABASE_URL) {
|
|
24
|
+
throw new Error(`Missing DATABASE_URL env var.`);
|
|
25
|
+
}
|
|
26
|
+
const databaseUrlUsername = pgConnectionString.parse(DATABASE_URL).user;
|
|
27
|
+
if (databaseUrlUsername !== "app_postgraphile") {
|
|
28
|
+
logger.warn(`DATABASE_URL username is ${databaseUrlUsername}, expected app_postgraphile`);
|
|
29
|
+
}
|
|
30
|
+
const SUPERUSER_DATABASE_URL = process.env.SUPERUSER_DATABASE_URL || "";
|
|
31
|
+
if (!SUPERUSER_DATABASE_URL) {
|
|
32
|
+
throw new Error("SUPERUSER_DATABASE_URL env var is required");
|
|
33
|
+
}
|
|
34
|
+
const HASHCHAINSERVER_URL = process.env.HASHCHAINSERVER_URL || "mock-server";
|
|
35
|
+
if (HASHCHAINSERVER_URL == "mock-server") {
|
|
36
|
+
logger.warn(`Missing HASHCHAINSERVER_URL env var, defaulting to mock-server.`);
|
|
37
|
+
if (NODE_ENV !== "development") {
|
|
38
|
+
logger.warn("Missing HASHCHAINSERVER_URL and NODE_ENV != 'development': You're using the hashchain mock-server outside of development. (If you aren't using the hashchain server, you can ignore this.)");
|
|
81
39
|
}
|
|
82
|
-
|
|
83
|
-
|
|
40
|
+
}
|
|
41
|
+
const HASHCHAINSERVER_APPLICATION_SECRET = process.env.HASHCHAINSERVER_APPLICATION_SECRET || "";
|
|
42
|
+
if (!HASHCHAINSERVER_APPLICATION_SECRET && NODE_ENV !== "development") {
|
|
43
|
+
logger.warn("Missing HASHCHAINSERVER_APPLICATION_SECRET and NODE_ENV != 'development': To use the hashchain server you must pick a random (but stable) HASHCHAINSERVER_APPLICATION_SECRET for secure communciation with it. (If you aren't using the hashchain server, you can ignore this.)");
|
|
44
|
+
}
|
|
45
|
+
const HASHCHAINSERVER_OPTS = {
|
|
46
|
+
baseUrl: HASHCHAINSERVER_URL,
|
|
47
|
+
applicationSecret: HASHCHAINSERVER_APPLICATION_SECRET,
|
|
48
|
+
};
|
|
49
|
+
const config = {
|
|
50
|
+
NODE_ENV,
|
|
51
|
+
PORT,
|
|
52
|
+
DATABASE_URL,
|
|
53
|
+
SUPERUSER_DATABASE_URL,
|
|
54
|
+
MP_GRAPHQL_URL,
|
|
55
|
+
HASHCHAINSERVER_OPTS,
|
|
56
|
+
};
|
|
57
|
+
export default config;
|
package/dist/src/db/index.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import * as pg from "pg";
|
|
|
2
2
|
import stream from "node:stream";
|
|
3
3
|
import { DbCasino, DbExperience, DbSession, DbTransferStatusKind, DbUser, DbWithdrawal } from "./types.js";
|
|
4
4
|
import { TransferStatusKind } from "../__generated__/graphql.js";
|
|
5
|
-
export * from "../hash-chain/db-hash-chain.js";
|
|
6
5
|
export * from "./types.js";
|
|
7
6
|
export * from "./public.js";
|
|
8
7
|
export * from "./util.js";
|
package/dist/src/db/index.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import * as pg from "pg";
|
|
2
|
-
import
|
|
2
|
+
import config from "../config.js";
|
|
3
3
|
import stream from "node:stream";
|
|
4
4
|
import { exactlyOneRow, maybeOneRow } from "./util.js";
|
|
5
5
|
import { logger } from "../logger.js";
|
|
6
6
|
import { setTimeout } from "node:timers/promises";
|
|
7
7
|
import { assert } from "tsafe";
|
|
8
|
-
export * from "../hash-chain/db-hash-chain.js";
|
|
9
8
|
export * from "./types.js";
|
|
10
9
|
export * from "./public.js";
|
|
11
10
|
export * from "./util.js";
|
package/dist/src/db/types.d.ts
CHANGED
|
@@ -114,31 +114,3 @@ export type DbTakeRequest = {
|
|
|
114
114
|
transfer_completion_attempted_at: Date | null;
|
|
115
115
|
updated_at: Date;
|
|
116
116
|
};
|
|
117
|
-
export type DbHashChain = {
|
|
118
|
-
id: string;
|
|
119
|
-
user_id: string;
|
|
120
|
-
experience_id: string;
|
|
121
|
-
casino_id: string;
|
|
122
|
-
client_seed: string;
|
|
123
|
-
max_iterations: number;
|
|
124
|
-
current_iteration: number;
|
|
125
|
-
active: boolean;
|
|
126
|
-
};
|
|
127
|
-
export declare const DbHashKind: {
|
|
128
|
-
readonly TERMINAL: "TERMINAL";
|
|
129
|
-
readonly INTERMEDIATE: "INTERMEDIATE";
|
|
130
|
-
readonly PREIMAGE: "PREIMAGE";
|
|
131
|
-
};
|
|
132
|
-
export type DbHashKind = (typeof DbHashKind)[keyof typeof DbHashKind];
|
|
133
|
-
export type DbHash = {
|
|
134
|
-
id: string;
|
|
135
|
-
kind: DbHashKind;
|
|
136
|
-
hash_chain_id: string;
|
|
137
|
-
iteration: number;
|
|
138
|
-
digest: Uint8Array;
|
|
139
|
-
metadata: Record<string, unknown>;
|
|
140
|
-
};
|
|
141
|
-
export type DbHubOutcome = {
|
|
142
|
-
weight: number;
|
|
143
|
-
profit: number;
|
|
144
|
-
};
|
package/dist/src/db/types.js
CHANGED
package/dist/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import PgUpgradeSchema, { DatabaseAheadError, } from "@moneypot/pg-upgrade-schema";
|
|
2
2
|
import * as db from "./db/index.js";
|
|
3
|
-
import
|
|
3
|
+
import config from "./config.js";
|
|
4
4
|
import { createHubServer } from "./server/index.js";
|
|
5
5
|
import { initializeTransferProcessors } from "./process-transfers.js";
|
|
6
6
|
import { join } from "path";
|
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
export declare const
|
|
4
|
-
forMpTakeRequestProcessing: (pgClient:
|
|
1
|
+
import { PoolClient } from "pg";
|
|
2
|
+
import { DbCasino, DbProcessedTakeRequest } from "./db/types.js";
|
|
3
|
+
export declare const pgAdvisoryLock: {
|
|
4
|
+
forMpTakeRequestProcessing: (pgClient: PoolClient, params: {
|
|
5
5
|
mpTakeRequestId: DbProcessedTakeRequest["mp_take_request_id"];
|
|
6
6
|
casinoId: DbCasino["id"];
|
|
7
7
|
}) => Promise<void>;
|
|
8
|
-
forNewHashChain: (pgClient: PgClientInTransaction, params: {
|
|
9
|
-
userId: DbUser["id"];
|
|
10
|
-
experienceId: DbExperience["id"];
|
|
11
|
-
casinoId: DbCasino["id"];
|
|
12
|
-
}) => Promise<void>;
|
|
13
8
|
};
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { assert } from "tsafe";
|
|
2
1
|
var LockNamespace;
|
|
3
2
|
(function (LockNamespace) {
|
|
4
3
|
LockNamespace[LockNamespace["MP_TAKE_REQUEST"] = 1] = "MP_TAKE_REQUEST";
|
|
5
|
-
LockNamespace[LockNamespace["NEW_HASH_CHAIN"] = 2] = "NEW_HASH_CHAIN";
|
|
6
4
|
})(LockNamespace || (LockNamespace = {}));
|
|
7
5
|
function simpleHash32(text) {
|
|
8
6
|
let hash = 0;
|
|
@@ -22,13 +20,8 @@ async function acquireAdvisoryLock(pgClient, namespace, hash) {
|
|
|
22
20
|
values: [namespace, hash],
|
|
23
21
|
});
|
|
24
22
|
}
|
|
25
|
-
export const
|
|
23
|
+
export const pgAdvisoryLock = {
|
|
26
24
|
forMpTakeRequestProcessing: async (pgClient, params) => {
|
|
27
|
-
assert(pgClient._inTransaction, "pgClient must be in a transaction");
|
|
28
25
|
await acquireAdvisoryLock(pgClient, LockNamespace.MP_TAKE_REQUEST, createHashKey(params.mpTakeRequestId, params.casinoId));
|
|
29
26
|
},
|
|
30
|
-
forNewHashChain: async (pgClient, params) => {
|
|
31
|
-
assert(pgClient._inTransaction, "pgClient must be in a transaction");
|
|
32
|
-
await acquireAdvisoryLock(pgClient, LockNamespace.NEW_HASH_CHAIN, createHashKey(params.userId, params.experienceId, params.casinoId));
|
|
33
|
-
},
|
|
34
27
|
};
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
drop table if exists hub.outcome_bet cascade;
|
|
2
|
-
drop type if exists hub.outcome cascade;
|
|
3
|
-
|
|
4
1
|
create type hub.outcome as (
|
|
5
2
|
weight float,
|
|
6
3
|
profit float
|
|
@@ -17,9 +14,6 @@ create table hub.outcome_bet (
|
|
|
17
14
|
experience_id uuid not null references hub.experience(id),
|
|
18
15
|
casino_id uuid not null references hub.casino(id),
|
|
19
16
|
|
|
20
|
-
-- provably fair hash chain
|
|
21
|
-
hash_chain_id uuid not null references hub.hash_chain(id),
|
|
22
|
-
|
|
23
17
|
currency_key text not null,
|
|
24
18
|
wager float not null,
|
|
25
19
|
|
|
@@ -30,7 +24,7 @@ create table hub.outcome_bet (
|
|
|
30
24
|
profit float not null,
|
|
31
25
|
|
|
32
26
|
-- null when no outcomes saved
|
|
33
|
-
outcome_idx smallint null
|
|
27
|
+
outcome_idx smallint null,
|
|
34
28
|
outcomes hub.outcome[] not null default '{}',
|
|
35
29
|
|
|
36
30
|
-- Operator-provided data per bet
|
|
@@ -43,7 +37,6 @@ create index outcome_bet_user_id_idx on hub.outcome_bet(user_id);
|
|
|
43
37
|
create index outcome_bet_experience_id_idx on hub.outcome_bet(experience_id);
|
|
44
38
|
create index outcome_bet_casino_id_idx on hub.outcome_bet(casino_id);
|
|
45
39
|
create index outcome_bet_kind_idx on hub.outcome_bet(kind);
|
|
46
|
-
create index outcome_bet_hash_chain_id_idx on hub.outcome_bet(hash_chain_id);
|
|
47
40
|
|
|
48
41
|
-- GRANT
|
|
49
42
|
|
|
@@ -1,15 +1,4 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { Result } from "../util.js";
|
|
3
|
-
declare const OutcomeSchema: z.ZodObject<{
|
|
4
|
-
weight: z.ZodNumber;
|
|
5
|
-
profit: z.ZodNumber;
|
|
6
|
-
}, "strict", z.ZodTypeAny, {
|
|
7
|
-
profit: number;
|
|
8
|
-
weight: number;
|
|
9
|
-
}, {
|
|
10
|
-
profit: number;
|
|
11
|
-
weight: number;
|
|
12
|
-
}>;
|
|
13
2
|
declare const InputSchema: z.ZodObject<{
|
|
14
3
|
kind: z.ZodString;
|
|
15
4
|
wager: z.ZodNumber;
|
|
@@ -18,62 +7,46 @@ declare const InputSchema: z.ZodObject<{
|
|
|
18
7
|
weight: z.ZodNumber;
|
|
19
8
|
profit: z.ZodNumber;
|
|
20
9
|
}, "strict", z.ZodTypeAny, {
|
|
21
|
-
profit: number;
|
|
22
10
|
weight: number;
|
|
23
|
-
}, {
|
|
24
11
|
profit: number;
|
|
12
|
+
}, {
|
|
25
13
|
weight: number;
|
|
26
|
-
}>, "many">, {
|
|
27
14
|
profit: number;
|
|
15
|
+
}>, "many">, {
|
|
28
16
|
weight: number;
|
|
29
|
-
}[], {
|
|
30
17
|
profit: number;
|
|
18
|
+
}[], {
|
|
31
19
|
weight: number;
|
|
32
|
-
}[]>, {
|
|
33
20
|
profit: number;
|
|
21
|
+
}[]>, {
|
|
34
22
|
weight: number;
|
|
35
|
-
}[], {
|
|
36
23
|
profit: number;
|
|
24
|
+
}[], {
|
|
37
25
|
weight: number;
|
|
26
|
+
profit: number;
|
|
38
27
|
}[]>;
|
|
39
|
-
hashChainId: z.ZodString;
|
|
40
|
-
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
41
28
|
}, "strict", z.ZodTypeAny, {
|
|
42
29
|
currency: string;
|
|
43
|
-
hashChainId: string;
|
|
44
30
|
kind: string;
|
|
45
31
|
wager: number;
|
|
46
32
|
outcomes: {
|
|
47
|
-
profit: number;
|
|
48
33
|
weight: number;
|
|
34
|
+
profit: number;
|
|
49
35
|
}[];
|
|
50
|
-
metadata?: Record<string, any> | undefined;
|
|
51
36
|
}, {
|
|
52
37
|
currency: string;
|
|
53
|
-
hashChainId: string;
|
|
54
38
|
kind: string;
|
|
55
39
|
wager: number;
|
|
56
40
|
outcomes: {
|
|
57
|
-
profit: number;
|
|
58
41
|
weight: number;
|
|
42
|
+
profit: number;
|
|
59
43
|
}[];
|
|
60
|
-
metadata?: Record<string, any> | undefined;
|
|
61
44
|
}>;
|
|
62
45
|
type Input = z.infer<typeof InputSchema>;
|
|
63
|
-
type Outcome = z.infer<typeof OutcomeSchema>;
|
|
64
|
-
type FinalizeMetadataData = {
|
|
65
|
-
wager: number;
|
|
66
|
-
currencyKey: string;
|
|
67
|
-
clientSeed: string;
|
|
68
|
-
hash: Uint8Array;
|
|
69
|
-
outcomes: Outcome[];
|
|
70
|
-
outcomeIdx: number;
|
|
71
|
-
};
|
|
72
46
|
export type OutcomeBetConfig = {
|
|
73
47
|
houseEdge: number;
|
|
74
48
|
saveOutcomes: boolean;
|
|
75
|
-
|
|
76
|
-
finalizeMetadata?: (validatedMetadata: Record<string, any>, data: FinalizeMetadataData) => Record<string, any>;
|
|
49
|
+
getMetadata?: (input: Input) => Promise<Record<string, any>>;
|
|
77
50
|
};
|
|
78
51
|
export type OutcomeBetConfigMap<BetKind extends string> = {
|
|
79
52
|
[betKind in BetKind]: OutcomeBetConfig;
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { context, object, sideEffect } from "postgraphile/grafast";
|
|
2
2
|
import { gql, makeExtendSchemaPlugin } from "postgraphile/utils";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { GraphQLError } from "graphql";
|
|
5
|
-
import {
|
|
5
|
+
import { exactlyOneRow, maybeOneRow, superuserPool, withPgPoolTransaction, } from "../db/index.js";
|
|
6
6
|
import { assert } from "tsafe";
|
|
7
|
-
import { dbInsertHubHash, dbLockHubHashChain, } from "../hash-chain/db-hash-chain.js";
|
|
8
|
-
import { getIntermediateHash, getPreimageHash, } from "../hash-chain/get-hash.js";
|
|
9
|
-
import { createHmac } from "node:crypto";
|
|
10
7
|
const FLOAT_EPSILON = 1e-10;
|
|
11
8
|
function sum(ns) {
|
|
12
9
|
return ns.reduce((a, b) => a + b, 0);
|
|
@@ -38,8 +35,6 @@ const InputSchema = z
|
|
|
38
35
|
.max(50, "Outcome count must be <= 50")
|
|
39
36
|
.refine((data) => data.some((o) => o.profit < 0), "At least one outcome should have profit < 0")
|
|
40
37
|
.refine((data) => data.some((o) => o.profit > 0), "At least one outcome should have profit > 0"),
|
|
41
|
-
hashChainId: z.string().uuid("Invalid hash chain ID"),
|
|
42
|
-
metadata: z.record(z.string(), z.any()).optional(),
|
|
43
38
|
})
|
|
44
39
|
.strict();
|
|
45
40
|
const BetKindSchema = z
|
|
@@ -53,11 +48,10 @@ const BetConfigsSchema = z.record(BetKindSchema, z.object({
|
|
|
53
48
|
.gte(0, "House edge must be >= 0")
|
|
54
49
|
.lte(1, "House edge must be <= 1"),
|
|
55
50
|
saveOutcomes: z.boolean(),
|
|
56
|
-
|
|
57
|
-
.function()
|
|
58
|
-
.optional(),
|
|
59
|
-
finalizeMetadata: z
|
|
51
|
+
getMetadata: z
|
|
60
52
|
.function()
|
|
53
|
+
.args(InputSchema)
|
|
54
|
+
.returns(z.record(z.string(), z.any()))
|
|
61
55
|
.optional(),
|
|
62
56
|
}));
|
|
63
57
|
export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
@@ -75,18 +69,10 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
75
69
|
wager: Int!
|
|
76
70
|
currency: String!
|
|
77
71
|
outcomes: [HubOutcomeInput!]!
|
|
78
|
-
hashChainId: UUID!
|
|
79
|
-
metadata: JSON
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
type HubMakeOutcomeBetSuccess {
|
|
83
|
-
bet: HubOutcomeBet!
|
|
84
72
|
}
|
|
85
73
|
|
|
86
|
-
union HubMakeOutcomeBetResult = HubMakeOutcomeBetSuccess | HubBadHashChainError
|
|
87
|
-
|
|
88
74
|
type HubMakeOutcomeBetPayload {
|
|
89
|
-
|
|
75
|
+
bet: HubOutcomeBet!
|
|
90
76
|
}
|
|
91
77
|
|
|
92
78
|
extend type Mutation {
|
|
@@ -99,7 +85,7 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
99
85
|
Mutation: {
|
|
100
86
|
hubMakeOutcomeBet: (_, { $input }) => {
|
|
101
87
|
const $identity = context().get("identity");
|
|
102
|
-
const $
|
|
88
|
+
const $betId = sideEffect([$identity, $input], async ([identity, rawInput]) => {
|
|
103
89
|
if (identity?.kind !== "user") {
|
|
104
90
|
throw new GraphQLError("Unauthorized");
|
|
105
91
|
}
|
|
@@ -122,16 +108,6 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
122
108
|
if (!betKinds.includes(rawInput.kind)) {
|
|
123
109
|
throw new GraphQLError(`Invalid bet kind`);
|
|
124
110
|
}
|
|
125
|
-
let initializedMetadata;
|
|
126
|
-
if (betConfig.initializeMetadataFromUntrustedUserInput) {
|
|
127
|
-
const result = betConfig.initializeMetadataFromUntrustedUserInput(input);
|
|
128
|
-
if (result.ok) {
|
|
129
|
-
initializedMetadata = result.value;
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
throw new GraphQLError(`Invalid input: ${result.error}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
111
|
const houseEV = calculateHouseEV(input.outcomes);
|
|
136
112
|
const minHouseEV = Math.max(0, betConfig.houseEdge - FLOAT_EPSILON);
|
|
137
113
|
if (houseEV < minHouseEV) {
|
|
@@ -182,76 +158,7 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
182
158
|
if (maxPotentialPayout > maxAllowablePayout) {
|
|
183
159
|
throw new GraphQLError(`House risk limit exceeded. Max payout: ${maxPotentialPayout.toFixed(4)}`);
|
|
184
160
|
}
|
|
185
|
-
const
|
|
186
|
-
userId: session.user_id,
|
|
187
|
-
experienceId: session.experience_id,
|
|
188
|
-
casinoId: session.casino_id,
|
|
189
|
-
hashChainId: input.hashChainId,
|
|
190
|
-
});
|
|
191
|
-
if (!dbHashChain || !dbHashChain.active) {
|
|
192
|
-
return {
|
|
193
|
-
__typename: "HubBadHashChainError",
|
|
194
|
-
message: "Active hash chain not found",
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
if (dbHashChain.current_iteration <= 1) {
|
|
198
|
-
if (dbHashChain.current_iteration === 1) {
|
|
199
|
-
finishHashChainInBackground({
|
|
200
|
-
hashChainId: input.hashChainId,
|
|
201
|
-
}).catch((e) => {
|
|
202
|
-
console.error("Error finishing hash chain in background", { hashChainId: input.hashChainId, error: e });
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
return {
|
|
206
|
-
__typename: "HubBadHashChainError",
|
|
207
|
-
message: "Hash chain drained. Create a new one.",
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
const betHashIteration = dbHashChain.current_iteration - 1;
|
|
211
|
-
assert(betHashIteration > 0, "Bet hash iteration must be > 0");
|
|
212
|
-
const betHashResult = await getIntermediateHash({
|
|
213
|
-
hashChainId: input.hashChainId,
|
|
214
|
-
iteration: betHashIteration,
|
|
215
|
-
});
|
|
216
|
-
switch (betHashResult.type) {
|
|
217
|
-
case "success":
|
|
218
|
-
break;
|
|
219
|
-
case "bad_hash_chain":
|
|
220
|
-
return {
|
|
221
|
-
__typename: "HubBadHashChainError",
|
|
222
|
-
message: "Hash chain not found",
|
|
223
|
-
};
|
|
224
|
-
default: {
|
|
225
|
-
const _exhaustiveCheck = betHashResult;
|
|
226
|
-
throw new Error(`Unknown bet hash result: ${_exhaustiveCheck}`);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
await dbInsertHubHash(pgClient, {
|
|
230
|
-
hashChainId: dbHashChain.id,
|
|
231
|
-
kind: DbHashKind.INTERMEDIATE,
|
|
232
|
-
digest: betHashResult.hash,
|
|
233
|
-
iteration: betHashIteration,
|
|
234
|
-
});
|
|
235
|
-
const result = await pgClient.query(`
|
|
236
|
-
UPDATE hub.hash_chain
|
|
237
|
-
SET current_iteration = $2
|
|
238
|
-
WHERE id = $1
|
|
239
|
-
`, [dbHashChain.id, betHashIteration]);
|
|
240
|
-
if (result.rowCount !== 1) {
|
|
241
|
-
throw new GraphQLError("Failed to update hash chain iteration");
|
|
242
|
-
}
|
|
243
|
-
const finalHash = (() => {
|
|
244
|
-
const serverHash = betHashResult.hash;
|
|
245
|
-
const clientSeed = dbHashChain.client_seed;
|
|
246
|
-
const finalHash = createHmac("sha256", serverHash)
|
|
247
|
-
.update(clientSeed)
|
|
248
|
-
.digest();
|
|
249
|
-
return finalHash;
|
|
250
|
-
})();
|
|
251
|
-
const { outcome, outcomeIdx } = pickRandomOutcome({
|
|
252
|
-
outcomes: input.outcomes,
|
|
253
|
-
hash: finalHash,
|
|
254
|
-
});
|
|
161
|
+
const { outcome, outcomeIdx } = pickRandomOutcome(input.outcomes);
|
|
255
162
|
const netPlayerAmount = input.wager * outcome.profit;
|
|
256
163
|
await pgClient.query({
|
|
257
164
|
text: `
|
|
@@ -286,27 +193,17 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
286
193
|
input.wager,
|
|
287
194
|
],
|
|
288
195
|
});
|
|
289
|
-
const immutableData = structuredClone({
|
|
290
|
-
wager: input.wager,
|
|
291
|
-
currencyKey: dbCurrency.key,
|
|
292
|
-
clientSeed: dbHashChain.client_seed,
|
|
293
|
-
hash: betHashResult.hash,
|
|
294
|
-
outcomes: input.outcomes,
|
|
295
|
-
outcomeIdx,
|
|
296
|
-
});
|
|
297
|
-
const finalizedMetadata = betConfig.finalizeMetadata
|
|
298
|
-
? betConfig.finalizeMetadata(initializedMetadata, immutableData)
|
|
299
|
-
: initializedMetadata;
|
|
300
196
|
const newBet = {
|
|
301
197
|
kind: rawInput.kind,
|
|
302
198
|
wager: input.wager,
|
|
303
199
|
profit: outcome.profit,
|
|
304
200
|
currency_key: dbCurrency.key,
|
|
305
|
-
hash_chain_id: input.hashChainId,
|
|
306
201
|
user_id: session.user_id,
|
|
307
202
|
casino_id: session.casino_id,
|
|
308
203
|
experience_id: session.experience_id,
|
|
309
|
-
metadata:
|
|
204
|
+
metadata: betConfig.getMetadata
|
|
205
|
+
? await betConfig.getMetadata(input)
|
|
206
|
+
: {},
|
|
310
207
|
...(betConfig.saveOutcomes
|
|
311
208
|
? {
|
|
312
209
|
outcomes: input.outcomes,
|
|
@@ -324,7 +221,6 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
324
221
|
user_id,
|
|
325
222
|
casino_id,
|
|
326
223
|
experience_id,
|
|
327
|
-
hash_chain_id,
|
|
328
224
|
kind,
|
|
329
225
|
currency_key,
|
|
330
226
|
wager,
|
|
@@ -333,14 +229,13 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
333
229
|
outcome_idx,
|
|
334
230
|
metadata
|
|
335
231
|
)
|
|
336
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10
|
|
232
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
337
233
|
RETURNING id
|
|
338
234
|
`,
|
|
339
235
|
values: [
|
|
340
236
|
newBet.user_id,
|
|
341
237
|
newBet.casino_id,
|
|
342
238
|
newBet.experience_id,
|
|
343
|
-
newBet.hash_chain_id,
|
|
344
239
|
newBet.kind,
|
|
345
240
|
newBet.currency_key,
|
|
346
241
|
newBet.wager,
|
|
@@ -351,31 +246,11 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
351
246
|
],
|
|
352
247
|
})
|
|
353
248
|
.then(exactlyOneRow);
|
|
354
|
-
return
|
|
355
|
-
__typename: "HubMakeOutcomeBetSuccess",
|
|
356
|
-
betId: bet.id,
|
|
357
|
-
};
|
|
249
|
+
return bet.id;
|
|
358
250
|
});
|
|
359
251
|
});
|
|
360
252
|
return object({
|
|
361
|
-
|
|
362
|
-
});
|
|
363
|
-
},
|
|
364
|
-
},
|
|
365
|
-
HubMakeOutcomeBetSuccess: {
|
|
366
|
-
__assertStep: ObjectStep,
|
|
367
|
-
bet($data) {
|
|
368
|
-
const $betId = access($data, "betId");
|
|
369
|
-
return outcomeBetTable.get({ id: $betId });
|
|
370
|
-
},
|
|
371
|
-
},
|
|
372
|
-
HubMakeOutcomeBetPayload: {
|
|
373
|
-
__assertStep: ObjectStep,
|
|
374
|
-
result($data) {
|
|
375
|
-
const $result = $data.get("result");
|
|
376
|
-
return polymorphicBranch($result, {
|
|
377
|
-
HubMakeOutcomeBetSuccess: {},
|
|
378
|
-
HubBadHashChainError: {},
|
|
253
|
+
bet: outcomeBetTable.get({ id: $betId }),
|
|
379
254
|
});
|
|
380
255
|
},
|
|
381
256
|
},
|
|
@@ -383,13 +258,12 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
383
258
|
};
|
|
384
259
|
}, "HubMakeOutcomeBetPlugin");
|
|
385
260
|
}
|
|
386
|
-
function
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
return uint32Value / Math.pow(2, 32);
|
|
261
|
+
function generateRandomNumber() {
|
|
262
|
+
const array = new Uint32Array(1);
|
|
263
|
+
crypto.getRandomValues(array);
|
|
264
|
+
return array[0] / 2 ** 32;
|
|
391
265
|
}
|
|
392
|
-
function pickRandomOutcome(
|
|
266
|
+
function pickRandomOutcome(outcomes) {
|
|
393
267
|
assert(outcomes.length >= 2, "Outcome count must be >= 2");
|
|
394
268
|
const totalWeight = sum(outcomes.map((o) => o.weight));
|
|
395
269
|
const outcomesWithProbability = outcomes.map((o) => ({
|
|
@@ -398,12 +272,12 @@ function pickRandomOutcome({ outcomes, hash, }) {
|
|
|
398
272
|
}));
|
|
399
273
|
const totalProb = outcomesWithProbability.reduce((sum, outcome) => sum + outcome.probability, 0);
|
|
400
274
|
assert(Math.abs(totalProb - 1.0) < FLOAT_EPSILON, "Probabilities must sum to ~1");
|
|
401
|
-
const randomValue =
|
|
402
|
-
let
|
|
275
|
+
const randomValue = generateRandomNumber();
|
|
276
|
+
let cumProb = 0;
|
|
403
277
|
for (let i = 0; i < outcomesWithProbability.length; i++) {
|
|
404
278
|
const outcome = outcomesWithProbability[i];
|
|
405
|
-
|
|
406
|
-
if (randomValue <=
|
|
279
|
+
cumProb += outcome.probability;
|
|
280
|
+
if (randomValue <= cumProb) {
|
|
407
281
|
return { outcome, outcomeIdx: i };
|
|
408
282
|
}
|
|
409
283
|
}
|
|
@@ -449,41 +323,3 @@ async function dbLockBalanceAndBankroll(pgClient, { userId, casinoId, experience
|
|
|
449
323
|
dbHouseBankroll: null,
|
|
450
324
|
};
|
|
451
325
|
}
|
|
452
|
-
async function finishHashChainInBackground({ hashChainId, }) {
|
|
453
|
-
console.log("Finishing hash chain in background", { hashChainId });
|
|
454
|
-
const preimageHashResult = await getPreimageHash({
|
|
455
|
-
hashChainId,
|
|
456
|
-
});
|
|
457
|
-
console.log("Preimage hash result", { preimageHashResult });
|
|
458
|
-
if (preimageHashResult.type === "success") {
|
|
459
|
-
console.log("Inserting preimage hash", {
|
|
460
|
-
hashChainId,
|
|
461
|
-
kind: DbHashKind.PREIMAGE,
|
|
462
|
-
digest: preimageHashResult.hash,
|
|
463
|
-
iteration: 0,
|
|
464
|
-
});
|
|
465
|
-
await withPgPoolTransaction(superuserPool, async (pgClient) => {
|
|
466
|
-
await dbInsertHubHash(pgClient, {
|
|
467
|
-
hashChainId,
|
|
468
|
-
kind: DbHashKind.PREIMAGE,
|
|
469
|
-
digest: preimageHashResult.hash,
|
|
470
|
-
iteration: 0,
|
|
471
|
-
});
|
|
472
|
-
const result = await pgClient.query(`
|
|
473
|
-
UPDATE hub.hash_chain
|
|
474
|
-
SET current_iteration = 0,
|
|
475
|
-
active = false
|
|
476
|
-
WHERE id = $1
|
|
477
|
-
`, [hashChainId]);
|
|
478
|
-
if (result.rowCount !== 1) {
|
|
479
|
-
throw new Error("Failed to update hash chain iteration");
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
else {
|
|
484
|
-
console.warn("Failed to insert preimage hash in background", {
|
|
485
|
-
hashChainId,
|
|
486
|
-
error: preimageHashResult,
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
}
|
|
@@ -6,7 +6,7 @@ import EventEmitter from "events";
|
|
|
6
6
|
import { superuserPool } from "./db/index.js";
|
|
7
7
|
import { dbGetCasinoById, dbGetCasinoSecretById } from "./db/internal.js";
|
|
8
8
|
import pg from "pg";
|
|
9
|
-
import
|
|
9
|
+
import config from "./config.js";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import { gql } from "./__generated__/gql.js";
|
|
12
12
|
import { logger } from "./logger.js";
|
|
@@ -2,7 +2,7 @@ import "graphile-config";
|
|
|
2
2
|
import "postgraphile";
|
|
3
3
|
import { makePgService } from "postgraphile/adaptors/pg";
|
|
4
4
|
import { PostGraphileAmberPreset } from "postgraphile/presets/amber";
|
|
5
|
-
import
|
|
5
|
+
import config from "../config.js";
|
|
6
6
|
import { maskError } from "./handle-errors.js";
|
|
7
7
|
import * as db from "../db/index.js";
|
|
8
8
|
import { SmartTagsPlugin } from "../smart-tags.js";
|
|
@@ -20,10 +20,6 @@ import { HubAddCasinoPlugin } from "../plugins/hub-add-casino.js";
|
|
|
20
20
|
import { HubBalanceAlertPlugin } from "../plugins/hub-balance-alert.js";
|
|
21
21
|
import { custom as customPgOmitArchivedPlugin } from "@graphile-contrib/pg-omit-archived";
|
|
22
22
|
import { HubCurrentXPlugin } from "../plugins/hub-current-x.js";
|
|
23
|
-
import { HubCreateHashChainPlugin } from "../hash-chain/plugins/hub-create-hash-chain.js";
|
|
24
|
-
import { HubBadHashChainErrorPlugin } from "../hash-chain/plugins/hub-bad-hash-chain-error.js";
|
|
25
|
-
import { HubUserActiveHashChainPlugin } from "../hash-chain/plugins/hub-user-active-hash-chain.js";
|
|
26
|
-
import { HubOutcomeInputNonNullFieldsPlugin } from "../plugins/hub-outcome-input-non-null-fields.js";
|
|
27
23
|
export const requiredPlugins = [
|
|
28
24
|
SmartTagsPlugin,
|
|
29
25
|
IdToNodeIdPlugin,
|
|
@@ -39,10 +35,6 @@ export const requiredPlugins = [
|
|
|
39
35
|
export const defaultPlugins = [
|
|
40
36
|
...(config.NODE_ENV === "development" ? [DebugPlugin] : []),
|
|
41
37
|
...requiredPlugins,
|
|
42
|
-
HubOutcomeInputNonNullFieldsPlugin,
|
|
43
|
-
HubBadHashChainErrorPlugin,
|
|
44
|
-
HubCreateHashChainPlugin,
|
|
45
|
-
HubUserActiveHashChainPlugin,
|
|
46
38
|
HubClaimFaucetPlugin,
|
|
47
39
|
customPgOmitArchivedPlugin("deleted"),
|
|
48
40
|
];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GraphQLError } from "postgraphile/graphql";
|
|
2
|
-
import
|
|
2
|
+
import config from "../config.js";
|
|
3
3
|
const isDev = config.NODE_ENV === "development";
|
|
4
4
|
const isTest = config.NODE_ENV === "test";
|
|
5
5
|
const camelCase = (s) => s.toLowerCase().replace(/(_\\w)/g, (m) => m[1].toUpperCase());
|
package/dist/src/server/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { grafserv } from "grafserv/express/v4";
|
|
|
3
3
|
import postgraphile from "postgraphile";
|
|
4
4
|
import { createPreset, defaultPlugins } from "./graphile.config.js";
|
|
5
5
|
import express from "express";
|
|
6
|
-
import
|
|
6
|
+
import config from "../config.js";
|
|
7
7
|
import { logger } from "../logger.js";
|
|
8
8
|
import cors from "./middleware/cors.js";
|
|
9
9
|
import authentication from "./middleware/authentication.js";
|
|
@@ -2,7 +2,7 @@ import { gql } from "../__generated__/gql.js";
|
|
|
2
2
|
import { TakeRequestStatus as MpTakeRequestStatus, TransferStatusKind as MpTransferStatus, } from "../__generated__/graphql.js";
|
|
3
3
|
import { exactlyOneRow, maybeOneRow, superuserPool, withPgPoolTransaction, } from "../db/index.js";
|
|
4
4
|
import { assert } from "tsafe";
|
|
5
|
-
import {
|
|
5
|
+
import { pgAdvisoryLock } from "../pg-advisory-lock.js";
|
|
6
6
|
const MP_PAGINATE_PENDING_TAKE_REQUESTS = gql(`
|
|
7
7
|
query MpPaginatedPendingTakeRequests($controllerId: UUID!, $after: Cursor) {
|
|
8
8
|
allTakeRequests(
|
|
@@ -178,7 +178,7 @@ async function fetchPendingTakeRequests(graphqlClient, controllerId) {
|
|
|
178
178
|
}
|
|
179
179
|
async function processSingleTakeRequest({ mpTakeRequestId, mpTakeRequest, casinoId, graphqlClient, }) {
|
|
180
180
|
return withPgPoolTransaction(superuserPool, async (pgClient) => {
|
|
181
|
-
await
|
|
181
|
+
await pgAdvisoryLock.forMpTakeRequestProcessing(pgClient, {
|
|
182
182
|
mpTakeRequestId,
|
|
183
183
|
casinoId,
|
|
184
184
|
});
|
|
@@ -457,7 +457,7 @@ async function processPendingTransferCompletions({ casinoId, graphqlClient, abor
|
|
|
457
457
|
}
|
|
458
458
|
async function completeTransfer({ mpTakeRequestId, takeRequestId, mpTransferId, graphqlClient, casinoId, }) {
|
|
459
459
|
return withPgPoolTransaction(superuserPool, async (pgClient) => {
|
|
460
|
-
await
|
|
460
|
+
await pgAdvisoryLock.forMpTakeRequestProcessing(pgClient, {
|
|
461
461
|
mpTakeRequestId,
|
|
462
462
|
casinoId,
|
|
463
463
|
});
|
package/package.json
CHANGED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { DbCasino, DbExperience, DbHash, DbHashChain, DbUser } from "../db/types.js";
|
|
2
|
-
import { PgClientInTransaction } from "../db/index.js";
|
|
3
|
-
export declare function dbLockHubHashChain(pgClient: PgClientInTransaction, { userId, experienceId, casinoId, hashChainId, }: {
|
|
4
|
-
userId: DbUser["id"];
|
|
5
|
-
experienceId: DbExperience["id"];
|
|
6
|
-
casinoId: DbCasino["id"];
|
|
7
|
-
hashChainId: DbHashChain["id"];
|
|
8
|
-
}): Promise<DbHashChain | null>;
|
|
9
|
-
export declare function dbInsertHubHash(pgClient: PgClientInTransaction, { hashChainId, kind, digest, iteration, metadata, }: {
|
|
10
|
-
hashChainId: DbHashChain["id"];
|
|
11
|
-
kind: DbHash["kind"];
|
|
12
|
-
digest: DbHash["digest"];
|
|
13
|
-
iteration: number;
|
|
14
|
-
metadata?: DbHash["metadata"];
|
|
15
|
-
}): Promise<DbHash>;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { exactlyOneRow, maybeOneRow, } from "../db/index.js";
|
|
2
|
-
import { assert } from "tsafe";
|
|
3
|
-
export async function dbLockHubHashChain(pgClient, { userId, experienceId, casinoId, hashChainId, }) {
|
|
4
|
-
assert(pgClient._inTransaction, "dbLockHubHashChain must be called in a transaction");
|
|
5
|
-
return pgClient
|
|
6
|
-
.query(`
|
|
7
|
-
SELECT *
|
|
8
|
-
FROM hub.hash_chain
|
|
9
|
-
WHERE id = $1
|
|
10
|
-
AND user_id = $2
|
|
11
|
-
AND experience_id = $3
|
|
12
|
-
AND casino_id = $4
|
|
13
|
-
AND active = TRUE
|
|
14
|
-
|
|
15
|
-
FOR UPDATE
|
|
16
|
-
`, [hashChainId, userId, experienceId, casinoId])
|
|
17
|
-
.then(maybeOneRow)
|
|
18
|
-
.then((row) => row ?? null);
|
|
19
|
-
}
|
|
20
|
-
export async function dbInsertHubHash(pgClient, { hashChainId, kind, digest, iteration, metadata = {}, }) {
|
|
21
|
-
assert(pgClient._inTransaction, "dbInsertHash must be called in a transaction");
|
|
22
|
-
return pgClient
|
|
23
|
-
.query(`
|
|
24
|
-
INSERT INTO hub.hash (hash_chain_id, kind, digest, iteration, metadata)
|
|
25
|
-
VALUES ($1, $2, $3, $4, $5)
|
|
26
|
-
RETURNING *
|
|
27
|
-
`, [
|
|
28
|
-
hashChainId,
|
|
29
|
-
kind,
|
|
30
|
-
digest,
|
|
31
|
-
iteration,
|
|
32
|
-
metadata,
|
|
33
|
-
])
|
|
34
|
-
.then(exactlyOneRow);
|
|
35
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export type HashResult = {
|
|
2
|
-
type: "bad_hash_chain";
|
|
3
|
-
reason: "hashchain_too_old" | "empty_response";
|
|
4
|
-
} | {
|
|
5
|
-
type: "success";
|
|
6
|
-
hash: Uint8Array;
|
|
7
|
-
};
|
|
8
|
-
export declare function getIntermediateHash({ hashChainId, iteration, }: {
|
|
9
|
-
hashChainId: string;
|
|
10
|
-
iteration: number;
|
|
11
|
-
}): Promise<HashResult>;
|
|
12
|
-
export declare function getPreimageHash({ hashChainId, }: {
|
|
13
|
-
hashChainId: string;
|
|
14
|
-
}): Promise<HashResult>;
|
|
15
|
-
export declare function getTerminalHash({ hashChainId, }: {
|
|
16
|
-
hashChainId: string;
|
|
17
|
-
}): Promise<HashResult>;
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { getHash } from "@moneypot/hash-herald";
|
|
2
|
-
import * as config from "../config.js";
|
|
3
|
-
const HASH_HERALD_OPTIONS = {
|
|
4
|
-
baseUrl: config.HASHCHAINSERVER_URL,
|
|
5
|
-
applicationSecret: config.HASHCHAINSERVER_APPLICATION_SECRET,
|
|
6
|
-
};
|
|
7
|
-
function resultFromGetHashResponse(response) {
|
|
8
|
-
if (!response.resp) {
|
|
9
|
-
return { type: "bad_hash_chain", reason: "empty_response" };
|
|
10
|
-
}
|
|
11
|
-
const $case = response.resp.$case;
|
|
12
|
-
switch ($case) {
|
|
13
|
-
case "hash":
|
|
14
|
-
return { type: "success", hash: response.resp.value };
|
|
15
|
-
case "hashchainTooOldError":
|
|
16
|
-
return { type: "bad_hash_chain", reason: "hashchain_too_old" };
|
|
17
|
-
default: {
|
|
18
|
-
const _exhaustiveCheck = $case;
|
|
19
|
-
throw new Error(`Unknown hash response: ${_exhaustiveCheck}`);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
export async function getIntermediateHash({ hashChainId, iteration, }) {
|
|
24
|
-
const req = {
|
|
25
|
-
hashchainId: hashChainId,
|
|
26
|
-
iterations: iteration,
|
|
27
|
-
context: undefined,
|
|
28
|
-
};
|
|
29
|
-
return getHash(HASH_HERALD_OPTIONS, req).then(resultFromGetHashResponse);
|
|
30
|
-
}
|
|
31
|
-
export async function getPreimageHash({ hashChainId, }) {
|
|
32
|
-
const req = {
|
|
33
|
-
hashchainId: hashChainId,
|
|
34
|
-
iterations: 0,
|
|
35
|
-
context: {
|
|
36
|
-
event: {
|
|
37
|
-
$case: "fetchingPreimage",
|
|
38
|
-
value: {},
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
return getHash(HASH_HERALD_OPTIONS, req).then(resultFromGetHashResponse);
|
|
43
|
-
}
|
|
44
|
-
export async function getTerminalHash({ hashChainId, }) {
|
|
45
|
-
const iterations = 1_000;
|
|
46
|
-
const req = {
|
|
47
|
-
hashchainId: hashChainId,
|
|
48
|
-
iterations,
|
|
49
|
-
context: {
|
|
50
|
-
event: {
|
|
51
|
-
$case: "fetchingTerminalHash",
|
|
52
|
-
value: {},
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
return getHash(HASH_HERALD_OPTIONS, req).then(resultFromGetHashResponse);
|
|
57
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const HubBadHashChainErrorPlugin: GraphileConfig.Plugin;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { ObjectStep, access } from "postgraphile/grafast";
|
|
2
|
-
import { gql, makeExtendSchemaPlugin } from "postgraphile/utils";
|
|
3
|
-
export const HubBadHashChainErrorPlugin = makeExtendSchemaPlugin(() => {
|
|
4
|
-
return {
|
|
5
|
-
typeDefs: gql `
|
|
6
|
-
type HubBadHashChainError {
|
|
7
|
-
message: String
|
|
8
|
-
}
|
|
9
|
-
`,
|
|
10
|
-
plans: {
|
|
11
|
-
HubBadHashChainError: {
|
|
12
|
-
__assertStep: ObjectStep,
|
|
13
|
-
message($data) {
|
|
14
|
-
const $message = access($data, "message");
|
|
15
|
-
return $message;
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
}, "HubBadHashChainErrorPlugin");
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const HubCreateHashChainPlugin: GraphileConfig.Plugin;
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { context, object, sideEffect } from "@moneypot/hub/grafast";
|
|
2
|
-
import { gql, makeExtendSchemaPlugin } from "@moneypot/hub/graphile";
|
|
3
|
-
import { GraphQLError } from "graphql";
|
|
4
|
-
import { z } from "zod";
|
|
5
|
-
import { PgAdvisoryLock } from "../../pg-advisory-lock.js";
|
|
6
|
-
import { exactlyOneRow, superuserPool, withPgPoolTransaction, } from "@moneypot/hub/db";
|
|
7
|
-
import * as HashCommon from "../get-hash.js";
|
|
8
|
-
import { DbHashKind } from "../../db/types.js";
|
|
9
|
-
import * as config from "../../config.js";
|
|
10
|
-
const InputSchema = z.object({
|
|
11
|
-
clientSeed: z.string(),
|
|
12
|
-
});
|
|
13
|
-
export const HubCreateHashChainPlugin = makeExtendSchemaPlugin((build) => {
|
|
14
|
-
const hashChainTable = build.input.pgRegistry.pgResources.hub_hash_chain;
|
|
15
|
-
return {
|
|
16
|
-
typeDefs: gql `
|
|
17
|
-
input HubCreateHashChainInput {
|
|
18
|
-
clientSeed: String!
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type HubCreateHashChainPayload {
|
|
22
|
-
hashChain: HubHashChain!
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
extend type Mutation {
|
|
26
|
-
hubCreateHashChain(
|
|
27
|
-
input: HubCreateHashChainInput!
|
|
28
|
-
): HubCreateHashChainPayload!
|
|
29
|
-
}
|
|
30
|
-
`,
|
|
31
|
-
plans: {
|
|
32
|
-
Mutation: {
|
|
33
|
-
hubCreateHashChain: (_, { $input }) => {
|
|
34
|
-
const $identity = context().get("identity");
|
|
35
|
-
const $hashChainId = sideEffect([$input, $identity], ([rawInput, identity]) => {
|
|
36
|
-
if (identity?.kind !== "user") {
|
|
37
|
-
throw new GraphQLError("Unauthorized");
|
|
38
|
-
}
|
|
39
|
-
const result = InputSchema.safeParse(rawInput);
|
|
40
|
-
if (!result.success) {
|
|
41
|
-
const message = result.error.errors[0].message;
|
|
42
|
-
throw new GraphQLError(message);
|
|
43
|
-
}
|
|
44
|
-
const { clientSeed } = result.data;
|
|
45
|
-
return withPgPoolTransaction(superuserPool, async (pgClient) => {
|
|
46
|
-
await PgAdvisoryLock.forNewHashChain(pgClient, {
|
|
47
|
-
userId: identity.session.user_id,
|
|
48
|
-
experienceId: identity.session.experience_id,
|
|
49
|
-
casinoId: identity.session.casino_id,
|
|
50
|
-
});
|
|
51
|
-
await pgClient.query(`
|
|
52
|
-
UPDATE hub.hash_chain
|
|
53
|
-
SET active = false
|
|
54
|
-
WHERE user_id = $1
|
|
55
|
-
AND experience_id = $2
|
|
56
|
-
AND casino_id = $3
|
|
57
|
-
AND active = true
|
|
58
|
-
`, [
|
|
59
|
-
identity.session.user_id,
|
|
60
|
-
identity.session.experience_id,
|
|
61
|
-
identity.session.casino_id,
|
|
62
|
-
]);
|
|
63
|
-
const dbHashChain = await pgClient
|
|
64
|
-
.query(`
|
|
65
|
-
INSERT INTO hub.hash_chain (
|
|
66
|
-
user_id,
|
|
67
|
-
experience_id,
|
|
68
|
-
casino_id,
|
|
69
|
-
client_seed,
|
|
70
|
-
active,
|
|
71
|
-
max_iteration,
|
|
72
|
-
current_iteration
|
|
73
|
-
)
|
|
74
|
-
VALUES ($1, $2, $3, $4, true, $5, $5)
|
|
75
|
-
RETURNING *
|
|
76
|
-
`, [
|
|
77
|
-
identity.session.user_id,
|
|
78
|
-
identity.session.experience_id,
|
|
79
|
-
identity.session.casino_id,
|
|
80
|
-
clientSeed,
|
|
81
|
-
config.HASHCHAINSERVER_MAX_ITERATIONS,
|
|
82
|
-
])
|
|
83
|
-
.then(exactlyOneRow);
|
|
84
|
-
const terminalHash = await HashCommon.getTerminalHash({
|
|
85
|
-
hashChainId: dbHashChain.id,
|
|
86
|
-
});
|
|
87
|
-
await pgClient.query(`
|
|
88
|
-
INSERT INTO hub.hash (
|
|
89
|
-
hash_chain_id,
|
|
90
|
-
kind,
|
|
91
|
-
digest,
|
|
92
|
-
iteration
|
|
93
|
-
)
|
|
94
|
-
VALUES ($1, $2, $3, $4)
|
|
95
|
-
`, [
|
|
96
|
-
dbHashChain.id,
|
|
97
|
-
DbHashKind.TERMINAL,
|
|
98
|
-
terminalHash,
|
|
99
|
-
config.HASHCHAINSERVER_MAX_ITERATIONS,
|
|
100
|
-
]);
|
|
101
|
-
return dbHashChain.id;
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
return object({
|
|
105
|
-
hashChain: hashChainTable.get({ id: $hashChainId }),
|
|
106
|
-
});
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
}, "CreateHashChainPlugin");
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const HubUserActiveHashChainPlugin: GraphileConfig.Plugin;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { maybeOneRow } from "@moneypot/hub/db";
|
|
2
|
-
import { inhibitOnNull } from "@moneypot/hub/grafast";
|
|
3
|
-
import { context } from "@moneypot/hub/grafast";
|
|
4
|
-
import { gql, makeExtendSchemaPlugin } from "@moneypot/hub/graphile";
|
|
5
|
-
import { withPgClient } from "postgraphile/@dataplan/pg";
|
|
6
|
-
import { object } from "postgraphile/grafast";
|
|
7
|
-
export const HubUserActiveHashChainPlugin = makeExtendSchemaPlugin((build) => {
|
|
8
|
-
const hashChainTable = build.input.pgRegistry.pgResources.hub_hash_chain;
|
|
9
|
-
return {
|
|
10
|
-
typeDefs: gql `
|
|
11
|
-
extend type HubUser {
|
|
12
|
-
activeHashChain: HubHashChain
|
|
13
|
-
}
|
|
14
|
-
`,
|
|
15
|
-
plans: {
|
|
16
|
-
HubUser: {
|
|
17
|
-
activeHashChain: ($record) => {
|
|
18
|
-
const $identity = context().get("identity");
|
|
19
|
-
const $hashChainId = withPgClient(hashChainTable.executor, object({ userId: $record.get("id"), identity: $identity }), async (pgClient, { userId, identity }) => {
|
|
20
|
-
if (identity?.kind !== "user") {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
const { session } = identity;
|
|
24
|
-
const activeHashChain = await pgClient
|
|
25
|
-
.query({
|
|
26
|
-
text: `
|
|
27
|
-
select id
|
|
28
|
-
from hub.hash_chain
|
|
29
|
-
where user_id = $1
|
|
30
|
-
and experience_id = $2
|
|
31
|
-
and casino_id = $3
|
|
32
|
-
and active = TRUE
|
|
33
|
-
order by id desc
|
|
34
|
-
limit 1
|
|
35
|
-
`,
|
|
36
|
-
values: [userId, session.experience_id, session.casino_id],
|
|
37
|
-
})
|
|
38
|
-
.then(maybeOneRow);
|
|
39
|
-
return activeHashChain?.id ?? null;
|
|
40
|
-
});
|
|
41
|
-
return hashChainTable.get({ id: inhibitOnNull($hashChainId) });
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
}, "HubActiveHashChainPlugin");
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
CREATE TABLE hub.hash_chain (
|
|
2
|
-
id uuid PRIMARY KEY DEFAULT hub_hidden.uuid_generate_v7(),
|
|
3
|
-
user_id uuid NOT NULL REFERENCES hub.user(id),
|
|
4
|
-
experience_id uuid NOT NULL REFERENCES hub.experience(id),
|
|
5
|
-
casino_id uuid NOT NULL REFERENCES hub.casino(id),
|
|
6
|
-
client_seed text NOT NULL,
|
|
7
|
-
active boolean NOT NULL,
|
|
8
|
-
|
|
9
|
-
max_iteration int NOT NULL check (max_iteration > 0),
|
|
10
|
-
current_iteration int NOT NULL check (current_iteration between 0 and max_iteration)
|
|
11
|
-
);
|
|
12
|
-
|
|
13
|
-
-- TODO: Should probably index current_iteration
|
|
14
|
-
-- CREATE INDEX hash_chain_current_iteration_idx ON hub.hash_chain(current_iteration);
|
|
15
|
-
|
|
16
|
-
CREATE INDEX hash_chain_user_id_idx ON hub.hash_chain(user_id);
|
|
17
|
-
CREATE INDEX hash_chain_experience_id_idx ON hub.hash_chain(experience_id);
|
|
18
|
-
CREATE INDEX hash_chain_casino_id_idx ON hub.hash_chain(casino_id);
|
|
19
|
-
|
|
20
|
-
-- Ensure only one active hash_chain per user per experience per casino
|
|
21
|
-
CREATE UNIQUE INDEX active_hash_chain_idx
|
|
22
|
-
ON hub.hash_chain (user_id, experience_id, casino_id)
|
|
23
|
-
WHERE active = true;
|
|
24
|
-
|
|
25
|
-
CREATE TYPE hub.hash_kind AS ENUM (
|
|
26
|
-
'TERMINAL', -- max iteration hash (e.g. iteration 1000)
|
|
27
|
-
'INTERMEDIATE', -- intermediate hash (e.g. iteration 1-999)
|
|
28
|
-
'PREIMAGE' -- preimage hash (always iteration 0)
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
-- this is the base table for all bets events
|
|
32
|
-
CREATE TABLE hub.hash (
|
|
33
|
-
id uuid PRIMARY KEY DEFAULT hub_hidden.uuid_generate_v7(),
|
|
34
|
-
kind hub.hash_kind NOT NULL,
|
|
35
|
-
hash_chain_id uuid NOT NULL REFERENCES hub.hash_chain(id),
|
|
36
|
-
iteration int NOT NULL check (iteration >= 0), -- which Nth value from the hash chain it is
|
|
37
|
-
digest bytea NOT NULL, -- the actual hash we got from hash chain server
|
|
38
|
-
metadata jsonb NOT NULL DEFAULT '{}' -- operator can store game-specific tags
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
CREATE INDEX hash_hash_chain_id_idx ON hub.hash(hash_chain_id);
|
|
42
|
-
|
|
43
|
-
-- Ensure iterations are unique per hash_chain to avoid dupe mistakes
|
|
44
|
-
CREATE UNIQUE INDEX hash_hash_chain_id_iteration_idx ON hub.hash(hash_chain_id, iteration);
|
|
45
|
-
|
|
46
|
-
-- Ensure a hash_chain only has of each end-type hash
|
|
47
|
-
CREATE UNIQUE INDEX hash_chain_terminal_hash_idx ON hub.hash (hash_chain_id)
|
|
48
|
-
WHERE kind = 'TERMINAL';
|
|
49
|
-
CREATE UNIQUE INDEX hash_chain_preimage_hash_idx ON hub.hash (hash_chain_id)
|
|
50
|
-
WHERE kind = 'PREIMAGE';
|
|
51
|
-
|
|
52
|
-
-- GRANTS
|
|
53
|
-
|
|
54
|
-
GRANT SELECT ON TABLE hub.hash_chain TO app_postgraphile;
|
|
55
|
-
GRANT SELECT ON TABLE hub.hash TO app_postgraphile;
|
|
56
|
-
|
|
57
|
-
-- RLS
|
|
58
|
-
ALTER TABLE hub.hash_chain ENABLE ROW LEVEL SECURITY;
|
|
59
|
-
ALTER TABLE hub.hash ENABLE ROW LEVEL SECURITY;
|
|
60
|
-
|
|
61
|
-
CREATE POLICY select_hash_chain ON hub.hash_chain FOR SELECT USING (
|
|
62
|
-
-- Operator can see all rows
|
|
63
|
-
hub_hidden.is_operator() OR
|
|
64
|
-
-- User can see their own rows
|
|
65
|
-
(
|
|
66
|
-
user_id = hub_hidden.current_user_id() AND
|
|
67
|
-
experience_id = hub_hidden.current_experience_id() AND
|
|
68
|
-
casino_id = hub_hidden.current_casino_id()
|
|
69
|
-
)
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
CREATE POLICY select_hash ON hub.hash FOR SELECT USING (
|
|
73
|
-
-- Operator can see all rows
|
|
74
|
-
hub_hidden.is_operator() OR
|
|
75
|
-
-- User can see their own rows by checking the associated hash_chain
|
|
76
|
-
EXISTS (
|
|
77
|
-
SELECT 1
|
|
78
|
-
FROM hub.hash_chain
|
|
79
|
-
WHERE hub.hash_chain.id = hub.hash.hash_chain_id
|
|
80
|
-
AND hub.hash_chain.user_id = hub_hidden.current_user_id()
|
|
81
|
-
AND hub.hash_chain.experience_id = hub_hidden.current_experience_id()
|
|
82
|
-
AND hub.hash_chain.casino_id = hub_hidden.current_casino_id()
|
|
83
|
-
)
|
|
84
|
-
);
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { GraphQLInputFieldConfig } from "graphql";
|
|
2
|
-
export declare const HubOutcomeInputNonNullFieldsPlugin: {
|
|
3
|
-
name: string;
|
|
4
|
-
version: string;
|
|
5
|
-
description: string;
|
|
6
|
-
schema: {
|
|
7
|
-
hooks: {
|
|
8
|
-
GraphQLInputObjectType_fields_field: (field: GraphQLInputFieldConfig, build: any, context: any) => {
|
|
9
|
-
type: any;
|
|
10
|
-
description?: import("graphql/jsutils/Maybe.js").Maybe<string>;
|
|
11
|
-
defaultValue?: unknown;
|
|
12
|
-
deprecationReason?: import("graphql/jsutils/Maybe.js").Maybe<string>;
|
|
13
|
-
extensions?: import("graphql/jsutils/Maybe.js").Maybe<Readonly<import("graphql").GraphQLInputFieldExtensions>>;
|
|
14
|
-
astNode?: import("graphql/jsutils/Maybe.js").Maybe<import("graphql").InputValueDefinitionNode>;
|
|
15
|
-
};
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export const HubOutcomeInputNonNullFieldsPlugin = {
|
|
2
|
-
name: "HubOutcomeInputNonNullFieldsPlugin",
|
|
3
|
-
version: "0.0.0",
|
|
4
|
-
description: "Specifies that HubOutcomeInput fields are non-null",
|
|
5
|
-
schema: {
|
|
6
|
-
hooks: {
|
|
7
|
-
GraphQLInputObjectType_fields_field: (field, build, context) => {
|
|
8
|
-
const { scope: { fieldName }, } = context;
|
|
9
|
-
if (context.scope.pgCodec?.name === "hubOutcome" &&
|
|
10
|
-
["profit", "weight"].includes(fieldName)) {
|
|
11
|
-
return {
|
|
12
|
-
...field,
|
|
13
|
-
type: new build.graphql.GraphQLNonNull(field.type),
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
return field;
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
};
|