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,208 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* TEST EVERY CONNECTION TYPE
|
|
4
|
+
*
|
|
5
|
+
* 6 connection combinations:
|
|
6
|
+
* 1. WireGuard + per-GB + direct
|
|
7
|
+
* 2. WireGuard + per-Hour + direct
|
|
8
|
+
* 3. V2Ray + per-GB + direct
|
|
9
|
+
* 4. V2Ray + per-Hour + direct
|
|
10
|
+
* 5. WireGuard + plan + fee-granted
|
|
11
|
+
* 6. V2Ray + plan + fee-granted
|
|
12
|
+
*
|
|
13
|
+
* Plus: session cancel for each
|
|
14
|
+
*/
|
|
15
|
+
import 'dotenv/config';
|
|
16
|
+
const mnemonic = process.env.MNEMONIC;
|
|
17
|
+
if (!mnemonic) { console.error('Set MNEMONIC in .env'); process.exit(1); }
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
createWallet, generateWallet, createClient, broadcast,
|
|
21
|
+
fetchAllNodes, connectDirect, connectViaPlan, disconnect,
|
|
22
|
+
subscribeToPlan, sendTokens, buildFeeGrantMsg,
|
|
23
|
+
registerCleanupHandlers, nodeStatusV3, createNodeHttpsAgent,
|
|
24
|
+
DEFAULT_RPC, MSG_TYPES,
|
|
25
|
+
} from './index.js';
|
|
26
|
+
import { buildEndSessionMsg, sentToSentprov, extractId } from './cosmjs-setup.js';
|
|
27
|
+
|
|
28
|
+
registerCleanupHandlers();
|
|
29
|
+
|
|
30
|
+
const R = { pass: 0, fail: 0, errors: [] };
|
|
31
|
+
async function t(name, fn) {
|
|
32
|
+
try {
|
|
33
|
+
const r = await fn();
|
|
34
|
+
if (r) { R.pass++; console.log(' ✓', name); return r; }
|
|
35
|
+
else { R.fail++; R.errors.push(name); console.log(' ✗', name); return null; }
|
|
36
|
+
} catch (e) {
|
|
37
|
+
R.fail++; R.errors.push(name + ': ' + e.message?.slice(0, 120));
|
|
38
|
+
console.log(' ✗', name, '→', e.message?.slice(0, 120));
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log('═══════════════════════════════════════════');
|
|
44
|
+
console.log(' EVERY CONNECTION TYPE — Live Mainnet');
|
|
45
|
+
console.log('═══════════════════════════════════════════\n');
|
|
46
|
+
|
|
47
|
+
const { wallet: opW, account: opA } = await createWallet(mnemonic);
|
|
48
|
+
const opC = await createClient(DEFAULT_RPC, opW);
|
|
49
|
+
const provAddr = sentToSentprov(opA.address);
|
|
50
|
+
|
|
51
|
+
// Find one WG and one V2Ray node
|
|
52
|
+
console.log('Finding nodes...');
|
|
53
|
+
const allNodes = await fetchAllNodes();
|
|
54
|
+
let wgNode = null, v2Node = null;
|
|
55
|
+
|
|
56
|
+
for (const n of allNodes.slice(0, 60)) {
|
|
57
|
+
if (wgNode && v2Node) break;
|
|
58
|
+
try {
|
|
59
|
+
const url = 'https://' + (n.remote_addrs?.[0] || '');
|
|
60
|
+
if (!url || url === 'https://') continue;
|
|
61
|
+
const agent = createNodeHttpsAgent(n.address, 'tofu');
|
|
62
|
+
const s = await nodeStatusV3(url, agent);
|
|
63
|
+
if (!s.address || s.address === n.address) {
|
|
64
|
+
if (s.type === 'wireguard' && !wgNode) {
|
|
65
|
+
wgNode = n; console.log(' WG:', n.address, '-', s.moniker);
|
|
66
|
+
}
|
|
67
|
+
if (s.type === 'v2ray' && !v2Node) {
|
|
68
|
+
v2Node = n; console.log(' V2:', n.address, '-', s.moniker);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch {}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!wgNode) { console.log('No WG node found!'); process.exit(1); }
|
|
75
|
+
if (!v2Node) { console.log('No V2Ray node found!'); process.exit(1); }
|
|
76
|
+
|
|
77
|
+
const V2RAY = process.env.V2RAY_PATH || undefined; // SDK auto-detects
|
|
78
|
+
const baseOpts = { fullTunnel: false, dns: 'handshake', v2rayExePath: V2RAY,
|
|
79
|
+
onProgress: (s, d) => console.log(' [' + s + ']', d) };
|
|
80
|
+
|
|
81
|
+
// ═══ 1. WireGuard per-GB ═══
|
|
82
|
+
console.log('\n═══ 1. WireGuard + per-GB + Direct ═══');
|
|
83
|
+
await t('WG-GB', async () => {
|
|
84
|
+
const r = await connectDirect({ ...baseOpts, mnemonic, nodeAddress: wgNode.address, gigabytes: 1 });
|
|
85
|
+
console.log(' Session:', r.sessionId, '| Type:', r.serviceType);
|
|
86
|
+
await disconnect();
|
|
87
|
+
return r.serviceType === 'wireguard';
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// ═══ 2. WireGuard per-Hour ═══
|
|
91
|
+
console.log('\n═══ 2. WireGuard + per-Hour + Direct ═══');
|
|
92
|
+
await t('WG-HOUR', async () => {
|
|
93
|
+
const r = await connectDirect({ ...baseOpts, mnemonic, nodeAddress: wgNode.address, hours: 1 });
|
|
94
|
+
console.log(' Session:', r.sessionId, '| Type:', r.serviceType, '(hourly)');
|
|
95
|
+
await disconnect();
|
|
96
|
+
return r.serviceType === 'wireguard';
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// ═══ 3. V2Ray per-GB ═══
|
|
100
|
+
console.log('\n═══ 3. V2Ray + per-GB + Direct ═══');
|
|
101
|
+
await t('V2-GB', async () => {
|
|
102
|
+
const r = await connectDirect({ ...baseOpts, mnemonic, nodeAddress: v2Node.address, gigabytes: 1 });
|
|
103
|
+
console.log(' Session:', r.sessionId, '| Type:', r.serviceType, '| SOCKS:', r.socksPort);
|
|
104
|
+
await disconnect();
|
|
105
|
+
return r.serviceType === 'v2ray';
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// ═══ 4. V2Ray per-Hour ═══
|
|
109
|
+
console.log('\n═══ 4. V2Ray + per-Hour + Direct ═══');
|
|
110
|
+
await t('V2-HOUR', async () => {
|
|
111
|
+
const r = await connectDirect({ ...baseOpts, mnemonic, nodeAddress: v2Node.address, hours: 1 });
|
|
112
|
+
console.log(' Session:', r.sessionId, '| Type:', r.serviceType, '| SOCKS:', r.socksPort, '(hourly)');
|
|
113
|
+
await disconnect();
|
|
114
|
+
return r.serviceType === 'v2ray';
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// ═══ 5. WireGuard + Plan + Fee Grant ═══
|
|
118
|
+
console.log('\n═══ 5. WireGuard + Plan + Fee-Granted ═══');
|
|
119
|
+
|
|
120
|
+
// Recreate operator client to reset sequence cache after direct connection TXs
|
|
121
|
+
const opC2 = await createClient(DEFAULT_RPC, opW);
|
|
122
|
+
|
|
123
|
+
// Create plan with WG node
|
|
124
|
+
console.log(' Setting up plan...');
|
|
125
|
+
const planMsg = { typeUrl: MSG_TYPES.CREATE_PLAN, value: { from: provAddr, bytes: '100000000', duration: { seconds: 3600 }, prices: [{ denom: 'udvpn', base_value: '0.000000100000000000', quote_value: '100000' }] } };
|
|
126
|
+
const planR = await broadcast(opC2 || opC, opA.address, [planMsg]);
|
|
127
|
+
const wgPlanId = extractId(planR, /plan/i, ['plan_id', 'id']);
|
|
128
|
+
console.log(' Plan:', wgPlanId);
|
|
129
|
+
await new Promise(r => setTimeout(r, 7000));
|
|
130
|
+
|
|
131
|
+
// Activate + lease + link WG node
|
|
132
|
+
await broadcast(opC2 || opC, opA.address, [{ typeUrl: MSG_TYPES.UPDATE_PLAN_STATUS, value: { from: provAddr, id: parseInt(wgPlanId), status: 1 } }]);
|
|
133
|
+
await new Promise(r => setTimeout(r, 7000));
|
|
134
|
+
const wgHrPrice = wgNode.hourly_prices?.find(p => p.denom === 'udvpn');
|
|
135
|
+
if (wgHrPrice) {
|
|
136
|
+
try { await broadcast(opC2 || opC, opA.address, [{ typeUrl: MSG_TYPES.START_LEASE, value: { from: provAddr, nodeAddress: wgNode.address, hours: 1, maxPrice: wgHrPrice, renewalPricePolicy: 0 } }]); } catch {}
|
|
137
|
+
await new Promise(r => setTimeout(r, 7000));
|
|
138
|
+
}
|
|
139
|
+
try { await broadcast(opC2 || opC, opA.address, [{ typeUrl: MSG_TYPES.LINK_NODE, value: { from: provAddr, id: parseInt(wgPlanId), nodeAddress: wgNode.address } }]); } catch {}
|
|
140
|
+
await new Promise(r => setTimeout(r, 7000));
|
|
141
|
+
|
|
142
|
+
// Create user + fund + subscribe + grant
|
|
143
|
+
const { mnemonic: userMn1, account: userA1 } = await generateWallet();
|
|
144
|
+
await sendTokens(opC2 || opC, opA.address, userA1.address, '3000000', 'udvpn');
|
|
145
|
+
await new Promise(r => setTimeout(r, 8000));
|
|
146
|
+
const { wallet: uW1 } = await createWallet(userMn1);
|
|
147
|
+
const uC1 = await createClient(DEFAULT_RPC, uW1);
|
|
148
|
+
await subscribeToPlan(uC1, userA1.address, wgPlanId);
|
|
149
|
+
await new Promise(r => setTimeout(r, 7000));
|
|
150
|
+
await broadcast(opC2 || opC, opA.address, [buildFeeGrantMsg(opA.address, userA1.address, { spendLimit: 5_000_000 })]);
|
|
151
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
152
|
+
|
|
153
|
+
await t('WG-PLAN', async () => {
|
|
154
|
+
const r = await connectViaPlan({ ...baseOpts, mnemonic: userMn1, planId: parseInt(wgPlanId), nodeAddress: wgNode.address, feeGranter: opA.address });
|
|
155
|
+
console.log(' Session:', r.sessionId, '| Type:', r.serviceType, '(plan, fee-granted)');
|
|
156
|
+
await disconnect();
|
|
157
|
+
return r.serviceType === 'wireguard';
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// ═══ 6. V2Ray + Plan + Fee Grant ═══
|
|
161
|
+
console.log('\n═══ 6. V2Ray + Plan + Fee-Granted ═══');
|
|
162
|
+
|
|
163
|
+
// Create plan with V2Ray node
|
|
164
|
+
await new Promise(r => setTimeout(r, 7000));
|
|
165
|
+
const planMsg2 = { typeUrl: MSG_TYPES.CREATE_PLAN, value: { from: provAddr, bytes: '100000000', duration: { seconds: 3600 }, prices: [{ denom: 'udvpn', base_value: '0.000000100000000000', quote_value: '100000' }] } };
|
|
166
|
+
const planR2 = await broadcast(opC2 || opC, opA.address, [planMsg2]);
|
|
167
|
+
const v2PlanId = extractId(planR2, /plan/i, ['plan_id', 'id']);
|
|
168
|
+
console.log(' Plan:', v2PlanId);
|
|
169
|
+
await new Promise(r => setTimeout(r, 7000));
|
|
170
|
+
|
|
171
|
+
await broadcast(opC2 || opC, opA.address, [{ typeUrl: MSG_TYPES.UPDATE_PLAN_STATUS, value: { from: provAddr, id: parseInt(v2PlanId), status: 1 } }]);
|
|
172
|
+
await new Promise(r => setTimeout(r, 7000));
|
|
173
|
+
// Lease V2Ray node first (required before linking)
|
|
174
|
+
const v2HrPrice = v2Node.hourly_prices?.find(p => p.denom === 'udvpn');
|
|
175
|
+
if (v2HrPrice) {
|
|
176
|
+
try { await broadcast(opC2 || opC, opA.address, [{ typeUrl: MSG_TYPES.START_LEASE, value: { from: provAddr, nodeAddress: v2Node.address, hours: 1, maxPrice: v2HrPrice, renewalPricePolicy: 0 } }]); console.log(' V2Ray node leased'); } catch (e) { console.log(' Lease:', e.message?.includes('already') ? 'active' : e.message?.slice(0, 60)); }
|
|
177
|
+
await new Promise(r => setTimeout(r, 7000));
|
|
178
|
+
}
|
|
179
|
+
// Link V2Ray node to plan
|
|
180
|
+
try { await broadcast(opC2 || opC, opA.address, [{ typeUrl: MSG_TYPES.LINK_NODE, value: { from: provAddr, id: parseInt(v2PlanId), nodeAddress: v2Node.address } }]); console.log(' V2Ray node linked'); } catch (e) { console.log(' Link:', e.message?.includes('duplicate') ? 'already linked' : e.message?.slice(0, 60)); }
|
|
181
|
+
await new Promise(r => setTimeout(r, 7000));
|
|
182
|
+
|
|
183
|
+
const { mnemonic: userMn2, account: userA2 } = await generateWallet();
|
|
184
|
+
await sendTokens(opC2 || opC, opA.address, userA2.address, '3000000', 'udvpn');
|
|
185
|
+
await new Promise(r => setTimeout(r, 8000));
|
|
186
|
+
const { wallet: uW2 } = await createWallet(userMn2);
|
|
187
|
+
const uC2 = await createClient(DEFAULT_RPC, uW2);
|
|
188
|
+
await subscribeToPlan(uC2, userA2.address, v2PlanId);
|
|
189
|
+
await new Promise(r => setTimeout(r, 7000));
|
|
190
|
+
await broadcast(opC2 || opC, opA.address, [buildFeeGrantMsg(opA.address, userA2.address, { spendLimit: 5_000_000 })]);
|
|
191
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
192
|
+
|
|
193
|
+
await t('V2-PLAN', async () => {
|
|
194
|
+
const r = await connectViaPlan({ ...baseOpts, mnemonic: userMn2, planId: parseInt(v2PlanId), nodeAddress: v2Node.address, feeGranter: opA.address });
|
|
195
|
+
console.log(' Session:', r.sessionId, '| Type:', r.serviceType, '| SOCKS:', r.socksPort, '(plan, fee-granted)');
|
|
196
|
+
await disconnect();
|
|
197
|
+
return r.serviceType === 'v2ray';
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// ═══ RESULTS ═══
|
|
201
|
+
console.log('\n═══════════════════════════════════════════');
|
|
202
|
+
console.log(' RESULTS:', R.pass, '/', (R.pass + R.fail), 'passed');
|
|
203
|
+
if (R.errors.length > 0) {
|
|
204
|
+
console.log('\n FAILURES:');
|
|
205
|
+
for (const e of R.errors) console.log(' ✗', e);
|
|
206
|
+
}
|
|
207
|
+
console.log('═══════════════════════════════════════════');
|
|
208
|
+
process.exit(R.fail > 0 ? 1 : 0);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Test: Fee-granted plan connection (the step that failed before).
|
|
4
|
+
* Uses existing Plan #44 with linked node.
|
|
5
|
+
* Creates new user, funds, subscribes, grants, connects — all fee-granted.
|
|
6
|
+
*/
|
|
7
|
+
import 'dotenv/config';
|
|
8
|
+
const opMnemonic = process.env.MNEMONIC;
|
|
9
|
+
if (!opMnemonic) { console.error('Set MNEMONIC in .env'); process.exit(1); }
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
createWallet, generateWallet, getBalance, formatP2P, sendTokens,
|
|
13
|
+
createClient, broadcast, broadcastWithFeeGrant,
|
|
14
|
+
subscribeToPlan, hasActiveSubscription, queryFeeGrants,
|
|
15
|
+
buildFeeGrantMsg, connectViaPlan, disconnect,
|
|
16
|
+
registerCleanupHandlers, DEFAULT_RPC, LCD_ENDPOINTS, MSG_TYPES,
|
|
17
|
+
} from './index.js';
|
|
18
|
+
|
|
19
|
+
registerCleanupHandlers();
|
|
20
|
+
const PLAN_ID = 44;
|
|
21
|
+
const NODE = 'sentnode1qqywpumwtxxgffqqr9eg94w72tlragzjg0zxs4';
|
|
22
|
+
|
|
23
|
+
console.log('═══ FEE GRANT CONNECT TEST ═══\n');
|
|
24
|
+
|
|
25
|
+
// 1. Operator setup
|
|
26
|
+
const { wallet: opW, account: opA } = await createWallet(opMnemonic);
|
|
27
|
+
const opC = await createClient(DEFAULT_RPC, opW);
|
|
28
|
+
console.log('Operator:', opA.address);
|
|
29
|
+
|
|
30
|
+
// 2. New user
|
|
31
|
+
const { mnemonic: userMnemonic, account: userA } = await generateWallet();
|
|
32
|
+
console.log('User:', userA.address);
|
|
33
|
+
|
|
34
|
+
// 3. Fund user with 3 P2P (subscription=1P2P + gas for sub TX ~0.3P2P + buffer)
|
|
35
|
+
console.log('\nFunding user with 3 P2P...');
|
|
36
|
+
const sendResult = await sendTokens(opC, opA.address, userA.address, '3000000', 'udvpn');
|
|
37
|
+
console.log(' TX:', sendResult.transactionHash, 'Code:', sendResult.code);
|
|
38
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
39
|
+
|
|
40
|
+
// 4. Subscribe user to plan
|
|
41
|
+
console.log('\nSubscribing to plan #' + PLAN_ID + '...');
|
|
42
|
+
const { wallet: uW } = await createWallet(userMnemonic);
|
|
43
|
+
const uC = await createClient(DEFAULT_RPC, uW);
|
|
44
|
+
const subResult = await subscribeToPlan(uC, userA.address, PLAN_ID);
|
|
45
|
+
console.log(' Subscription:', subResult.subscriptionId, 'TX:', subResult.txHash);
|
|
46
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
47
|
+
|
|
48
|
+
// 5. Verify subscription
|
|
49
|
+
const hasSub = await hasActiveSubscription(userA.address, PLAN_ID);
|
|
50
|
+
console.log(' Has subscription:', hasSub.has);
|
|
51
|
+
|
|
52
|
+
// 6. Fee grant from operator to user
|
|
53
|
+
console.log('\nIssuing fee grant...');
|
|
54
|
+
const grantMsg = buildFeeGrantMsg(opA.address, userA.address, { spendLimit: 5_000_000 });
|
|
55
|
+
const grantResult = await broadcast(opC, opA.address, [grantMsg]);
|
|
56
|
+
console.log(' TX:', grantResult.transactionHash, 'Code:', grantResult.code);
|
|
57
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
58
|
+
|
|
59
|
+
// 7. Verify fee grant
|
|
60
|
+
const lcd = LCD_ENDPOINTS[0]?.url || LCD_ENDPOINTS[0];
|
|
61
|
+
const grants = await queryFeeGrants(lcd, userA.address);
|
|
62
|
+
console.log(' Grants:', grants.length, grants.length > 0 ? 'from ' + grants[0].granter : '');
|
|
63
|
+
|
|
64
|
+
// 8. User balance check
|
|
65
|
+
const uBal = await getBalance(uC, userA.address);
|
|
66
|
+
console.log('\nUser balance before connect:', formatP2P(uBal.udvpn));
|
|
67
|
+
|
|
68
|
+
// 9. Connect via plan with fee grant
|
|
69
|
+
console.log('\nConnecting via plan with fee grant...');
|
|
70
|
+
try {
|
|
71
|
+
const conn = await connectViaPlan({
|
|
72
|
+
mnemonic: userMnemonic,
|
|
73
|
+
planId: PLAN_ID,
|
|
74
|
+
nodeAddress: NODE,
|
|
75
|
+
feeGranter: opA.address,
|
|
76
|
+
fullTunnel: false,
|
|
77
|
+
dns: 'handshake',
|
|
78
|
+
v2rayExePath: process.env.V2RAY_PATH || undefined, // SDK auto-detects
|
|
79
|
+
onProgress: (step, detail) => console.log(' [' + step + ']', detail),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
console.log('\n✓ CONNECTED!');
|
|
83
|
+
console.log(' Session:', conn.sessionId);
|
|
84
|
+
console.log(' Type:', conn.serviceType);
|
|
85
|
+
|
|
86
|
+
// Check user balance after (should be same — gas was free)
|
|
87
|
+
const uBalAfter = await getBalance(uC, userA.address);
|
|
88
|
+
console.log(' User balance after connect:', formatP2P(uBalAfter.udvpn));
|
|
89
|
+
console.log(' Gas cost to user:', formatP2P(uBal.udvpn - uBalAfter.udvpn));
|
|
90
|
+
|
|
91
|
+
await disconnect();
|
|
92
|
+
console.log(' Disconnected');
|
|
93
|
+
console.log('\n═══ TEST PASSED ═══');
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.error('\n✗ CONNECT FAILED:', e.message);
|
|
96
|
+
console.log('\n═══ TEST FAILED ═══');
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
package/test-logic.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Pure logic tests — matches Go's 45 tests exactly.
|
|
4
|
+
* NO chain calls, NO network, NO tokens. Instant and safe.
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
formatP2P, formatPriceP2P, formatNodePricing, estimateSessionPrice,
|
|
8
|
+
formatBytes, formatUptime, shortAddress, computeSessionAllocation,
|
|
9
|
+
countryNameToCode, getFlagUrl, getFlagEmoji,
|
|
10
|
+
GB_OPTIONS, HOUR_OPTIONS,
|
|
11
|
+
DNS_PRESETS, DEFAULT_DNS_PRESET, DNS_FALLBACK_ORDER, resolveDnsServers,
|
|
12
|
+
ErrorCodes, ERROR_SEVERITY, isRetryable, userMessage,
|
|
13
|
+
APP_TYPES, APP_TYPE_CONFIG, validateAppConfig,
|
|
14
|
+
cached, cacheInvalidate, diskSave, diskLoad,
|
|
15
|
+
trackSession, getSessionMode, loadAppSettings, APP_SETTINGS_DEFAULTS,
|
|
16
|
+
} from './index.js';
|
|
17
|
+
|
|
18
|
+
let pass = 0, fail = 0;
|
|
19
|
+
function t(name, result) {
|
|
20
|
+
if (result) { pass++; }
|
|
21
|
+
else { fail++; console.log(' ✗', name); }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log('═══ HELPERS (19 tests) ═══');
|
|
25
|
+
// FormatP2P
|
|
26
|
+
t('formatP2P 40152030', formatP2P(40152030) === '40.15 P2P');
|
|
27
|
+
t('formatP2P 1000000', formatP2P(1000000) === '1.00 P2P');
|
|
28
|
+
t('formatP2P 0', formatP2P(0) === '0.00 P2P');
|
|
29
|
+
|
|
30
|
+
// FormatPriceP2P
|
|
31
|
+
t('formatPriceP2P 40152030', formatPriceP2P(40152030) === '40.15');
|
|
32
|
+
t('formatPriceP2P string', formatPriceP2P('1000000') === '1.00');
|
|
33
|
+
|
|
34
|
+
// FormatBytes
|
|
35
|
+
t('formatBytes 1.5GB', formatBytes(1500000000).includes('GB'));
|
|
36
|
+
t('formatBytes 250MB', formatBytes(250000000).includes('MB'));
|
|
37
|
+
t('formatBytes 1000', formatBytes(1000) === '1000 B' || formatBytes(1000).includes('KB'));
|
|
38
|
+
t('formatBytes 0', formatBytes(0) === '0 B');
|
|
39
|
+
|
|
40
|
+
// FormatUptime
|
|
41
|
+
t('formatUptime 2h2m', formatUptime(7350000) === '2h 2m');
|
|
42
|
+
t('formatUptime 1m30s', formatUptime(90000) === '1m 30s');
|
|
43
|
+
t('formatUptime 45s', formatUptime(45000) === '45s');
|
|
44
|
+
t('formatUptime 0', formatUptime(0) === '0s');
|
|
45
|
+
|
|
46
|
+
// ShortAddress
|
|
47
|
+
t('shortAddress', shortAddress('sent1example9pqrse8q4m6lz8alxqv5hkx3fkxe0q', 12, 6).includes('...'));
|
|
48
|
+
|
|
49
|
+
// ComputeAllocation GB-based
|
|
50
|
+
const allocGb = computeSessionAllocation({ downloadBytes: '500000000', uploadBytes: '100000000', maxBytes: '1000000000', max_duration: '0s' });
|
|
51
|
+
t('allocation GB percent', allocGb.usedPercent === 60);
|
|
52
|
+
t('allocation GB isGbBased', allocGb.isGbBased === true);
|
|
53
|
+
|
|
54
|
+
// ComputeAllocation hourly
|
|
55
|
+
const allocHr = computeSessionAllocation({ downloadBytes: '100000', uploadBytes: '50000', maxBytes: '1000000000', max_duration: '3600s' });
|
|
56
|
+
t('allocation hourly', allocHr.isHourlyBased === true);
|
|
57
|
+
|
|
58
|
+
// Options
|
|
59
|
+
t('GB_OPTIONS has 1 and 50', GB_OPTIONS.includes(1) && GB_OPTIONS.includes(50));
|
|
60
|
+
t('HOUR_OPTIONS has 1 and 24', HOUR_OPTIONS.includes(1) && HOUR_OPTIONS.includes(24));
|
|
61
|
+
|
|
62
|
+
console.log('═══ COUNTRY & FLAGS (13 tests) ═══');
|
|
63
|
+
t('Netherlands → NL', countryNameToCode('The Netherlands') === 'NL');
|
|
64
|
+
t('Türkiye → TR', countryNameToCode('Türkiye') === 'TR');
|
|
65
|
+
t('DR Congo → CD', countryNameToCode('DR Congo') === 'CD');
|
|
66
|
+
t('Czechia → CZ', countryNameToCode('Czechia') === 'CZ');
|
|
67
|
+
t('Russian Federation → RU', countryNameToCode('Russian Federation') === 'RU');
|
|
68
|
+
t('Viet Nam → VN', countryNameToCode('Viet Nam') === 'VN');
|
|
69
|
+
t('South Korea → KR', countryNameToCode('South Korea') === 'KR');
|
|
70
|
+
t('UAE → AE', countryNameToCode('UAE') === 'AE');
|
|
71
|
+
t('us → US', countryNameToCode('us') === 'US');
|
|
72
|
+
t('Atlantis → null', countryNameToCode('Atlantis') === null);
|
|
73
|
+
t('empty → null', countryNameToCode('') === null);
|
|
74
|
+
t('getFlagUrl US', getFlagUrl('US').includes('flagcdn.com'));
|
|
75
|
+
t('getFlagEmoji DE', getFlagEmoji('DE') === '🇩🇪');
|
|
76
|
+
|
|
77
|
+
console.log('═══ DNS (11 tests) ═══');
|
|
78
|
+
t('default is handshake', DEFAULT_DNS_PRESET === 'handshake');
|
|
79
|
+
t('3 presets', Object.keys(DNS_PRESETS).length === 3);
|
|
80
|
+
t('fallback order 3', DNS_FALLBACK_ORDER.length === 3);
|
|
81
|
+
t('resolve default has handshake', resolveDnsServers().includes('103.196.38.38'));
|
|
82
|
+
t('resolve default has google fallback', resolveDnsServers().includes('8.8.8.8'));
|
|
83
|
+
t('resolve default has cloudflare fallback', resolveDnsServers().includes('1.1.1.1'));
|
|
84
|
+
t('resolve google starts with 8.8.8.8', resolveDnsServers('google').startsWith('8.8.8.8'));
|
|
85
|
+
t('resolve google has handshake fallback', resolveDnsServers('google').includes('103.196.38.38'));
|
|
86
|
+
t('resolve cloudflare starts with 1.1.1.1', resolveDnsServers('cloudflare').startsWith('1.1.1.1'));
|
|
87
|
+
t('resolve custom has fallbacks', resolveDnsServers('9.9.9.9').includes('103.196.38.38'));
|
|
88
|
+
const dnsServers = resolveDnsServers().split(', ');
|
|
89
|
+
t('no duplicate DNS servers', dnsServers.length === new Set(dnsServers).size);
|
|
90
|
+
|
|
91
|
+
console.log('═══ ERRORS (15 tests) ═══');
|
|
92
|
+
const allCodes = Object.values(ErrorCodes);
|
|
93
|
+
t('33 error codes', allCodes.length === 33);
|
|
94
|
+
t('all have user messages', allCodes.every(c => userMessage(c) !== 'An unexpected error occurred.'));
|
|
95
|
+
t('unknown code → default', userMessage('FAKE_CODE') === 'An unexpected error occurred.');
|
|
96
|
+
t('INSUFFICIENT_BALANCE is fatal', ERROR_SEVERITY[ErrorCodes.INSUFFICIENT_BALANCE] === 'fatal');
|
|
97
|
+
t('NODE_OFFLINE is retryable', ERROR_SEVERITY[ErrorCodes.NODE_OFFLINE] === 'retryable');
|
|
98
|
+
t('SESSION_EXISTS is recoverable', ERROR_SEVERITY[ErrorCodes.SESSION_EXISTS] === 'recoverable');
|
|
99
|
+
t('V2RAY_NOT_FOUND is infrastructure', ERROR_SEVERITY[ErrorCodes.V2RAY_NOT_FOUND] === 'infrastructure');
|
|
100
|
+
t('isRetryable NODE_OFFLINE', isRetryable(ErrorCodes.NODE_OFFLINE) === true);
|
|
101
|
+
t('isRetryable INVALID_MNEMONIC', isRetryable(ErrorCodes.INVALID_MNEMONIC) === false);
|
|
102
|
+
t('userMessage INSUFFICIENT_BALANCE', userMessage(ErrorCodes.INSUFFICIENT_BALANCE).includes('P2P'));
|
|
103
|
+
t('userMessage NODE_OFFLINE', userMessage(ErrorCodes.NODE_OFFLINE).includes('offline'));
|
|
104
|
+
t('userMessage V2RAY_NOT_FOUND', userMessage(ErrorCodes.V2RAY_NOT_FOUND).includes('V2Ray'));
|
|
105
|
+
t('userMessage ABORTED', userMessage(ErrorCodes.ABORTED).includes('cancelled'));
|
|
106
|
+
t('userMessage INVALID_ASSIGNED_IP', userMessage(ErrorCodes.INVALID_ASSIGNED_IP).includes('invalid'));
|
|
107
|
+
t('userMessage CHAIN_LAG', userMessage(ErrorCodes.CHAIN_LAG).includes('confirmed'));
|
|
108
|
+
|
|
109
|
+
console.log('═══ APP TYPES (6 tests) ═══');
|
|
110
|
+
t('3 app types', Object.keys(APP_TYPES).length === 3);
|
|
111
|
+
t('WHITE_LABEL exists', APP_TYPES.WHITE_LABEL === 'white_label');
|
|
112
|
+
t('DIRECT_P2P exists', APP_TYPES.DIRECT_P2P === 'direct_p2p');
|
|
113
|
+
t('validate valid', validateAppConfig('white_label', { planId: 42, mnemonic: 'x' }).valid === true);
|
|
114
|
+
t('validate invalid', validateAppConfig('white_label', {}).valid === false);
|
|
115
|
+
t('validate bad type', validateAppConfig('fake', {}).valid === false);
|
|
116
|
+
|
|
117
|
+
console.log('═══ CACHE & SETTINGS (6 tests) ═══');
|
|
118
|
+
const v1 = await cached('logic-test', 5000, async () => 42);
|
|
119
|
+
const v2 = await cached('logic-test', 5000, async () => 99);
|
|
120
|
+
t('cached returns first', v1 === 42 && v2 === 42);
|
|
121
|
+
cacheInvalidate('logic-test');
|
|
122
|
+
const v3 = await cached('logic-test', 5000, async () => 77);
|
|
123
|
+
t('cache invalidate works', v3 === 77);
|
|
124
|
+
diskSave('logic-test-rt', { x: 1 });
|
|
125
|
+
t('diskSave + diskLoad', diskLoad('logic-test-rt', 60000)?.data?.x === 1);
|
|
126
|
+
trackSession('99999', 'hour');
|
|
127
|
+
t('trackSession', getSessionMode('99999') === 'hour');
|
|
128
|
+
const settings = loadAppSettings();
|
|
129
|
+
t('default settings', settings.dnsPreset === 'handshake' && settings.fullTunnel === true);
|
|
130
|
+
t('APP_SETTINGS_DEFAULTS', APP_SETTINGS_DEFAULTS.statusPollSec === 3);
|
|
131
|
+
|
|
132
|
+
// ═══ PRICING (5 tests) ═══
|
|
133
|
+
console.log('═══ PRICING (5 tests) ═══');
|
|
134
|
+
const mockNode = {
|
|
135
|
+
gigabyte_prices: [{ denom: 'udvpn', quote_value: '40152030' }],
|
|
136
|
+
hourly_prices: [{ denom: 'udvpn', quote_value: '18384000' }],
|
|
137
|
+
};
|
|
138
|
+
const pricing = formatNodePricing(mockNode);
|
|
139
|
+
t('formatNodePricing perGb', pricing.perGb?.includes('P2P/GB'));
|
|
140
|
+
t('formatNodePricing perHour', pricing.perHour?.includes('P2P/hr'));
|
|
141
|
+
t('formatNodePricing cheapest', pricing.cheapest === 'hour');
|
|
142
|
+
const gbCost = estimateSessionPrice(mockNode, 'gb', 5);
|
|
143
|
+
t('estimateSessionPrice gb', gbCost.costUdvpn > 0);
|
|
144
|
+
const hrCost = estimateSessionPrice(mockNode, 'hour', 4);
|
|
145
|
+
t('estimateSessionPrice hour', hrCost.costUdvpn > 0);
|
|
146
|
+
|
|
147
|
+
console.log(`\n═══ RESULTS: ${pass} passed, ${fail} failed (${pass + fail} total) ═══`);
|
|
148
|
+
process.exit(fail > 0 ? 1 : 0);
|
package/test-mainnet.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Live mainnet verification — tests every SDK feature against the real chain.
|
|
4
|
+
* Uses the node-tester wallet. Costs ~1-2 P2P per run (one session).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import 'dotenv/config';
|
|
8
|
+
const mnemonic = process.env.MNEMONIC;
|
|
9
|
+
if (!mnemonic) { console.error('Set MNEMONIC in .env'); process.exit(1); }
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
createWallet, getBalance, formatP2P, fetchAllNodes,
|
|
13
|
+
formatNodePricing, estimateSessionPrice, countryNameToCode, getFlagUrl,
|
|
14
|
+
GB_OPTIONS, HOUR_OPTIONS, discoverPlans, preflight,
|
|
15
|
+
validateAppConfig, cached, diskSave, diskLoad, trackSession, getSessionMode,
|
|
16
|
+
loadAppSettings, formatUptime, computeSessionAllocation,
|
|
17
|
+
resolveDnsServers, APP_TYPES, connectDirect, disconnect,
|
|
18
|
+
registerCleanupHandlers, nodeStatusV3, createNodeHttpsAgent,
|
|
19
|
+
DEFAULT_RPC, userMessage, ErrorCodes,
|
|
20
|
+
} from './index.js';
|
|
21
|
+
import { createClient } from './cosmjs-setup.js';
|
|
22
|
+
|
|
23
|
+
registerCleanupHandlers();
|
|
24
|
+
|
|
25
|
+
const R = { pass: 0, fail: 0, errors: [] };
|
|
26
|
+
async function t(name, fn) {
|
|
27
|
+
try {
|
|
28
|
+
const r = await fn();
|
|
29
|
+
if (r) { R.pass++; console.log(' ✓', name); }
|
|
30
|
+
else { R.fail++; R.errors.push(name); console.log(' ✗', name, '→ falsy'); }
|
|
31
|
+
} catch (e) {
|
|
32
|
+
R.fail++; R.errors.push(name + ': ' + e.message?.slice(0, 120));
|
|
33
|
+
console.log(' ✗', name, '→', e.message?.slice(0, 120));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ═══ WALLET ═══
|
|
38
|
+
console.log('\n═══ WALLET ═══');
|
|
39
|
+
const { wallet, account } = await createWallet(mnemonic);
|
|
40
|
+
await t('1.1 address starts with sent1', async () => account.address.startsWith('sent1'));
|
|
41
|
+
const client = await createClient(DEFAULT_RPC, wallet);
|
|
42
|
+
const bal = await getBalance(client, account.address);
|
|
43
|
+
await t('1.2 balance > 0', async () => { console.log(' Balance:', formatP2P(bal.udvpn)); return bal.udvpn > 0; });
|
|
44
|
+
|
|
45
|
+
// ═══ CHAIN QUERIES ═══
|
|
46
|
+
console.log('\n═══ CHAIN QUERIES ═══');
|
|
47
|
+
const allNodes = await fetchAllNodes();
|
|
48
|
+
await t('2.1 fetchAllNodes > 900', async () => { console.log(' Nodes:', allNodes.length); return allNodes.length > 900; });
|
|
49
|
+
await t('2.2 nodes have pricing', async () => allNodes.filter(n => n.gigabyte_prices?.length > 0).length > 500);
|
|
50
|
+
const plans = await discoverPlans(undefined, { maxId: 30 });
|
|
51
|
+
await t('2.5 discoverPlans finds plans', async () => { console.log(' Plans:', plans.length); return plans.length > 0; });
|
|
52
|
+
|
|
53
|
+
// ═══ PRICING ═══
|
|
54
|
+
console.log('\n═══ PRICING ═══');
|
|
55
|
+
const dualNode = allNodes.find(n => n.gigabyte_prices?.length > 0 && n.hourly_prices?.length > 0);
|
|
56
|
+
await t('7.1 formatNodePricing both', async () => {
|
|
57
|
+
const p = formatNodePricing(dualNode);
|
|
58
|
+
console.log(' GB:', p.perGb, '| Hr:', p.perHour);
|
|
59
|
+
return p.perGb && p.perHour;
|
|
60
|
+
});
|
|
61
|
+
await t('7.2 estimateSessionPrice gb', async () => estimateSessionPrice(dualNode, 'gb', 5).costUdvpn > 0);
|
|
62
|
+
await t('7.3 estimateSessionPrice hour', async () => estimateSessionPrice(dualNode, 'hour', 4).costUdvpn > 0);
|
|
63
|
+
|
|
64
|
+
// ═══ COUNTRY ═══
|
|
65
|
+
console.log('\n═══ COUNTRY & FLAGS ═══');
|
|
66
|
+
await t('8.1 The Netherlands → NL', async () => countryNameToCode('The Netherlands') === 'NL');
|
|
67
|
+
await t('8.2 Türkiye → TR', async () => countryNameToCode('Türkiye') === 'TR');
|
|
68
|
+
await t('8.3 DR Congo → CD', async () => countryNameToCode('DR Congo') === 'CD');
|
|
69
|
+
await t('8.4 getFlagUrl', async () => getFlagUrl('US').includes('flagcdn.com'));
|
|
70
|
+
await t('8.6 unknown → null', async () => countryNameToCode('Atlantis') === null);
|
|
71
|
+
|
|
72
|
+
// ═══ CACHE ═══
|
|
73
|
+
console.log('\n═══ CACHE & PERSISTENCE ═══');
|
|
74
|
+
await t('9.1 cached TTL + dedup', async () => {
|
|
75
|
+
let calls = 0;
|
|
76
|
+
const v1 = await cached('t1', 5000, async () => { calls++; return 42; });
|
|
77
|
+
const v2 = await cached('t1', 5000, async () => { calls++; return 99; });
|
|
78
|
+
return v1 === 42 && v2 === 42 && calls === 1;
|
|
79
|
+
});
|
|
80
|
+
await t('9.3 cached stale fallback', async () => {
|
|
81
|
+
await cached('t2', 1, async () => 'good'); // cache with 1ms TTL
|
|
82
|
+
await new Promise(r => setTimeout(r, 5)); // expire it
|
|
83
|
+
const val = await cached('t2', 1, async () => { throw new Error('fail'); });
|
|
84
|
+
return val === 'good'; // stale fallback
|
|
85
|
+
});
|
|
86
|
+
await t('9.4 diskSave + diskLoad', async () => {
|
|
87
|
+
diskSave('test-rt', { x: 1 });
|
|
88
|
+
return diskLoad('test-rt', 60000)?.data?.x === 1;
|
|
89
|
+
});
|
|
90
|
+
await t('9.5 trackSession persist', async () => {
|
|
91
|
+
trackSession('88888', 'hour');
|
|
92
|
+
return getSessionMode('88888') === 'hour';
|
|
93
|
+
});
|
|
94
|
+
await t('9.6 loadAppSettings defaults', async () => {
|
|
95
|
+
const s = loadAppSettings();
|
|
96
|
+
return s.dnsPreset === 'handshake' && s.fullTunnel === true;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// ═══ DISPLAY ═══
|
|
100
|
+
console.log('\n═══ DISPLAY HELPERS ═══');
|
|
101
|
+
await t('10.1 formatUptime', async () => formatUptime(7350000) === '2h 2m');
|
|
102
|
+
await t('10.3 computeSessionAllocation', async () => {
|
|
103
|
+
const a = computeSessionAllocation({ downloadBytes: '500000000', uploadBytes: '100000000', maxBytes: '1000000000', max_duration: '0s' });
|
|
104
|
+
return a.usedPercent === 60 && a.isGbBased;
|
|
105
|
+
});
|
|
106
|
+
await t('10.5 userMessage covers all codes', async () => {
|
|
107
|
+
const codes = Object.values(ErrorCodes);
|
|
108
|
+
const covered = codes.filter(c => userMessage(c) !== 'An unexpected error occurred.');
|
|
109
|
+
console.log(' Covered:', covered.length + '/' + codes.length);
|
|
110
|
+
return covered.length === codes.length;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ═══ APP TYPES ═══
|
|
114
|
+
console.log('\n═══ APP TYPES ═══');
|
|
115
|
+
await t('11.1 white_label valid', async () => validateAppConfig('white_label', { planId: 42, mnemonic: 'x' }).valid);
|
|
116
|
+
await t('11.2 white_label missing → errors', async () => !validateAppConfig('white_label', {}).valid);
|
|
117
|
+
await t('11.3 direct_p2p valid', async () => validateAppConfig('direct_p2p', { mnemonic: 'x' }).valid);
|
|
118
|
+
|
|
119
|
+
// ═══ DNS ═══
|
|
120
|
+
console.log('\n═══ DNS ═══');
|
|
121
|
+
await t('DNS handshake default', async () => resolveDnsServers().includes('103.196.38.38'));
|
|
122
|
+
await t('DNS fallback chain', async () => resolveDnsServers('google').includes('103.196.38.38'));
|
|
123
|
+
|
|
124
|
+
// ═══ PREFLIGHT ═══
|
|
125
|
+
console.log('\n═══ PREFLIGHT ═══');
|
|
126
|
+
await t('6.5 preflight', async () => {
|
|
127
|
+
const r = preflight();
|
|
128
|
+
console.log(' WG:', r.ready.wireguard, '| V2:', r.ready.v2ray);
|
|
129
|
+
return r.ready.anyProtocol;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// ═══ LIVE CONNECTION ═══
|
|
133
|
+
console.log('\n═══ LIVE WireGuard CONNECTION ═══');
|
|
134
|
+
let wgNode = null;
|
|
135
|
+
for (const n of allNodes.slice(0, 25)) {
|
|
136
|
+
try {
|
|
137
|
+
const url = 'https://' + (n.remote_addrs?.[0] || '');
|
|
138
|
+
if (!url || url === 'https://') continue;
|
|
139
|
+
const agent = createNodeHttpsAgent(n.address, 'tofu');
|
|
140
|
+
const status = await nodeStatusV3(url, agent);
|
|
141
|
+
if (status.type === 'wireguard' && (!status.address || status.address === n.address)) {
|
|
142
|
+
wgNode = n;
|
|
143
|
+
console.log(' Found WG:', n.address, '-', status.moniker);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
} catch {}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (wgNode) {
|
|
150
|
+
await t('3.1 connectDirect WG per-GB', async () => {
|
|
151
|
+
const result = await connectDirect({
|
|
152
|
+
mnemonic, nodeAddress: wgNode.address, gigabytes: 1,
|
|
153
|
+
fullTunnel: false, dns: 'handshake',
|
|
154
|
+
v2rayExePath: process.env.V2RAY_PATH || undefined, // SDK auto-detects
|
|
155
|
+
onProgress: (step, detail) => console.log(' [' + step + ']', detail),
|
|
156
|
+
});
|
|
157
|
+
console.log(' Session:', result.sessionId, 'Type:', result.serviceType);
|
|
158
|
+
trackSession(result.sessionId, 'gb');
|
|
159
|
+
console.log(' Tracked mode:', getSessionMode(result.sessionId));
|
|
160
|
+
await disconnect();
|
|
161
|
+
console.log(' Disconnected OK');
|
|
162
|
+
return result.serviceType === 'wireguard';
|
|
163
|
+
});
|
|
164
|
+
} else {
|
|
165
|
+
console.log(' SKIP: No WG node reachable in first 25');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ═══ RESULTS ═══
|
|
169
|
+
console.log('\n═══════════════════════════════════════');
|
|
170
|
+
console.log('RESULTS:', R.pass, 'passed,', R.fail, 'failed');
|
|
171
|
+
if (R.errors.length > 0) {
|
|
172
|
+
console.log('\nFAILURES:');
|
|
173
|
+
for (const e of R.errors) console.log(' ✗', e);
|
|
174
|
+
}
|
|
175
|
+
console.log('═══════════════════════════════════════');
|
|
176
|
+
process.exit(R.fail > 0 ? 1 : 0);
|