@moneypot/hub 1.2.7 → 1.3.0-dev.10
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/config.d.ts +8 -13
- package/dist/src/config.js +78 -52
- package/dist/src/db/index.d.ts +2 -2
- package/dist/src/db/index.js +1 -1
- package/dist/src/db/types.d.ts +28 -0
- package/dist/src/db/types.js +5 -1
- package/dist/src/hash-chain/db-hash-chain.d.ts +15 -0
- package/dist/src/hash-chain/db-hash-chain.js +35 -0
- package/dist/src/hash-chain/get-hash.d.ts +17 -0
- package/dist/src/hash-chain/get-hash.js +57 -0
- package/dist/src/hash-chain/plugins/hub-bad-hash-chain-error.d.ts +1 -0
- package/dist/src/hash-chain/plugins/hub-bad-hash-chain-error.js +20 -0
- package/dist/src/hash-chain/plugins/hub-create-hash-chain.d.ts +1 -0
- package/dist/src/hash-chain/plugins/hub-create-hash-chain.js +111 -0
- package/dist/src/hash-chain/plugins/hub-user-active-hash-chain.d.ts +1 -0
- package/dist/src/hash-chain/plugins/hub-user-active-hash-chain.js +46 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +5 -1
- package/dist/src/pg-advisory-lock.d.ts +9 -4
- package/dist/src/pg-advisory-lock.js +8 -1
- package/dist/src/pg-versions/005-hash-chain.sql +84 -0
- package/dist/src/pg-versions/006-outcome-bet.sql +63 -0
- package/dist/src/plugins/hub-authenticate.js +24 -3
- package/dist/src/plugins/hub-make-outcome-bet.d.ts +84 -0
- package/dist/src/plugins/hub-make-outcome-bet.js +477 -0
- package/dist/src/process-transfers.js +1 -1
- package/dist/src/server/graphile.config.js +7 -1
- 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
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
drop table if exists hub.outcome_bet cascade;
|
|
2
|
+
drop type if exists hub.outcome cascade;
|
|
3
|
+
|
|
4
|
+
create type hub.outcome as (
|
|
5
|
+
weight float,
|
|
6
|
+
profit float
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
create table hub.outcome_bet (
|
|
10
|
+
id uuid primary key default hub_hidden.uuid_generate_v7(),
|
|
11
|
+
|
|
12
|
+
-- Operator-given kind like "WHEEL", "PLINKO",
|
|
13
|
+
-- Probably not worth using a postgres enum here since they are not flexible.
|
|
14
|
+
kind text not null,
|
|
15
|
+
|
|
16
|
+
user_id uuid not null references hub.user(id),
|
|
17
|
+
experience_id uuid not null references hub.experience(id),
|
|
18
|
+
casino_id uuid not null references hub.casino(id),
|
|
19
|
+
|
|
20
|
+
-- provably fair hash chain
|
|
21
|
+
hash_chain_id uuid not null references hub.hash_chain(id),
|
|
22
|
+
|
|
23
|
+
currency_key text not null,
|
|
24
|
+
wager float not null,
|
|
25
|
+
|
|
26
|
+
-- -1 = lose wager, -2 = lose 2x wager
|
|
27
|
+
-- 0.5 = 1.5x wager, 1 = 2x wager
|
|
28
|
+
-- In other words, a multiplier in a game might say "2.5x",
|
|
29
|
+
-- but the profit is multiplier-1 = 1.5 profit
|
|
30
|
+
profit float not null,
|
|
31
|
+
|
|
32
|
+
-- null when no outcomes saved
|
|
33
|
+
outcome_idx smallint null check (outcome_idx between 0 and array_length(outcomes,1)-1),
|
|
34
|
+
outcomes hub.outcome[] not null default '{}',
|
|
35
|
+
|
|
36
|
+
-- Operator-provided data per bet
|
|
37
|
+
metadata jsonb not null default '{}',
|
|
38
|
+
|
|
39
|
+
foreign key (currency_key, casino_id) references hub.currency(key, casino_id)
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
create index outcome_bet_user_id_idx on hub.outcome_bet(user_id);
|
|
43
|
+
create index outcome_bet_experience_id_idx on hub.outcome_bet(experience_id);
|
|
44
|
+
create index outcome_bet_casino_id_idx on hub.outcome_bet(casino_id);
|
|
45
|
+
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
|
+
|
|
48
|
+
-- GRANT
|
|
49
|
+
|
|
50
|
+
grant select on hub.outcome_bet to app_postgraphile;
|
|
51
|
+
|
|
52
|
+
-- RLS
|
|
53
|
+
|
|
54
|
+
alter table hub.outcome_bet enable row level security;
|
|
55
|
+
|
|
56
|
+
create policy select_outcome_bet on hub.outcome_bet for select using (
|
|
57
|
+
hub_hidden.is_operator() or
|
|
58
|
+
(
|
|
59
|
+
user_id = hub_hidden.current_user_id() and
|
|
60
|
+
experience_id = hub_hidden.current_experience_id() and
|
|
61
|
+
casino_id = hub_hidden.current_casino_id()
|
|
62
|
+
)
|
|
63
|
+
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GraphQLError } from "graphql";
|
|
2
2
|
import { gql, makeExtendSchemaPlugin } from "postgraphile/utils";
|
|
3
|
-
import { assert
|
|
3
|
+
import { assert } from "tsafe";
|
|
4
4
|
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";
|
|
@@ -9,6 +9,18 @@ import { superuserPool, 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";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
const BaseUrlSchema = z
|
|
14
|
+
.string()
|
|
15
|
+
.refine((val) => URL.canParse(val), "Base URL must be a valid url")
|
|
16
|
+
.transform((val) => new URL(val))
|
|
17
|
+
.refine((val) => val.protocol === "http:" || val.protocol === "https:", "Base URL must use http or https protocol")
|
|
18
|
+
.refine((val) => val.pathname === "/" && val.search === "" && val.hash === "", "Base URL must have no path, query, nor hash")
|
|
19
|
+
.transform((val) => val.toString().replace(/\/$/, ""));
|
|
20
|
+
const InputSchema = z.object({
|
|
21
|
+
casinoBaseUrl: BaseUrlSchema,
|
|
22
|
+
userToken: z.string().min(1, "User token required"),
|
|
23
|
+
});
|
|
12
24
|
export const HubAuthenticatePlugin = makeExtendSchemaPlugin(() => {
|
|
13
25
|
return {
|
|
14
26
|
typeDefs: gql `
|
|
@@ -38,9 +50,18 @@ export const HubAuthenticatePlugin = makeExtendSchemaPlugin(() => {
|
|
|
38
50
|
hubAuthenticate(_, { $input }) {
|
|
39
51
|
try {
|
|
40
52
|
const $context = context();
|
|
41
|
-
const $success = sideEffect([$input, $context], ([
|
|
53
|
+
const $success = sideEffect([$input, $context], ([rawInput, context]) => {
|
|
42
54
|
return withPgPoolTransaction(superuserPool, async (pgClient) => {
|
|
43
|
-
|
|
55
|
+
let input;
|
|
56
|
+
try {
|
|
57
|
+
input = InputSchema.parse(rawInput);
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
if (e instanceof z.ZodError) {
|
|
61
|
+
throw new GraphQLError(e.errors[0].message);
|
|
62
|
+
}
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
44
65
|
const { userToken: jwt, casinoBaseUrl } = input;
|
|
45
66
|
const casino = await pgClient
|
|
46
67
|
.query({
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
weight: number;
|
|
8
|
+
profit: number;
|
|
9
|
+
}, {
|
|
10
|
+
weight: number;
|
|
11
|
+
profit: number;
|
|
12
|
+
}>;
|
|
13
|
+
declare const InputSchema: z.ZodObject<{
|
|
14
|
+
kind: z.ZodString;
|
|
15
|
+
wager: z.ZodNumber;
|
|
16
|
+
currency: z.ZodString;
|
|
17
|
+
outcomes: z.ZodEffects<z.ZodEffects<z.ZodArray<z.ZodObject<{
|
|
18
|
+
weight: z.ZodNumber;
|
|
19
|
+
profit: z.ZodNumber;
|
|
20
|
+
}, "strict", z.ZodTypeAny, {
|
|
21
|
+
weight: number;
|
|
22
|
+
profit: number;
|
|
23
|
+
}, {
|
|
24
|
+
weight: number;
|
|
25
|
+
profit: number;
|
|
26
|
+
}>, "many">, {
|
|
27
|
+
weight: number;
|
|
28
|
+
profit: number;
|
|
29
|
+
}[], {
|
|
30
|
+
weight: number;
|
|
31
|
+
profit: number;
|
|
32
|
+
}[]>, {
|
|
33
|
+
weight: number;
|
|
34
|
+
profit: number;
|
|
35
|
+
}[], {
|
|
36
|
+
weight: number;
|
|
37
|
+
profit: number;
|
|
38
|
+
}[]>;
|
|
39
|
+
hashChainId: z.ZodString;
|
|
40
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
41
|
+
}, "strict", z.ZodTypeAny, {
|
|
42
|
+
currency: string;
|
|
43
|
+
kind: string;
|
|
44
|
+
hashChainId: string;
|
|
45
|
+
wager: number;
|
|
46
|
+
outcomes: {
|
|
47
|
+
weight: number;
|
|
48
|
+
profit: number;
|
|
49
|
+
}[];
|
|
50
|
+
metadata?: Record<string, any> | undefined;
|
|
51
|
+
}, {
|
|
52
|
+
currency: string;
|
|
53
|
+
kind: string;
|
|
54
|
+
hashChainId: string;
|
|
55
|
+
wager: number;
|
|
56
|
+
outcomes: {
|
|
57
|
+
weight: number;
|
|
58
|
+
profit: number;
|
|
59
|
+
}[];
|
|
60
|
+
metadata?: Record<string, any> | undefined;
|
|
61
|
+
}>;
|
|
62
|
+
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
|
+
export type OutcomeBetConfig = {
|
|
73
|
+
houseEdge: number;
|
|
74
|
+
saveOutcomes: boolean;
|
|
75
|
+
initializeMetadataFromUntrustedUserInput?: (input: Input) => Result<Record<string, any>, string>;
|
|
76
|
+
finalizeMetadata?: (validatedMetadata: Record<string, any>, data: FinalizeMetadataData) => Record<string, any>;
|
|
77
|
+
};
|
|
78
|
+
export type OutcomeBetConfigMap<BetKind extends string> = {
|
|
79
|
+
[betKind in BetKind]: OutcomeBetConfig;
|
|
80
|
+
};
|
|
81
|
+
export declare function MakeOutcomeBetPlugin<BetKind extends string>({ betConfigs }: {
|
|
82
|
+
betConfigs: OutcomeBetConfigMap<BetKind>;
|
|
83
|
+
}): GraphileConfig.Plugin;
|
|
84
|
+
export {};
|