@riocrypto/common 1.0.2670 → 1.0.2673

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.
@@ -2,7 +2,7 @@ import { Country } from "../types/country";
2
2
  import { CountryOfOrigin } from "../types/country-of-origin";
3
3
  import { MexicoInvoicingMethod } from "../types/mexico-invoicing-method";
4
4
  import { OnboardingStatus } from "../types/onboarding-status";
5
- import { PlatformFeeRange } from "../types/platform-fee-range";
5
+ import { PlatformFeesForCountry } from "../types/platform-fee-range";
6
6
  import { UserAttribute } from "../types/user-attribute";
7
7
  import { Subjects } from "./subjects";
8
8
  export interface UserUpdatedEvent {
@@ -18,18 +18,9 @@ export interface UserUpdatedEvent {
18
18
  authorizedPhoneNumbers?: string[];
19
19
  quotationTime?: number;
20
20
  platformFeeRanges?: {
21
- MX?: {
22
- buy?: PlatformFeeRange[];
23
- sell?: PlatformFeeRange[];
24
- };
25
- PE?: {
26
- buy?: PlatformFeeRange[];
27
- sell?: PlatformFeeRange[];
28
- };
29
- CO?: {
30
- buy?: PlatformFeeRange[];
31
- sell?: PlatformFeeRange[];
32
- };
21
+ MX?: PlatformFeesForCountry;
22
+ PE?: PlatformFeesForCountry;
23
+ CO?: PlatformFeesForCountry;
33
24
  };
34
25
  onboarding?: {
35
26
  country?: Country;
@@ -0,0 +1,13 @@
1
+ import { PlatformFeeRange } from "../types/platform-fee-range";
2
+ /**
3
+ * Returns the first pair of ranges (indices into `ranges`) that could
4
+ * both apply to the same order — i.e. their amount ranges intersect and
5
+ * their schedules can be simultaneously active — or undefined if the
6
+ * set is conflict-free. Used on both the dashboard and the users
7
+ * service so that fee resolution is always unambiguous: at any moment
8
+ * and any amount, at most one range matches.
9
+ */
10
+ export declare const findPlatformFeeRangeConflict: (ranges: PlatformFeeRange[]) => {
11
+ firstIndex: number;
12
+ secondIndex: number;
13
+ } | undefined;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findPlatformFeeRangeConflict = void 0;
4
+ const schedulesShareTime = (a, b) => {
5
+ // An unscheduled range applies at all times, so it shares time with
6
+ // everything.
7
+ if (!a || !a.daysOfWeek || a.daysOfWeek.length === 0)
8
+ return true;
9
+ if (!b || !b.daysOfWeek || b.daysOfWeek.length === 0)
10
+ return true;
11
+ const shareDay = a.daysOfWeek.some((day) => b.daysOfWeek.includes(day));
12
+ if (!shareDay)
13
+ return false;
14
+ return a.startMinute < b.endMinute && b.startMinute < a.endMinute;
15
+ };
16
+ // Strict interior overlap: existing ladders share single boundary
17
+ // points (e.g. $0-100k followed by $100k-10M), which lookup resolves by
18
+ // first match, so touching endpoints are not treated as a conflict.
19
+ const rangesShareAmounts = (a, b) => a.min < b.max && b.min < a.max;
20
+ /**
21
+ * Returns the first pair of ranges (indices into `ranges`) that could
22
+ * both apply to the same order — i.e. their amount ranges intersect and
23
+ * their schedules can be simultaneously active — or undefined if the
24
+ * set is conflict-free. Used on both the dashboard and the users
25
+ * service so that fee resolution is always unambiguous: at any moment
26
+ * and any amount, at most one range matches.
27
+ */
28
+ const findPlatformFeeRangeConflict = (ranges) => {
29
+ for (let i = 0; i < ranges.length; i++) {
30
+ for (let j = i + 1; j < ranges.length; j++) {
31
+ if (rangesShareAmounts(ranges[i], ranges[j]) &&
32
+ schedulesShareTime(ranges[i].schedule, ranges[j].schedule)) {
33
+ return { firstIndex: i, secondIndex: j };
34
+ }
35
+ }
36
+ }
37
+ return undefined;
38
+ };
39
+ exports.findPlatformFeeRangeConflict = findPlatformFeeRangeConflict;
@@ -0,0 +1,37 @@
1
+ import { Country } from "../types/country";
2
+ import { DayOfWeek } from "../types/day-of-week";
3
+ import { PlatformFeeRange, PlatformFeeSchedule, PlatformFeesForCountry } from "../types/platform-fee-range";
4
+ import { Side } from "../types/side";
5
+ /**
6
+ * Returns the day of week and minute-of-day ([0, 1440)) for `now` in the
7
+ * given IANA timezone. Uses `Intl.DateTimeFormat` so DST transitions are
8
+ * applied automatically, mirroring the FX trading policy resolver.
9
+ */
10
+ export declare const getLocalDayAndMinute: (now: Date, timezone: string) => {
11
+ dayOfWeek: DayOfWeek;
12
+ minuteOfDay: number;
13
+ };
14
+ /**
15
+ * A range with no schedule (or a malformed schedule with no days)
16
+ * applies at all times, which preserves the behavior of every range
17
+ * created before schedules existed.
18
+ */
19
+ export declare const isPlatformFeeScheduleActive: (schedule: PlatformFeeSchedule | undefined, dayOfWeek: DayOfWeek, minuteOfDay: number) => boolean;
20
+ /**
21
+ * Resolves the platform fee ranges that are in effect right now for a
22
+ * country/side: ranges whose schedule covers the current day and
23
+ * minute-of-day in the country's capital timezone (`getCountryTimezone`:
24
+ * Mexico City for MX, Lima for PE, Bogota for CO, New York for US), plus
25
+ * all unscheduled ranges. The result is sorted by `min` so callers can
26
+ * treat it as a volume ladder. Overlap validation at write time
27
+ * guarantees that at most one returned range covers any given amount.
28
+ *
29
+ * `now` is injectable so a quote and its downstream fee lookups can pin a
30
+ * single timestamp, and for testability.
31
+ */
32
+ export declare const getActivePlatformFeeRanges: ({ platformFees, country, side, now, }: {
33
+ platformFees: PlatformFeesForCountry | undefined;
34
+ country: Country;
35
+ side: Side;
36
+ now?: Date | undefined;
37
+ }) => PlatformFeeRange[] | undefined;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getActivePlatformFeeRanges = exports.isPlatformFeeScheduleActive = exports.getLocalDayAndMinute = void 0;
4
+ const day_of_week_1 = require("../types/day-of-week");
5
+ const get_country_timezone_1 = require("./get-country-timezone");
6
+ const WEEKDAY_TO_DAY_OF_WEEK = {
7
+ Mon: day_of_week_1.DayOfWeek.Monday,
8
+ Tue: day_of_week_1.DayOfWeek.Tuesday,
9
+ Wed: day_of_week_1.DayOfWeek.Wednesday,
10
+ Thu: day_of_week_1.DayOfWeek.Thursday,
11
+ Fri: day_of_week_1.DayOfWeek.Friday,
12
+ Sat: day_of_week_1.DayOfWeek.Saturday,
13
+ Sun: day_of_week_1.DayOfWeek.Sunday,
14
+ };
15
+ /**
16
+ * Returns the day of week and minute-of-day ([0, 1440)) for `now` in the
17
+ * given IANA timezone. Uses `Intl.DateTimeFormat` so DST transitions are
18
+ * applied automatically, mirroring the FX trading policy resolver.
19
+ */
20
+ const getLocalDayAndMinute = (now, timezone) => {
21
+ var _a;
22
+ const parts = new Intl.DateTimeFormat("en-US", {
23
+ timeZone: timezone,
24
+ hour12: false,
25
+ weekday: "short",
26
+ hour: "2-digit",
27
+ minute: "2-digit",
28
+ }).formatToParts(now);
29
+ let weekday = "";
30
+ let hour = 0;
31
+ let minute = 0;
32
+ for (const part of parts) {
33
+ if (part.type === "weekday")
34
+ weekday = part.value;
35
+ if (part.type === "hour")
36
+ hour = parseInt(part.value, 10);
37
+ if (part.type === "minute")
38
+ minute = parseInt(part.value, 10);
39
+ }
40
+ // Intl formats midnight as "24" in some locales/Node versions; clamp
41
+ // defensively so we never return 1440 (which would miss a schedule's
42
+ // exclusive end bound).
43
+ if (hour === 24)
44
+ hour = 0;
45
+ return {
46
+ dayOfWeek: (_a = WEEKDAY_TO_DAY_OF_WEEK[weekday]) !== null && _a !== void 0 ? _a : day_of_week_1.DayOfWeek.Monday,
47
+ minuteOfDay: hour * 60 + minute,
48
+ };
49
+ };
50
+ exports.getLocalDayAndMinute = getLocalDayAndMinute;
51
+ /**
52
+ * A range with no schedule (or a malformed schedule with no days)
53
+ * applies at all times, which preserves the behavior of every range
54
+ * created before schedules existed.
55
+ */
56
+ const isPlatformFeeScheduleActive = (schedule, dayOfWeek, minuteOfDay) => {
57
+ if (!schedule || !schedule.daysOfWeek || schedule.daysOfWeek.length === 0) {
58
+ return true;
59
+ }
60
+ return (schedule.daysOfWeek.includes(dayOfWeek) &&
61
+ minuteOfDay >= schedule.startMinute &&
62
+ minuteOfDay < schedule.endMinute);
63
+ };
64
+ exports.isPlatformFeeScheduleActive = isPlatformFeeScheduleActive;
65
+ /**
66
+ * Resolves the platform fee ranges that are in effect right now for a
67
+ * country/side: ranges whose schedule covers the current day and
68
+ * minute-of-day in the country's capital timezone (`getCountryTimezone`:
69
+ * Mexico City for MX, Lima for PE, Bogota for CO, New York for US), plus
70
+ * all unscheduled ranges. The result is sorted by `min` so callers can
71
+ * treat it as a volume ladder. Overlap validation at write time
72
+ * guarantees that at most one returned range covers any given amount.
73
+ *
74
+ * `now` is injectable so a quote and its downstream fee lookups can pin a
75
+ * single timestamp, and for testability.
76
+ */
77
+ const getActivePlatformFeeRanges = ({ platformFees, country, side, now = new Date(), }) => {
78
+ const ranges = platformFees === null || platformFees === void 0 ? void 0 : platformFees[side];
79
+ if (!ranges || ranges.length === 0)
80
+ return ranges;
81
+ const { dayOfWeek, minuteOfDay } = (0, exports.getLocalDayAndMinute)(now, (0, get_country_timezone_1.getCountryTimezone)(country));
82
+ return ranges
83
+ .filter((range) => (0, exports.isPlatformFeeScheduleActive)(range.schedule, dayOfWeek, minuteOfDay))
84
+ .sort((a, b) => a.min - b.min);
85
+ };
86
+ exports.getActivePlatformFeeRanges = getActivePlatformFeeRanges;
package/build/index.d.ts CHANGED
@@ -312,6 +312,7 @@ export * from "./types/compliance-tree-user";
312
312
  export * from "./types/compliance-recent-session";
313
313
  export * from "./types/compliance-step-type";
314
314
  export * from "./types/platform-fee-range";
315
+ export * from "./types/day-of-week";
315
316
  export * from "./types/api-key";
316
317
  export * from "./types/authorization-type";
317
318
  export * from "./types/refresh-token";
@@ -555,6 +556,8 @@ export * from "./helpers/is-overnight-or-weekend-mexico-city";
555
556
  export * from "./helpers/get-user-payout-vault-id";
556
557
  export * from "./helpers/should-add-one-day-to-settlement-date";
557
558
  export * from "./helpers/get-country-timezone";
559
+ export * from "./helpers/get-active-platform-fee-ranges";
560
+ export * from "./helpers/find-platform-fee-range-conflict";
558
561
  export * from "./helpers/get-country-for-fiat";
559
562
  export * from "./helpers/get-days-between-agreed-two-way-settlement-date-and-payment-date";
560
563
  export * from "./helpers/get-days-until-two-way-settlement-date";
package/build/index.js CHANGED
@@ -328,6 +328,7 @@ __exportStar(require("./types/compliance-tree-user"), exports);
328
328
  __exportStar(require("./types/compliance-recent-session"), exports);
329
329
  __exportStar(require("./types/compliance-step-type"), exports);
330
330
  __exportStar(require("./types/platform-fee-range"), exports);
331
+ __exportStar(require("./types/day-of-week"), exports);
331
332
  __exportStar(require("./types/api-key"), exports);
332
333
  __exportStar(require("./types/authorization-type"), exports);
333
334
  __exportStar(require("./types/refresh-token"), exports);
@@ -571,6 +572,8 @@ __exportStar(require("./helpers/is-overnight-or-weekend-mexico-city"), exports);
571
572
  __exportStar(require("./helpers/get-user-payout-vault-id"), exports);
572
573
  __exportStar(require("./helpers/should-add-one-day-to-settlement-date"), exports);
573
574
  __exportStar(require("./helpers/get-country-timezone"), exports);
575
+ __exportStar(require("./helpers/get-active-platform-fee-ranges"), exports);
576
+ __exportStar(require("./helpers/find-platform-fee-range-conflict"), exports);
574
577
  __exportStar(require("./helpers/get-country-for-fiat"), exports);
575
578
  __exportStar(require("./helpers/get-days-between-agreed-two-way-settlement-date-and-payment-date"), exports);
576
579
  __exportStar(require("./helpers/get-days-until-two-way-settlement-date"), exports);
@@ -0,0 +1,9 @@
1
+ export declare enum DayOfWeek {
2
+ Monday = "monday",
3
+ Tuesday = "tuesday",
4
+ Wednesday = "wednesday",
5
+ Thursday = "thursday",
6
+ Friday = "friday",
7
+ Saturday = "saturday",
8
+ Sunday = "sunday"
9
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DayOfWeek = void 0;
4
+ var DayOfWeek;
5
+ (function (DayOfWeek) {
6
+ DayOfWeek["Monday"] = "monday";
7
+ DayOfWeek["Tuesday"] = "tuesday";
8
+ DayOfWeek["Wednesday"] = "wednesday";
9
+ DayOfWeek["Thursday"] = "thursday";
10
+ DayOfWeek["Friday"] = "friday";
11
+ DayOfWeek["Saturday"] = "saturday";
12
+ DayOfWeek["Sunday"] = "sunday";
13
+ })(DayOfWeek = exports.DayOfWeek || (exports.DayOfWeek = {}));
@@ -1,8 +1,25 @@
1
+ import { DayOfWeek } from "./day-of-week";
1
2
  import { DeferredPaymentType } from "./deferred-payment-type";
3
+ /**
4
+ * Restricts when a fee range applies. The range is active when the
5
+ * current time in the country's capital timezone (Mexico City for MX
6
+ * ranges, Lima for PE, Bogota for CO, New York for US) falls on one of
7
+ * `daysOfWeek` and within `[startMinute, endMinute)` (minutes from
8
+ * midnight, end exclusive). Schedules that span midnight must be split
9
+ * into two ranges (one ending at 1440, one starting at 0 on the next
10
+ * day), mirroring the FX trading policy time-range convention. A range
11
+ * without a schedule applies at all times.
12
+ */
13
+ export interface PlatformFeeSchedule {
14
+ daysOfWeek: DayOfWeek[];
15
+ startMinute: number;
16
+ endMinute: number;
17
+ }
2
18
  export interface PlatformFeeRange {
3
19
  min: number;
4
20
  max: number;
5
21
  fee: number;
22
+ schedule?: PlatformFeeSchedule;
6
23
  deferredPaymentFee?: {
7
24
  [deferredPaymentType in DeferredPaymentType]: number;
8
25
  };
@@ -14,3 +31,7 @@ export interface PlatformFeeRange {
14
31
  [key: number]: number;
15
32
  };
16
33
  }
34
+ export interface PlatformFeesForCountry {
35
+ buy?: PlatformFeeRange[];
36
+ sell?: PlatformFeeRange[];
37
+ }
@@ -3,7 +3,7 @@ import { CountryOfOrigin } from "./country-of-origin";
3
3
  import { Fiat } from "./fiat";
4
4
  import { MexicoInvoicingMethod } from "./mexico-invoicing-method";
5
5
  import { OnboardingStatus } from "./onboarding-status";
6
- import { PlatformFeeRange } from "./platform-fee-range";
6
+ import { PlatformFeesForCountry } from "./platform-fee-range";
7
7
  import { UserAttribute } from "./user-attribute";
8
8
  import { UserType } from "./user-type";
9
9
  type IndividualUser = {
@@ -30,26 +30,11 @@ export type UserInput = {
30
30
  cosignerGroupId?: string;
31
31
  attributes?: UserAttribute[];
32
32
  platformFeeRanges?: {
33
- MX?: {
34
- buy?: PlatformFeeRange[];
35
- sell?: PlatformFeeRange[];
36
- };
37
- PE?: {
38
- buy?: PlatformFeeRange[];
39
- sell?: PlatformFeeRange[];
40
- };
41
- CO?: {
42
- buy?: PlatformFeeRange[];
43
- sell?: PlatformFeeRange[];
44
- };
45
- BR?: {
46
- buy?: PlatformFeeRange[];
47
- sell?: PlatformFeeRange[];
48
- };
49
- US?: {
50
- buy?: PlatformFeeRange[];
51
- sell?: PlatformFeeRange[];
52
- };
33
+ MX?: PlatformFeesForCountry;
34
+ PE?: PlatformFeesForCountry;
35
+ CO?: PlatformFeesForCountry;
36
+ BR?: PlatformFeesForCountry;
37
+ US?: PlatformFeesForCountry;
53
38
  };
54
39
  onboarding?: {
55
40
  country?: Country;
@@ -1,6 +1,6 @@
1
1
  import { CountryOfOrigin } from "./country-of-origin";
2
2
  import { OnboardingStatus } from "./onboarding-status";
3
- import { PlatformFeeRange } from "./platform-fee-range";
3
+ import { PlatformFeesForCountry } from "./platform-fee-range";
4
4
  import { UserAttribute } from "./user-attribute";
5
5
  import { UserType } from "./user-type";
6
6
  import { Country } from "./country";
@@ -32,26 +32,11 @@ export type UserUpdate = {
32
32
  cosignerGroupId?: string;
33
33
  attributes?: UserAttribute[];
34
34
  platformFeeRanges?: {
35
- MX?: {
36
- buy?: PlatformFeeRange[];
37
- sell?: PlatformFeeRange[];
38
- };
39
- PE?: {
40
- buy?: PlatformFeeRange[];
41
- sell?: PlatformFeeRange[];
42
- };
43
- CO?: {
44
- buy?: PlatformFeeRange[];
45
- sell?: PlatformFeeRange[];
46
- };
47
- BR?: {
48
- buy?: PlatformFeeRange[];
49
- sell?: PlatformFeeRange[];
50
- };
51
- US?: {
52
- buy?: PlatformFeeRange[];
53
- sell?: PlatformFeeRange[];
54
- };
35
+ MX?: PlatformFeesForCountry;
36
+ PE?: PlatformFeesForCountry;
37
+ CO?: PlatformFeesForCountry;
38
+ BR?: PlatformFeesForCountry;
39
+ US?: PlatformFeesForCountry;
55
40
  };
56
41
  onboarding?: {
57
42
  country?: Country;
@@ -1,6 +1,6 @@
1
1
  import { CountryOfOrigin } from "./country-of-origin";
2
2
  import { OnboardingStatus } from "./onboarding-status";
3
- import { PlatformFeeRange } from "./platform-fee-range";
3
+ import { PlatformFeesForCountry } from "./platform-fee-range";
4
4
  import { UserAttribute } from "./user-attribute";
5
5
  import { UserType } from "./user-type";
6
6
  import { Country } from "./country";
@@ -35,26 +35,11 @@ export type User = {
35
35
  cosignerGroupId?: string;
36
36
  attributes?: UserAttribute[];
37
37
  platformFeeRanges?: {
38
- MX?: {
39
- buy?: PlatformFeeRange[];
40
- sell?: PlatformFeeRange[];
41
- };
42
- PE?: {
43
- buy?: PlatformFeeRange[];
44
- sell?: PlatformFeeRange[];
45
- };
46
- CO?: {
47
- buy?: PlatformFeeRange[];
48
- sell?: PlatformFeeRange[];
49
- };
50
- BR?: {
51
- buy?: PlatformFeeRange[];
52
- sell?: PlatformFeeRange[];
53
- };
54
- US?: {
55
- buy?: PlatformFeeRange[];
56
- sell?: PlatformFeeRange[];
57
- };
38
+ MX?: PlatformFeesForCountry;
39
+ PE?: PlatformFeesForCountry;
40
+ CO?: PlatformFeesForCountry;
41
+ BR?: PlatformFeesForCountry;
42
+ US?: PlatformFeesForCountry;
58
43
  };
59
44
  onboarding: {
60
45
  country: Country;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riocrypto/common",
3
- "version": "1.0.2670",
3
+ "version": "1.0.2673",
4
4
  "description": "",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",