@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.
Files changed (32) hide show
  1. package/README.md +4 -0
  2. package/dist/src/config.d.ts +8 -13
  3. package/dist/src/config.js +78 -52
  4. package/dist/src/db/index.d.ts +2 -2
  5. package/dist/src/db/index.js +1 -1
  6. package/dist/src/db/types.d.ts +28 -0
  7. package/dist/src/db/types.js +5 -1
  8. package/dist/src/hash-chain/db-hash-chain.d.ts +15 -0
  9. package/dist/src/hash-chain/db-hash-chain.js +35 -0
  10. package/dist/src/hash-chain/get-hash.d.ts +17 -0
  11. package/dist/src/hash-chain/get-hash.js +57 -0
  12. package/dist/src/hash-chain/plugins/hub-bad-hash-chain-error.d.ts +1 -0
  13. package/dist/src/hash-chain/plugins/hub-bad-hash-chain-error.js +20 -0
  14. package/dist/src/hash-chain/plugins/hub-create-hash-chain.d.ts +1 -0
  15. package/dist/src/hash-chain/plugins/hub-create-hash-chain.js +111 -0
  16. package/dist/src/hash-chain/plugins/hub-user-active-hash-chain.d.ts +1 -0
  17. package/dist/src/hash-chain/plugins/hub-user-active-hash-chain.js +46 -0
  18. package/dist/src/index.d.ts +1 -0
  19. package/dist/src/index.js +5 -1
  20. package/dist/src/pg-advisory-lock.d.ts +9 -4
  21. package/dist/src/pg-advisory-lock.js +8 -1
  22. package/dist/src/pg-versions/005-hash-chain.sql +84 -0
  23. package/dist/src/pg-versions/006-outcome-bet.sql +63 -0
  24. package/dist/src/plugins/hub-authenticate.js +24 -3
  25. package/dist/src/plugins/hub-make-outcome-bet.d.ts +84 -0
  26. package/dist/src/plugins/hub-make-outcome-bet.js +477 -0
  27. package/dist/src/process-transfers.js +1 -1
  28. package/dist/src/server/graphile.config.js +7 -1
  29. package/dist/src/server/handle-errors.js +1 -1
  30. package/dist/src/server/index.js +1 -1
  31. package/dist/src/take-request/process-take-request.js +3 -3
  32. 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, is } from "tsafe";
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], ([input, context]) => {
53
+ const $success = sideEffect([$input, $context], ([rawInput, context]) => {
42
54
  return withPgPoolTransaction(superuserPool, async (pgClient) => {
43
- assert(is(input));
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 {};