@moneypot/hub 1.4.3 → 1.4.5
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/README.md +4 -0
- package/dist/src/db/public.d.ts +3 -4
- package/dist/src/db/public.js +9 -9
- package/dist/src/db/types.d.ts +0 -1
- package/dist/src/hash-chain/plugins/hub-create-hash-chain.js +4 -22
- package/dist/src/pg-versions/008-move-client-seed.sql +9 -0
- package/dist/src/plugins/hub-make-outcome-bet.d.ts +5 -0
- package/dist/src/plugins/hub-make-outcome-bet.js +21 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,6 +76,10 @@ insert into hub.api_key default values returning key;
|
|
|
76
76
|
|
|
77
77
|
## Changelog
|
|
78
78
|
|
|
79
|
+
### 1.4.4
|
|
80
|
+
|
|
81
|
+
- Added `allowLossBeyondWager` option to `MakeOutcomeBetPlugin`. Must be set to `true` to allow outcome profits to be less than -1.
|
|
82
|
+
|
|
79
83
|
### 1.4.1
|
|
80
84
|
|
|
81
85
|
- Added `hubPutAlert` subscription
|
package/dist/src/db/public.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import * as pg from "pg";
|
|
2
1
|
import { DbBalance, DbBankroll, DbCurrency, DbSession } from "./types.js";
|
|
3
|
-
import { PgClientInTransaction } from "./index.js";
|
|
4
|
-
export declare function dbGetActiveSessionById(pgClient:
|
|
5
|
-
export declare function dbGetCasinoCurrencyByKey(pgClient:
|
|
2
|
+
import { PgClientInTransaction, QueryExecutor } from "./index.js";
|
|
3
|
+
export declare function dbGetActiveSessionById(pgClient: QueryExecutor, sessionId: string): Promise<DbSession | undefined>;
|
|
4
|
+
export declare function dbGetCasinoCurrencyByKey(pgClient: QueryExecutor, { currencyKey, casinoId }: {
|
|
6
5
|
currencyKey: string;
|
|
7
6
|
casinoId: string;
|
|
8
7
|
}): Promise<DbCurrency | undefined>;
|
package/dist/src/db/public.js
CHANGED
|
@@ -2,21 +2,21 @@ import { maybeOneRow } from "./util.js";
|
|
|
2
2
|
import { assert } from "tsafe";
|
|
3
3
|
export async function dbGetActiveSessionById(pgClient, sessionId) {
|
|
4
4
|
return pgClient
|
|
5
|
-
.query(
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
.query(`
|
|
6
|
+
SELECT *
|
|
7
|
+
FROM hub.active_session
|
|
8
|
+
WHERE id = $1
|
|
9
|
+
`, [sessionId])
|
|
8
10
|
.then(maybeOneRow);
|
|
9
11
|
}
|
|
10
12
|
export async function dbGetCasinoCurrencyByKey(pgClient, { currencyKey, casinoId }) {
|
|
11
13
|
return pgClient
|
|
12
|
-
.query(
|
|
13
|
-
text: `
|
|
14
|
+
.query(`
|
|
14
15
|
SELECT *
|
|
15
16
|
FROM hub.currency
|
|
16
|
-
WHERE key = $1
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
})
|
|
17
|
+
WHERE key = $1
|
|
18
|
+
AND casino_id = $2
|
|
19
|
+
`, [currencyKey, casinoId])
|
|
20
20
|
.then(maybeOneRow);
|
|
21
21
|
}
|
|
22
22
|
export async function dbLockPlayerBalanceAndHouseBankroll(pgClient, { userId, casinoId, experienceId, currencyKey, }) {
|
package/dist/src/db/types.d.ts
CHANGED
|
@@ -1,47 +1,31 @@
|
|
|
1
1
|
import { context, object, sideEffect } from "@moneypot/hub/grafast";
|
|
2
2
|
import { gql, makeExtendSchemaPlugin } from "@moneypot/hub/graphile";
|
|
3
3
|
import { GraphQLError } from "graphql";
|
|
4
|
-
import { z } from "zod";
|
|
5
4
|
import { PgAdvisoryLock } from "../../pg-advisory-lock.js";
|
|
6
5
|
import { exactlyOneRow, superuserPool, withPgPoolTransaction, } from "@moneypot/hub/db";
|
|
7
6
|
import * as HashCommon from "../get-hash.js";
|
|
8
7
|
import { DbHashKind } from "../../db/types.js";
|
|
9
8
|
import * as config from "../../config.js";
|
|
10
|
-
const InputSchema = z.object({
|
|
11
|
-
clientSeed: z.string(),
|
|
12
|
-
});
|
|
13
9
|
export const HubCreateHashChainPlugin = makeExtendSchemaPlugin((build) => {
|
|
14
10
|
const hashChainTable = build.input.pgRegistry.pgResources.hub_hash_chain;
|
|
15
11
|
return {
|
|
16
12
|
typeDefs: gql `
|
|
17
|
-
input HubCreateHashChainInput {
|
|
18
|
-
clientSeed: String!
|
|
19
|
-
}
|
|
20
|
-
|
|
21
13
|
type HubCreateHashChainPayload {
|
|
22
14
|
hashChain: HubHashChain!
|
|
23
15
|
}
|
|
24
16
|
|
|
25
17
|
extend type Mutation {
|
|
26
|
-
hubCreateHashChain
|
|
27
|
-
input: HubCreateHashChainInput!
|
|
28
|
-
): HubCreateHashChainPayload!
|
|
18
|
+
hubCreateHashChain: HubCreateHashChainPayload!
|
|
29
19
|
}
|
|
30
20
|
`,
|
|
31
21
|
plans: {
|
|
32
22
|
Mutation: {
|
|
33
|
-
hubCreateHashChain: (
|
|
23
|
+
hubCreateHashChain: () => {
|
|
34
24
|
const $identity = context().get("identity");
|
|
35
|
-
const $hashChainId = sideEffect([$
|
|
25
|
+
const $hashChainId = sideEffect([$identity], ([identity]) => {
|
|
36
26
|
if (identity?.kind !== "user") {
|
|
37
27
|
throw new GraphQLError("Unauthorized");
|
|
38
28
|
}
|
|
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
29
|
return withPgPoolTransaction(superuserPool, async (pgClient) => {
|
|
46
30
|
await PgAdvisoryLock.forNewHashChain(pgClient, {
|
|
47
31
|
userId: identity.session.user_id,
|
|
@@ -66,18 +50,16 @@ export const HubCreateHashChainPlugin = makeExtendSchemaPlugin((build) => {
|
|
|
66
50
|
user_id,
|
|
67
51
|
experience_id,
|
|
68
52
|
casino_id,
|
|
69
|
-
client_seed,
|
|
70
53
|
active,
|
|
71
54
|
max_iteration,
|
|
72
55
|
current_iteration
|
|
73
56
|
)
|
|
74
|
-
VALUES ($1, $2, $3,
|
|
57
|
+
VALUES ($1, $2, $3, true, $4, $4)
|
|
75
58
|
RETURNING *
|
|
76
59
|
`, [
|
|
77
60
|
identity.session.user_id,
|
|
78
61
|
identity.session.experience_id,
|
|
79
62
|
identity.session.casino_id,
|
|
80
|
-
clientSeed,
|
|
81
63
|
config.HASHCHAINSERVER_MAX_ITERATIONS,
|
|
82
64
|
])
|
|
83
65
|
.then(exactlyOneRow);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
alter table hub.outcome_bet add column client_seed text;
|
|
2
|
+
|
|
3
|
+
update hub.outcome_bet
|
|
4
|
+
set client_seed = hash_chain.client_seed
|
|
5
|
+
from hub.hash_chain where outcome_bet.hash_chain_id = hash_chain.id;
|
|
6
|
+
|
|
7
|
+
alter table hub.outcome_bet alter column client_seed set not null;
|
|
8
|
+
|
|
9
|
+
alter table hub.hash_chain drop column client_seed;
|
|
@@ -12,6 +12,7 @@ declare const OutcomeSchema: z.ZodObject<{
|
|
|
12
12
|
}>;
|
|
13
13
|
declare const InputSchema: z.ZodObject<{
|
|
14
14
|
kind: z.ZodString;
|
|
15
|
+
clientSeed: z.ZodString;
|
|
15
16
|
wager: z.ZodNumber;
|
|
16
17
|
currency: z.ZodString;
|
|
17
18
|
outcomes: z.ZodEffects<z.ZodEffects<z.ZodArray<z.ZodObject<{
|
|
@@ -42,6 +43,7 @@ declare const InputSchema: z.ZodObject<{
|
|
|
42
43
|
currency: string;
|
|
43
44
|
hashChainId: string;
|
|
44
45
|
kind: string;
|
|
46
|
+
clientSeed: string;
|
|
45
47
|
wager: number;
|
|
46
48
|
outcomes: {
|
|
47
49
|
profit: number;
|
|
@@ -52,6 +54,7 @@ declare const InputSchema: z.ZodObject<{
|
|
|
52
54
|
currency: string;
|
|
53
55
|
hashChainId: string;
|
|
54
56
|
kind: string;
|
|
57
|
+
clientSeed: string;
|
|
55
58
|
wager: number;
|
|
56
59
|
outcomes: {
|
|
57
60
|
profit: number;
|
|
@@ -69,10 +72,12 @@ type FinalizeMetadataData = {
|
|
|
69
72
|
hash: Uint8Array;
|
|
70
73
|
outcomes: Outcome[];
|
|
71
74
|
outcomeIdx: number;
|
|
75
|
+
roll: number;
|
|
72
76
|
};
|
|
73
77
|
export type OutcomeBetConfig = {
|
|
74
78
|
houseEdge: number;
|
|
75
79
|
saveOutcomes: boolean;
|
|
80
|
+
allowLossBeyondWager?: boolean;
|
|
76
81
|
initializeMetadataFromUntrustedUserInput?: (input: Input) => Result<Metadata, string>;
|
|
77
82
|
finalizeMetadata?: (validatedMetadata: Metadata, data: FinalizeMetadataData) => Metadata;
|
|
78
83
|
};
|
|
@@ -27,6 +27,7 @@ const OutcomeSchema = z
|
|
|
27
27
|
const InputSchema = z
|
|
28
28
|
.object({
|
|
29
29
|
kind: z.string(),
|
|
30
|
+
clientSeed: z.string(),
|
|
30
31
|
wager: z
|
|
31
32
|
.number()
|
|
32
33
|
.int("Wager must be an integer")
|
|
@@ -53,6 +54,7 @@ const BetConfigsSchema = z.record(BetKindSchema, z.object({
|
|
|
53
54
|
.gte(0, "House edge must be >= 0")
|
|
54
55
|
.lte(1, "House edge must be <= 1"),
|
|
55
56
|
saveOutcomes: z.boolean(),
|
|
57
|
+
allowLossBeyondWager: z.boolean().default(false),
|
|
56
58
|
initializeMetadataFromUntrustedUserInput: z
|
|
57
59
|
.function()
|
|
58
60
|
.optional(),
|
|
@@ -76,6 +78,7 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
76
78
|
currency: String!
|
|
77
79
|
outcomes: [HubOutcomeInput!]!
|
|
78
80
|
hashChainId: UUID!
|
|
81
|
+
clientSeed: String!
|
|
79
82
|
metadata: JSON
|
|
80
83
|
}
|
|
81
84
|
|
|
@@ -119,6 +122,12 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
119
122
|
if (!betConfig) {
|
|
120
123
|
throw new GraphQLError(`Invalid bet kind`);
|
|
121
124
|
}
|
|
125
|
+
if (!betConfig.allowLossBeyondWager) {
|
|
126
|
+
const minProfit = Math.min(...input.outcomes.map((o) => o.profit));
|
|
127
|
+
if (minProfit < -1) {
|
|
128
|
+
throw new GraphQLError(`Loss beyond wager not allowed: outcome profit must be >= -1`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
122
131
|
let initializedMetadata;
|
|
123
132
|
if (betConfig.initializeMetadataFromUntrustedUserInput) {
|
|
124
133
|
const result = betConfig.initializeMetadataFromUntrustedUserInput(input);
|
|
@@ -239,13 +248,13 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
239
248
|
}
|
|
240
249
|
const finalHash = (() => {
|
|
241
250
|
const serverHash = betHashResult.hash;
|
|
242
|
-
const clientSeed =
|
|
251
|
+
const clientSeed = input.clientSeed;
|
|
243
252
|
const finalHash = createHmac("sha256", serverHash)
|
|
244
253
|
.update(clientSeed)
|
|
245
254
|
.digest();
|
|
246
255
|
return finalHash;
|
|
247
256
|
})();
|
|
248
|
-
const { outcome, outcomeIdx } = pickRandomOutcome({
|
|
257
|
+
const { outcome, outcomeIdx, roll } = pickRandomOutcome({
|
|
249
258
|
outcomes: input.outcomes,
|
|
250
259
|
hash: finalHash,
|
|
251
260
|
});
|
|
@@ -286,10 +295,11 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
286
295
|
const immutableData = structuredClone({
|
|
287
296
|
wager: input.wager,
|
|
288
297
|
currencyKey: dbCurrency.key,
|
|
289
|
-
clientSeed:
|
|
298
|
+
clientSeed: input.clientSeed,
|
|
290
299
|
hash: betHashResult.hash,
|
|
291
300
|
outcomes: input.outcomes,
|
|
292
301
|
outcomeIdx,
|
|
302
|
+
roll,
|
|
293
303
|
});
|
|
294
304
|
const finalizedMetadata = betConfig.finalizeMetadata
|
|
295
305
|
? betConfig.finalizeMetadata(initializedMetadata, immutableData)
|
|
@@ -303,6 +313,7 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
303
313
|
user_id: session.user_id,
|
|
304
314
|
casino_id: session.casino_id,
|
|
305
315
|
experience_id: session.experience_id,
|
|
316
|
+
client_seed: input.clientSeed,
|
|
306
317
|
metadata: finalizedMetadata || {},
|
|
307
318
|
...(betConfig.saveOutcomes
|
|
308
319
|
? {
|
|
@@ -328,9 +339,10 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
328
339
|
profit,
|
|
329
340
|
outcomes,
|
|
330
341
|
outcome_idx,
|
|
342
|
+
client_seed,
|
|
331
343
|
metadata
|
|
332
344
|
)
|
|
333
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
345
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
334
346
|
RETURNING id
|
|
335
347
|
`,
|
|
336
348
|
values: [
|
|
@@ -344,6 +356,7 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
|
|
|
344
356
|
newBet.profit,
|
|
345
357
|
newBet.outcomes.map((o) => `(${o.weight},${o.profit})`),
|
|
346
358
|
newBet.outcome_idx,
|
|
359
|
+
newBet.client_seed,
|
|
347
360
|
newBet.metadata,
|
|
348
361
|
],
|
|
349
362
|
})
|
|
@@ -395,18 +408,19 @@ function pickRandomOutcome({ outcomes, hash, }) {
|
|
|
395
408
|
}));
|
|
396
409
|
const totalProb = outcomesWithProbability.reduce((sum, outcome) => sum + outcome.probability, 0);
|
|
397
410
|
assert(Math.abs(totalProb - 1.0) < FLOAT_EPSILON, "Probabilities must sum to ~1");
|
|
398
|
-
const
|
|
411
|
+
const roll = normalizeHash(hash);
|
|
399
412
|
let cumulativeProb = 0;
|
|
400
413
|
for (let i = 0; i < outcomesWithProbability.length; i++) {
|
|
401
414
|
const outcome = outcomesWithProbability[i];
|
|
402
415
|
cumulativeProb += outcome.probability;
|
|
403
|
-
if (
|
|
404
|
-
return { outcome, outcomeIdx: i };
|
|
416
|
+
if (roll <= cumulativeProb) {
|
|
417
|
+
return { outcome, outcomeIdx: i, roll };
|
|
405
418
|
}
|
|
406
419
|
}
|
|
407
420
|
return {
|
|
408
421
|
outcome: outcomes[outcomes.length - 1],
|
|
409
422
|
outcomeIdx: outcomes.length - 1,
|
|
423
|
+
roll,
|
|
410
424
|
};
|
|
411
425
|
}
|
|
412
426
|
async function finishHashChainInBackground({ hashChainId, }) {
|