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.
Files changed (215) hide show
  1. package/CHANGELOG.md +446 -0
  2. package/LICENSE +21 -0
  3. package/README.md +75 -0
  4. package/ai-path/ADMIN-ELEVATION.md +116 -0
  5. package/ai-path/AI-MANIFESTO.md +185 -0
  6. package/ai-path/BREAKING.md +74 -0
  7. package/ai-path/CHECKLIST.md +619 -0
  8. package/ai-path/CONNECTION-STEPS.md +724 -0
  9. package/ai-path/DECISION-TREE.md +378 -0
  10. package/ai-path/DEPENDENCIES.md +459 -0
  11. package/ai-path/E2E-FLOW.md +1555 -0
  12. package/ai-path/FAILURES.md +403 -0
  13. package/ai-path/GUIDE.md +1217 -0
  14. package/ai-path/README.md +558 -0
  15. package/ai-path/SPLIT-TUNNEL.md +266 -0
  16. package/ai-path/cli.js +535 -0
  17. package/ai-path/connect.js +884 -0
  18. package/ai-path/discover.js +178 -0
  19. package/ai-path/environment.js +266 -0
  20. package/ai-path/errors.js +86 -0
  21. package/ai-path/examples/autonomous-agent.mjs +220 -0
  22. package/ai-path/examples/multi-region.mjs +174 -0
  23. package/ai-path/examples/one-shot.mjs +31 -0
  24. package/ai-path/index.js +60 -0
  25. package/ai-path/pricing.js +136 -0
  26. package/ai-path/recommend.js +413 -0
  27. package/ai-path/run-admin.vbs +25 -0
  28. package/ai-path/setup.js +291 -0
  29. package/ai-path/wallet.js +137 -0
  30. package/app-helpers.js +363 -0
  31. package/app-settings.js +95 -0
  32. package/app-types.js +267 -0
  33. package/audit.js +847 -0
  34. package/batch.js +293 -0
  35. package/bin/setup.js +376 -0
  36. package/chain/authz.js +109 -0
  37. package/chain/broadcast.js +472 -0
  38. package/chain/client.js +160 -0
  39. package/chain/fee-grants.js +305 -0
  40. package/chain/index.js +891 -0
  41. package/chain/lcd.js +313 -0
  42. package/chain/queries.js +547 -0
  43. package/chain/rpc.js +408 -0
  44. package/chain/wallet.js +141 -0
  45. package/cli/config.js +143 -0
  46. package/cli/index.js +463 -0
  47. package/cli/output.js +182 -0
  48. package/cli.js +491 -0
  49. package/client/index.js +251 -0
  50. package/client.js +271 -0
  51. package/config/index.js +255 -0
  52. package/connection/connect.js +849 -0
  53. package/connection/disconnect.js +180 -0
  54. package/connection/discovery.js +321 -0
  55. package/connection/index.js +76 -0
  56. package/connection/proxy.js +148 -0
  57. package/connection/resilience.js +428 -0
  58. package/connection/security.js +232 -0
  59. package/connection/state.js +369 -0
  60. package/connection/tunnel.js +691 -0
  61. package/consumer.js +132 -0
  62. package/cosmjs-setup.js +1884 -0
  63. package/defaults.js +366 -0
  64. package/disk-cache.js +107 -0
  65. package/dist/client.d.ts +108 -0
  66. package/dist/client.d.ts.map +1 -0
  67. package/dist/client.js +400 -0
  68. package/dist/client.js.map +1 -0
  69. package/dist/index.d.ts +8 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +8 -0
  72. package/dist/index.js.map +1 -0
  73. package/errors/index.js +112 -0
  74. package/errors.js +218 -0
  75. package/examples/README.md +64 -0
  76. package/examples/connect-direct.mjs +106 -0
  77. package/examples/connect-plan.mjs +125 -0
  78. package/examples/error-handling.mjs +109 -0
  79. package/examples/query-nodes.mjs +94 -0
  80. package/examples/wallet-basics.mjs +61 -0
  81. package/generated/amino/amino.ts +9 -0
  82. package/generated/cosmos/base/v1beta1/coin.ts +365 -0
  83. package/generated/cosmos_proto/cosmos.ts +323 -0
  84. package/generated/gogoproto/gogo.ts +9 -0
  85. package/generated/google/protobuf/descriptor.ts +7601 -0
  86. package/generated/google/protobuf/duration.ts +208 -0
  87. package/generated/google/protobuf/timestamp.ts +238 -0
  88. package/generated/sentinel/lease/v1/events.ts +924 -0
  89. package/generated/sentinel/lease/v1/lease.ts +292 -0
  90. package/generated/sentinel/lease/v1/msg.ts +949 -0
  91. package/generated/sentinel/lease/v1/params.ts +164 -0
  92. package/generated/sentinel/node/v3/events.ts +881 -0
  93. package/generated/sentinel/node/v3/msg.ts +1002 -0
  94. package/generated/sentinel/node/v3/node.ts +263 -0
  95. package/generated/sentinel/node/v3/params.ts +183 -0
  96. package/generated/sentinel/plan/v3/events.ts +675 -0
  97. package/generated/sentinel/plan/v3/msg.ts +1191 -0
  98. package/generated/sentinel/plan/v3/plan.ts +283 -0
  99. package/generated/sentinel/provider/v2/events.ts +171 -0
  100. package/generated/sentinel/provider/v2/msg.ts +480 -0
  101. package/generated/sentinel/provider/v2/params.ts +131 -0
  102. package/generated/sentinel/provider/v2/provider.ts +246 -0
  103. package/generated/sentinel/session/v3/events.ts +480 -0
  104. package/generated/sentinel/session/v3/msg.ts +616 -0
  105. package/generated/sentinel/session/v3/params.ts +260 -0
  106. package/generated/sentinel/session/v3/proof.ts +180 -0
  107. package/generated/sentinel/session/v3/session.ts +384 -0
  108. package/generated/sentinel/subscription/v3/events.ts +1181 -0
  109. package/generated/sentinel/subscription/v3/msg.ts +1305 -0
  110. package/generated/sentinel/subscription/v3/params.ts +167 -0
  111. package/generated/sentinel/subscription/v3/subscription.ts +315 -0
  112. package/generated/sentinel/types/v1/bandwidth.ts +124 -0
  113. package/generated/sentinel/types/v1/price.ts +149 -0
  114. package/generated/sentinel/types/v1/renewal.ts +87 -0
  115. package/generated/sentinel/types/v1/status.ts +54 -0
  116. package/generated/typeRegistry.ts +27 -0
  117. package/index.js +486 -0
  118. package/node-connect.js +3015 -0
  119. package/operator.js +134 -0
  120. package/package.json +113 -0
  121. package/plan-operations.js +199 -0
  122. package/preflight.js +352 -0
  123. package/pricing/index.js +262 -0
  124. package/proto/amino/amino.proto +84 -0
  125. package/proto/cosmos/base/v1beta1/coin.proto +61 -0
  126. package/proto/cosmos_proto/cosmos.proto +112 -0
  127. package/proto/gogoproto/gogo.proto +145 -0
  128. package/proto/google/api/annotations.proto +31 -0
  129. package/proto/google/api/http.proto +370 -0
  130. package/proto/google/protobuf/any.proto +106 -0
  131. package/proto/google/protobuf/duration.proto +115 -0
  132. package/proto/google/protobuf/timestamp.proto +145 -0
  133. package/proto/sentinel/lease/v1/events.proto +52 -0
  134. package/proto/sentinel/lease/v1/genesis.proto +15 -0
  135. package/proto/sentinel/lease/v1/lease.proto +25 -0
  136. package/proto/sentinel/lease/v1/msg.proto +62 -0
  137. package/proto/sentinel/lease/v1/params.proto +17 -0
  138. package/proto/sentinel/node/v3/events.proto +50 -0
  139. package/proto/sentinel/node/v3/genesis.proto +15 -0
  140. package/proto/sentinel/node/v3/msg.proto +63 -0
  141. package/proto/sentinel/node/v3/node.proto +27 -0
  142. package/proto/sentinel/node/v3/params.proto +21 -0
  143. package/proto/sentinel/node/v3/querier.proto +63 -0
  144. package/proto/sentinel/plan/v3/events.proto +41 -0
  145. package/proto/sentinel/plan/v3/genesis.proto +21 -0
  146. package/proto/sentinel/plan/v3/msg.proto +83 -0
  147. package/proto/sentinel/plan/v3/plan.proto +32 -0
  148. package/proto/sentinel/plan/v3/querier.proto +53 -0
  149. package/proto/sentinel/provider/v2/events.proto +16 -0
  150. package/proto/sentinel/provider/v2/genesis.proto +15 -0
  151. package/proto/sentinel/provider/v2/msg.proto +35 -0
  152. package/proto/sentinel/provider/v2/params.proto +17 -0
  153. package/proto/sentinel/provider/v2/provider.proto +24 -0
  154. package/proto/sentinel/provider/v3/genesis.proto +15 -0
  155. package/proto/sentinel/provider/v3/params.proto +13 -0
  156. package/proto/sentinel/session/v3/events.proto +30 -0
  157. package/proto/sentinel/session/v3/genesis.proto +15 -0
  158. package/proto/sentinel/session/v3/msg.proto +50 -0
  159. package/proto/sentinel/session/v3/params.proto +25 -0
  160. package/proto/sentinel/session/v3/proof.proto +25 -0
  161. package/proto/sentinel/session/v3/querier.proto +100 -0
  162. package/proto/sentinel/session/v3/session.proto +50 -0
  163. package/proto/sentinel/subscription/v2/allocation.proto +21 -0
  164. package/proto/sentinel/subscription/v2/payout.proto +22 -0
  165. package/proto/sentinel/subscription/v3/events.proto +65 -0
  166. package/proto/sentinel/subscription/v3/genesis.proto +17 -0
  167. package/proto/sentinel/subscription/v3/msg.proto +83 -0
  168. package/proto/sentinel/subscription/v3/params.proto +21 -0
  169. package/proto/sentinel/subscription/v3/subscription.proto +33 -0
  170. package/proto/sentinel/types/v1/bandwidth.proto +19 -0
  171. package/proto/sentinel/types/v1/price.proto +21 -0
  172. package/proto/sentinel/types/v1/renewal.proto +21 -0
  173. package/proto/sentinel/types/v1/status.proto +16 -0
  174. package/protocol/encoding.js +341 -0
  175. package/protocol/events.js +361 -0
  176. package/protocol/handshake.js +297 -0
  177. package/protocol/index.js +15 -0
  178. package/protocol/messages.js +346 -0
  179. package/protocol/plans.js +199 -0
  180. package/protocol/v2ray.js +268 -0
  181. package/protocol/v3.js +723 -0
  182. package/protocol/wireguard.js +125 -0
  183. package/security/index.js +132 -0
  184. package/session-manager.js +329 -0
  185. package/session-tracker.js +80 -0
  186. package/setup.js +376 -0
  187. package/speedtest/index.js +528 -0
  188. package/speedtest.js +567 -0
  189. package/src/client.ts +502 -0
  190. package/src/index.ts +20 -0
  191. package/state/index.js +347 -0
  192. package/state.js +516 -0
  193. package/test-all-chain-ops.js +493 -0
  194. package/test-all-logic.js +199 -0
  195. package/test-all-msg-types.js +292 -0
  196. package/test-every-connection.js +208 -0
  197. package/test-feegrant-connect.js +98 -0
  198. package/test-logic.js +148 -0
  199. package/test-mainnet.js +176 -0
  200. package/test-plan-lifecycle.js +335 -0
  201. package/tls-trust.js +132 -0
  202. package/tsconfig.build.json +20 -0
  203. package/tsconfig.json +34 -0
  204. package/types/chain.d.ts +746 -0
  205. package/types/connection.d.ts +425 -0
  206. package/types/errors.d.ts +174 -0
  207. package/types/index.d.ts +1380 -0
  208. package/types/nodes.d.ts +187 -0
  209. package/types/pricing.d.ts +156 -0
  210. package/types/protocol.d.ts +332 -0
  211. package/types/session.d.ts +236 -0
  212. package/types/settings.d.ts +192 -0
  213. package/v3protocol.js +1053 -0
  214. package/wallet/index.js +153 -0
  215. package/wireguard.js +307 -0
