blue-js-sdk 2.2.0 → 2.4.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/chain/rpc.js CHANGED
@@ -722,6 +722,175 @@ function _decodeBasicAllowance(bytes) {
722
722
  return { spend_limit: spendLimit, expiration };
723
723
  }
724
724
 
725
+ /**
726
+ * Query all fee grants for a grantee via RPC.
727
+ * Returns array of grant objects matching LCD format.
728
+ *
729
+ * @param {{ queryClient: QueryClient }} client - From createRpcQueryClient()
730
+ * @param {string} grantee - Grantee address (sent1...)
731
+ * @param {{ limit?: number }} [opts]
732
+ * @returns {Promise<Array<{ granter: string, grantee: string, allowance: object }>>}
733
+ */
734
+ export async function rpcQueryFeeGrants(client, grantee, { limit = 100 } = {}) {
735
+ const path = '/cosmos.feegrant.v1beta1.Query/Allowances';
736
+ const request = concat([
737
+ encodeString(1, grantee),
738
+ encodeEmbedded(2, encodePagination({ limit })),
739
+ ]);
740
+
741
+ try {
742
+ const response = await abciQuery(client.queryClient, path, request);
743
+ const fields = decodeProto(new Uint8Array(response));
744
+ // Field 1 = repeated Grant (granter, grantee, allowance)
745
+ return (fields[1] || []).map(entry => _decodeFeeGrant(entry.value, grantee));
746
+ } catch {
747
+ return [];
748
+ }
749
+ }
750
+
751
+ /**
752
+ * Query all fee grants issued BY a granter via RPC.
753
+ *
754
+ * @param {{ queryClient: QueryClient }} client
755
+ * @param {string} granter - Granter address (sent1...)
756
+ * @param {{ limit?: number }} [opts]
757
+ * @returns {Promise<Array<{ granter: string, grantee: string, allowance: object }>>}
758
+ */
759
+ export async function rpcQueryFeeGrantsIssued(client, granter, { limit = 100 } = {}) {
760
+ const path = '/cosmos.feegrant.v1beta1.Query/AllowancesByGranter';
761
+ const request = concat([
762
+ encodeString(1, granter),
763
+ encodeEmbedded(2, encodePagination({ limit })),
764
+ ]);
765
+
766
+ try {
767
+ const response = await abciQuery(client.queryClient, path, request);
768
+ const fields = decodeProto(new Uint8Array(response));
769
+ // Field 1 = repeated Grant
770
+ return (fields[1] || []).map(entry => _decodeFeeGrant(entry.value, null, granter));
771
+ } catch {
772
+ return [];
773
+ }
774
+ }
775
+
776
+ /**
777
+ * Decode a cosmos.feegrant.v1beta1.Grant proto.
778
+ * Fields: 1=granter, 2=grantee, 3=allowance (Any)
779
+ */
780
+ function _decodeFeeGrant(bytes, defaultGrantee, defaultGranter) {
781
+ const grantFields = decodeProto(bytes);
782
+ const result = {
783
+ granter: grantFields[1]?.[0] ? decodeString(grantFields[1][0].value) : (defaultGranter || ''),
784
+ grantee: grantFields[2]?.[0] ? decodeString(grantFields[2][0].value) : (defaultGrantee || ''),
785
+ allowance: null,
786
+ };
787
+
788
+ if (!grantFields[3]?.[0]) return result;
789
+ const anyFields = decodeProto(grantFields[3][0].value);
790
+ const typeUrl = anyFields[1]?.[0] ? decodeString(anyFields[1][0].value) : '';
791
+ const innerBytes = anyFields[2]?.[0]?.value;
792
+
793
+ if (typeUrl.includes('AllowedMsgAllowance') && innerBytes) {
794
+ const amFields = decodeProto(innerBytes);
795
+ const allowedMessages = (amFields[2] || []).map(f => decodeString(f.value));
796
+ let innerAllowance = null;
797
+ if (amFields[1]?.[0]) {
798
+ const innerAnyFields = decodeProto(amFields[1][0].value);
799
+ const innerTypeUrl = innerAnyFields[1]?.[0] ? decodeString(innerAnyFields[1][0].value) : '';
800
+ const basicBytes = innerAnyFields[2]?.[0]?.value;
801
+ if (basicBytes) {
802
+ innerAllowance = _decodeBasicAllowance(basicBytes);
803
+ innerAllowance['@type'] = innerTypeUrl;
804
+ }
805
+ }
806
+ result.allowance = { '@type': typeUrl, allowance: innerAllowance, allowed_messages: allowedMessages };
807
+ } else if (typeUrl.includes('BasicAllowance') && innerBytes) {
808
+ result.allowance = _decodeBasicAllowance(innerBytes);
809
+ result.allowance['@type'] = typeUrl;
810
+ } else {
811
+ result.allowance = { '@type': typeUrl };
812
+ }
813
+
814
+ return result;
815
+ }
816
+
817
+ /**
818
+ * Query authz grants between granter and grantee via RPC.
819
+ *
820
+ * @param {{ queryClient: QueryClient }} client
821
+ * @param {string} granter - Granter address (sent1...)
822
+ * @param {string} grantee - Grantee address (sent1...)
823
+ * @param {{ msgTypeUrl?: string, limit?: number }} [opts]
824
+ * @returns {Promise<Array<{ granter: string, grantee: string, authorization: object, expiration: string|null }>>}
825
+ */
826
+ export async function rpcQueryAuthzGrants(client, granter, grantee, { msgTypeUrl, limit = 100 } = {}) {
827
+ const path = '/cosmos.authz.v1beta1.Query/Grants';
828
+ const parts = [
829
+ encodeString(1, granter),
830
+ encodeString(2, grantee),
831
+ ];
832
+ if (msgTypeUrl) parts.push(encodeString(3, msgTypeUrl));
833
+ parts.push(encodeEmbedded(4, encodePagination({ limit })));
834
+ const request = concat(parts);
835
+
836
+ try {
837
+ const response = await abciQuery(client.queryClient, path, request);
838
+ const fields = decodeProto(new Uint8Array(response));
839
+ // Field 1 = repeated GrantAuthorization
840
+ return (fields[1] || []).map(entry => {
841
+ const gf = decodeProto(entry.value);
842
+ const result = {
843
+ granter: gf[1]?.[0] ? decodeString(gf[1][0].value) : granter,
844
+ grantee: gf[2]?.[0] ? decodeString(gf[2][0].value) : grantee,
845
+ authorization: null,
846
+ expiration: gf[4]?.[0] ? decodeTimestamp(gf[4][0].value) : null,
847
+ };
848
+ // Field 3 = authorization (Any)
849
+ if (gf[3]?.[0]) {
850
+ const anyF = decodeProto(gf[3][0].value);
851
+ result.authorization = {
852
+ '@type': anyF[1]?.[0] ? decodeString(anyF[1][0].value) : '',
853
+ };
854
+ }
855
+ return result;
856
+ });
857
+ } catch {
858
+ return [];
859
+ }
860
+ }
861
+
862
+ /**
863
+ * Query a provider by address via RPC.
864
+ * Provider is still v2 on chain (NOT v3).
865
+ *
866
+ * @param {{ queryClient: QueryClient }} client
867
+ * @param {string} provAddress - sentprov1... address
868
+ * @returns {Promise<object|null>} Provider object or null
869
+ */
870
+ export async function rpcQueryProvider(client, provAddress) {
871
+ const path = '/sentinel.provider.v2.QueryService/QueryProvider';
872
+ const request = encodeString(1, provAddress);
873
+
874
+ try {
875
+ const response = await abciQuery(client.queryClient, path, request);
876
+ const fields = decodeProto(new Uint8Array(response));
877
+ // Field 1 = Provider
878
+ if (!fields[1]?.[0]) return null;
879
+ const pf = decodeProto(fields[1][0].value);
880
+ return {
881
+ address: pf[1]?.[0] ? decodeString(pf[1][0].value) : provAddress,
882
+ name: pf[2]?.[0] ? decodeString(pf[2][0].value) : '',
883
+ identity: pf[3]?.[0] ? decodeString(pf[3][0].value) : '',
884
+ website: pf[4]?.[0] ? decodeString(pf[4][0].value) : '',
885
+ description: pf[5]?.[0] ? decodeString(pf[5][0].value) : '',
886
+ status: pf[6]?.[0] ? Number(pf[6][0].value) : 0,
887
+ status_at: pf[7]?.[0] ? decodeTimestamp(pf[7][0].value) : null,
888
+ };
889
+ } catch {
890
+ return null;
891
+ }
892
+ }
893
+
725
894
  export async function rpcQueryBalance(client, address, denom = 'udvpn') {
726
895
  const path = '/cosmos.bank.v1beta1.Query/Balance';
727
896
  const request = concat([
package/client/index.js CHANGED
@@ -33,9 +33,7 @@ import {
33
33
  events as sdkEvents, ConnectionState,
34
34
  } from '../connection/index.js';
35
35
  import {
36
- createWallet, privKeyFromMnemonic, createClient, broadcast,
37
- createSafeBroadcaster, getBalance, findExistingSession, fetchActiveNodes,
38
- discoverPlanIds, resolveNodeUrl, lcd, MSG_TYPES,
36
+ createWallet, createClient, getBalance,
39
37
  } from '../chain/index.js';
40
38
  import { nodeStatusV3 } from '../protocol/index.js';
41
39
  import { createNodeHttpsAgent, clearKnownNode, clearAllKnownNodes, getKnownNode } from '../security/index.js';
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Node Discovery — query, fetch, enrich, index, and score nodes.
3
3
  *
4
- * Handles LCD queries for online nodes, caching, quality scoring,
5
- * and geographic indexing.
4
+ * Handles RPC-first queries (LCD fallback) for online nodes, caching,
5
+ * quality scoring, and geographic indexing.
6
6
  */
7
7
 
8
8
  import {
@@ -63,7 +63,7 @@ export function scoreNode(status) {
63
63
  // ─── Query Nodes ─────────────────────────────────────────────────────────────
64
64
 
65
65
  /**
66
- * Fetch active nodes from LCD and check which are actually online.
66
+ * Fetch active nodes via RPC-first (LCD fallback) and check which are actually online.
67
67
  * Returns array sorted by quality score (best first).
68
68
  *
69
69
  * Built-in quality scoring (from 400+ node tests):
@@ -119,12 +119,12 @@ async function _queryOnlineNodesImpl(options = {}) {
119
119
  const logFn = options.log || null;
120
120
  const brokenAddrs = new Set(BROKEN_NODES.map(n => n.address));
121
121
 
122
- // 1. Fetch ALL active nodes from LCD uses lcdPaginatedSafe (handles broken pagination)
122
+ // 1. Fetch ALL active nodes via RPC-first (falls back to LCD if RPC fails)
123
123
  let nodes = [];
124
124
  if (options.lcdUrl) {
125
125
  nodes = await fetchActiveNodes(options.lcdUrl);
126
126
  } else {
127
- const { result } = await tryWithFallback(LCD_ENDPOINTS, fetchActiveNodes, 'LCD node list');
127
+ const { result } = await tryWithFallback(LCD_ENDPOINTS, fetchActiveNodes, 'RPC-first node list');
128
128
  nodes = result;
129
129
  }
130
130
 
@@ -194,12 +194,12 @@ async function _queryOnlineNodesImpl(options = {}) {
194
194
  return online;
195
195
  }
196
196
 
197
- // ─── Full Node Catalog (LCD only, no per-node status checks) ────────────────
197
+ // ─── Full Node Catalog (RPC-first, no per-node status checks) ───────────────
198
198
 
199
199
  /**
200
- * Fetch ALL active nodes from the LCD. No per-node HTTP checks — instant.
200
+ * Fetch ALL active nodes via RPC-first (LCD fallback). No per-node HTTP checks — instant.
201
201
  *
202
- * Returns every node that accepts udvpn, with LCD data only:
202
+ * Returns every node that accepts udvpn, with chain data:
203
203
  * address, remote_url, gigabyte_prices, hourly_prices.
204
204
  *
205
205
  * Use this for: building node lists/maps, country pickers, price comparisons.
@@ -217,7 +217,7 @@ export async function fetchAllNodes(options = {}) {
217
217
  const { result } = await tryWithFallback(
218
218
  LCD_ENDPOINTS,
219
219
  async (url) => fetchActiveNodes(url),
220
- 'LCD full node list',
220
+ 'RPC-first full node list',
221
221
  );
222
222
  nodes = result;
223
223
  }
@@ -275,9 +275,9 @@ export function buildNodeIndex(nodes) {
275
275
  }
276
276
 
277
277
  /**
278
- * Enrich LCD nodes with type/country/city by probing each node's status API.
278
+ * Enrich chain nodes with type/country/city by probing each node's status API.
279
279
  *
280
- * @param {Array} nodes - Raw LCD nodes from fetchAllNodes()
280
+ * @param {Array} nodes - Raw chain nodes from fetchAllNodes()
281
281
  * @param {object} [options]
282
282
  * @param {number} [options.concurrency=30] - Parallel probes
283
283
  * @param {function} [options.onProgress] - Callback: ({ total, done, enriched }) => void