blue-js-sdk 2.0.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/CHANGELOG.md +446 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/ai-path/ADMIN-ELEVATION.md +116 -0
- package/ai-path/AI-MANIFESTO.md +185 -0
- package/ai-path/BREAKING.md +74 -0
- package/ai-path/CHECKLIST.md +619 -0
- package/ai-path/CONNECTION-STEPS.md +724 -0
- package/ai-path/DECISION-TREE.md +378 -0
- package/ai-path/DEPENDENCIES.md +459 -0
- package/ai-path/E2E-FLOW.md +1555 -0
- package/ai-path/FAILURES.md +403 -0
- package/ai-path/GUIDE.md +1217 -0
- package/ai-path/README.md +558 -0
- package/ai-path/SPLIT-TUNNEL.md +266 -0
- package/ai-path/cli.js +535 -0
- package/ai-path/connect.js +884 -0
- package/ai-path/discover.js +178 -0
- package/ai-path/environment.js +266 -0
- package/ai-path/errors.js +86 -0
- package/ai-path/examples/autonomous-agent.mjs +220 -0
- package/ai-path/examples/multi-region.mjs +174 -0
- package/ai-path/examples/one-shot.mjs +31 -0
- package/ai-path/index.js +60 -0
- package/ai-path/pricing.js +136 -0
- package/ai-path/recommend.js +413 -0
- package/ai-path/run-admin.vbs +25 -0
- package/ai-path/setup.js +291 -0
- package/ai-path/wallet.js +137 -0
- package/app-helpers.js +363 -0
- package/app-settings.js +95 -0
- package/app-types.js +267 -0
- package/audit.js +847 -0
- package/batch.js +293 -0
- package/bin/setup.js +376 -0
- package/chain/authz.js +109 -0
- package/chain/broadcast.js +472 -0
- package/chain/client.js +160 -0
- package/chain/fee-grants.js +305 -0
- package/chain/index.js +891 -0
- package/chain/lcd.js +313 -0
- package/chain/queries.js +547 -0
- package/chain/rpc.js +408 -0
- package/chain/wallet.js +141 -0
- package/cli/config.js +143 -0
- package/cli/index.js +463 -0
- package/cli/output.js +182 -0
- package/cli.js +491 -0
- package/client/index.js +251 -0
- package/client.js +271 -0
- package/config/index.js +255 -0
- package/connection/connect.js +849 -0
- package/connection/disconnect.js +180 -0
- package/connection/discovery.js +321 -0
- package/connection/index.js +76 -0
- package/connection/proxy.js +148 -0
- package/connection/resilience.js +428 -0
- package/connection/security.js +232 -0
- package/connection/state.js +369 -0
- package/connection/tunnel.js +691 -0
- package/consumer.js +132 -0
- package/cosmjs-setup.js +1884 -0
- package/defaults.js +366 -0
- package/disk-cache.js +107 -0
- package/dist/client.d.ts +108 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +400 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/errors/index.js +112 -0
- package/errors.js +218 -0
- package/examples/README.md +64 -0
- package/examples/connect-direct.mjs +106 -0
- package/examples/connect-plan.mjs +125 -0
- package/examples/error-handling.mjs +109 -0
- package/examples/query-nodes.mjs +94 -0
- package/examples/wallet-basics.mjs +61 -0
- package/generated/amino/amino.ts +9 -0
- package/generated/cosmos/base/v1beta1/coin.ts +365 -0
- package/generated/cosmos_proto/cosmos.ts +323 -0
- package/generated/gogoproto/gogo.ts +9 -0
- package/generated/google/protobuf/descriptor.ts +7601 -0
- package/generated/google/protobuf/duration.ts +208 -0
- package/generated/google/protobuf/timestamp.ts +238 -0
- package/generated/sentinel/lease/v1/events.ts +924 -0
- package/generated/sentinel/lease/v1/lease.ts +292 -0
- package/generated/sentinel/lease/v1/msg.ts +949 -0
- package/generated/sentinel/lease/v1/params.ts +164 -0
- package/generated/sentinel/node/v3/events.ts +881 -0
- package/generated/sentinel/node/v3/msg.ts +1002 -0
- package/generated/sentinel/node/v3/node.ts +263 -0
- package/generated/sentinel/node/v3/params.ts +183 -0
- package/generated/sentinel/plan/v3/events.ts +675 -0
- package/generated/sentinel/plan/v3/msg.ts +1191 -0
- package/generated/sentinel/plan/v3/plan.ts +283 -0
- package/generated/sentinel/provider/v2/events.ts +171 -0
- package/generated/sentinel/provider/v2/msg.ts +480 -0
- package/generated/sentinel/provider/v2/params.ts +131 -0
- package/generated/sentinel/provider/v2/provider.ts +246 -0
- package/generated/sentinel/session/v3/events.ts +480 -0
- package/generated/sentinel/session/v3/msg.ts +616 -0
- package/generated/sentinel/session/v3/params.ts +260 -0
- package/generated/sentinel/session/v3/proof.ts +180 -0
- package/generated/sentinel/session/v3/session.ts +384 -0
- package/generated/sentinel/subscription/v3/events.ts +1181 -0
- package/generated/sentinel/subscription/v3/msg.ts +1305 -0
- package/generated/sentinel/subscription/v3/params.ts +167 -0
- package/generated/sentinel/subscription/v3/subscription.ts +315 -0
- package/generated/sentinel/types/v1/bandwidth.ts +124 -0
- package/generated/sentinel/types/v1/price.ts +149 -0
- package/generated/sentinel/types/v1/renewal.ts +87 -0
- package/generated/sentinel/types/v1/status.ts +54 -0
- package/generated/typeRegistry.ts +27 -0
- package/index.js +486 -0
- package/node-connect.js +3015 -0
- package/operator.js +134 -0
- package/package.json +113 -0
- package/plan-operations.js +199 -0
- package/preflight.js +352 -0
- package/pricing/index.js +262 -0
- package/proto/amino/amino.proto +84 -0
- package/proto/cosmos/base/v1beta1/coin.proto +61 -0
- package/proto/cosmos_proto/cosmos.proto +112 -0
- package/proto/gogoproto/gogo.proto +145 -0
- package/proto/google/api/annotations.proto +31 -0
- package/proto/google/api/http.proto +370 -0
- package/proto/google/protobuf/any.proto +106 -0
- package/proto/google/protobuf/duration.proto +115 -0
- package/proto/google/protobuf/timestamp.proto +145 -0
- package/proto/sentinel/lease/v1/events.proto +52 -0
- package/proto/sentinel/lease/v1/genesis.proto +15 -0
- package/proto/sentinel/lease/v1/lease.proto +25 -0
- package/proto/sentinel/lease/v1/msg.proto +62 -0
- package/proto/sentinel/lease/v1/params.proto +17 -0
- package/proto/sentinel/node/v3/events.proto +50 -0
- package/proto/sentinel/node/v3/genesis.proto +15 -0
- package/proto/sentinel/node/v3/msg.proto +63 -0
- package/proto/sentinel/node/v3/node.proto +27 -0
- package/proto/sentinel/node/v3/params.proto +21 -0
- package/proto/sentinel/node/v3/querier.proto +63 -0
- package/proto/sentinel/plan/v3/events.proto +41 -0
- package/proto/sentinel/plan/v3/genesis.proto +21 -0
- package/proto/sentinel/plan/v3/msg.proto +83 -0
- package/proto/sentinel/plan/v3/plan.proto +32 -0
- package/proto/sentinel/plan/v3/querier.proto +53 -0
- package/proto/sentinel/provider/v2/events.proto +16 -0
- package/proto/sentinel/provider/v2/genesis.proto +15 -0
- package/proto/sentinel/provider/v2/msg.proto +35 -0
- package/proto/sentinel/provider/v2/params.proto +17 -0
- package/proto/sentinel/provider/v2/provider.proto +24 -0
- package/proto/sentinel/provider/v3/genesis.proto +15 -0
- package/proto/sentinel/provider/v3/params.proto +13 -0
- package/proto/sentinel/session/v3/events.proto +30 -0
- package/proto/sentinel/session/v3/genesis.proto +15 -0
- package/proto/sentinel/session/v3/msg.proto +50 -0
- package/proto/sentinel/session/v3/params.proto +25 -0
- package/proto/sentinel/session/v3/proof.proto +25 -0
- package/proto/sentinel/session/v3/querier.proto +100 -0
- package/proto/sentinel/session/v3/session.proto +50 -0
- package/proto/sentinel/subscription/v2/allocation.proto +21 -0
- package/proto/sentinel/subscription/v2/payout.proto +22 -0
- package/proto/sentinel/subscription/v3/events.proto +65 -0
- package/proto/sentinel/subscription/v3/genesis.proto +17 -0
- package/proto/sentinel/subscription/v3/msg.proto +83 -0
- package/proto/sentinel/subscription/v3/params.proto +21 -0
- package/proto/sentinel/subscription/v3/subscription.proto +33 -0
- package/proto/sentinel/types/v1/bandwidth.proto +19 -0
- package/proto/sentinel/types/v1/price.proto +21 -0
- package/proto/sentinel/types/v1/renewal.proto +21 -0
- package/proto/sentinel/types/v1/status.proto +16 -0
- package/protocol/encoding.js +341 -0
- package/protocol/events.js +361 -0
- package/protocol/handshake.js +297 -0
- package/protocol/index.js +15 -0
- package/protocol/messages.js +346 -0
- package/protocol/plans.js +199 -0
- package/protocol/v2ray.js +268 -0
- package/protocol/v3.js +723 -0
- package/protocol/wireguard.js +125 -0
- package/security/index.js +132 -0
- package/session-manager.js +329 -0
- package/session-tracker.js +80 -0
- package/setup.js +376 -0
- package/speedtest/index.js +528 -0
- package/speedtest.js +567 -0
- package/src/client.ts +502 -0
- package/src/index.ts +20 -0
- package/state/index.js +347 -0
- package/state.js +516 -0
- package/test-all-chain-ops.js +493 -0
- package/test-all-logic.js +199 -0
- package/test-all-msg-types.js +292 -0
- package/test-every-connection.js +208 -0
- package/test-feegrant-connect.js +98 -0
- package/test-logic.js +148 -0
- package/test-mainnet.js +176 -0
- package/test-plan-lifecycle.js +335 -0
- package/tls-trust.js +132 -0
- package/tsconfig.build.json +20 -0
- package/tsconfig.json +34 -0
- package/types/chain.d.ts +746 -0
- package/types/connection.d.ts +425 -0
- package/types/errors.d.ts +174 -0
- package/types/index.d.ts +1380 -0
- package/types/nodes.d.ts +187 -0
- package/types/pricing.d.ts +156 -0
- package/types/protocol.d.ts +332 -0
- package/types/session.d.ts +236 -0
- package/types/settings.d.ts +192 -0
- package/v3protocol.js +1053 -0
- package/wallet/index.js +153 -0
- package/wireguard.js +307 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentinel SDK — Protocol / Typed Event Parsers
|
|
3
|
+
*
|
|
4
|
+
* Type-safe event parsers for Sentinel chain transaction events.
|
|
5
|
+
* Replaces regex string matching with structured parsers that
|
|
6
|
+
* guarantee field access and type correctness.
|
|
7
|
+
*
|
|
8
|
+
* Pattern matches TKD Alex's sentinel-js-sdk typed event approach.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* import { NodeEventCreateSession, searchEvent } from './protocol/events.js';
|
|
12
|
+
* const event = searchEvent(NodeEventCreateSession.type, txResult.events);
|
|
13
|
+
* if (event) {
|
|
14
|
+
* const parsed = NodeEventCreateSession.parse(event);
|
|
15
|
+
* console.log(parsed.sessionId); // bigint, guaranteed
|
|
16
|
+
* }
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Decode an event attribute key or value.
|
|
23
|
+
* Chain events may be base64-encoded (older CosmJS) or plain strings.
|
|
24
|
+
*/
|
|
25
|
+
function decodeAttr(raw) {
|
|
26
|
+
if (typeof raw === 'string') return raw.replace(/^"|"$/g, '');
|
|
27
|
+
if (raw instanceof Uint8Array || Buffer.isBuffer(raw)) return Buffer.from(raw).toString('utf8').replace(/^"|"$/g, '');
|
|
28
|
+
try { return Buffer.from(String(raw), 'base64').toString('utf8').replace(/^"|"$/g, ''); } catch { return String(raw); }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Parse event attributes into a key-value object.
|
|
33
|
+
* Handles both base64-encoded and plain string attributes.
|
|
34
|
+
*/
|
|
35
|
+
function parseAttributes(attributes) {
|
|
36
|
+
const result = {};
|
|
37
|
+
for (const attr of (attributes || [])) {
|
|
38
|
+
const key = decodeAttr(attr.key);
|
|
39
|
+
const value = decodeAttr(attr.value);
|
|
40
|
+
result[key] = value;
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Search transaction events for a specific event type.
|
|
47
|
+
* @param {string} eventType - The event type URL to search for
|
|
48
|
+
* @param {Array} events - Transaction result events array
|
|
49
|
+
* @returns {object|null} The matched event or null
|
|
50
|
+
*/
|
|
51
|
+
export function searchEvent(eventType, events) {
|
|
52
|
+
if (!events || !Array.isArray(events)) return null;
|
|
53
|
+
for (const event of events) {
|
|
54
|
+
if (event.type === eventType) return event;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Search for ALL events matching a type.
|
|
61
|
+
* @param {string} eventType - The event type URL
|
|
62
|
+
* @param {Array} events - Transaction result events array
|
|
63
|
+
* @returns {Array} All matching events
|
|
64
|
+
*/
|
|
65
|
+
export function searchEvents(eventType, events) {
|
|
66
|
+
if (!events || !Array.isArray(events)) return [];
|
|
67
|
+
return events.filter(e => e.type === eventType);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── Node Events ────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* sentinel.node.v3.EventCreateSession — emitted when a direct node session starts.
|
|
74
|
+
* Fields: session_id, acc_address, node_address, price (denom, base_value, quote_value), max_bytes, max_duration
|
|
75
|
+
*/
|
|
76
|
+
export const NodeEventCreateSession = {
|
|
77
|
+
type: 'sentinel.node.v3.EventCreateSession',
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {object} event - Raw chain event
|
|
81
|
+
* @returns {{ sessionId: bigint, accAddress: string, nodeAddress: string, maxBytes: string, maxDuration: string, price: { denom: string, baseValue: string, quoteValue: string } }}
|
|
82
|
+
*/
|
|
83
|
+
parse(event) {
|
|
84
|
+
const attrs = parseAttributes(event.attributes);
|
|
85
|
+
return {
|
|
86
|
+
sessionId: BigInt(attrs.session_id || attrs.SessionID || attrs.id || '0'),
|
|
87
|
+
accAddress: attrs.acc_address || attrs.address || '',
|
|
88
|
+
nodeAddress: attrs.node_address || '',
|
|
89
|
+
maxBytes: attrs.max_bytes || '0',
|
|
90
|
+
maxDuration: attrs.max_duration || '0',
|
|
91
|
+
price: {
|
|
92
|
+
denom: attrs.price_denom || attrs.denom || '',
|
|
93
|
+
baseValue: attrs.price_base_value || attrs.base_value || '0',
|
|
94
|
+
quoteValue: attrs.price_quote_value || attrs.quote_value || '0',
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
/** Type guard. */
|
|
100
|
+
is(event) { return event?.type === NodeEventCreateSession.type; },
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* sentinel.node.v3.EventPay — emitted when session payment is settled.
|
|
105
|
+
* Fields: session_id, acc_address, node_address, payment, staking_reward
|
|
106
|
+
*/
|
|
107
|
+
export const NodeEventPay = {
|
|
108
|
+
type: 'sentinel.node.v3.EventPay',
|
|
109
|
+
|
|
110
|
+
parse(event) {
|
|
111
|
+
const attrs = parseAttributes(event.attributes);
|
|
112
|
+
return {
|
|
113
|
+
sessionId: BigInt(attrs.session_id || '0'),
|
|
114
|
+
accAddress: attrs.acc_address || '',
|
|
115
|
+
nodeAddress: attrs.node_address || '',
|
|
116
|
+
payment: attrs.payment || '0',
|
|
117
|
+
stakingReward: attrs.staking_reward || '0',
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
is(event) { return event?.type === NodeEventPay.type; },
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* sentinel.node.v3.EventRefund — emitted when unused session deposit is refunded.
|
|
126
|
+
*/
|
|
127
|
+
export const NodeEventRefund = {
|
|
128
|
+
type: 'sentinel.node.v3.EventRefund',
|
|
129
|
+
|
|
130
|
+
parse(event) {
|
|
131
|
+
const attrs = parseAttributes(event.attributes);
|
|
132
|
+
return {
|
|
133
|
+
sessionId: BigInt(attrs.session_id || '0'),
|
|
134
|
+
accAddress: attrs.acc_address || '',
|
|
135
|
+
amount: attrs.amount || attrs.value || '0',
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
is(event) { return event?.type === NodeEventRefund.type; },
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* sentinel.node.v3.EventUpdateStatus — emitted when node status changes.
|
|
144
|
+
*/
|
|
145
|
+
export const NodeEventUpdateStatus = {
|
|
146
|
+
type: 'sentinel.node.v3.EventUpdateStatus',
|
|
147
|
+
|
|
148
|
+
parse(event) {
|
|
149
|
+
const attrs = parseAttributes(event.attributes);
|
|
150
|
+
return {
|
|
151
|
+
address: attrs.address || '',
|
|
152
|
+
status: parseInt(attrs.status || '0', 10),
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
is(event) { return event?.type === NodeEventUpdateStatus.type; },
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// ─── Session Events ─────────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* sentinel.session.v3.EventEnd — emitted when any session ends.
|
|
163
|
+
*/
|
|
164
|
+
export const SessionEventEnd = {
|
|
165
|
+
type: 'sentinel.session.v3.EventEnd',
|
|
166
|
+
|
|
167
|
+
parse(event) {
|
|
168
|
+
const attrs = parseAttributes(event.attributes);
|
|
169
|
+
return {
|
|
170
|
+
sessionId: BigInt(attrs.session_id || attrs.id || '0'),
|
|
171
|
+
accAddress: attrs.acc_address || '',
|
|
172
|
+
nodeAddress: attrs.node_address || '',
|
|
173
|
+
};
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
is(event) { return event?.type === SessionEventEnd.type; },
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* sentinel.session.v3.EventUpdateDetails — emitted when session bandwidth is updated.
|
|
181
|
+
*/
|
|
182
|
+
export const SessionEventUpdateDetails = {
|
|
183
|
+
type: 'sentinel.session.v3.EventUpdateDetails',
|
|
184
|
+
|
|
185
|
+
parse(event) {
|
|
186
|
+
const attrs = parseAttributes(event.attributes);
|
|
187
|
+
return {
|
|
188
|
+
sessionId: BigInt(attrs.session_id || attrs.id || '0'),
|
|
189
|
+
accAddress: attrs.acc_address || '',
|
|
190
|
+
nodeAddress: attrs.node_address || '',
|
|
191
|
+
downloadBytes: attrs.download_bytes || '0',
|
|
192
|
+
uploadBytes: attrs.upload_bytes || '0',
|
|
193
|
+
duration: attrs.duration || '0',
|
|
194
|
+
};
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
is(event) { return event?.type === SessionEventUpdateDetails.type; },
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// ─── Subscription Events ────────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* sentinel.subscription.v3.EventCreate — emitted when a subscription is created.
|
|
204
|
+
*/
|
|
205
|
+
export const SubscriptionEventCreate = {
|
|
206
|
+
type: 'sentinel.subscription.v3.EventCreate',
|
|
207
|
+
|
|
208
|
+
parse(event) {
|
|
209
|
+
const attrs = parseAttributes(event.attributes);
|
|
210
|
+
return {
|
|
211
|
+
subscriptionId: BigInt(attrs.subscription_id || attrs.id || '0'),
|
|
212
|
+
planId: BigInt(attrs.plan_id || '0'),
|
|
213
|
+
accAddress: attrs.acc_address || '',
|
|
214
|
+
price: {
|
|
215
|
+
denom: attrs.price_denom || attrs.denom || '',
|
|
216
|
+
baseValue: attrs.price_base_value || '0',
|
|
217
|
+
quoteValue: attrs.price_quote_value || '0',
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
is(event) { return event?.type === SubscriptionEventCreate.type; },
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* sentinel.subscription.v3.EventCreateSession — emitted when a session starts via subscription.
|
|
227
|
+
*/
|
|
228
|
+
export const SubscriptionEventCreateSession = {
|
|
229
|
+
type: 'sentinel.subscription.v3.EventCreateSession',
|
|
230
|
+
|
|
231
|
+
parse(event) {
|
|
232
|
+
const attrs = parseAttributes(event.attributes);
|
|
233
|
+
return {
|
|
234
|
+
sessionId: BigInt(attrs.session_id || attrs.id || '0'),
|
|
235
|
+
subscriptionId: BigInt(attrs.subscription_id || '0'),
|
|
236
|
+
accAddress: attrs.acc_address || '',
|
|
237
|
+
nodeAddress: attrs.node_address || '',
|
|
238
|
+
};
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
is(event) { return event?.type === SubscriptionEventCreateSession.type; },
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* sentinel.subscription.v3.EventPay — emitted when subscription payment processed.
|
|
246
|
+
*/
|
|
247
|
+
export const SubscriptionEventPay = {
|
|
248
|
+
type: 'sentinel.subscription.v3.EventPay',
|
|
249
|
+
|
|
250
|
+
parse(event) {
|
|
251
|
+
const attrs = parseAttributes(event.attributes);
|
|
252
|
+
return {
|
|
253
|
+
subscriptionId: BigInt(attrs.subscription_id || attrs.id || '0'),
|
|
254
|
+
planId: BigInt(attrs.plan_id || '0'),
|
|
255
|
+
accAddress: attrs.acc_address || '',
|
|
256
|
+
provAddress: attrs.prov_address || '',
|
|
257
|
+
payment: attrs.payment || '0',
|
|
258
|
+
stakingReward: attrs.staking_reward || '0',
|
|
259
|
+
};
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
is(event) { return event?.type === SubscriptionEventPay.type; },
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* sentinel.subscription.v3.EventEnd — emitted when subscription ends.
|
|
267
|
+
*/
|
|
268
|
+
export const SubscriptionEventEnd = {
|
|
269
|
+
type: 'sentinel.subscription.v3.EventEnd',
|
|
270
|
+
|
|
271
|
+
parse(event) {
|
|
272
|
+
const attrs = parseAttributes(event.attributes);
|
|
273
|
+
return {
|
|
274
|
+
subscriptionId: BigInt(attrs.subscription_id || attrs.id || '0'),
|
|
275
|
+
planId: BigInt(attrs.plan_id || '0'),
|
|
276
|
+
accAddress: attrs.acc_address || '',
|
|
277
|
+
};
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
is(event) { return event?.type === SubscriptionEventEnd.type; },
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// ─── Lease Events ───────────────────────────────────────────────────────────
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* sentinel.lease.v1.EventCreate — emitted when a lease starts.
|
|
287
|
+
*/
|
|
288
|
+
export const LeaseEventCreate = {
|
|
289
|
+
type: 'sentinel.lease.v1.EventCreate',
|
|
290
|
+
|
|
291
|
+
parse(event) {
|
|
292
|
+
const attrs = parseAttributes(event.attributes);
|
|
293
|
+
return {
|
|
294
|
+
leaseId: BigInt(attrs.lease_id || attrs.id || '0'),
|
|
295
|
+
nodeAddress: attrs.node_address || '',
|
|
296
|
+
provAddress: attrs.prov_address || '',
|
|
297
|
+
maxHours: parseInt(attrs.max_hours || '0', 10),
|
|
298
|
+
};
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
is(event) { return event?.type === LeaseEventCreate.type; },
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* sentinel.lease.v1.EventEnd — emitted when a lease ends.
|
|
306
|
+
*/
|
|
307
|
+
export const LeaseEventEnd = {
|
|
308
|
+
type: 'sentinel.lease.v1.EventEnd',
|
|
309
|
+
|
|
310
|
+
parse(event) {
|
|
311
|
+
const attrs = parseAttributes(event.attributes);
|
|
312
|
+
return {
|
|
313
|
+
leaseId: BigInt(attrs.lease_id || attrs.id || '0'),
|
|
314
|
+
nodeAddress: attrs.node_address || '',
|
|
315
|
+
provAddress: attrs.prov_address || '',
|
|
316
|
+
};
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
is(event) { return event?.type === LeaseEventEnd.type; },
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// ─── Utility: Extract Session ID (typed replacement for old extractSessionId) ──
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Extract session ID from a transaction result using typed event parsers.
|
|
326
|
+
* Checks both node.v3.EventCreateSession and subscription.v3.EventCreateSession.
|
|
327
|
+
*
|
|
328
|
+
* @param {{ events?: Array }} txResult - Transaction broadcast result
|
|
329
|
+
* @returns {bigint|null} Session ID or null
|
|
330
|
+
*/
|
|
331
|
+
export function extractSessionIdTyped(txResult) {
|
|
332
|
+
const events = txResult?.events || [];
|
|
333
|
+
|
|
334
|
+
// Try node direct session event
|
|
335
|
+
const nodeEvent = searchEvent(NodeEventCreateSession.type, events);
|
|
336
|
+
if (nodeEvent) {
|
|
337
|
+
const parsed = NodeEventCreateSession.parse(nodeEvent);
|
|
338
|
+
if (parsed.sessionId > 0n) return parsed.sessionId;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Try subscription session event
|
|
342
|
+
const subEvent = searchEvent(SubscriptionEventCreateSession.type, events);
|
|
343
|
+
if (subEvent) {
|
|
344
|
+
const parsed = SubscriptionEventCreateSession.parse(subEvent);
|
|
345
|
+
if (parsed.sessionId > 0n) return parsed.sessionId;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Fallback: scan all events for session_id attribute (handles edge cases)
|
|
349
|
+
for (const event of events) {
|
|
350
|
+
if (/session/i.test(event.type)) {
|
|
351
|
+
const attrs = parseAttributes(event.attributes);
|
|
352
|
+
const id = attrs.session_id || attrs.SessionID || attrs.id;
|
|
353
|
+
if (id) {
|
|
354
|
+
const parsed = BigInt(String(id).replace(/"/g, ''));
|
|
355
|
+
if (parsed > 0n) return parsed;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentinel v3 Node Handshake
|
|
3
|
+
*
|
|
4
|
+
* Handles the v3 node handshake protocol for both WireGuard and V2Ray nodes.
|
|
5
|
+
*
|
|
6
|
+
* v3 handshake request body:
|
|
7
|
+
* - data: base64(JSON.stringify({public_key: "<base64_wg_pubkey>"})) [WG]
|
|
8
|
+
* base64(JSON.stringify({uuid: [byte_array]})) [V2Ray]
|
|
9
|
+
* - id: session ID (uint64 number)
|
|
10
|
+
* - pub_key: "secp256k1:<base64_cosmos_pubkey>"
|
|
11
|
+
* - signature: base64(secp256k1_sign(SHA256(BigEndian8(id) + data_bytes)))
|
|
12
|
+
*
|
|
13
|
+
* Sources verified from:
|
|
14
|
+
* github.com/sentinel-official/dvpn-node development branch (Dec 2025, v8.3.1)
|
|
15
|
+
* github.com/sentinel-official/sentinel-go-sdk main branch
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import https from 'https';
|
|
19
|
+
import net from 'net';
|
|
20
|
+
import axios from 'axios';
|
|
21
|
+
import { Secp256k1, sha256 } from '@cosmjs/crypto';
|
|
22
|
+
import { secp256k1 as nobleSecp } from '@noble/curves/secp256k1.js';
|
|
23
|
+
import { NodeError, ErrorCodes } from '../errors.js';
|
|
24
|
+
|
|
25
|
+
// Legacy fallback — node-connect.js passes TOFU agent; this only used for direct calls
|
|
26
|
+
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
27
|
+
|
|
28
|
+
// ─── IP/CIDR Validation ─────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Validate an IP/CIDR string (e.g. "10.8.0.2/24" or "fd1d::2/128").
|
|
32
|
+
* Returns true if valid IPv4 or IPv6 CIDR, false otherwise.
|
|
33
|
+
*/
|
|
34
|
+
export function validateCIDR(cidr) {
|
|
35
|
+
if (typeof cidr !== 'string') return false;
|
|
36
|
+
const parts = cidr.split('/');
|
|
37
|
+
if (parts.length !== 2) return false;
|
|
38
|
+
const [ip, prefix] = parts;
|
|
39
|
+
const prefixNum = parseInt(prefix, 10);
|
|
40
|
+
if (isNaN(prefixNum) || prefixNum < 0) return false;
|
|
41
|
+
// IPv4
|
|
42
|
+
if (net.isIPv4(ip)) return prefixNum <= 32;
|
|
43
|
+
// IPv6
|
|
44
|
+
if (net.isIPv6(ip)) return prefixNum <= 128;
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Node Status (v3: GET /) ──────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Fetch node info from v3 node API.
|
|
52
|
+
* Returns a normalised object compatible with the rest of the codebase.
|
|
53
|
+
*/
|
|
54
|
+
export async function nodeStatusV3(remoteUrl, agent) {
|
|
55
|
+
const stripped = remoteUrl.replace(/\/+$/, '').trim();
|
|
56
|
+
const url = stripped.startsWith('http') ? stripped : `https://${stripped}`;
|
|
57
|
+
const before = Date.now();
|
|
58
|
+
const res = await axios.get(url + '/', { httpsAgent: agent || httpsAgent, timeout: 12_000 });
|
|
59
|
+
const after = Date.now();
|
|
60
|
+
const r = res.data?.result;
|
|
61
|
+
if (!r) throw new NodeError(ErrorCodes.NODE_OFFLINE, 'No result in node status response', { remoteUrl });
|
|
62
|
+
|
|
63
|
+
// Detect server clock drift from the HTTP Date header.
|
|
64
|
+
// VMess AEAD auth fails if |client_time - server_time| > 120 seconds.
|
|
65
|
+
let clockDriftSec = null;
|
|
66
|
+
const dateHeader = res.headers?.['date'];
|
|
67
|
+
if (dateHeader) {
|
|
68
|
+
const serverTime = new Date(dateHeader).getTime();
|
|
69
|
+
if (!isNaN(serverTime)) {
|
|
70
|
+
const localMidpoint = before + (after - before) / 2;
|
|
71
|
+
clockDriftSec = Math.round((serverTime - localMidpoint) / 1000);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Normalise to match the shape the rest of server.js expects
|
|
76
|
+
return {
|
|
77
|
+
address: r.address || '',
|
|
78
|
+
type: r.service_type === 'wireguard' ? 'wireguard' : 'v2ray',
|
|
79
|
+
moniker: r.moniker || '',
|
|
80
|
+
peers: r.peers || 0,
|
|
81
|
+
bandwidth: {
|
|
82
|
+
// downlink/uplink are bytes/s (string in v3)
|
|
83
|
+
download: parseInt(r.downlink || '0', 10),
|
|
84
|
+
upload: parseInt(r.uplink || '0', 10),
|
|
85
|
+
},
|
|
86
|
+
location: {
|
|
87
|
+
city: r.location?.city || '',
|
|
88
|
+
country: r.location?.country || '',
|
|
89
|
+
country_code: r.location?.country_code || '',
|
|
90
|
+
latitude: r.location?.latitude || 0,
|
|
91
|
+
longitude: r.location?.longitude || 0,
|
|
92
|
+
},
|
|
93
|
+
qos: { max_peers: r.qos?.max_peers || null },
|
|
94
|
+
clockDriftSec,
|
|
95
|
+
gigabyte_prices: [], // not in v3 status; fetched from LCD
|
|
96
|
+
_raw: r,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ─── Handshake Signing (shared between WG and V2Ray) ────────────────────────
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Build signed handshake body for v3 node protocol.
|
|
104
|
+
* @param {Buffer} dataBytes - Peer request JSON bytes
|
|
105
|
+
* @param {bigint} sessionId - Session ID (uint64)
|
|
106
|
+
* @param {Buffer} cosmosPrivKey - Raw secp256k1 private key (32 bytes)
|
|
107
|
+
* @returns {{ body: object }} - Signed request body ready for POST
|
|
108
|
+
*/
|
|
109
|
+
async function buildSignedBody(dataBytes, sessionId, cosmosPrivKey) {
|
|
110
|
+
// Build message: BigEndian uint64 (8 bytes) ++ data
|
|
111
|
+
const idBuf = Buffer.alloc(8);
|
|
112
|
+
idBuf.writeBigUInt64BE(BigInt(sessionId));
|
|
113
|
+
const msg = Buffer.concat([idBuf, dataBytes]);
|
|
114
|
+
|
|
115
|
+
// Sign: SHA256(msg) → secp256k1 compact 64-byte sig (r+s, no recovery byte) → base64
|
|
116
|
+
// IMPORTANT: Go's VerifySignature requires EXACTLY 64 bytes (len != 64 → false)
|
|
117
|
+
const msgHash = sha256(msg);
|
|
118
|
+
const sig = await Secp256k1.createSignature(msgHash, cosmosPrivKey);
|
|
119
|
+
// toFixedLength() returns 65 bytes (r+s+recovery) — take only first 64 (r+s)
|
|
120
|
+
const sigBytes = Buffer.from(sig.toFixedLength()).slice(0, 64);
|
|
121
|
+
const signature = sigBytes.toString('base64');
|
|
122
|
+
|
|
123
|
+
// Encode Cosmos public key (compressed, 33 bytes): "secp256k1:<base64>"
|
|
124
|
+
const compressedPubKey = nobleSecp.getPublicKey(cosmosPrivKey, true);
|
|
125
|
+
const pubKeyEncoded = 'secp256k1:' + Buffer.from(compressedPubKey).toString('base64');
|
|
126
|
+
|
|
127
|
+
const idNum = Number(sessionId);
|
|
128
|
+
if (!Number.isSafeInteger(idNum)) {
|
|
129
|
+
throw new NodeError(ErrorCodes.INVALID_OPTIONS, `Session ID ${sessionId} exceeds safe integer range (max ${Number.MAX_SAFE_INTEGER})`, { sessionId });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
body: {
|
|
134
|
+
data: dataBytes.toString('base64'),
|
|
135
|
+
id: idNum,
|
|
136
|
+
pub_key: pubKeyEncoded,
|
|
137
|
+
signature,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ─── Chain-Lag Retry POST ───────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* POST to node with chain-lag retry logic.
|
|
146
|
+
* After MsgStartSession, the node may not see the session on-chain yet.
|
|
147
|
+
* If the node returns "does not exist" or HTTP 404 code 5, wait and retry once.
|
|
148
|
+
*/
|
|
149
|
+
async function postWithChainLagRetry(url, body, agent, label = 'Node') {
|
|
150
|
+
const doPost = async () => {
|
|
151
|
+
let res;
|
|
152
|
+
try {
|
|
153
|
+
res = await axios.post(url, body, {
|
|
154
|
+
httpsAgent: agent || httpsAgent,
|
|
155
|
+
timeout: 90_000,
|
|
156
|
+
headers: { 'Content-Type': 'application/json' },
|
|
157
|
+
});
|
|
158
|
+
} catch (err) {
|
|
159
|
+
// 409 "session already exists" — node already has our session and peer data.
|
|
160
|
+
// Extract the config from the error response instead of wasting tokens on a new session.
|
|
161
|
+
if (err.response?.status === 409 && err.response?.data?.result?.data) {
|
|
162
|
+
return err.response; // Treat as success — node accepted our session
|
|
163
|
+
}
|
|
164
|
+
const errData = err.response?.data;
|
|
165
|
+
const code = err.code || '';
|
|
166
|
+
const status = err.response?.status;
|
|
167
|
+
const detail = errData ? JSON.stringify(errData) : err.message;
|
|
168
|
+
const bodyStr = typeof errData === 'string' ? errData : JSON.stringify(errData || '');
|
|
169
|
+
// Detect corrupted node database (HTTP 500 with sqlite errors)
|
|
170
|
+
if (status === 500 && (bodyStr.includes('no such table') || bodyStr.includes('database is locked') || bodyStr.includes('disk I/O error'))) {
|
|
171
|
+
throw new NodeError('NODE_DATABASE_CORRUPT', `Node ${url} has a corrupted database: ${bodyStr.substring(0, 200)}`, { remoteUrl: url, status });
|
|
172
|
+
}
|
|
173
|
+
const isChainLag = bodyStr.includes('does not exist') ||
|
|
174
|
+
(status === 404 && errData?.code === 5);
|
|
175
|
+
if (isChainLag) return { _chainLag: true, detail };
|
|
176
|
+
throw new NodeError(ErrorCodes.NODE_OFFLINE, `${label} handshake failed (HTTP ${status}${code ? ', ' + code : ''}): ${detail}`, { status, code });
|
|
177
|
+
}
|
|
178
|
+
return res;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
let res = await doPost();
|
|
182
|
+
|
|
183
|
+
// Retry once on chain lag
|
|
184
|
+
if (res?._chainLag) {
|
|
185
|
+
console.log('Session not yet visible on node — waiting 10s for chain propagation...');
|
|
186
|
+
await new Promise(r => setTimeout(r, 10_000));
|
|
187
|
+
res = await doPost();
|
|
188
|
+
if (res?._chainLag) {
|
|
189
|
+
throw new NodeError(ErrorCodes.NODE_OFFLINE, `${label} handshake failed: session does not exist on node after retry (chain propagation delay). Detail: ${res.detail}`, { chainLag: true });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return res;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ─── v3 WireGuard Handshake (POST /) ────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Perform v3 node handshake for WireGuard.
|
|
200
|
+
* @param {string} remoteUrl - Node's HTTPS base URL
|
|
201
|
+
* @param {bigint} sessionId - Session ID (uint64)
|
|
202
|
+
* @param {Buffer} cosmosPrivKey - Raw secp256k1 private key bytes (32 bytes)
|
|
203
|
+
* @param {Buffer} wgPublicKey - WireGuard public key (32 bytes)
|
|
204
|
+
* @returns {{ assignedAddrs: string[], serverPubKey: string, serverEndpoints: string[] }}
|
|
205
|
+
*/
|
|
206
|
+
export async function initHandshakeV3(remoteUrl, sessionId, cosmosPrivKey, wgPublicKey, agent) {
|
|
207
|
+
// 1. Build peer request data
|
|
208
|
+
const peerRequest = { public_key: wgPublicKey.toString('base64') };
|
|
209
|
+
const dataBytes = Buffer.from(JSON.stringify(peerRequest));
|
|
210
|
+
|
|
211
|
+
// 2. Sign and build body
|
|
212
|
+
const { body } = await buildSignedBody(dataBytes, sessionId, cosmosPrivKey);
|
|
213
|
+
|
|
214
|
+
// 3. POST with chain-lag retry
|
|
215
|
+
const url = remoteUrl.replace(/\/+$/, '') + '/';
|
|
216
|
+
const res = await postWithChainLagRetry(url, body, agent, 'Node');
|
|
217
|
+
|
|
218
|
+
const result = res.data?.result;
|
|
219
|
+
if (!result) {
|
|
220
|
+
const errInfo = res.data?.error;
|
|
221
|
+
throw new NodeError(ErrorCodes.NODE_OFFLINE, `Node handshake error: ${JSON.stringify(errInfo || res.data)}`, { response: errInfo || res.data });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 4. Parse AddPeerResponse from result.data (base64-encoded JSON bytes)
|
|
225
|
+
const addPeerData = Buffer.from(result.data, 'base64').toString('utf8');
|
|
226
|
+
const addPeerResp = JSON.parse(addPeerData);
|
|
227
|
+
|
|
228
|
+
// result.addrs = node's WireGuard listening addresses (["IP:PORT", ...])
|
|
229
|
+
// addPeerResp.addrs = our assigned IPs (["10.x.x.x/24", ...])
|
|
230
|
+
// addPeerResp.metadata = [{port, public_key}, ...]
|
|
231
|
+
|
|
232
|
+
const metadata = (addPeerResp.metadata || [])[0] || {};
|
|
233
|
+
const serverPubKeyBase64 = metadata.public_key || '';
|
|
234
|
+
const serverPort = parseInt(metadata.port, 10) || 51820;
|
|
235
|
+
|
|
236
|
+
// Validate handshake response — garbage data from node → clear error instead of opaque WG failure
|
|
237
|
+
if (!serverPubKeyBase64) throw new NodeError(ErrorCodes.NODE_OFFLINE, 'Handshake failed: node returned empty WireGuard public key');
|
|
238
|
+
if (serverPort < 1 || serverPort > 65535) throw new NodeError(ErrorCodes.NODE_OFFLINE, `Handshake failed: invalid port ${serverPort} from node`, { serverPort });
|
|
239
|
+
|
|
240
|
+
const assignedAddrs = addPeerResp.addrs || [];
|
|
241
|
+
if (assignedAddrs.length === 0) throw new NodeError(ErrorCodes.NODE_OFFLINE, 'Handshake failed: node returned no assigned addresses');
|
|
242
|
+
for (const addr of assignedAddrs) {
|
|
243
|
+
if (!validateCIDR(addr)) {
|
|
244
|
+
throw new NodeError(ErrorCodes.INVALID_ASSIGNED_IP, `Handshake failed: node returned invalid IP/CIDR "${addr}"`, { assignedAddrs });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Node's WireGuard endpoint: use first entry of result.addrs
|
|
249
|
+
// If it doesn't include a port, append the metadata port
|
|
250
|
+
const rawEndpoint = (result.addrs || [])[0] || '';
|
|
251
|
+
if (!rawEndpoint) throw new NodeError(ErrorCodes.NODE_OFFLINE, 'Handshake failed: node returned no WireGuard endpoint addresses');
|
|
252
|
+
const serverEndpoint = rawEndpoint.includes(':')
|
|
253
|
+
? rawEndpoint
|
|
254
|
+
: `${rawEndpoint}:${serverPort}`;
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
assignedAddrs, // our IPs e.g. ["10.8.0.2/24"]
|
|
258
|
+
serverPubKey: serverPubKeyBase64, // server WG pub key (base64)
|
|
259
|
+
serverEndpoint, // "IP:PORT" for WireGuard Endpoint
|
|
260
|
+
serverEndpoints: result.addrs || [],
|
|
261
|
+
rawAddPeerResp: addPeerResp,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ─── v3 V2Ray Handshake (POST /) ────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Perform v3 V2Ray handshake.
|
|
269
|
+
* Returns the V2Ray client config (JSON string in result.data).
|
|
270
|
+
*/
|
|
271
|
+
export async function initHandshakeV3V2Ray(remoteUrl, sessionId, cosmosPrivKey, uuid, agent) {
|
|
272
|
+
const hex = uuid.replace(/-/g, '');
|
|
273
|
+
const uuidBytes = [];
|
|
274
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
275
|
+
uuidBytes.push(parseInt(hex.substring(i, i + 2), 16));
|
|
276
|
+
}
|
|
277
|
+
const peerRequest = { uuid: uuidBytes };
|
|
278
|
+
const dataBytes = Buffer.from(JSON.stringify(peerRequest));
|
|
279
|
+
|
|
280
|
+
const { body } = await buildSignedBody(dataBytes, sessionId, cosmosPrivKey);
|
|
281
|
+
|
|
282
|
+
const url = remoteUrl.replace(/\/+$/, '') + '/';
|
|
283
|
+
const res = await postWithChainLagRetry(url, body, agent, 'V2Ray');
|
|
284
|
+
|
|
285
|
+
const result = res.data?.result;
|
|
286
|
+
if (!result) {
|
|
287
|
+
throw new NodeError(ErrorCodes.NODE_OFFLINE, `V2Ray handshake error: ${JSON.stringify(res.data?.error || res.data)}`, { response: res.data?.error || res.data });
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// result.data is base64-encoded V2Ray client config JSON
|
|
291
|
+
const v2rayConfig = Buffer.from(result.data, 'base64').toString('utf8');
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
config: v2rayConfig,
|
|
295
|
+
serverEndpoints: result.addrs || [],
|
|
296
|
+
};
|
|
297
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Protocol — Handshakes, Config Builders, Protobuf Encoders
|
|
2
|
+
export {
|
|
3
|
+
nodeStatusV3, generateWgKeyPair, initHandshakeV3, initHandshakeV3V2Ray,
|
|
4
|
+
writeWgConfig, buildV2RayClientConfig,
|
|
5
|
+
generateV2RayUUID, extractSessionId, waitForPort,
|
|
6
|
+
encodePrice, encodeMsgStartSession, encodeMsgStartSubscription, encodeMsgSubStartSession,
|
|
7
|
+
encodeVarint, protoString, protoInt64, protoEmbedded, decToScaledInt,
|
|
8
|
+
} from './v3.js';
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
encodeMsgRegisterProvider, encodeMsgUpdateProviderDetails, encodeMsgUpdateProviderStatus,
|
|
12
|
+
encodeMsgCreatePlan, encodeMsgUpdatePlanStatus, encodeMsgLinkNode, encodeMsgUnlinkNode,
|
|
13
|
+
encodeMsgPlanStartSession, encodeMsgStartLease, encodeMsgEndLease,
|
|
14
|
+
encodeDuration, protoUint64, protoBool,
|
|
15
|
+
} from './plans.js';
|