@moneypot/hub 1.3.0-dev.15 → 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 +25 -186
- package/dist/src/plugins/hub-user-balance-by-currency.js +40 -15
- 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;
|