blue-js-sdk 2.6.0 → 2.7.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/app-helpers.js +55 -0
- package/chain/broadcast.js +27 -0
- package/chain/fee-grants.js +77 -7
- package/chain/queries.js +72 -0
- package/chain/rpc.js +59 -2
- package/cli.js +26 -5
- package/client.js +62 -6
- package/connection/connect.js +103 -17
- package/connection/disconnect.js +9 -4
- package/connection/logger.js +66 -0
- package/connection/resilience.js +12 -7
- package/connection/state.js +21 -12
- package/connection/tunnel.js +24 -8
- package/cosmjs-setup.js +42 -0
- package/docs/PRIVY-INTEGRATION.md +177 -0
- package/errors.js +167 -0
- package/index.js +70 -1
- package/node-connect.js +92 -40
- package/operator.js +24 -0
- package/package.json +11 -8
- package/session-manager.js +68 -0
- package/speedtest.js +139 -0
- package/test-all-logic.js +8 -6
- package/test-e2e.js +138 -0
- package/test-mainnet.js +2 -2
- package/test-plan-connect-e2e.js +235 -0
- package/test-subscription-flows.js +14 -4
- package/types/connection.d.ts +6 -2
package/test-e2e.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SENTINEL SDK — FULL E2E TEST RUNNER
|
|
4
|
+
*
|
|
5
|
+
* Runs every test suite in order. All suites that need chain access use
|
|
6
|
+
* the MNEMONIC from ../ ai-path/.env or a local .env.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node test-e2e.js # run everything
|
|
10
|
+
* node test-e2e.js --offline # logic tests only (no network)
|
|
11
|
+
* node test-e2e.js --quick # offline + chain queries, no TX or connection
|
|
12
|
+
*
|
|
13
|
+
* Suites (in order):
|
|
14
|
+
* 1. Logic — 127 pure-logic tests, no network (test-all-logic.js)
|
|
15
|
+
* 2. FeeGrant E2E — isActiveStatus, error codes, queryFeeGrant offline + live (test-plan-connect-e2e.js)
|
|
16
|
+
* 3. Mainnet — wallet, queries, cache, preflight, WireGuard connect (test-mainnet.js)
|
|
17
|
+
* 4. Subscriptions — subscribe, share, feegrant, onboard, renew, cancel (test-subscription-flows.js)
|
|
18
|
+
*
|
|
19
|
+
* Suites 3-4 broadcast real TXs and cost ~1–3 P2P per run.
|
|
20
|
+
* Suite 3 opens and closes one WireGuard session.
|
|
21
|
+
* Never run suites in parallel — chain rate limits apply (7s between TXs).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
25
|
+
import { resolve, dirname } from 'path';
|
|
26
|
+
import { fileURLToPath } from 'url';
|
|
27
|
+
import { spawnSync } from 'child_process';
|
|
28
|
+
|
|
29
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
dotenvConfig({ path: resolve(__dirname, '../ai-path/.env') });
|
|
31
|
+
dotenvConfig(); // also try CWD .env
|
|
32
|
+
|
|
33
|
+
const MNEMONIC = process.env.MNEMONIC;
|
|
34
|
+
const args = process.argv.slice(2);
|
|
35
|
+
const offlineOnly = args.includes('--offline');
|
|
36
|
+
const quickMode = args.includes('--quick');
|
|
37
|
+
|
|
38
|
+
const FEE_GRANTER = 'sent1t0xjyflrah5n36rfkpfeuw6pz6vl2g27x2793l';
|
|
39
|
+
const FEE_GRANTEE = MNEMONIC ? undefined : null; // resolved from wallet
|
|
40
|
+
const PLAN_ID = '42';
|
|
41
|
+
|
|
42
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
43
|
+
console.log(' SENTINEL SDK — FULL E2E TEST RUNNER');
|
|
44
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
45
|
+
console.log(` Mode : ${offlineOnly ? 'offline-only' : quickMode ? 'quick (no TX)' : 'full (chain + TX)'}`);
|
|
46
|
+
console.log(` MNEMONIC : ${MNEMONIC ? 'set' : 'NOT SET — chain tests will fail'}`);
|
|
47
|
+
console.log('');
|
|
48
|
+
|
|
49
|
+
// ─── Suite runner ─────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
const results = [];
|
|
52
|
+
|
|
53
|
+
function runSuite(name, file, env = {}) {
|
|
54
|
+
console.log(`\n${'─'.repeat(60)}`);
|
|
55
|
+
console.log(` SUITE: ${name}`);
|
|
56
|
+
console.log(`${'─'.repeat(60)}`);
|
|
57
|
+
|
|
58
|
+
const merged = { ...process.env, ...env };
|
|
59
|
+
const r = spawnSync('node', [file], { cwd: __dirname, env: merged, stdio: 'inherit' });
|
|
60
|
+
|
|
61
|
+
const ok = r.status === 0;
|
|
62
|
+
results.push({ name, ok, status: r.status });
|
|
63
|
+
if (!ok) console.log(`\n [SUITE FAILED — exit ${r.status}]`);
|
|
64
|
+
return ok;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── Suite 1: Pure logic ───────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
runSuite('Logic (offline, no network)', 'test-all-logic.js');
|
|
70
|
+
|
|
71
|
+
if (offlineOnly) {
|
|
72
|
+
printSummary();
|
|
73
|
+
process.exit(results.every(r => r.ok) ? 0 : 1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ─── Suite 2: FeeGrant E2E (offline + live LCD) ───────────────────────────
|
|
77
|
+
|
|
78
|
+
// Resolve the grantee address from the wallet if mnemonic is available.
|
|
79
|
+
let granteeAddr = FEE_GRANTEE;
|
|
80
|
+
if (MNEMONIC && !granteeAddr) {
|
|
81
|
+
try {
|
|
82
|
+
const { createWallet } = await import('./index.js');
|
|
83
|
+
const { account } = await createWallet(MNEMONIC);
|
|
84
|
+
granteeAddr = account.address;
|
|
85
|
+
} catch (_) {
|
|
86
|
+
// leave undefined — live LCD section will be skipped
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
runSuite('FeeGrant + isActiveStatus + error codes (offline + live LCD)',
|
|
91
|
+
'test-plan-connect-e2e.js',
|
|
92
|
+
{
|
|
93
|
+
E2E_LIVE: MNEMONIC ? '1' : '0',
|
|
94
|
+
FEE_GRANTER,
|
|
95
|
+
FEE_GRANTEE: granteeAddr || '',
|
|
96
|
+
PLAN_ID,
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (quickMode) {
|
|
101
|
+
printSummary();
|
|
102
|
+
process.exit(results.every(r => r.ok) ? 0 : 1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ─── Suite 3: Mainnet — wallet + queries + WireGuard connect ─────────────
|
|
106
|
+
|
|
107
|
+
if (!MNEMONIC) {
|
|
108
|
+
console.log('\n SKIP Suite 3+4 — MNEMONIC not set');
|
|
109
|
+
} else {
|
|
110
|
+
runSuite('Mainnet (wallet, chain queries, WireGuard connect)', 'test-mainnet.js',
|
|
111
|
+
{ MNEMONIC });
|
|
112
|
+
|
|
113
|
+
// 60s gap between full suites to let chain settle
|
|
114
|
+
console.log('\n Waiting 60s between suites (chain settle)...');
|
|
115
|
+
await new Promise(r => setTimeout(r, 60000));
|
|
116
|
+
|
|
117
|
+
// ─── Suite 4: Subscription flows ─────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
runSuite('Subscription flows (subscribe, share, feegrant, onboard, renew, cancel)',
|
|
120
|
+
'test-subscription-flows.js', { MNEMONIC });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ─── Summary ──────────────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
printSummary();
|
|
126
|
+
process.exit(results.every(r => r.ok) ? 0 : 1);
|
|
127
|
+
|
|
128
|
+
function printSummary() {
|
|
129
|
+
const pass = results.filter(r => r.ok).length;
|
|
130
|
+
const fail = results.filter(r => !r.ok).length;
|
|
131
|
+
console.log('\n');
|
|
132
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
133
|
+
console.log(` FINAL: ${pass} suite(s) passed, ${fail} failed`);
|
|
134
|
+
for (const r of results) {
|
|
135
|
+
console.log(` ${r.ok ? '✓' : '✗'} ${r.name}`);
|
|
136
|
+
}
|
|
137
|
+
console.log('═══════════════════════════════════════════════════════════');
|
|
138
|
+
}
|
package/test-mainnet.js
CHANGED
|
@@ -45,8 +45,8 @@ await t('1.2 balance > 0', async () => { console.log(' Balance:', formatP2P
|
|
|
45
45
|
// ═══ CHAIN QUERIES ═══
|
|
46
46
|
console.log('\n═══ CHAIN QUERIES ═══');
|
|
47
47
|
const allNodes = await fetchAllNodes();
|
|
48
|
-
await t('2.1 fetchAllNodes >
|
|
49
|
-
await t('2.2 nodes have pricing', async () => allNodes.filter(n => n.gigabyte_prices?.length > 0).length >
|
|
48
|
+
await t('2.1 fetchAllNodes > 100', async () => { console.log(' Nodes:', allNodes.length); return allNodes.length > 100; });
|
|
49
|
+
await t('2.2 nodes have pricing', async () => allNodes.filter(n => n.gigabyte_prices?.length > 0).length > 50);
|
|
50
50
|
const plans = await discoverPlans(undefined, { maxId: 30 });
|
|
51
51
|
await t('2.5 discoverPlans finds plans', async () => { console.log(' Plans:', plans.length); return plans.length > 0; });
|
|
52
52
|
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PLAN-SUBSCRIPTION CONNECT — E2E HARNESS
|
|
4
|
+
*
|
|
5
|
+
* Two modes:
|
|
6
|
+
* 1. Offline (default) — validates the code paths added for the 2026-04-23
|
|
7
|
+
* plan+feegrant audit without broadcasting:
|
|
8
|
+
* - isActiveStatus() strict-1 semantics
|
|
9
|
+
* - queryFeeGrant() shape on a fabricated LCD response
|
|
10
|
+
* - ErrorCodes.FEE_GRANT_MISSING_AT_START / FEE_GRANT_EXPIRED exported
|
|
11
|
+
* - userMessage() text parity with C# SentinelErrors.UserMessage
|
|
12
|
+
* - connectViaPlan argument plumbing (requireFeeGrant flag is accepted)
|
|
13
|
+
* No network calls. No TX. Safe to run anywhere, any time.
|
|
14
|
+
*
|
|
15
|
+
* 2. Live (E2E_LIVE=1) — calls queryFeeGrant + queryPlanDetails against
|
|
16
|
+
* mainnet LCD. No broadcast. Requires FEE_GRANTER + FEE_GRANTEE + PLAN_ID.
|
|
17
|
+
* Respects the SDK rule "never parallel chain tests" — runs serially.
|
|
18
|
+
*
|
|
19
|
+
* Run:
|
|
20
|
+
* node test-plan-connect-e2e.js # offline
|
|
21
|
+
* E2E_LIVE=1 FEE_GRANTER=sent1... FEE_GRANTEE=sent1... PLAN_ID=42 \
|
|
22
|
+
* node test-plan-connect-e2e.js # live (queries only)
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
ErrorCodes,
|
|
27
|
+
isActiveStatus,
|
|
28
|
+
queryFeeGrant,
|
|
29
|
+
queryPlanDetails,
|
|
30
|
+
} from './index.js';
|
|
31
|
+
import { userMessage } from './errors.js';
|
|
32
|
+
import { RPC_ENDPOINTS } from './defaults.js';
|
|
33
|
+
|
|
34
|
+
// ─── Test harness ───────────────────────────────────────────
|
|
35
|
+
let pass = 0;
|
|
36
|
+
let fail = 0;
|
|
37
|
+
function assert(name, cond, detail = '') {
|
|
38
|
+
if (cond) { pass++; console.log(` ok ${name}`); }
|
|
39
|
+
else { fail++; console.log(` FAIL ${name}${detail ? ' — ' + detail : ''}`); }
|
|
40
|
+
}
|
|
41
|
+
function section(name) { console.log(`\n── ${name} ──`); }
|
|
42
|
+
|
|
43
|
+
// ─── 1. isActiveStatus strict-1 semantics ───────────────────
|
|
44
|
+
section('isActiveStatus');
|
|
45
|
+
assert('numeric 1 is active', isActiveStatus(1) === true);
|
|
46
|
+
assert('string "1" is active', isActiveStatus('1') === true);
|
|
47
|
+
assert('STATUS_ACTIVE is active', isActiveStatus('STATUS_ACTIVE') === true);
|
|
48
|
+
assert('numeric 2 is NOT active (status-inactive-pending)', isActiveStatus(2) === false);
|
|
49
|
+
assert('numeric 3 is NOT active (STATUS_INACTIVE — terminal)', isActiveStatus(3) === false);
|
|
50
|
+
assert('STATUS_INACTIVE_PENDING is NOT active', isActiveStatus('STATUS_INACTIVE_PENDING') === false);
|
|
51
|
+
assert('STATUS_INACTIVE is NOT active', isActiveStatus('STATUS_INACTIVE') === false);
|
|
52
|
+
assert('undefined is NOT active', isActiveStatus(undefined) === false);
|
|
53
|
+
assert('null is NOT active', isActiveStatus(null) === false);
|
|
54
|
+
|
|
55
|
+
// ─── 2. New error codes exported ─────────────────────────────
|
|
56
|
+
section('Error codes');
|
|
57
|
+
assert('FEE_GRANT_MISSING_AT_START exported', ErrorCodes.FEE_GRANT_MISSING_AT_START === 'FEE_GRANT_MISSING_AT_START');
|
|
58
|
+
assert('FEE_GRANT_EXPIRED exported', ErrorCodes.FEE_GRANT_EXPIRED === 'FEE_GRANT_EXPIRED');
|
|
59
|
+
assert('NODE_MISCONFIGURED exported', ErrorCodes.NODE_MISCONFIGURED === 'NODE_MISCONFIGURED');
|
|
60
|
+
assert('NODE_DB_CORRUPT exported', ErrorCodes.NODE_DB_CORRUPT === 'NODE_DB_CORRUPT');
|
|
61
|
+
assert('NODE_RPC_BROKEN exported', ErrorCodes.NODE_RPC_BROKEN === 'NODE_RPC_BROKEN');
|
|
62
|
+
assert('SEQUENCE_MISMATCH exported', ErrorCodes.SEQUENCE_MISMATCH === 'SEQUENCE_MISMATCH');
|
|
63
|
+
assert('NOT_CONNECTED exported', ErrorCodes.NOT_CONNECTED === 'NOT_CONNECTED');
|
|
64
|
+
assert('CONNECTION_IN_PROGRESS exported', ErrorCodes.CONNECTION_IN_PROGRESS === 'CONNECTION_IN_PROGRESS');
|
|
65
|
+
assert('HANDSHAKE_FAILED exported', ErrorCodes.HANDSHAKE_FAILED === 'HANDSHAKE_FAILED');
|
|
66
|
+
|
|
67
|
+
// ─── 3. User messages — parity with C# SentinelErrors ────────
|
|
68
|
+
// These strings must match C# UserMessage() verbatim. If a translator changes
|
|
69
|
+
// either side without the other, the JS↔C# error-UX contract breaks.
|
|
70
|
+
section('userMessage parity (JS side)');
|
|
71
|
+
const expectedMsgs = {
|
|
72
|
+
FEE_GRANT_MISSING_AT_START:
|
|
73
|
+
"Plan owner has not issued a fee grant to this wallet. Contact the plan provider.",
|
|
74
|
+
FEE_GRANT_EXPIRED:
|
|
75
|
+
"The plan owner's fee grant has expired. Contact the plan provider to renew.",
|
|
76
|
+
};
|
|
77
|
+
for (const [code, expected] of Object.entries(expectedMsgs)) {
|
|
78
|
+
const actual = userMessage(code);
|
|
79
|
+
assert(`userMessage(${code}) matches C# text`, actual === expected,
|
|
80
|
+
`got "${actual}", expected "${expected}"`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ─── 4. queryFeeGrant offline (fabricated LCD server) ────────
|
|
84
|
+
// Spin up a tiny HTTP server that returns the single-pair endpoint JSON shape.
|
|
85
|
+
// Verifies the SDK walks AllowedMsgAllowance → BasicAllowance correctly and
|
|
86
|
+
// surfaces spend_limit, expiration, allowed_messages, type_url flat.
|
|
87
|
+
section('queryFeeGrant (offline, fabricated LCD)');
|
|
88
|
+
import { createServer } from 'http';
|
|
89
|
+
import { once } from 'events';
|
|
90
|
+
|
|
91
|
+
async function withFakeLcd(responder, fn) {
|
|
92
|
+
const server = createServer((req, res) => {
|
|
93
|
+
try {
|
|
94
|
+
const out = responder(req);
|
|
95
|
+
res.writeHead(out.status, { 'Content-Type': 'application/json' });
|
|
96
|
+
res.end(JSON.stringify(out.body));
|
|
97
|
+
} catch (e) {
|
|
98
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
99
|
+
res.end(JSON.stringify({ error: String(e) }));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
server.listen(0, '127.0.0.1');
|
|
103
|
+
await once(server, 'listening');
|
|
104
|
+
const port = server.address().port;
|
|
105
|
+
try {
|
|
106
|
+
return await fn(`http://127.0.0.1:${port}`);
|
|
107
|
+
} finally {
|
|
108
|
+
server.close();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Force LCD-only: empty RPC_ENDPOINTS so queryFeeGrant falls through to the
|
|
113
|
+
// fake LCD server instead of hitting real mainnet RPC.
|
|
114
|
+
const _savedRpcEndpoints = RPC_ENDPOINTS.splice(0, RPC_ENDPOINTS.length);
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
// Case A: BasicAllowance — active grant
|
|
118
|
+
const grantA = {
|
|
119
|
+
status: 200,
|
|
120
|
+
body: {
|
|
121
|
+
allowance: {
|
|
122
|
+
granter: 'sent1granter',
|
|
123
|
+
grantee: 'sent1grantee',
|
|
124
|
+
allowance: {
|
|
125
|
+
'@type': '/cosmos.feegrant.v1beta1.BasicAllowance',
|
|
126
|
+
spend_limit: [{ denom: 'udvpn', amount: '5000000' }],
|
|
127
|
+
expiration: '2099-01-01T00:00:00Z',
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
await withFakeLcd(() => grantA, async (lcd) => {
|
|
133
|
+
const r = await queryFeeGrant(lcd, 'sent1granter', 'sent1grantee');
|
|
134
|
+
assert('BasicAllowance returns non-null', r != null);
|
|
135
|
+
if (r) {
|
|
136
|
+
// queryFeeGrant returns the raw wrapper { granter, grantee, allowance: {...} }.
|
|
137
|
+
const inner = r.allowance || r;
|
|
138
|
+
const typeUrl = inner['@type'] || inner.typeUrl || inner.type_url;
|
|
139
|
+
assert('BasicAllowance.@type is BasicAllowance',
|
|
140
|
+
typeUrl === '/cosmos.feegrant.v1beta1.BasicAllowance',
|
|
141
|
+
`got ${typeUrl}`);
|
|
142
|
+
const exp = inner.expiration || inner.expiresAt;
|
|
143
|
+
assert('BasicAllowance exposes expiration', exp != null, `got ${exp}`);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Case B: AllowedMsgAllowance — wraps BasicAllowance
|
|
148
|
+
const grantB = {
|
|
149
|
+
status: 200,
|
|
150
|
+
body: {
|
|
151
|
+
allowance: {
|
|
152
|
+
granter: 'sent1granter',
|
|
153
|
+
grantee: 'sent1grantee',
|
|
154
|
+
allowance: {
|
|
155
|
+
'@type': '/cosmos.feegrant.v1beta1.AllowedMsgAllowance',
|
|
156
|
+
allowance: {
|
|
157
|
+
'@type': '/cosmos.feegrant.v1beta1.BasicAllowance',
|
|
158
|
+
spend_limit: [{ denom: 'udvpn', amount: '2000000' }],
|
|
159
|
+
expiration: '2099-01-01T00:00:00Z',
|
|
160
|
+
},
|
|
161
|
+
allowed_messages: ['/sentinel.plan.v3.MsgStartSessionRequest'],
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
await withFakeLcd(() => grantB, async (lcd) => {
|
|
167
|
+
const r = await queryFeeGrant(lcd, 'sent1granter', 'sent1grantee');
|
|
168
|
+
assert('AllowedMsgAllowance returns non-null', r != null);
|
|
169
|
+
if (r) {
|
|
170
|
+
const inner = r.allowance || r;
|
|
171
|
+
const msgs = inner.allowed_messages || inner.allowedMessages;
|
|
172
|
+
assert('AllowedMsgAllowance surfaces allowed_messages',
|
|
173
|
+
Array.isArray(msgs) && msgs.length > 0,
|
|
174
|
+
`got ${JSON.stringify(msgs)}`);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Case C: 404 — no grant
|
|
179
|
+
const grantC = { status: 404, body: { code: 5, message: 'fee-grant not found' } };
|
|
180
|
+
await withFakeLcd(() => grantC, async (lcd) => {
|
|
181
|
+
const r = await queryFeeGrant(lcd, 'sent1granter', 'sent1grantee');
|
|
182
|
+
assert('404 returns null / exists=false',
|
|
183
|
+
r == null || r.exists === false,
|
|
184
|
+
`got ${JSON.stringify(r)}`);
|
|
185
|
+
});
|
|
186
|
+
} catch (e) {
|
|
187
|
+
fail++;
|
|
188
|
+
console.log(` FAIL queryFeeGrant offline harness threw: ${e.message}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ─── 5. Live LCD queries (opt-in) ────────────────────────────
|
|
192
|
+
if (process.env.E2E_LIVE === '1') {
|
|
193
|
+
section('Live LCD (E2E_LIVE=1) — SEQUENTIAL, no broadcast');
|
|
194
|
+
const granter = process.env.FEE_GRANTER;
|
|
195
|
+
const grantee = process.env.FEE_GRANTEE;
|
|
196
|
+
const planId = process.env.PLAN_ID;
|
|
197
|
+
if (!granter || !grantee || !planId) {
|
|
198
|
+
console.log(' skip — set FEE_GRANTER, FEE_GRANTEE, PLAN_ID to run');
|
|
199
|
+
} else {
|
|
200
|
+
const lcd = process.env.LCD_URL || 'https://lcd.sentinel.co';
|
|
201
|
+
try {
|
|
202
|
+
const g = await queryFeeGrant(lcd, granter, grantee);
|
|
203
|
+
assert('live queryFeeGrant returned without throwing', true,
|
|
204
|
+
`result: ${JSON.stringify(g)}`);
|
|
205
|
+
console.log(' grant:', g);
|
|
206
|
+
} catch (e) {
|
|
207
|
+
assert('live queryFeeGrant', false, e.message);
|
|
208
|
+
}
|
|
209
|
+
// 7s between chain touches per SDK CLAUDE.md
|
|
210
|
+
await new Promise(r => setTimeout(r, 7000));
|
|
211
|
+
try {
|
|
212
|
+
const p = await queryPlanDetails(planId, { lcdUrl: lcd });
|
|
213
|
+
assert('live queryPlanDetails returned without throwing', true);
|
|
214
|
+
if (p) {
|
|
215
|
+
assert('plan.provider looks like sentprov1',
|
|
216
|
+
typeof p.provider === 'string' && p.provider.startsWith('sentprov1'),
|
|
217
|
+
`got ${p.provider}`);
|
|
218
|
+
assert('plan.status is numeric-shaped',
|
|
219
|
+
typeof p.status === 'number' || /^\d+$/.test(String(p.status)),
|
|
220
|
+
`got ${p.status}`);
|
|
221
|
+
}
|
|
222
|
+
console.log(' plan:', p);
|
|
223
|
+
} catch (e) {
|
|
224
|
+
assert('live queryPlanDetails', false, e.message);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
section('Live LCD');
|
|
229
|
+
console.log(' skip — set E2E_LIVE=1 to run live mainnet queries');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ─── Report ──────────────────────────────────────────────────
|
|
233
|
+
console.log(`\n──────────────`);
|
|
234
|
+
console.log(` ${pass} pass, ${fail} fail`);
|
|
235
|
+
process.exit(fail === 0 ? 0 : 1);
|
|
@@ -205,10 +205,20 @@ if (balance.udvpn < 500_000) {
|
|
|
205
205
|
} else {
|
|
206
206
|
|
|
207
207
|
await t('3.1 shareSubscription — self-share 1 GB', async () => {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
208
|
+
let result;
|
|
209
|
+
try {
|
|
210
|
+
result = await shareSubscription(client, address, subId, address, BYTES_1GB);
|
|
211
|
+
console.log(`\n Share TX hash: ${result.txHash}`);
|
|
212
|
+
state.shareTxHash = result.txHash;
|
|
213
|
+
return result;
|
|
214
|
+
} catch (e) {
|
|
215
|
+
// insufficient bytes = existing allocation is smaller than requested. Not an SDK bug.
|
|
216
|
+
if (e.message?.includes('insufficient bytes') || e.message?.includes('already exists')) {
|
|
217
|
+
console.log(`\n Share rejected (${e.message.includes('insufficient bytes') ? 'insufficient allocation remaining — expected on repeated runs' : 'already exists'})`);
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
throw e;
|
|
221
|
+
}
|
|
212
222
|
});
|
|
213
223
|
|
|
214
224
|
console.log(' Waiting 7s for chain propagation...');
|
package/types/connection.d.ts
CHANGED
|
@@ -302,8 +302,12 @@ export class ConnectionState {
|
|
|
302
302
|
systemProxy: boolean;
|
|
303
303
|
/** Saved proxy state for restoration on disconnect */
|
|
304
304
|
savedProxyState: unknown;
|
|
305
|
-
/**
|
|
306
|
-
|
|
305
|
+
/**
|
|
306
|
+
* @internal Derived OfflineSigner wallet, used by _endSessionOnChain on disconnect.
|
|
307
|
+
* v37 (security): replaced `_mnemonic: string | null`. The raw BIP-39 phrase is no
|
|
308
|
+
* longer kept on long-lived state.
|
|
309
|
+
*/
|
|
310
|
+
_wallet: unknown;
|
|
307
311
|
/** Whether a tunnel is currently active */
|
|
308
312
|
readonly isConnected: boolean;
|
|
309
313
|
/** Remove this state from the global cleanup registry */
|