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/CHANGELOG.md +34 -0
- package/batch.js +6 -10
- package/chain/authz.js +1 -9
- package/chain/fee-grants.js +53 -2
- package/chain/index.js +30 -167
- package/chain/queries.js +98 -12
- package/chain/rpc.js +169 -0
- package/client/index.js +1 -3
- package/connection/discovery.js +11 -11
- package/cosmjs-setup.js +68 -521
- package/defaults.js +121 -1
- package/index.js +13 -0
- package/node-connect.js +23 -14
- package/package.json +1 -1
- package/pricing/index.js +3 -26
- package/session-manager.js +6 -4
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,
|
|
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';
|
package/connection/discovery.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node Discovery — query, fetch, enrich, index, and score nodes.
|
|
3
3
|
*
|
|
4
|
-
* Handles
|
|
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
|
|
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
|
|
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, '
|
|
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 (
|
|
197
|
+
// ─── Full Node Catalog (RPC-first, no per-node status checks) ───────────────
|
|
198
198
|
|
|
199
199
|
/**
|
|
200
|
-
* Fetch ALL active nodes
|
|
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
|
|
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
|
-
'
|
|
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
|
|
278
|
+
* Enrich chain nodes with type/country/city by probing each node's status API.
|
|
279
279
|
*
|
|
280
|
-
* @param {Array} nodes - Raw
|
|
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
|