@basedone/core 0.2.8 → 0.3.0
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/{chunk-NKSQEISP.mjs → chunk-35WGIB5F.mjs} +149 -1
- package/dist/{client-DMVXX1Gw.d.mts → client-BQzYwHDY.d.mts} +2 -2
- package/dist/{client-DMVXX1Gw.d.ts → client-BQzYwHDY.d.ts} +2 -2
- package/dist/ecommerce.d.mts +122 -2
- package/dist/ecommerce.d.ts +122 -2
- package/dist/ecommerce.js +158 -0
- package/dist/ecommerce.mjs +1 -1
- package/dist/index.d.mts +113 -3
- package/dist/index.d.ts +113 -3
- package/dist/index.js +299 -0
- package/dist/index.mjs +134 -2
- package/dist/react.d.mts +1 -1
- package/dist/react.d.ts +1 -1
- package/index.ts +3 -0
- package/lib/abstraction/api.ts +106 -0
- package/lib/abstraction/index.ts +3 -0
- package/lib/abstraction/ratio.ts +61 -0
- package/lib/abstraction/types.ts +73 -0
- package/lib/constants/admin.ts +30 -0
- package/lib/ecommerce/client/customer.ts +42 -0
- package/lib/ecommerce/index.ts +14 -0
- package/lib/ecommerce/types/entities.ts +14 -0
- package/lib/ecommerce/types/enums.ts +5 -1
- package/lib/ecommerce/types/responses.ts +28 -0
- package/lib/ecommerce/utils/orderStateMachine.ts +197 -0
- package/lib/types.ts +29 -0
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MultiverseMeta,
|
|
3
|
+
PerpDexClearinghouseState,
|
|
4
|
+
SpotBalance,
|
|
5
|
+
} from "./types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Compute the unified account ratio for monitoring liquidation risk.
|
|
9
|
+
*
|
|
10
|
+
* The ratio represents cross maintenance margin used / available balance
|
|
11
|
+
* for the most leveraged collateral token. A higher ratio means closer
|
|
12
|
+
* to liquidation.
|
|
13
|
+
*
|
|
14
|
+
* @param multiverse - Map of DEX name to its metadata (index and collateral token)
|
|
15
|
+
* @param perpDexStates - Array of per-DEX clearinghouse states
|
|
16
|
+
* @param spotBalances - Array of spot balances per token
|
|
17
|
+
* @returns The maximum ratio across all collateral tokens (0 if no margin used)
|
|
18
|
+
*/
|
|
19
|
+
export function computeUnifiedAccountRatio(
|
|
20
|
+
multiverse: Record<string, MultiverseMeta>,
|
|
21
|
+
perpDexStates: PerpDexClearinghouseState[],
|
|
22
|
+
spotBalances: SpotBalance[],
|
|
23
|
+
): number {
|
|
24
|
+
const indexToCollateralToken: Record<number, number> = {};
|
|
25
|
+
for (const meta of Object.values(multiverse)) {
|
|
26
|
+
indexToCollateralToken[meta.index] = meta.collateralToken;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const crossMarginByToken: Record<number, number> = {};
|
|
30
|
+
const isolatedMarginByToken: Record<number, number> = {};
|
|
31
|
+
|
|
32
|
+
for (let index = 0; index < perpDexStates.length; index++) {
|
|
33
|
+
const dex = perpDexStates[index];
|
|
34
|
+
const token = indexToCollateralToken[index];
|
|
35
|
+
if (dex === undefined || token === undefined) continue;
|
|
36
|
+
|
|
37
|
+
crossMarginByToken[token] =
|
|
38
|
+
(crossMarginByToken[token] ?? 0) +
|
|
39
|
+
dex.clearinghouseState.crossMaintenanceMarginUsed;
|
|
40
|
+
|
|
41
|
+
for (const ap of dex.clearinghouseState.assetPositions) {
|
|
42
|
+
if (ap.position.leverage.type === "isolated") {
|
|
43
|
+
isolatedMarginByToken[token] =
|
|
44
|
+
(isolatedMarginByToken[token] ?? 0) + ap.position.marginUsed;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let maxRatio = 0;
|
|
50
|
+
for (const [tokenStr, crossMargin] of Object.entries(crossMarginByToken)) {
|
|
51
|
+
const token = Number(tokenStr);
|
|
52
|
+
const spotTotal = spotBalances.find((b) => b.token === token)?.total ?? 0;
|
|
53
|
+
const isolatedMargin = isolatedMarginByToken[token] ?? 0;
|
|
54
|
+
const available = spotTotal - isolatedMargin;
|
|
55
|
+
if (available > 0) {
|
|
56
|
+
maxRatio = Math.max(maxRatio, crossMargin / available);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return maxRatio;
|
|
61
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User abstraction modes for controlling how spot and perps balances interact.
|
|
3
|
+
*
|
|
4
|
+
* - `disabled` (Standard): Separate perp and spot balances, separate DEX balances.
|
|
5
|
+
* - `unifiedAccount`: Single balance per asset collateralizing all cross margin positions.
|
|
6
|
+
* - `portfolioMargin`: Single portfolio unifying all eligible assets (pre-alpha).
|
|
7
|
+
* - `dexAbstraction`: Legacy mode (to be discontinued).
|
|
8
|
+
* - `default`: Server default (equivalent to standard/disabled for most users).
|
|
9
|
+
*/
|
|
10
|
+
export type UserAbstractionMode =
|
|
11
|
+
| "unifiedAccount"
|
|
12
|
+
| "portfolioMargin"
|
|
13
|
+
| "disabled"
|
|
14
|
+
| "default"
|
|
15
|
+
| "dexAbstraction";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Shorthand codes used with agent-based abstraction setting.
|
|
19
|
+
* - `i` = disabled (standard)
|
|
20
|
+
* - `u` = unifiedAccount
|
|
21
|
+
* - `p` = portfolioMargin
|
|
22
|
+
*/
|
|
23
|
+
export type AgentAbstractionCode = "i" | "u" | "p";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Settable abstraction modes (excludes read-only states like "default" and "dexAbstraction").
|
|
27
|
+
*/
|
|
28
|
+
export type SettableAbstractionMode =
|
|
29
|
+
| "disabled"
|
|
30
|
+
| "unifiedAccount"
|
|
31
|
+
| "portfolioMargin";
|
|
32
|
+
|
|
33
|
+
export const ABSTRACTION_MODE_TO_AGENT_CODE: Record<
|
|
34
|
+
SettableAbstractionMode,
|
|
35
|
+
AgentAbstractionCode
|
|
36
|
+
> = {
|
|
37
|
+
disabled: "i",
|
|
38
|
+
unifiedAccount: "u",
|
|
39
|
+
portfolioMargin: "p",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const AGENT_CODE_TO_ABSTRACTION_MODE: Record<
|
|
43
|
+
AgentAbstractionCode,
|
|
44
|
+
SettableAbstractionMode
|
|
45
|
+
> = {
|
|
46
|
+
i: "disabled",
|
|
47
|
+
u: "unifiedAccount",
|
|
48
|
+
p: "portfolioMargin",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export interface MultiverseMeta {
|
|
52
|
+
index: number;
|
|
53
|
+
collateralToken: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface PerpDexAssetPosition {
|
|
57
|
+
position: {
|
|
58
|
+
leverage: { type: string };
|
|
59
|
+
marginUsed: number;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface PerpDexClearinghouseState {
|
|
64
|
+
clearinghouseState: {
|
|
65
|
+
crossMaintenanceMarginUsed: number;
|
|
66
|
+
assetPositions: PerpDexAssetPosition[];
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface SpotBalance {
|
|
71
|
+
token: number;
|
|
72
|
+
total: number;
|
|
73
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Super admin Privy user IDs — single source of truth for all apps
|
|
2
|
+
export const ADMINS = [
|
|
3
|
+
"did:privy:cmc3hycnb00pljs0m1z010geq",
|
|
4
|
+
"did:privy:cmc8w2ddl01rdju0nylb6rp1v",
|
|
5
|
+
"did:privy:cmc3oftiz005fjx0msox3xgey",
|
|
6
|
+
"did:privy:cmcbx7i4400q8l90mawdymaag",
|
|
7
|
+
"did:privy:cmctbwphs00vzjz0m3la3zm4c",
|
|
8
|
+
"did:privy:cmcblrplb009ol70mia13winn",
|
|
9
|
+
"did:privy:cmcmizpxm02xfju0oc81alfy0",
|
|
10
|
+
"did:privy:cmcd2bvft00w3l90ljeyb4wn6",
|
|
11
|
+
"did:privy:cmc4y13ka0119kv0nckvgla1u",
|
|
12
|
+
"did:privy:cmc8qc36g00c1l70nnutuscue",
|
|
13
|
+
"did:privy:cmc8f1tf9019zlh0myjpi420p",
|
|
14
|
+
"did:privy:cmc6bturg002ql80mhzqr1d76",
|
|
15
|
+
"did:privy:cmc8uyr4t01kljo0mk3unzjl3",
|
|
16
|
+
"did:privy:cmcke9v7h00yijy0o2a41nhms",
|
|
17
|
+
"did:privy:cmgfcjt2y0024kz0cpymoqbmp",
|
|
18
|
+
"did:privy:cmeqj8tsi01k6la0c3s7gsl6w", // elroy
|
|
19
|
+
"did:privy:cmiwrno1i00cdl70c5ro13eyp", // adele
|
|
20
|
+
"did:privy:cmkc0jyy200abji0d42a0syil", // matthew
|
|
21
|
+
"did:privy:cmlgc2zjk005vjo0cct10trdl", // adele
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
// Global admin wallet addresses
|
|
25
|
+
export const ADMIN_WALLETS: string[] = [
|
|
26
|
+
"0x0c7582A67B8B6AD04Ea404A6C2A06aAc9E0d4e7c",
|
|
27
|
+
"0xDec587aDD20A6447fF0b29D70E95b10b197b1283",
|
|
28
|
+
"0x3e83987019c4CE29680401b72F8b18A2dE3f8fe6",
|
|
29
|
+
"0x5446A5Bc711170d5197DE33D8C193487794f30C0",
|
|
30
|
+
];
|
|
@@ -68,6 +68,8 @@ import type {
|
|
|
68
68
|
GetExpiringGemsResponse,
|
|
69
69
|
CashAccountBalanceResponse,
|
|
70
70
|
DeliveryAddressResponse,
|
|
71
|
+
CustomerNotificationsResponse,
|
|
72
|
+
MarkNotificationsReadResponse,
|
|
71
73
|
} from "../types";
|
|
72
74
|
|
|
73
75
|
/**
|
|
@@ -1110,4 +1112,44 @@ export class CustomerEcommerceClient extends BaseEcommerceClient {
|
|
|
1110
1112
|
async getDeliveryAddress(): Promise<DeliveryAddressResponse> {
|
|
1111
1113
|
return this.get("/api/basedpay/delivery-address");
|
|
1112
1114
|
}
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* Get user's Hyperliquid USDC balance (perp withdrawable)
|
|
1118
|
+
*
|
|
1119
|
+
* Returns the USDC balance available for escrow deposits via usdSend.
|
|
1120
|
+
*
|
|
1121
|
+
* @returns Balance response with amount and currency
|
|
1122
|
+
*/
|
|
1123
|
+
async getUsdcBalance(): Promise<CashAccountBalanceResponse> {
|
|
1124
|
+
return this.get("/api/marketplace/usdc-balance");
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// ============================================================================
|
|
1128
|
+
// Notifications API
|
|
1129
|
+
// ============================================================================
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* List notifications for the authenticated customer
|
|
1133
|
+
*
|
|
1134
|
+
* @param params - Query parameters for filtering and pagination
|
|
1135
|
+
* @returns Paginated list of notifications with unread count
|
|
1136
|
+
*/
|
|
1137
|
+
async listNotifications(params?: {
|
|
1138
|
+
limit?: number;
|
|
1139
|
+
offset?: number;
|
|
1140
|
+
unreadOnly?: boolean;
|
|
1141
|
+
}): Promise<CustomerNotificationsResponse> {
|
|
1142
|
+
const queryString = params ? buildQueryString(params) : "";
|
|
1143
|
+
return this.get(`/api/marketplace/notifications${queryString}`);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* Mark notifications as read
|
|
1148
|
+
*
|
|
1149
|
+
* @param ids - Specific notification IDs to mark as read. If omitted, marks all as read.
|
|
1150
|
+
* @returns Count of updated notifications
|
|
1151
|
+
*/
|
|
1152
|
+
async markNotificationsAsRead(ids?: string[]): Promise<MarkNotificationsReadResponse> {
|
|
1153
|
+
return this.patch("/api/marketplace/notifications/read", { ids });
|
|
1154
|
+
}
|
|
1113
1155
|
}
|
package/lib/ecommerce/index.ts
CHANGED
|
@@ -46,6 +46,20 @@ export {
|
|
|
46
46
|
calculateFinalPrice,
|
|
47
47
|
} from "./utils/helpers";
|
|
48
48
|
|
|
49
|
+
// Export order state machine
|
|
50
|
+
export {
|
|
51
|
+
ORDER_STATUS_TRANSITIONS,
|
|
52
|
+
validateStatusTransition,
|
|
53
|
+
getNextStatuses,
|
|
54
|
+
isPickupOrder,
|
|
55
|
+
getStatusLabel,
|
|
56
|
+
getStatusColor,
|
|
57
|
+
canCancelOrder,
|
|
58
|
+
requiresTrackingInfo,
|
|
59
|
+
shouldNotifyCustomer,
|
|
60
|
+
getStatusProgress,
|
|
61
|
+
} from "./utils/orderStateMachine";
|
|
62
|
+
|
|
49
63
|
// Export client config type
|
|
50
64
|
export type { EcommerceClientConfig } from "./client/base";
|
|
51
65
|
|
|
@@ -357,6 +357,20 @@ export interface Order extends BaseEntity {
|
|
|
357
357
|
};
|
|
358
358
|
/** Order events */
|
|
359
359
|
events?: OrderEvent[];
|
|
360
|
+
/** Expected ship date */
|
|
361
|
+
expectedShipDate?: string | null;
|
|
362
|
+
/** Estimated delivery date */
|
|
363
|
+
estimatedDeliveryDate?: string | null;
|
|
364
|
+
/** Estimated delivery days */
|
|
365
|
+
estimatedDeliveryDays?: number | null;
|
|
366
|
+
/** Auto-complete deadline */
|
|
367
|
+
autoCompleteDeadline?: string | null;
|
|
368
|
+
/** Customer confirmed receipt timestamp */
|
|
369
|
+
customerConfirmedAt?: string | null;
|
|
370
|
+
/** Auto-completed timestamp */
|
|
371
|
+
autoCompletedAt?: string | null;
|
|
372
|
+
/** Status transition timestamps */
|
|
373
|
+
statusTransitions?: Record<string, string> | null;
|
|
360
374
|
}
|
|
361
375
|
|
|
362
376
|
/**
|
|
@@ -22,6 +22,8 @@ export enum OrderStatus {
|
|
|
22
22
|
DELIVERED = "DELIVERED",
|
|
23
23
|
/** Order has been cancelled */
|
|
24
24
|
CANCELLED = "CANCELLED",
|
|
25
|
+
/** Payment settled / escrow released to merchant */
|
|
26
|
+
SETTLED = "SETTLED",
|
|
25
27
|
/** Order is confirmed (legacy status) */
|
|
26
28
|
CONFIRMED = "CONFIRMED",
|
|
27
29
|
/** Order is completed (legacy status) */
|
|
@@ -286,8 +288,10 @@ export enum ProductSortBy {
|
|
|
286
288
|
PRICE_ASC = "price_asc",
|
|
287
289
|
/** Sort by price (high to low) */
|
|
288
290
|
PRICE_DESC = "price_desc",
|
|
289
|
-
/** Sort by popularity */
|
|
291
|
+
/** Sort by popularity (views) */
|
|
290
292
|
POPULAR = "popular",
|
|
293
|
+
/** Sort by best selling (sold quantity) */
|
|
294
|
+
BEST_SELLING = "best_selling",
|
|
291
295
|
/** Sort by featured status */
|
|
292
296
|
FEATURED = "featured",
|
|
293
297
|
/** Sort by proximity to user location (requires lat/lng) */
|
|
@@ -286,6 +286,10 @@ export interface ValidateDiscountResponse {
|
|
|
286
286
|
/** Discount amount */
|
|
287
287
|
discountAmount: number;
|
|
288
288
|
};
|
|
289
|
+
/** Merchant ID the discount belongs to */
|
|
290
|
+
merchantId?: string;
|
|
291
|
+
/** Merchant name the discount belongs to */
|
|
292
|
+
merchantName?: string | null;
|
|
289
293
|
/** Subtotal */
|
|
290
294
|
subtotal?: number;
|
|
291
295
|
/** Total */
|
|
@@ -1325,6 +1329,30 @@ export interface CashAccountBalanceResponse {
|
|
|
1325
1329
|
currency: string;
|
|
1326
1330
|
}
|
|
1327
1331
|
|
|
1332
|
+
// ============================================================================
|
|
1333
|
+
// Customer Notification Responses
|
|
1334
|
+
// ============================================================================
|
|
1335
|
+
|
|
1336
|
+
export interface CustomerNotification {
|
|
1337
|
+
id: string;
|
|
1338
|
+
type: string;
|
|
1339
|
+
title: string;
|
|
1340
|
+
message: string;
|
|
1341
|
+
metadata: Record<string, any> | null;
|
|
1342
|
+
isRead: boolean;
|
|
1343
|
+
createdAt: string;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
export interface CustomerNotificationsResponse {
|
|
1347
|
+
notifications: CustomerNotification[];
|
|
1348
|
+
stats: { unread: number };
|
|
1349
|
+
pagination: { total: number; limit: number; offset: number; hasMore: boolean };
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
export interface MarkNotificationsReadResponse {
|
|
1353
|
+
updated: number;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1328
1356
|
export interface DeliveryAddressResponse {
|
|
1329
1357
|
name: string;
|
|
1330
1358
|
phoneNumber: string;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { OrderStatus } from "../types/enums";
|
|
2
|
+
|
|
3
|
+
/** Detect pickup / on-site-collection shipping methods (normalised matching) */
|
|
4
|
+
export function isPickupOrder(
|
|
5
|
+
shippingMethod?: string | null,
|
|
6
|
+
shippingRateId?: string | null
|
|
7
|
+
): boolean {
|
|
8
|
+
// Primary check: shippingRateId === "PICKUP" (from mobile app)
|
|
9
|
+
if (shippingRateId && shippingRateId.trim().toUpperCase() === "PICKUP") {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!shippingMethod) return false;
|
|
14
|
+
const normalized = shippingMethod
|
|
15
|
+
.trim()
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[\s-]+/g, " ");
|
|
18
|
+
return (
|
|
19
|
+
normalized === "pickup" ||
|
|
20
|
+
normalized === "on site collection" ||
|
|
21
|
+
normalized === "onsite collection"
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Canonical order status transitions.
|
|
27
|
+
*
|
|
28
|
+
* Buyer-protected escrow flow:
|
|
29
|
+
* CREATED → PAYMENT_RESERVED → MERCHANT_ACCEPTED → SHIPPED → DELIVERED → SETTLED
|
|
30
|
+
*
|
|
31
|
+
* Cancellation is allowed from any non-terminal state except DELIVERED (already in settlement).
|
|
32
|
+
*/
|
|
33
|
+
export const ORDER_STATUS_TRANSITIONS: Partial<Record<OrderStatus, OrderStatus[]>> = {
|
|
34
|
+
[OrderStatus.CREATED]: [
|
|
35
|
+
OrderStatus.PAYMENT_RESERVED,
|
|
36
|
+
OrderStatus.MERCHANT_ACCEPTED,
|
|
37
|
+
OrderStatus.CANCELLED,
|
|
38
|
+
],
|
|
39
|
+
[OrderStatus.PAYMENT_RESERVED]: [
|
|
40
|
+
OrderStatus.MERCHANT_ACCEPTED,
|
|
41
|
+
OrderStatus.CANCELLED,
|
|
42
|
+
],
|
|
43
|
+
[OrderStatus.MERCHANT_ACCEPTED]: [
|
|
44
|
+
OrderStatus.SHIPPED,
|
|
45
|
+
OrderStatus.CANCELLED,
|
|
46
|
+
],
|
|
47
|
+
// Backward compat for existing SETTLED orders created before escrow change
|
|
48
|
+
// Note: CANCELLED removed — settled orders have funds paid out, no clawback mechanism
|
|
49
|
+
[OrderStatus.SETTLED]: [OrderStatus.SHIPPED],
|
|
50
|
+
[OrderStatus.SHIPPED]: [OrderStatus.DELIVERED, OrderStatus.CANCELLED],
|
|
51
|
+
// Settlement triggered on delivery (buyer-protected escrow)
|
|
52
|
+
[OrderStatus.DELIVERED]: [OrderStatus.SETTLED],
|
|
53
|
+
// Terminal states
|
|
54
|
+
[OrderStatus.CANCELLED]: [],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Validate whether transitioning from `currentStatus` to `newStatus` is allowed.
|
|
59
|
+
*
|
|
60
|
+
* For pickup / on-site-collection orders, MERCHANT_ACCEPTED → DELIVERED and
|
|
61
|
+
* SETTLED → DELIVERED are permitted (skipping SHIPPED).
|
|
62
|
+
*/
|
|
63
|
+
export function validateStatusTransition(
|
|
64
|
+
currentStatus: string,
|
|
65
|
+
newStatus: string,
|
|
66
|
+
options?: {
|
|
67
|
+
shippingMethod?: string | null;
|
|
68
|
+
shippingRateId?: string | null;
|
|
69
|
+
}
|
|
70
|
+
): { valid: boolean; error?: string } {
|
|
71
|
+
// Pickup orders can skip SHIPPED and go directly to DELIVERED
|
|
72
|
+
if (
|
|
73
|
+
isPickupOrder(options?.shippingMethod, options?.shippingRateId) &&
|
|
74
|
+
(currentStatus === OrderStatus.MERCHANT_ACCEPTED ||
|
|
75
|
+
currentStatus === OrderStatus.SETTLED) &&
|
|
76
|
+
newStatus === OrderStatus.DELIVERED
|
|
77
|
+
) {
|
|
78
|
+
return { valid: true };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const allowed =
|
|
82
|
+
ORDER_STATUS_TRANSITIONS[currentStatus as OrderStatus] ?? [];
|
|
83
|
+
if (!allowed.includes(newStatus as OrderStatus)) {
|
|
84
|
+
if (allowed.length === 0) {
|
|
85
|
+
return {
|
|
86
|
+
valid: false,
|
|
87
|
+
error: `Cannot change status from ${currentStatus} — this is a final state`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
valid: false,
|
|
92
|
+
error: `Cannot transition from ${currentStatus} to ${newStatus}. Allowed: ${allowed.join(", ")}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { valid: true };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Return the list of statuses reachable from `currentStatus`.
|
|
101
|
+
* For pickup orders, DELIVERED is added when on MERCHANT_ACCEPTED or SETTLED.
|
|
102
|
+
*/
|
|
103
|
+
export function getNextStatuses(
|
|
104
|
+
currentStatus: string,
|
|
105
|
+
options?: {
|
|
106
|
+
shippingMethod?: string | null;
|
|
107
|
+
shippingRateId?: string | null;
|
|
108
|
+
}
|
|
109
|
+
): string[] {
|
|
110
|
+
const base = [
|
|
111
|
+
...(ORDER_STATUS_TRANSITIONS[currentStatus as OrderStatus] ?? []),
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
if (
|
|
115
|
+
isPickupOrder(options?.shippingMethod, options?.shippingRateId) &&
|
|
116
|
+
(currentStatus === OrderStatus.MERCHANT_ACCEPTED ||
|
|
117
|
+
currentStatus === OrderStatus.SETTLED) &&
|
|
118
|
+
!base.includes(OrderStatus.DELIVERED)
|
|
119
|
+
) {
|
|
120
|
+
base.push(OrderStatus.DELIVERED);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return base;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Human-readable label for a status. */
|
|
127
|
+
export function getStatusLabel(status: string): string {
|
|
128
|
+
const labels: Record<string, string> = {
|
|
129
|
+
CREATED: "Created",
|
|
130
|
+
PAYMENT_RESERVED: "Payment Reserved",
|
|
131
|
+
MERCHANT_ACCEPTED: "Accepted",
|
|
132
|
+
SETTLED: "Completed (Paid)",
|
|
133
|
+
SHIPPED: "Shipped",
|
|
134
|
+
DELIVERED: "Delivered",
|
|
135
|
+
CANCELLED: "Cancelled",
|
|
136
|
+
};
|
|
137
|
+
return labels[status] || status;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** UI colour key for a status. */
|
|
141
|
+
export function getStatusColor(status: string): string {
|
|
142
|
+
const colors: Record<string, string> = {
|
|
143
|
+
CREATED: "gray",
|
|
144
|
+
PAYMENT_RESERVED: "blue",
|
|
145
|
+
MERCHANT_ACCEPTED: "purple",
|
|
146
|
+
SETTLED: "indigo",
|
|
147
|
+
SHIPPED: "yellow",
|
|
148
|
+
DELIVERED: "green",
|
|
149
|
+
CANCELLED: "red",
|
|
150
|
+
};
|
|
151
|
+
return colors[status] || "gray";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Whether the order can be cancelled from its current status. */
|
|
155
|
+
export function canCancelOrder(currentStatus: string): boolean {
|
|
156
|
+
return (
|
|
157
|
+
ORDER_STATUS_TRANSITIONS[currentStatus as OrderStatus]?.includes(
|
|
158
|
+
OrderStatus.CANCELLED
|
|
159
|
+
) ?? false
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Whether tracking info is required for a status change. */
|
|
164
|
+
export function requiresTrackingInfo(
|
|
165
|
+
newStatus: string,
|
|
166
|
+
options?: {
|
|
167
|
+
shippingMethod?: string | null;
|
|
168
|
+
shippingRateId?: string | null;
|
|
169
|
+
}
|
|
170
|
+
): boolean {
|
|
171
|
+
if (newStatus !== OrderStatus.SHIPPED) return false;
|
|
172
|
+
return !isPickupOrder(options?.shippingMethod, options?.shippingRateId);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Whether a status change should trigger customer notification. */
|
|
176
|
+
export function shouldNotifyCustomer(newStatus: string): boolean {
|
|
177
|
+
return [
|
|
178
|
+
OrderStatus.MERCHANT_ACCEPTED,
|
|
179
|
+
OrderStatus.SHIPPED,
|
|
180
|
+
OrderStatus.DELIVERED,
|
|
181
|
+
OrderStatus.CANCELLED,
|
|
182
|
+
].includes(newStatus as OrderStatus);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Status progression percentage (for progress bars). */
|
|
186
|
+
export function getStatusProgress(status: string): number {
|
|
187
|
+
const progressMap: Record<string, number> = {
|
|
188
|
+
CREATED: 10,
|
|
189
|
+
PAYMENT_RESERVED: 25,
|
|
190
|
+
MERCHANT_ACCEPTED: 40,
|
|
191
|
+
SETTLED: 50,
|
|
192
|
+
SHIPPED: 75,
|
|
193
|
+
DELIVERED: 100,
|
|
194
|
+
CANCELLED: 0,
|
|
195
|
+
};
|
|
196
|
+
return progressMap[status] || 0;
|
|
197
|
+
}
|
package/lib/types.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { UserAbstractionMode } from "./abstraction";
|
|
2
|
+
|
|
3
|
+
export interface PerpDexState {
|
|
4
|
+
totalVaultEquity: number;
|
|
5
|
+
perpsAtOpenInterestCap?: Array<string>;
|
|
6
|
+
leadingVaults?: Array<LeadingVault>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
// Additional undocumented fields in WebData3 will be removed on a future update
|
|
11
|
+
export interface WsWebData3 {
|
|
12
|
+
userState: {
|
|
13
|
+
abstraction: UserAbstractionMode;
|
|
14
|
+
agentAddress: string | null;
|
|
15
|
+
agentValidUntil: number | null;
|
|
16
|
+
serverTime: number;
|
|
17
|
+
cumLedger: number;
|
|
18
|
+
isVault: boolean;
|
|
19
|
+
user: string;
|
|
20
|
+
optOutOfSpotDusting?: boolean;
|
|
21
|
+
dexAbstractionEnabled?: boolean;
|
|
22
|
+
};
|
|
23
|
+
perpDexStates: Array<PerpDexState>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface LeadingVault {
|
|
27
|
+
address: string;
|
|
28
|
+
name: string;
|
|
29
|
+
}
|