@0xmonaco/react 1.0.69 → 1.0.70
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/coverage.d.ts +13 -0
- package/dist/coverage.js +13 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/useCopyEvents/index.d.ts +2 -0
- package/dist/hooks/useCopyEvents/index.js +2 -0
- package/dist/hooks/useCopyEvents/types.d.ts +37 -0
- package/dist/hooks/useCopyEvents/types.js +2 -0
- package/dist/hooks/useCopyEvents/useCopyEvents.d.ts +20 -0
- package/dist/hooks/useCopyEvents/useCopyEvents.js +121 -0
- package/dist/hooks/useCopyTrading/index.d.ts +2 -0
- package/dist/hooks/useCopyTrading/index.js +1 -0
- package/dist/hooks/useCopyTrading/types.d.ts +6 -0
- package/dist/hooks/useCopyTrading/types.js +0 -0
- package/dist/hooks/useCopyTrading/useCopyTrading.d.ts +22 -0
- package/dist/hooks/useCopyTrading/useCopyTrading.js +58 -0
- package/package.json +3 -3
package/dist/coverage.d.ts
CHANGED
|
@@ -9,6 +9,19 @@ export declare const COVERED: {
|
|
|
9
9
|
cancel_conditional_order: string;
|
|
10
10
|
get_conditional_order: string;
|
|
11
11
|
cancel_order: string;
|
|
12
|
+
upsert_lead_trader: string;
|
|
13
|
+
get_my_lead_trader: string;
|
|
14
|
+
list_my_followers: string;
|
|
15
|
+
list_lead_traders: string;
|
|
16
|
+
get_lead_trader: string;
|
|
17
|
+
list_lead_trader_closed_trades: string;
|
|
18
|
+
list_lead_trader_open_positions: string;
|
|
19
|
+
upsert_follow: string;
|
|
20
|
+
stop_follow: string;
|
|
21
|
+
list_my_follows: string;
|
|
22
|
+
get_follow: string;
|
|
23
|
+
list_follow_leader_positions: string;
|
|
24
|
+
preview_follow: string;
|
|
12
25
|
close_position: string;
|
|
13
26
|
batch_close_all_positions: string;
|
|
14
27
|
create_challenge: string;
|
package/dist/coverage.js
CHANGED
|
@@ -9,6 +9,19 @@ export const COVERED = {
|
|
|
9
9
|
cancel_conditional_order: "useTrade",
|
|
10
10
|
get_conditional_order: "useTrade",
|
|
11
11
|
cancel_order: "useTrade",
|
|
12
|
+
upsert_lead_trader: "useCopyTrading",
|
|
13
|
+
get_my_lead_trader: "useCopyTrading",
|
|
14
|
+
list_my_followers: "useCopyTrading",
|
|
15
|
+
list_lead_traders: "useCopyTrading",
|
|
16
|
+
get_lead_trader: "useCopyTrading",
|
|
17
|
+
list_lead_trader_closed_trades: "useCopyTrading",
|
|
18
|
+
list_lead_trader_open_positions: "useCopyTrading",
|
|
19
|
+
upsert_follow: "useCopyTrading",
|
|
20
|
+
stop_follow: "useCopyTrading",
|
|
21
|
+
list_my_follows: "useCopyTrading",
|
|
22
|
+
get_follow: "useCopyTrading",
|
|
23
|
+
list_follow_leader_positions: "useCopyTrading",
|
|
24
|
+
preview_follow: "useCopyTrading",
|
|
12
25
|
close_position: "usePositions",
|
|
13
26
|
batch_close_all_positions: "usePositions",
|
|
14
27
|
create_challenge: "useAuth",
|
package/dist/hooks/index.d.ts
CHANGED
package/dist/hooks/index.js
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { CopyRelationshipEventData, CopySkipEventData } from "@0xmonaco/types";
|
|
2
|
+
/**
|
|
3
|
+
* Return type of {@link useCopyEvents}.
|
|
4
|
+
*/
|
|
5
|
+
export interface UseCopyEventsReturn {
|
|
6
|
+
/**
|
|
7
|
+
* Your follows as last reported on the `copy` channel, newest first (by
|
|
8
|
+
* `startedAt`, then `relationshipId`). A follow stopped while the socket was
|
|
9
|
+
* up keeps its entry with status `STOPPED` until the next snapshot, which
|
|
10
|
+
* carries live follows only.
|
|
11
|
+
*/
|
|
12
|
+
relationships: CopyRelationshipEventData[];
|
|
13
|
+
/** The same entries keyed by `relationshipId` (the follow id). */
|
|
14
|
+
relationshipsById: Record<string, CopyRelationshipEventData>;
|
|
15
|
+
/**
|
|
16
|
+
* Leader fills your follows did not mirror, newest first, capped at
|
|
17
|
+
* {@link MAX_RECENT_COPY_SKIPS}. Only skips received while subscribed; the
|
|
18
|
+
* snapshot carries none. Read `getFollow` for a follow's persisted history.
|
|
19
|
+
*/
|
|
20
|
+
recentSkips: CopySkipEventData[];
|
|
21
|
+
/**
|
|
22
|
+
* Whether a subscription to the `copy` channel is currently registered. Not
|
|
23
|
+
* a connection indicator: the client accepts subscriptions while the socket
|
|
24
|
+
* is down and sends them on the next open.
|
|
25
|
+
*/
|
|
26
|
+
subscribed: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* A rejected subscription request (the core helper's synchronous throw). Not
|
|
29
|
+
* a transport signal: connection loss and malformed frames never appear
|
|
30
|
+
* here.
|
|
31
|
+
*/
|
|
32
|
+
error: Error | null;
|
|
33
|
+
/** Clear the current error state. */
|
|
34
|
+
clearError: () => void;
|
|
35
|
+
}
|
|
36
|
+
/** How many skips {@link UseCopyEventsReturn.recentSkips} keeps. */
|
|
37
|
+
export declare const MAX_RECENT_COPY_SKIPS = 50;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type UseCopyEventsReturn } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Stream your copy trading follows over the authenticated `copy` channel.
|
|
4
|
+
* The subscription runs only while a user is signed in: it starts on login,
|
|
5
|
+
* and logout (or an account switch) clears the follows and skips.
|
|
6
|
+
*
|
|
7
|
+
* The subscribe-time snapshot seeds your live follows (`ACTIVE` or
|
|
8
|
+
* `CLOSE_ONLY`); each `copy_relationship_update` frame then carries a follow's
|
|
9
|
+
* FULL state and replaces its entry when its `version` is not older than the
|
|
10
|
+
* stored one. A later snapshot (after a reconnect) REPLACES the set
|
|
11
|
+
* wholesale, so a follow that stopped while the socket was down leaves it.
|
|
12
|
+
*
|
|
13
|
+
* `copy_skip` frames (leader fills a follow could not mirror) collect in
|
|
14
|
+
* `recentSkips`.
|
|
15
|
+
*
|
|
16
|
+
* `cumNetRealized` and `hwm` on each follow are its profit-share basis, not
|
|
17
|
+
* the copy account's balance; read the copy risk bucket (`copyRiskBucketId`)
|
|
18
|
+
* for that.
|
|
19
|
+
*/
|
|
20
|
+
export declare function useCopyEvents(): UseCopyEventsReturn;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { AuthenticationStatus, useMonacoContext } from "../../provider";
|
|
3
|
+
import { useMonacoSDK } from "../useMonaco";
|
|
4
|
+
import { MAX_RECENT_COPY_SKIPS } from "./types";
|
|
5
|
+
const IDLE = { source: undefined, userId: undefined, byId: {}, skips: [], subscribed: false, error: null };
|
|
6
|
+
/** Shared empties, so an out-of-scope render keeps a stable identity. */
|
|
7
|
+
const NO_RELATIONSHIPS = {};
|
|
8
|
+
const NO_SKIPS = [];
|
|
9
|
+
/**
|
|
10
|
+
* Whether a live frame supersedes the stored row. `version` is the durable-log
|
|
11
|
+
* sequence that produced the row: an older one is a late frame and is ignored.
|
|
12
|
+
* Equal is applied (the same step, re-sent). When either side has no version
|
|
13
|
+
* the frame is applied, since there is nothing to order it by.
|
|
14
|
+
*/
|
|
15
|
+
function supersedes(incoming, stored) {
|
|
16
|
+
if (!stored || stored.version === undefined || incoming.version === undefined)
|
|
17
|
+
return true;
|
|
18
|
+
return incoming.version >= stored.version;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Newest follow first. `startedAt` is compared as a parsed instant, not as a
|
|
22
|
+
* string: the server's RFC 3339 stamps carry 0, 3 or 6 fractional digits, so
|
|
23
|
+
* `…05Z` would string-sort after `…05.500Z` although it is earlier. A stamp
|
|
24
|
+
* that does not parse falls back to plain string order against its peer. The
|
|
25
|
+
* id breaks ties with a codepoint compare, so the order never shifts with the
|
|
26
|
+
* runtime locale.
|
|
27
|
+
*/
|
|
28
|
+
function newestFirst(left, right) {
|
|
29
|
+
const leftAt = Date.parse(left.startedAt);
|
|
30
|
+
const rightAt = Date.parse(right.startedAt);
|
|
31
|
+
if (!Number.isNaN(leftAt) && !Number.isNaN(rightAt)) {
|
|
32
|
+
if (leftAt !== rightAt)
|
|
33
|
+
return rightAt - leftAt;
|
|
34
|
+
}
|
|
35
|
+
else if (left.startedAt !== right.startedAt) {
|
|
36
|
+
return left.startedAt > right.startedAt ? -1 : 1;
|
|
37
|
+
}
|
|
38
|
+
if (left.relationshipId === right.relationshipId)
|
|
39
|
+
return 0;
|
|
40
|
+
return left.relationshipId < right.relationshipId ? -1 : 1;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Stream your copy trading follows over the authenticated `copy` channel.
|
|
44
|
+
* The subscription runs only while a user is signed in: it starts on login,
|
|
45
|
+
* and logout (or an account switch) clears the follows and skips.
|
|
46
|
+
*
|
|
47
|
+
* The subscribe-time snapshot seeds your live follows (`ACTIVE` or
|
|
48
|
+
* `CLOSE_ONLY`); each `copy_relationship_update` frame then carries a follow's
|
|
49
|
+
* FULL state and replaces its entry when its `version` is not older than the
|
|
50
|
+
* stored one. A later snapshot (after a reconnect) REPLACES the set
|
|
51
|
+
* wholesale, so a follow that stopped while the socket was down leaves it.
|
|
52
|
+
*
|
|
53
|
+
* `copy_skip` frames (leader fills a follow could not mirror) collect in
|
|
54
|
+
* `recentSkips`.
|
|
55
|
+
*
|
|
56
|
+
* `cumNetRealized` and `hwm` on each follow are its profit-share basis, not
|
|
57
|
+
* the copy account's balance; read the copy risk bucket (`copyRiskBucketId`)
|
|
58
|
+
* for that.
|
|
59
|
+
*/
|
|
60
|
+
export function useCopyEvents() {
|
|
61
|
+
const { sdk } = useMonacoSDK();
|
|
62
|
+
const { authenticationStatus } = useMonacoContext();
|
|
63
|
+
const ws = sdk?.ws;
|
|
64
|
+
// The channel is private, and login/logout leave `sdk.ws` the same object:
|
|
65
|
+
// a subscribe sent before login is refused and never resent, and logout
|
|
66
|
+
// tears down the client's handlers. Scope the subscription to the signed-in
|
|
67
|
+
// user, derived reactively from the provider's status, so it starts on
|
|
68
|
+
// login, stops (and clears) on logout, and restarts on an account switch.
|
|
69
|
+
const userId = authenticationStatus === AuthenticationStatus.AUTHENTICATED ? sdk?.getAuthState()?.user.id : undefined;
|
|
70
|
+
const [subscription, setSubscription] = useState(IDLE);
|
|
71
|
+
const inScope = subscription.source === ws && subscription.userId === userId && userId !== undefined;
|
|
72
|
+
const clearError = useCallback(() => {
|
|
73
|
+
setSubscription((prev) => (prev.error === null ? prev : { ...prev, error: null }));
|
|
74
|
+
}, []);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!ws || !userId) {
|
|
77
|
+
setSubscription(IDLE);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const belongsToThisSubscription = (prev) => prev.source === ws && prev.userId === userId;
|
|
81
|
+
setSubscription({ source: ws, userId, byId: {}, skips: [], subscribed: false, error: null });
|
|
82
|
+
let unsubscribe;
|
|
83
|
+
try {
|
|
84
|
+
unsubscribe = ws.copy((event) => {
|
|
85
|
+
setSubscription((prev) => {
|
|
86
|
+
if (!belongsToThisSubscription(prev))
|
|
87
|
+
return prev;
|
|
88
|
+
if (event.eventType === "copy_skip") {
|
|
89
|
+
return { ...prev, skips: [event.data, ...prev.skips].slice(0, MAX_RECENT_COPY_SKIPS) };
|
|
90
|
+
}
|
|
91
|
+
const id = event.data.relationshipId;
|
|
92
|
+
if (!supersedes(event.data, prev.byId[id]))
|
|
93
|
+
return prev;
|
|
94
|
+
return { ...prev, byId: { ...prev.byId, [id]: event.data } };
|
|
95
|
+
});
|
|
96
|
+
}, (items) => {
|
|
97
|
+
const byId = Object.fromEntries(items.map((item) => [item.relationshipId, item]));
|
|
98
|
+
setSubscription((prev) => (belongsToThisSubscription(prev) ? { ...prev, byId } : prev));
|
|
99
|
+
});
|
|
100
|
+
setSubscription((prev) => (belongsToThisSubscription(prev) ? { ...prev, subscribed: true } : prev));
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
104
|
+
setSubscription((prev) => (belongsToThisSubscription(prev) ? { ...prev, subscribed: false, error } : prev));
|
|
105
|
+
}
|
|
106
|
+
return () => {
|
|
107
|
+
unsubscribe?.();
|
|
108
|
+
setSubscription((prev) => (belongsToThisSubscription(prev) ? { ...prev, subscribed: false } : prev));
|
|
109
|
+
};
|
|
110
|
+
}, [ws, userId]);
|
|
111
|
+
const relationshipsById = inScope ? subscription.byId : NO_RELATIONSHIPS;
|
|
112
|
+
const relationships = useMemo(() => Object.values(relationshipsById).sort(newestFirst), [relationshipsById]);
|
|
113
|
+
return {
|
|
114
|
+
relationships,
|
|
115
|
+
relationshipsById,
|
|
116
|
+
recentSkips: inScope ? subscription.skips : NO_SKIPS,
|
|
117
|
+
subscribed: inScope && subscription.subscribed,
|
|
118
|
+
error: inScope ? subscription.error : null,
|
|
119
|
+
clearError,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useCopyTrading } from "./useCopyTrading";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CopyTradingAPI } from "@0xmonaco/types";
|
|
2
|
+
/**
|
|
3
|
+
* Return type of {@link useCopyTrading}: the copy trading client methods, each
|
|
4
|
+
* with the same signature and documentation as `sdk.copyTrading`.
|
|
5
|
+
*/
|
|
6
|
+
export type UseCopyTradingReturn = Pick<CopyTradingAPI, "listLeadTraders" | "getLeadTrader" | "listLeadTraderClosedTrades" | "listLeadTraderOpenPositions" | "upsertLeadTrader" | "getMyLeadTrader" | "listMyFollowers" | "upsertFollow" | "stopFollow" | "listMyFollows" | "getFollow" | "listFollowLeaderPositions" | "previewFollow">;
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { UseCopyTradingReturn } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Copy trading: the public leaderboard and lead trader profiles, your own lead
|
|
4
|
+
* trader profile and followers, and your follows.
|
|
5
|
+
*
|
|
6
|
+
* Every method delegates to `sdk.copyTrading` (see `@0xmonaco/core` for the
|
|
7
|
+
* request contract). The leaderboard, `getLeadTrader` and
|
|
8
|
+
* `listLeadTraderClosedTrades` need no session; everything else is signed and
|
|
9
|
+
* scoped to the authenticated caller. Each method throws when the SDK is not
|
|
10
|
+
* available yet.
|
|
11
|
+
*
|
|
12
|
+
* For live follow state (status changes and skipped mirrors), use
|
|
13
|
+
* {@link useCopyEvents}.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* const { listLeadTraders, upsertFollow } = useCopyTrading();
|
|
18
|
+
* const { leaders } = await listLeadTraders({ sort: "roi", window: "30d" });
|
|
19
|
+
* await upsertFollow({ leader: "alpha_trader", allocation: "1000", slPct: "0.25" });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare const useCopyTrading: () => UseCopyTradingReturn;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { useMonacoSDK } from "../useMonaco";
|
|
3
|
+
/**
|
|
4
|
+
* Copy trading: the public leaderboard and lead trader profiles, your own lead
|
|
5
|
+
* trader profile and followers, and your follows.
|
|
6
|
+
*
|
|
7
|
+
* Every method delegates to `sdk.copyTrading` (see `@0xmonaco/core` for the
|
|
8
|
+
* request contract). The leaderboard, `getLeadTrader` and
|
|
9
|
+
* `listLeadTraderClosedTrades` need no session; everything else is signed and
|
|
10
|
+
* scoped to the authenticated caller. Each method throws when the SDK is not
|
|
11
|
+
* available yet.
|
|
12
|
+
*
|
|
13
|
+
* For live follow state (status changes and skipped mirrors), use
|
|
14
|
+
* {@link useCopyEvents}.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* const { listLeadTraders, upsertFollow } = useCopyTrading();
|
|
19
|
+
* const { leaders } = await listLeadTraders({ sort: "roi", window: "30d" });
|
|
20
|
+
* await upsertFollow({ leader: "alpha_trader", allocation: "1000", slPct: "0.25" });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export const useCopyTrading = () => {
|
|
24
|
+
const { sdk } = useMonacoSDK();
|
|
25
|
+
const client = useCallback(() => {
|
|
26
|
+
if (!sdk)
|
|
27
|
+
throw new Error("SDK not available");
|
|
28
|
+
return sdk.copyTrading;
|
|
29
|
+
}, [sdk]);
|
|
30
|
+
const listLeadTraders = useCallback(async (params) => await client().listLeadTraders(params), [client]);
|
|
31
|
+
const getLeadTrader = useCallback(async (handle) => await client().getLeadTrader(handle), [client]);
|
|
32
|
+
const listLeadTraderClosedTrades = useCallback(async (handle, params) => await client().listLeadTraderClosedTrades(handle, params), [client]);
|
|
33
|
+
const listLeadTraderOpenPositions = useCallback(async (handle) => await client().listLeadTraderOpenPositions(handle), [client]);
|
|
34
|
+
const upsertLeadTrader = useCallback(async (params) => await client().upsertLeadTrader(params), [client]);
|
|
35
|
+
const getMyLeadTrader = useCallback(async () => await client().getMyLeadTrader(), [client]);
|
|
36
|
+
const listMyFollowers = useCallback(async (params) => await client().listMyFollowers(params), [client]);
|
|
37
|
+
const upsertFollow = useCallback(async (params) => await client().upsertFollow(params), [client]);
|
|
38
|
+
const stopFollow = useCallback(async (followId) => await client().stopFollow(followId), [client]);
|
|
39
|
+
const listMyFollows = useCallback(async (params) => await client().listMyFollows(params), [client]);
|
|
40
|
+
const getFollow = useCallback(async (followId) => await client().getFollow(followId), [client]);
|
|
41
|
+
const previewFollow = useCallback(async (params) => await client().previewFollow(params), [client]);
|
|
42
|
+
const listFollowLeaderPositions = useCallback(async (followId) => await client().listFollowLeaderPositions(followId), [client]);
|
|
43
|
+
return {
|
|
44
|
+
listLeadTraders,
|
|
45
|
+
getLeadTrader,
|
|
46
|
+
listLeadTraderClosedTrades,
|
|
47
|
+
listLeadTraderOpenPositions,
|
|
48
|
+
upsertLeadTrader,
|
|
49
|
+
getMyLeadTrader,
|
|
50
|
+
listMyFollowers,
|
|
51
|
+
upsertFollow,
|
|
52
|
+
stopFollow,
|
|
53
|
+
listMyFollows,
|
|
54
|
+
getFollow,
|
|
55
|
+
listFollowLeaderPositions,
|
|
56
|
+
previewFollow,
|
|
57
|
+
};
|
|
58
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xmonaco/react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.70",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"homepage": "https://docs.0xmonaco.com/sdk/typescript",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"lint": "biome lint ."
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@0xmonaco/core": "1.0.
|
|
20
|
-
"@0xmonaco/types": "1.0.
|
|
19
|
+
"@0xmonaco/core": "1.0.70",
|
|
20
|
+
"@0xmonaco/types": "1.0.70"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/react": "^19.2.18",
|