package/src/client.ts ADDED
@@ -0,0 +1,502 @@
1
+ /**
2
+ * BlueSentinelClient — Extends CosmJS SigningStargateClient with Sentinel query extensions.
3
+ *
4
+ * Follows the TKD Alex pattern: extend the ecosystem standard, don't replace it.
5
+ * Every CosmJS method works. Sentinel-specific additions are additive.
6
+ *
7
+ * Usage:
8
+ * const client = await BlueSentinelClient.connectWithSigner(rpcUrl, signer);
9
+ * const nodes = await client.sentinelQuery.nodes({ status: 1, limit: 100 });
10
+ * const balance = await client.getBalance(address, 'udvpn');
11
+ * const result = await client.signAndBroadcast(address, [msg], fee);
12
+ */
13
+
14
+ import {
15
+ SigningStargateClient,
16
+ StargateClient,
17
+ QueryClient,
18
+ type StargateClientOptions,
19
+ type SigningStargateClientOptions,
20
+ GasPrice,
21
+ } from '@cosmjs/stargate';
22
+ import { Tendermint37Client, type CometClient } from '@cosmjs/tendermint-rpc';
23
+ import type { OfflineSigner } from '@cosmjs/proto-signing';
24
+ import type { Coin } from '@cosmjs/amino';
25
+
26
+ // ─── Sentinel Types ─────────────────────────────────────────────────────────
27
+
28
+ export interface SentinelNode {
29
+ address: string;
30
+ gigabyte_prices: SentinelPrice[];
31
+ hourly_prices: SentinelPrice[];
32
+ remote_addrs: string[];
33
+ status: number;
34
+ }
35
+
36
+ export interface SentinelPrice {
37
+ denom: string;
38
+ base_value: string;
39
+ quote_value: string;
40
+ }
41
+
42
+ export interface SentinelSession {
43
+ id: bigint;
44
+ acc_address: string;
45
+ node_address: string;
46
+ download_bytes: string;
47
+ upload_bytes: string;
48
+ max_bytes: string;
49
+ status: number;
50
+ }
51
+
52
+ export interface PaginationOptions {
53
+ limit?: number;
54
+ key?: Uint8Array;
55
+ }
56
+
57
+ export interface NodeQueryOptions extends PaginationOptions {
58
+ status?: number;
59
+ }
60
+
61
+ // ─── Sentinel Query Extension ───────────────────────────────────────────────
62
+
63
+ export interface SentinelQueryExtension {
64
+ nodes(opts?: NodeQueryOptions): Promise<SentinelNode[]>;
65
+ node(address: string): Promise<SentinelNode | null>;
66
+ nodesForPlan(planId: number | bigint, opts?: NodeQueryOptions): Promise<SentinelNode[]>;
67
+ balance(address: string, denom?: string): Promise<Coin>;
68
+ sessionsForAccount(address: string, opts?: PaginationOptions): Promise<Uint8Array[]>;
69
+ subscriptionsForAccount(address: string, opts?: PaginationOptions): Promise<Uint8Array[]>;
70
+ plan(planId: number | bigint): Promise<Uint8Array | null>;
71
+ }
72
+
73
+ // ─── Protobuf Encoding Helpers ──────────────────────────────────────────────
74
+
75
+ function encodeVarint(value: number | bigint): Uint8Array {
76
+ let n = BigInt(value);
77
+ const bytes: number[] = [];
78
+ do {
79
+ let b = Number(n & 0x7fn);
80
+ n >>= 7n;
81
+ if (n > 0n) b |= 0x80;
82
+ bytes.push(b);
83
+ } while (n > 0n);
84
+ return new Uint8Array(bytes);
85
+ }
86
+
87
+ function concat(arrays: Uint8Array[]): Uint8Array {
88
+ const totalLen = arrays.reduce((sum, a) => sum + a.length, 0);
89
+ const result = new Uint8Array(totalLen);
90
+ let offset = 0;
91
+ for (const arr of arrays) {
92
+ result.set(arr, offset);
93
+ offset += arr.length;
94
+ }
95
+ return result;
96
+ }
97
+
98
+ function encodeString(fieldNum: number, str: string): Uint8Array {
99
+ if (!str) return new Uint8Array(0);
100
+ const encoder = new TextEncoder();
101
+ const b = encoder.encode(str);
102
+ const tag = encodeVarint((BigInt(fieldNum) << 3n) | 2n);
103
+ const len = encodeVarint(b.length);
104
+ return concat([tag, len, b]);
105
+ }
106
+
107
+ function encodeUint64(fieldNum: number, value: number | bigint): Uint8Array {
108
+ if (!value && value !== 0) return new Uint8Array(0);
109
+ const tag = encodeVarint((BigInt(fieldNum) << 3n) | 0n);
110
+ const val = encodeVarint(value);
111
+ return concat([tag, val]);
112
+ }
113
+
114
+ function encodeEmbedded(fieldNum: number, bytes: Uint8Array): Uint8Array {
115
+ if (!bytes || bytes.length === 0) return new Uint8Array(0);
116
+ const tag = encodeVarint((BigInt(fieldNum) << 3n) | 2n);
117
+ const len = encodeVarint(bytes.length);
118
+ return concat([tag, len, bytes]);
119
+ }
120
+
121
+ function encodePagination(opts: PaginationOptions = {}): Uint8Array {
122
+ const parts: Uint8Array[] = [];
123
+ if (opts.key) parts.push(encodeEmbedded(1, opts.key));
124
+ parts.push(encodeUint64(2, opts.limit ?? 100));
125
+ return concat(parts);
126
+ }
127
+
128
+ // ─── Protobuf Decoding ─────────────────────────────────────────────────────
129
+
130
+ interface ProtoField {
131
+ wireType: number;
132
+ value: bigint | Uint8Array;
133
+ }
134
+
135
+ function decodeProto(buf: Uint8Array): Record<number, ProtoField[]> {
136
+ const fields: Record<number, ProtoField[]> = {};
137
+ let i = 0;
138
+
139
+ while (i < buf.length) {
140
+ let tag = 0n;
141
+ let shift = 0n;
142
+ while (i < buf.length) {
143
+ const b = buf[i++];
144
+ tag |= BigInt(b & 0x7f) << shift;
145
+ shift += 7n;
146
+ if (!(b & 0x80)) break;
147
+ }
148
+
149
+ const fieldNum = Number(tag >> 3n);
150
+ const wireType = Number(tag & 0x7n);
151
+
152
+ if (wireType === 0) {
153
+ let val = 0n;
154
+ let s = 0n;
155
+ while (i < buf.length) {
156
+ const b = buf[i++];
157
+ val |= BigInt(b & 0x7f) << s;
158
+ s += 7n;
159
+ if (!(b & 0x80)) break;
160
+ }
161
+ if (!fields[fieldNum]) fields[fieldNum] = [];
162
+ fields[fieldNum].push({ wireType, value: val });
163
+ } else if (wireType === 2) {
164
+ let len = 0n;
165
+ let s = 0n;
166
+ while (i < buf.length) {
167
+ const b = buf[i++];
168
+ len |= BigInt(b & 0x7f) << s;
169
+ s += 7n;
170
+ if (!(b & 0x80)) break;
171
+ }
172
+ const numLen = Number(len);
173
+ const data = buf.slice(i, i + numLen);
174
+ i += numLen;
175
+ if (!fields[fieldNum]) fields[fieldNum] = [];
176
+ fields[fieldNum].push({ wireType, value: data });
177
+ } else if (wireType === 5) {
178
+ i += 4;
179
+ } else if (wireType === 1) {
180
+ i += 8;
181
+ }
182
+ }
183
+
184
+ return fields;
185
+ }
186
+
187
+ function decodeStr(data: Uint8Array): string {
188
+ return new TextDecoder().decode(data);
189
+ }
190
+
191
+ function decodePrice(fields: Record<number, ProtoField[]>): SentinelPrice {
192
+ return {
193
+ denom: fields[1]?.[0] ? decodeStr(fields[1][0].value as Uint8Array) : '',
194
+ base_value: fields[2]?.[0] ? decodeStr(fields[2][0].value as Uint8Array) : '0',
195
+ quote_value: fields[3]?.[0] ? decodeStr(fields[3][0].value as Uint8Array) : '0',
196
+ };
197
+ }
198
+
199
+ function decodeNode(fields: Record<number, ProtoField[]>): SentinelNode {
200
+ return {
201
+ address: fields[1]?.[0] ? decodeStr(fields[1][0].value as Uint8Array) : '',
202
+ gigabyte_prices: (fields[2] || []).map(f => decodePrice(decodeProto(f.value as Uint8Array))),
203
+ hourly_prices: (fields[3] || []).map(f => decodePrice(decodeProto(f.value as Uint8Array))),
204
+ remote_addrs: (fields[4] || []).map(f => decodeStr(f.value as Uint8Array)),
205
+ status: fields[6]?.[0] ? Number(fields[6][0].value) : 0,
206
+ };
207
+ }
208
+
209
+ // ─── Query-Only Client ──────────────────────────────────────────────────────
210
+
211
+ export class SentinelQueryClient extends StargateClient {
212
+ public readonly sentinelQuery: SentinelQueryExtension;
213
+ private readonly qc: QueryClient;
214
+
215
+ protected constructor(tmClient: CometClient) {
216
+ super(tmClient, {});
217
+ this.qc = QueryClient.withExtensions(tmClient as Tendermint37Client);
218
+ this.sentinelQuery = this.buildQueryExtension();
219
+ }
220
+
221
+ static override async connect(endpoint: string): Promise<SentinelQueryClient> {
222
+ const tmClient = await Tendermint37Client.connect(endpoint);
223
+ return new SentinelQueryClient(tmClient);
224
+ }
225
+
226
+ private buildQueryExtension(): SentinelQueryExtension {
227
+ const qc = this.qc;
228
+
229
+ return {
230
+ async nodes(opts: NodeQueryOptions = {}): Promise<SentinelNode[]> {
231
+ const request = concat([
232
+ encodeUint64(1, opts.status ?? 1),
233
+ encodeEmbedded(2, encodePagination({ limit: opts.limit ?? 500 })),
234
+ ]);
235
+ const response = await qc.queryAbci('/sentinel.node.v3.QueryService/QueryNodes', request);
236
+ const fields = decodeProto(new Uint8Array(response.value));
237
+ return (fields[1] || []).map(entry => decodeNode(decodeProto(entry.value as Uint8Array)));
238
+ },
239
+
240
+ async node(address: string): Promise<SentinelNode | null> {
241
+ try {
242
+ const response = await qc.queryAbci('/sentinel.node.v3.QueryService/QueryNode', encodeString(1, address));
243
+ const fields = decodeProto(new Uint8Array(response.value));
244
+ if (!fields[1]?.[0]) return null;
245
+ return decodeNode(decodeProto(fields[1][0].value as Uint8Array));
246
+ } catch {
247
+ return null;
248
+ }
249
+ },
250
+
251
+ async nodesForPlan(planId: number | bigint, opts: NodeQueryOptions = {}): Promise<SentinelNode[]> {
252
+ const request = concat([
253
+ encodeUint64(1, planId),
254
+ encodeUint64(2, opts.status ?? 1),
255
+ encodeEmbedded(3, encodePagination({ limit: opts.limit ?? 500 })),
256
+ ]);
257
+ const response = await qc.queryAbci('/sentinel.node.v3.QueryService/QueryNodesForPlan', request);
258
+ const fields = decodeProto(new Uint8Array(response.value));
259
+ return (fields[1] || []).map(entry => decodeNode(decodeProto(entry.value as Uint8Array)));
260
+ },
261
+
262
+ async balance(address: string, denom: string = 'udvpn'): Promise<Coin> {
263
+ const request = concat([encodeString(1, address), encodeString(2, denom)]);
264
+ const response = await qc.queryAbci('/cosmos.bank.v1beta1.Query/Balance', request);
265
+ const fields = decodeProto(new Uint8Array(response.value));
266
+ if (!fields[1]?.[0]) return { denom, amount: '0' };
267
+ const coinFields = decodeProto(fields[1][0].value as Uint8Array);
268
+ return {
269
+ denom: coinFields[1]?.[0] ? decodeStr(coinFields[1][0].value as Uint8Array) : denom,
270
+ amount: coinFields[2]?.[0] ? decodeStr(coinFields[2][0].value as Uint8Array) : '0',
271
+ };
272
+ },
273
+
274
+ async sessionsForAccount(address: string, opts: PaginationOptions = {}): Promise<Uint8Array[]> {
275
+ const request = concat([encodeString(1, address), encodeEmbedded(2, encodePagination(opts))]);
276
+ const response = await qc.queryAbci('/sentinel.session.v3.QueryService/QuerySessionsForAccount', request);
277
+ const fields = decodeProto(new Uint8Array(response.value));
278
+ return (fields[1] || []).map(entry => entry.value as Uint8Array);
279
+ },
280
+
281
+ async subscriptionsForAccount(address: string, opts: PaginationOptions = {}): Promise<Uint8Array[]> {
282
+ const request = concat([encodeString(1, address), encodeEmbedded(2, encodePagination(opts))]);
283
+ const response = await qc.queryAbci('/sentinel.subscription.v3.QueryService/QuerySubscriptionsForAccount', request);
284
+ const fields = decodeProto(new Uint8Array(response.value));
285
+ return (fields[1] || []).map(entry => entry.value as Uint8Array);
286
+ },
287
+
288
+ async plan(planId: number | bigint): Promise<Uint8Array | null> {
289
+ try {
290
+ const response = await qc.queryAbci('/sentinel.plan.v3.QueryService/QueryPlan', encodeUint64(1, planId));
291
+ const fields = decodeProto(new Uint8Array(response.value));
292
+ return (fields[1]?.[0]?.value as Uint8Array) || null;
293
+ } catch {
294
+ return null;
295
+ }
296
+ },
297
+ };
298
+ }
299
+ }
300
+
301
+ // ─── Signing Client (Full) ──────────────────────────────────────────────────
302
+
303
+ export class BlueSentinelClient extends SigningStargateClient {
304
+ public readonly sentinelQuery: SentinelQueryExtension;
305
+ private readonly qc: QueryClient;
306
+
307
+ protected constructor(
308
+ tmClient: CometClient,
309
+ signer: OfflineSigner,
310
+ options: SigningStargateClientOptions,
311
+ ) {
312
+ super(tmClient, signer, options);
313
+ this.qc = QueryClient.withExtensions(tmClient as Tendermint37Client);
314
+ this.sentinelQuery = this.buildQueryExtension();
315
+ }
316
+
317
+ /**
318
+ * Connect with a signer for full TX + query capabilities.
319
+ * This is the primary entry point — matches TKD Alex's pattern exactly.
320
+ */
321
+ static async connectWithSigner(
322
+ endpoint: string,
323
+ signer: OfflineSigner,
324
+ options?: SigningStargateClientOptions,
325
+ ): Promise<BlueSentinelClient> {
326
+ const tmClient = await Tendermint37Client.connect(endpoint);
327
+ return new BlueSentinelClient(tmClient, signer, {
328
+ gasPrice: GasPrice.fromString('0.1udvpn'),
329
+ ...options,
330
+ });
331
+ }
332
+
333
+ private buildQueryExtension(): SentinelQueryExtension {
334
+ const qc = this.qc;
335
+
336
+ return {
337
+ async nodes(opts: NodeQueryOptions = {}): Promise<SentinelNode[]> {
338
+ const request = concat([
339
+ encodeUint64(1, opts.status ?? 1),
340
+ encodeEmbedded(2, encodePagination({ limit: opts.limit ?? 500 })),
341
+ ]);
342
+ const response = await qc.queryAbci('/sentinel.node.v3.QueryService/QueryNodes', request);
343
+ const fields = decodeProto(new Uint8Array(response.value));
344
+ return (fields[1] || []).map(entry => decodeNode(decodeProto(entry.value as Uint8Array)));
345
+ },
346
+
347
+ async node(address: string): Promise<SentinelNode | null> {
348
+ try {
349
+ const response = await qc.queryAbci('/sentinel.node.v3.QueryService/QueryNode', encodeString(1, address));
350
+ const fields = decodeProto(new Uint8Array(response.value));
351
+ if (!fields[1]?.[0]) return null;
352
+ return decodeNode(decodeProto(fields[1][0].value as Uint8Array));
353
+ } catch {
354
+ return null;
355
+ }
356
+ },
357
+
358
+ async nodesForPlan(planId: number | bigint, opts: NodeQueryOptions = {}): Promise<SentinelNode[]> {
359
+ const request = concat([
360
+ encodeUint64(1, planId),
361
+ encodeUint64(2, opts.status ?? 1),
362
+ encodeEmbedded(3, encodePagination({ limit: opts.limit ?? 500 })),
363
+ ]);
364
+ const response = await qc.queryAbci('/sentinel.node.v3.QueryService/QueryNodesForPlan', request);
365
+ const fields = decodeProto(new Uint8Array(response.value));
366
+ return (fields[1] || []).map(entry => decodeNode(decodeProto(entry.value as Uint8Array)));
367
+ },
368
+
369
+ async balance(address: string, denom: string = 'udvpn'): Promise<Coin> {
370
+ const request = concat([encodeString(1, address), encodeString(2, denom)]);
371
+ const response = await qc.queryAbci('/cosmos.bank.v1beta1.Query/Balance', request);
372
+ const fields = decodeProto(new Uint8Array(response.value));
373
+ if (!fields[1]?.[0]) return { denom, amount: '0' };
374
+ const coinFields = decodeProto(fields[1][0].value as Uint8Array);
375
+ return {
376
+ denom: coinFields[1]?.[0] ? decodeStr(coinFields[1][0].value as Uint8Array) : denom,
377
+ amount: coinFields[2]?.[0] ? decodeStr(coinFields[2][0].value as Uint8Array) : '0',
378
+ };
379
+ },
380
+
381
+ async sessionsForAccount(address: string, opts: PaginationOptions = {}): Promise<Uint8Array[]> {
382
+ const request = concat([encodeString(1, address), encodeEmbedded(2, encodePagination(opts))]);
383
+ const response = await qc.queryAbci('/sentinel.session.v3.QueryService/QuerySessionsForAccount', request);
384
+ const fields = decodeProto(new Uint8Array(response.value));
385
+ return (fields[1] || []).map(entry => entry.value as Uint8Array);
386
+ },
387
+
388
+ async subscriptionsForAccount(address: string, opts: PaginationOptions = {}): Promise<Uint8Array[]> {
389
+ const request = concat([encodeString(1, address), encodeEmbedded(2, encodePagination(opts))]);
390
+ const response = await qc.queryAbci('/sentinel.subscription.v3.QueryService/QuerySubscriptionsForAccount', request);
391
+ const fields = decodeProto(new Uint8Array(response.value));
392
+ return (fields[1] || []).map(entry => entry.value as Uint8Array);
393
+ },
394
+
395
+ async plan(planId: number | bigint): Promise<Uint8Array | null> {
396
+ try {
397
+ const response = await qc.queryAbci('/sentinel.plan.v3.QueryService/QueryPlan', encodeUint64(1, planId));
398
+ const fields = decodeProto(new Uint8Array(response.value));
399
+ return (fields[1]?.[0]?.value as Uint8Array) || null;
400
+ } catch {
401
+ return null;
402
+ }
403
+ },
404
+ };
405
+ }
406
+ }
407
+
408
+ // ─── WebSocket Client ───────────────────────────────────────────────────────
409
+
410
+ export interface SentinelEvent {
411
+ type: string;
412
+ attributes: Array<{ key: string; value: string }>;
413
+ }
414
+
415
+ export type EventCallback = (event: SentinelEvent) => void;
416
+
417
+ /**
418
+ * Real-time event subscription via Tendermint WebSocket.
419
+ *
420
+ * Usage:
421
+ * const ws = await SentinelWsClient.connect('wss://rpc.sentinel.co/websocket');
422
+ * ws.subscribe("tm.event='Tx'", (event) => console.log(event));
423
+ * ws.disconnect();
424
+ */
425
+ export class SentinelWsClient {
426
+ private ws: any = null;
427
+ private listeners: Map<string, EventCallback[]> = new Map();
428
+ private nextId = 1;
429
+
430
+ private constructor(private readonly endpoint: string) {}
431
+
432
+ static async connect(endpoint: string): Promise<SentinelWsClient> {
433
+ const client = new SentinelWsClient(endpoint);
434
+ await client.open();
435
+ return client;
436
+ }
437
+
438
+ private open(): Promise<void> {
439
+ return new Promise((resolve, reject) => {
440
+ // Use globalThis.WebSocket (browser) or dynamic import of 'ws' (Node.js)
441
+ const WS = typeof globalThis !== 'undefined' && (globalThis as any).WebSocket
442
+ ? (globalThis as any).WebSocket
443
+ : null;
444
+ if (!WS) {
445
+ reject(new Error('WebSocket not available. In Node.js, install the "ws" package.'));
446
+ return;
447
+ }
448
+ this.ws = new WS(this.endpoint);
449
+ (this.ws as any).onopen = () => resolve();
450
+ (this.ws as any).onerror = (err: any) => reject(new Error(`WebSocket error: ${err}`));
451
+ (this.ws as any).onmessage = (msg: any) => this.handleMessage(msg.data as string);
452
+ });
453
+ }
454
+
455
+ /**
456
+ * Subscribe to chain events matching a Tendermint query.
457
+ *
458
+ * Common queries:
459
+ * - "tm.event='Tx'" — all transactions
460
+ * - "tm.event='Tx' AND sentinel.node.v3.EventCreateSession.session_id EXISTS" — session starts
461
+ * - "tm.event='Tx' AND sentinel.session.v3.EventEnd.session_id EXISTS" — session ends
462
+ */
463
+ subscribe(query: string, callback: EventCallback): void {
464
+ const id = this.nextId++;
465
+ if (!this.listeners.has(query)) {
466
+ this.listeners.set(query, []);
467
+ }
468
+ this.listeners.get(query)!.push(callback);
469
+
470
+ (this.ws as any)?.send(JSON.stringify({
471
+ jsonrpc: '2.0',
472
+ method: 'subscribe',
473
+ id,
474
+ params: { query },
475
+ }));
476
+ }
477
+
478
+ private handleMessage(data: string): void {
479
+ try {
480
+ const msg = JSON.parse(data);
481
+ if (msg.result?.events) {
482
+ // Broadcast to all listeners
483
+ for (const [, callbacks] of this.listeners) {
484
+ for (const cb of callbacks) {
485
+ const events = msg.result.events;
486
+ for (const [type, values] of Object.entries(events)) {
487
+ cb({ type, attributes: Array.isArray(values) ? values.map((v: string) => ({ key: type, value: v })) : [] });
488
+ }
489
+ }
490
+ }
491
+ }
492
+ } catch {
493
+ // Ignore parse errors
494
+ }
495
+ }
496
+
497
+ disconnect(): void {
498
+ this.ws?.close();
499
+ this.ws = null;
500
+ this.listeners.clear();
501
+ }
502
+ }
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Blue Sentinel SDK — TypeScript Layer
3
+ *
4
+ * New TypeScript code that extends CosmJS properly.
5
+ * Import from 'sentinel-dvpn-sdk/blue' for the TypeScript client.
6
+ */
7
+
8
+ export {
9
+ BlueSentinelClient,
10
+ SentinelQueryClient,
11
+ SentinelWsClient,
12
+ type SentinelNode,
13
+ type SentinelPrice,
14
+ type SentinelSession,
15
+ type SentinelQueryExtension,
16
+ type PaginationOptions,
17
+ type NodeQueryOptions,
18
+ type SentinelEvent,
19
+ type EventCallback,
20
+ } from './client.js';