@moneypot/hub 1.9.0-dev.1 → 1.9.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.
@@ -132,7 +132,7 @@ export async function getTransferCursor(pgClient, { casinoId, }) {
132
132
  return row?.cursor;
133
133
  }
134
134
  export async function setTransferCursor(pgClient, { cursor, casinoId, }) {
135
- logger.debug(cursor, `[setTransferCursor] Setting cursor`);
135
+ logger.debug({ cursor }, `[setTransferCursor] Setting cursor`);
136
136
  await pgClient.query(`
137
137
  insert into hub_hidden.transfer_cursor (casino_id, cursor)
138
138
  values ($1, $2)
@@ -0,0 +1,9 @@
1
+ export declare function formatCurrency(amount: number, currency: {
2
+ displayUnitScale: number;
3
+ displayUnitName: string;
4
+ }, options?: {
5
+ excludeUnit?: boolean;
6
+ }): string;
7
+ export declare function pluralize(word: string, count: number, suffix?: string): string;
8
+ export declare function getDecimalPlaces(displayUnitScale: number): number;
9
+ export declare function truncateDecimalPrecision(value: number, decimals: number): number;
@@ -0,0 +1,40 @@
1
+ export function formatCurrency(amount, currency, options = {
2
+ excludeUnit: false,
3
+ }) {
4
+ const decimalPlaces = getDecimalPlaces(currency.displayUnitScale);
5
+ const scaledAmount = amount / (currency.displayUnitScale || 1);
6
+ const truncatedAmount = truncateDecimalPrecision(scaledAmount, decimalPlaces);
7
+ const formatter = new Intl.NumberFormat("en-US", {
8
+ minimumFractionDigits: decimalPlaces,
9
+ maximumFractionDigits: decimalPlaces,
10
+ useGrouping: true,
11
+ });
12
+ const formatted = formatter.format(truncatedAmount);
13
+ if (options.excludeUnit) {
14
+ return formatted;
15
+ }
16
+ return `${formatted} ${pluralize(currency.displayUnitName, truncatedAmount)}`;
17
+ }
18
+ export function pluralize(word, count, suffix = "s") {
19
+ return count === 1 ? word : word + suffix;
20
+ }
21
+ export function getDecimalPlaces(displayUnitScale) {
22
+ return displayUnitScale < 10 ? 0 : Math.log10(displayUnitScale);
23
+ }
24
+ export function truncateDecimalPrecision(value, decimals) {
25
+ if (decimals < 0) {
26
+ throw new Error("Decimals must be a non-negative integer");
27
+ }
28
+ if (!Number.isFinite(value)) {
29
+ return value;
30
+ }
31
+ const str = value.toString();
32
+ const dotIndex = str.indexOf(".");
33
+ if (dotIndex === -1) {
34
+ return value;
35
+ }
36
+ const truncatedStr = decimals === 0
37
+ ? str.substring(0, dotIndex)
38
+ : str.substring(0, dotIndex + decimals + 1);
39
+ return parseFloat(truncatedStr);
40
+ }
package/dist/src/index.js CHANGED
@@ -22,7 +22,7 @@ async function initialize(options) {
22
22
  });
23
23
  }
24
24
  catch (e) {
25
- logger.error("Error upgrading core schema", e);
25
+ logger.error(e, "Error upgrading core schema");
26
26
  if (e instanceof DatabaseAheadError) {
27
27
  logger.error(`${"⚠️".repeat(10)}\n@moneypot/hub database was reset to prepare for a production release and you must reset your database to continue. Please see <https://www.npmjs.com/package/@moneypot/hub#change-log> for more info.`);
28
28
  process.exit(1);
@@ -1,3 +1,3 @@
1
- import { type Logger } from "pino";
1
+ import pino, { type Logger } from "pino";
2
2
  export { type Logger };
3
- export declare const logger: Logger;
3
+ export declare const logger: pino.Logger;
@@ -1,4 +1,4 @@
1
- import { pino } from "pino";
1
+ import pino from "pino";
2
2
  import { LOG_LEVEL, LOG_PRETTY, NODE_ENV } from "./config.js";
3
3
  function createLogger(options) {
4
4
  const prettify = LOG_PRETTY === undefined ? NODE_ENV !== "production" : LOG_PRETTY;
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import * as z from "zod";
2
2
  import { DbOutcome } from "../db/index.js";
3
3
  import { Result } from "../util.js";
4
4
  declare const InputSchema: z.ZodObject<{
@@ -75,10 +75,11 @@ export type OutcomeBetConfig = {
75
75
  export type OutcomeBetConfigMap<BetKind extends string> = {
76
76
  [betKind in BetKind]: OutcomeBetConfig;
77
77
  };
78
- export type RiskLimits = {
78
+ type AtLeastOneKey<T, Keys extends keyof T = keyof T> = Keys extends keyof T ? Required<Pick<T, Keys>> & Partial<Omit<T, Keys>> : never;
79
+ export type RiskLimits = AtLeastOneKey<{
79
80
  maxWager?: number;
80
81
  maxPayout?: number;
81
- };
82
+ }>;
82
83
  export type RiskPolicy = (args: {
83
84
  currency: string;
84
85
  wager: number;
@@ -1,6 +1,6 @@
1
1
  import { access, context, object, ObjectStep, sideEffect, } from "postgraphile/grafast";
2
2
  import { gql, makeExtendSchemaPlugin } from "postgraphile/utils";
3
- import { z } from "zod";
3
+ import * as z from "zod";
4
4
  import { GraphQLError } from "graphql";
5
5
  import { DbHashKind, dbLockPlayerBalanceAndHouseBankroll, exactlyOneRow, maybeOneRow, superuserPool, withPgPoolTransaction, } from "../db/index.js";
6
6
  import { assert } from "tsafe";
@@ -8,6 +8,7 @@ import { dbInsertHubHash, dbLockHubHashChain, } from "../hash-chain/db-hash-chai
8
8
  import { getIntermediateHash, getPreimageHash, } from "../hash-chain/get-hash.js";
9
9
  import { makeFinalHash, pickRandomOutcome } from "../hash-chain/util.js";
10
10
  import { logger } from "../logger.js";
11
+ import { formatCurrency } from "../format-currency.js";
11
12
  const FLOAT_EPSILON = 1e-10;
12
13
  function sum(ns) {
13
14
  return ns.reduce((a, b) => a + b, 0);
@@ -64,9 +65,14 @@ const BetConfigsSchema = z.record(BetKindSchema, z.object({
64
65
  .function()
65
66
  .optional(),
66
67
  }));
67
- const RiskLimitsSchema = z.object({
68
- maxWager: z.number().finite().int().positive(),
69
- maxPayout: z.number().finite().int().positive(),
68
+ const RiskLimitsSchema = z
69
+ .object({
70
+ maxWager: z.number().positive().optional(),
71
+ maxPayout: z.number().positive().optional(),
72
+ })
73
+ .strict()
74
+ .refine((v) => v.maxWager !== undefined || v.maxPayout !== undefined, {
75
+ message: "Provide at least one of maxWager or maxPayout.",
70
76
  });
71
77
  export function MakeOutcomeBetPlugin({ betConfigs }) {
72
78
  BetConfigsSchema.parse(betConfigs);
@@ -154,7 +160,7 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
154
160
  const dbCurrency = await superuserPool
155
161
  .query({
156
162
  text: `
157
- SELECT key
163
+ SELECT key, display_unit_name, display_unit_scale
158
164
  FROM hub.currency
159
165
  WHERE key = $1
160
166
  AND casino_id = $2
@@ -192,7 +198,13 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
192
198
  const maxProfitMultiplier = Math.max(...input.outcomes.map((o) => o.profit));
193
199
  const maxPayout = input.wager * maxProfitMultiplier;
194
200
  if (maxPayout > dbHouseBankroll.amount) {
195
- throw new GraphQLError(`House cannot cover potential payout (${maxPayout}). Bankroll: ${dbHouseBankroll.amount}`);
201
+ throw new GraphQLError(`House cannot cover potential payout (${formatCurrency(maxPayout, {
202
+ displayUnitName: dbCurrency.display_unit_name,
203
+ displayUnitScale: dbCurrency.display_unit_scale,
204
+ })}). Bankroll: ${formatCurrency(dbHouseBankroll.amount, {
205
+ displayUnitName: dbCurrency.display_unit_name,
206
+ displayUnitScale: dbCurrency.display_unit_scale,
207
+ })}`);
196
208
  }
197
209
  const riskLimitsResult = RiskLimitsSchema.safeParse(betConfig.riskPolicy
198
210
  ? betConfig.riskPolicy({
@@ -202,16 +214,29 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
202
214
  })
203
215
  : {});
204
216
  if (!riskLimitsResult.success) {
205
- throw new GraphQLError(`Invalid risk policy: ${riskLimitsResult.error.issues[0].message}`);
217
+ logger.error(riskLimitsResult.error, "Invalid risk policy");
218
+ throw new GraphQLError("Invalid risk policy");
206
219
  }
207
220
  const riskLimits = riskLimitsResult.data;
208
221
  if (riskLimits.maxWager != null &&
209
222
  input.wager > riskLimits.maxWager) {
210
- throw new GraphQLError(`Wager exceeds limit (${riskLimits.maxWager}). Your wager: ${input.wager}`);
223
+ throw new GraphQLError(`Wager exceeds limit (${formatCurrency(riskLimits.maxWager, {
224
+ displayUnitName: dbCurrency.display_unit_name,
225
+ displayUnitScale: dbCurrency.display_unit_scale,
226
+ })}). Your wager: ${formatCurrency(input.wager, {
227
+ displayUnitName: dbCurrency.display_unit_name,
228
+ displayUnitScale: dbCurrency.display_unit_scale,
229
+ })}`);
211
230
  }
212
231
  if (riskLimits.maxPayout != null &&
213
232
  maxPayout > riskLimits.maxPayout) {
214
- throw new GraphQLError(`Payout exceeds limit (${riskLimits.maxPayout}). Your payout: ${maxPayout}`);
233
+ throw new GraphQLError(`Payout exceeds limit (${formatCurrency(riskLimits.maxPayout, {
234
+ displayUnitName: dbCurrency.display_unit_name,
235
+ displayUnitScale: dbCurrency.display_unit_scale,
236
+ })}). Your payout: ${formatCurrency(maxPayout, {
237
+ displayUnitName: dbCurrency.display_unit_name,
238
+ displayUnitScale: dbCurrency.display_unit_scale,
239
+ })}`);
215
240
  }
216
241
  const dbHashChain = await dbLockHubHashChain(pgClient, {
217
242
  userId: session.user_id,
@@ -46,7 +46,7 @@ export function initializeTransferProcessors({ signal, }) {
46
46
  await listenForNewCasinos({ signal });
47
47
  }
48
48
  catch (e) {
49
- logger.error(`Error initializing transfer processors:`, e);
49
+ logger.error(e, `Error initializing transfer processors`);
50
50
  }
51
51
  })();
52
52
  }
@@ -139,7 +139,7 @@ export async function processWithdrawalRequests({ casinoId, graphqlClient, }) {
139
139
  });
140
140
  }
141
141
  catch (error) {
142
- logger.error(`Failed to process withdrawal request ${request.id}:`, error);
142
+ logger.error(error, `Failed to process withdrawal request ${request.id}`);
143
143
  }
144
144
  }
145
145
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moneypot/hub",
3
- "version": "1.9.0-dev.1",
3
+ "version": "1.9.0-dev.10",
4
4
  "author": "moneypot.com",
5
5
  "homepage": "https://moneypot.com/hub",
6
6
  "keywords": [