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
package/chain/client.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentinel SDK — Chain / Client Module
|
|
3
|
+
*
|
|
4
|
+
* CosmJS Registry building, SigningStargateClient creation,
|
|
5
|
+
* and all MSG_TYPES constants.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { createClient, buildRegistry, MSG_TYPES } from './chain/client.js';
|
|
9
|
+
* const client = await createClient(rpcUrl, wallet);
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Registry } from '@cosmjs/proto-signing';
|
|
13
|
+
import { SigningStargateClient, GasPrice, defaultRegistryTypes } from '@cosmjs/stargate';
|
|
14
|
+
|
|
15
|
+
// Protobuf encoders from v3protocol.js
|
|
16
|
+
import {
|
|
17
|
+
encodeMsgStartSession,
|
|
18
|
+
encodeMsgEndSession,
|
|
19
|
+
encodeMsgStartSubscription,
|
|
20
|
+
encodeMsgSubStartSession,
|
|
21
|
+
encodeMsgCancelSubscription,
|
|
22
|
+
encodeMsgRenewSubscription,
|
|
23
|
+
encodeMsgShareSubscription,
|
|
24
|
+
encodeMsgUpdateSubscription,
|
|
25
|
+
encodeMsgUpdateSession,
|
|
26
|
+
encodeMsgRegisterNode,
|
|
27
|
+
encodeMsgUpdateNodeDetails,
|
|
28
|
+
encodeMsgUpdateNodeStatus,
|
|
29
|
+
encodeMsgUpdatePlanDetails,
|
|
30
|
+
} from '../v3protocol.js';
|
|
31
|
+
|
|
32
|
+
// Plan/provider/lease encoders from plan-operations.js
|
|
33
|
+
import {
|
|
34
|
+
encodeMsgRegisterProvider,
|
|
35
|
+
encodeMsgUpdateProviderDetails,
|
|
36
|
+
encodeMsgUpdateProviderStatus,
|
|
37
|
+
encodeMsgCreatePlan,
|
|
38
|
+
encodeMsgUpdatePlanStatus,
|
|
39
|
+
encodeMsgLinkNode,
|
|
40
|
+
encodeMsgUnlinkNode,
|
|
41
|
+
encodeMsgPlanStartSession,
|
|
42
|
+
encodeMsgStartLease,
|
|
43
|
+
encodeMsgEndLease,
|
|
44
|
+
} from '../plan-operations.js';
|
|
45
|
+
|
|
46
|
+
import { GAS_PRICE } from '../defaults.js';
|
|
47
|
+
|
|
48
|
+
// ─── CosmJS Registry ─────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Adapter that wraps a manual protobuf encoder for CosmJS's Registry.
|
|
52
|
+
* CosmJS expects { fromPartial, encode, decode } — we only need encode.
|
|
53
|
+
*/
|
|
54
|
+
function makeMsgType(encodeFn) {
|
|
55
|
+
return {
|
|
56
|
+
fromPartial: (v) => v,
|
|
57
|
+
encode: (inst) => ({ finish: () => encodeFn(inst) }),
|
|
58
|
+
decode: () => ({}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Build a CosmJS Registry with ALL 14 Sentinel message types registered.
|
|
64
|
+
* This is required for signAndBroadcast to encode Sentinel-specific messages.
|
|
65
|
+
*/
|
|
66
|
+
export function buildRegistry() {
|
|
67
|
+
return new Registry([
|
|
68
|
+
...defaultRegistryTypes,
|
|
69
|
+
// Direct node session (v3protocol.js)
|
|
70
|
+
['/sentinel.node.v3.MsgStartSessionRequest', makeMsgType(encodeMsgStartSession)],
|
|
71
|
+
// End session (v3protocol.js)
|
|
72
|
+
['/sentinel.session.v3.MsgCancelSessionRequest', makeMsgType(encodeMsgEndSession)],
|
|
73
|
+
// Subscription (v3protocol.js)
|
|
74
|
+
['/sentinel.subscription.v3.MsgStartSubscriptionRequest', makeMsgType(encodeMsgStartSubscription)],
|
|
75
|
+
['/sentinel.subscription.v3.MsgStartSessionRequest', makeMsgType(encodeMsgSubStartSession)],
|
|
76
|
+
// Plan (plan-operations.js)
|
|
77
|
+
['/sentinel.plan.v3.MsgStartSessionRequest', makeMsgType(encodeMsgPlanStartSession)],
|
|
78
|
+
['/sentinel.plan.v3.MsgCreatePlanRequest', makeMsgType(encodeMsgCreatePlan)],
|
|
79
|
+
['/sentinel.plan.v3.MsgLinkNodeRequest', makeMsgType(encodeMsgLinkNode)],
|
|
80
|
+
['/sentinel.plan.v3.MsgUnlinkNodeRequest', makeMsgType(encodeMsgUnlinkNode)],
|
|
81
|
+
['/sentinel.plan.v3.MsgUpdatePlanStatusRequest', makeMsgType(encodeMsgUpdatePlanStatus)],
|
|
82
|
+
// Provider (plan-operations.js)
|
|
83
|
+
['/sentinel.provider.v3.MsgRegisterProviderRequest', makeMsgType(encodeMsgRegisterProvider)],
|
|
84
|
+
['/sentinel.provider.v3.MsgUpdateProviderDetailsRequest', makeMsgType(encodeMsgUpdateProviderDetails)],
|
|
85
|
+
['/sentinel.provider.v3.MsgUpdateProviderStatusRequest', makeMsgType(encodeMsgUpdateProviderStatus)],
|
|
86
|
+
// Plan details update (v3 — NEW, from sentinel-go-sdk)
|
|
87
|
+
['/sentinel.plan.v3.MsgUpdatePlanDetailsRequest', makeMsgType(encodeMsgUpdatePlanDetails)],
|
|
88
|
+
// Lease (plan-operations.js)
|
|
89
|
+
['/sentinel.lease.v1.MsgStartLeaseRequest', makeMsgType(encodeMsgStartLease)],
|
|
90
|
+
['/sentinel.lease.v1.MsgEndLeaseRequest', makeMsgType(encodeMsgEndLease)],
|
|
91
|
+
// Subscription management (v3 — from sentinel-go-sdk)
|
|
92
|
+
['/sentinel.subscription.v3.MsgCancelSubscriptionRequest', makeMsgType(encodeMsgCancelSubscription)],
|
|
93
|
+
['/sentinel.subscription.v3.MsgRenewSubscriptionRequest', makeMsgType(encodeMsgRenewSubscription)],
|
|
94
|
+
['/sentinel.subscription.v3.MsgShareSubscriptionRequest', makeMsgType(encodeMsgShareSubscription)],
|
|
95
|
+
['/sentinel.subscription.v3.MsgUpdateSubscriptionRequest', makeMsgType(encodeMsgUpdateSubscription)],
|
|
96
|
+
// Session management (v3)
|
|
97
|
+
['/sentinel.session.v3.MsgUpdateSessionRequest', makeMsgType(encodeMsgUpdateSession)],
|
|
98
|
+
// Node operator (v3 — for node operators, NOT consumer apps)
|
|
99
|
+
['/sentinel.node.v3.MsgRegisterNodeRequest', makeMsgType(encodeMsgRegisterNode)],
|
|
100
|
+
['/sentinel.node.v3.MsgUpdateNodeDetailsRequest', makeMsgType(encodeMsgUpdateNodeDetails)],
|
|
101
|
+
['/sentinel.node.v3.MsgUpdateNodeStatusRequest', makeMsgType(encodeMsgUpdateNodeStatus)],
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ─── Signing Client ──────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Create a SigningStargateClient connected to Sentinel RPC.
|
|
109
|
+
* Gas price: from defaults.js GAS_PRICE (chain minimum).
|
|
110
|
+
*/
|
|
111
|
+
export async function createClient(rpcUrl, wallet) {
|
|
112
|
+
return SigningStargateClient.connectWithSigner(rpcUrl, wallet, {
|
|
113
|
+
gasPrice: GasPrice.fromString(GAS_PRICE),
|
|
114
|
+
registry: buildRegistry(),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ─── All Type URL Constants ──────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
export const MSG_TYPES = {
|
|
121
|
+
// Direct node session
|
|
122
|
+
START_SESSION: '/sentinel.node.v3.MsgStartSessionRequest',
|
|
123
|
+
END_SESSION: '/sentinel.session.v3.MsgCancelSessionRequest',
|
|
124
|
+
// Subscription
|
|
125
|
+
START_SUBSCRIPTION: '/sentinel.subscription.v3.MsgStartSubscriptionRequest',
|
|
126
|
+
SUB_START_SESSION: '/sentinel.subscription.v3.MsgStartSessionRequest',
|
|
127
|
+
// Plan
|
|
128
|
+
PLAN_START_SESSION: '/sentinel.plan.v3.MsgStartSessionRequest',
|
|
129
|
+
CREATE_PLAN: '/sentinel.plan.v3.MsgCreatePlanRequest',
|
|
130
|
+
UPDATE_PLAN_STATUS: '/sentinel.plan.v3.MsgUpdatePlanStatusRequest',
|
|
131
|
+
LINK_NODE: '/sentinel.plan.v3.MsgLinkNodeRequest',
|
|
132
|
+
UNLINK_NODE: '/sentinel.plan.v3.MsgUnlinkNodeRequest',
|
|
133
|
+
// Provider
|
|
134
|
+
REGISTER_PROVIDER: '/sentinel.provider.v3.MsgRegisterProviderRequest',
|
|
135
|
+
UPDATE_PROVIDER: '/sentinel.provider.v3.MsgUpdateProviderDetailsRequest',
|
|
136
|
+
UPDATE_PROVIDER_STATUS: '/sentinel.provider.v3.MsgUpdateProviderStatusRequest',
|
|
137
|
+
// Plan details update (v3 — NEW)
|
|
138
|
+
UPDATE_PLAN_DETAILS: '/sentinel.plan.v3.MsgUpdatePlanDetailsRequest',
|
|
139
|
+
// Lease
|
|
140
|
+
START_LEASE: '/sentinel.lease.v1.MsgStartLeaseRequest',
|
|
141
|
+
END_LEASE: '/sentinel.lease.v1.MsgEndLeaseRequest',
|
|
142
|
+
// Subscription management (v3)
|
|
143
|
+
CANCEL_SUBSCRIPTION: '/sentinel.subscription.v3.MsgCancelSubscriptionRequest',
|
|
144
|
+
RENEW_SUBSCRIPTION: '/sentinel.subscription.v3.MsgRenewSubscriptionRequest',
|
|
145
|
+
SHARE_SUBSCRIPTION: '/sentinel.subscription.v3.MsgShareSubscriptionRequest',
|
|
146
|
+
UPDATE_SUBSCRIPTION: '/sentinel.subscription.v3.MsgUpdateSubscriptionRequest',
|
|
147
|
+
// Session management (v3)
|
|
148
|
+
UPDATE_SESSION: '/sentinel.session.v3.MsgUpdateSessionRequest',
|
|
149
|
+
// Node operator (v3)
|
|
150
|
+
REGISTER_NODE: '/sentinel.node.v3.MsgRegisterNodeRequest',
|
|
151
|
+
UPDATE_NODE_DETAILS: '/sentinel.node.v3.MsgUpdateNodeDetailsRequest',
|
|
152
|
+
UPDATE_NODE_STATUS: '/sentinel.node.v3.MsgUpdateNodeStatusRequest',
|
|
153
|
+
// Cosmos FeeGrant
|
|
154
|
+
GRANT_FEE_ALLOWANCE: '/cosmos.feegrant.v1beta1.MsgGrantAllowance',
|
|
155
|
+
REVOKE_FEE_ALLOWANCE: '/cosmos.feegrant.v1beta1.MsgRevokeAllowance',
|
|
156
|
+
// Cosmos Authz
|
|
157
|
+
AUTHZ_GRANT: '/cosmos.authz.v1beta1.MsgGrant',
|
|
158
|
+
AUTHZ_REVOKE: '/cosmos.authz.v1beta1.MsgRevoke',
|
|
159
|
+
AUTHZ_EXEC: '/cosmos.authz.v1beta1.MsgExec',
|
|
160
|
+
};
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentinel SDK — Chain / Fee Grants Module
|
|
3
|
+
*
|
|
4
|
+
* FeeGrant message builders, queries, monitoring, and workflow helpers.
|
|
5
|
+
* Gas-free UX: granter pays fees for grantee's transactions.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { buildFeeGrantMsg, queryFeeGrants, monitorFeeGrants } from './chain/fee-grants.js';
|
|
9
|
+
* const msg = buildFeeGrantMsg(serviceAddr, userAddr, { spendLimit: 5000000 });
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { EventEmitter } from 'events';
|
|
13
|
+
import { protoString, protoInt64, protoEmbedded } from '../v3protocol.js';
|
|
14
|
+
import { LCD_ENDPOINTS } from '../defaults.js';
|
|
15
|
+
import { ValidationError, ErrorCodes } from '../errors.js';
|
|
16
|
+
import { lcd, lcdPaginatedSafe, lcdQueryAll } from './lcd.js';
|
|
17
|
+
import { isSameKey } from './wallet.js';
|
|
18
|
+
import { queryPlanSubscribers } from './queries.js';
|
|
19
|
+
|
|
20
|
+
// ─── Protobuf Helpers for FeeGrant ──────────────────────────────────────────
|
|
21
|
+
// Uses the same manual protobuf encoding as Sentinel types — no codegen needed.
|
|
22
|
+
|
|
23
|
+
function encodeCoin(denom, amount) {
|
|
24
|
+
return Buffer.concat([protoString(1, denom), protoString(2, String(amount))]);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function encodeTimestamp(date) {
|
|
28
|
+
const ms = date.getTime();
|
|
29
|
+
if (Number.isNaN(ms)) throw new ValidationError(ErrorCodes.INVALID_OPTIONS, 'encodeTimestamp(): invalid date', { date });
|
|
30
|
+
const seconds = BigInt(Math.floor(ms / 1000));
|
|
31
|
+
return Buffer.concat([protoInt64(1, seconds)]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function encodeBasicAllowance(spendLimit, expiration) {
|
|
35
|
+
const parts = [];
|
|
36
|
+
if (spendLimit != null && spendLimit !== false) {
|
|
37
|
+
const coins = Array.isArray(spendLimit) ? spendLimit : [{ denom: 'udvpn', amount: String(spendLimit) }];
|
|
38
|
+
for (const coin of coins) {
|
|
39
|
+
parts.push(protoEmbedded(1, encodeCoin(coin.denom || 'udvpn', coin.amount)));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (expiration) {
|
|
43
|
+
parts.push(protoEmbedded(2, encodeTimestamp(expiration instanceof Date ? expiration : new Date(expiration))));
|
|
44
|
+
}
|
|
45
|
+
return Buffer.concat(parts);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function encodeAllowedMsgAllowance(innerTypeUrl, innerBytes, allowedMessages) {
|
|
49
|
+
const parts = [protoEmbedded(1, encodeAny(innerTypeUrl, innerBytes))];
|
|
50
|
+
for (const msg of allowedMessages) {
|
|
51
|
+
parts.push(protoString(2, msg));
|
|
52
|
+
}
|
|
53
|
+
return Buffer.concat(parts);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function encodeAny(typeUrl, valueBytes) {
|
|
57
|
+
return Buffer.concat([
|
|
58
|
+
protoString(1, typeUrl),
|
|
59
|
+
protoEmbedded(2, valueBytes),
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─── FeeGrant (cosmos.feegrant.v1beta1) ─────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Build a MsgGrantAllowance message.
|
|
67
|
+
* @param {string} granter - Address paying fees (sent1...)
|
|
68
|
+
* @param {string} grantee - Address receiving fee grant (sent1...)
|
|
69
|
+
* @param {object} opts
|
|
70
|
+
* @param {number|Array} opts.spendLimit - Max spend in udvpn (number) or [{denom, amount}]
|
|
71
|
+
* @param {Date|string} opts.expiration - Optional expiry date
|
|
72
|
+
* @param {string[]} opts.allowedMessages - Optional: restrict to specific msg types (uses AllowedMsgAllowance)
|
|
73
|
+
*/
|
|
74
|
+
export function buildFeeGrantMsg(granter, grantee, opts = {}) {
|
|
75
|
+
const { spendLimit, expiration, allowedMessages } = opts;
|
|
76
|
+
const basicBytes = encodeBasicAllowance(spendLimit, expiration);
|
|
77
|
+
|
|
78
|
+
let allowanceTypeUrl, allowanceBytes;
|
|
79
|
+
if (allowedMessages?.length) {
|
|
80
|
+
allowanceTypeUrl = '/cosmos.feegrant.v1beta1.AllowedMsgAllowance';
|
|
81
|
+
allowanceBytes = encodeAllowedMsgAllowance(
|
|
82
|
+
'/cosmos.feegrant.v1beta1.BasicAllowance', basicBytes, allowedMessages
|
|
83
|
+
);
|
|
84
|
+
} else {
|
|
85
|
+
allowanceTypeUrl = '/cosmos.feegrant.v1beta1.BasicAllowance';
|
|
86
|
+
allowanceBytes = basicBytes;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// MsgGrantAllowance: field 1=granter, field 2=grantee, field 3=allowance(Any)
|
|
90
|
+
return {
|
|
91
|
+
typeUrl: '/cosmos.feegrant.v1beta1.MsgGrantAllowance',
|
|
92
|
+
value: { granter, grantee, allowance: { typeUrl: allowanceTypeUrl, value: Uint8Array.from(allowanceBytes) } },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Build a MsgRevokeAllowance message.
|
|
98
|
+
*/
|
|
99
|
+
export function buildRevokeFeeGrantMsg(granter, grantee) {
|
|
100
|
+
return {
|
|
101
|
+
typeUrl: '/cosmos.feegrant.v1beta1.MsgRevokeAllowance',
|
|
102
|
+
value: { granter, grantee },
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Query fee grants given to a grantee.
|
|
108
|
+
* @returns {Promise<Array>} Array of allowance objects
|
|
109
|
+
*/
|
|
110
|
+
export async function queryFeeGrants(lcdUrl, grantee) {
|
|
111
|
+
const { items } = await lcdPaginatedSafe(lcdUrl, `/cosmos/feegrant/v1beta1/allowances/${grantee}`, 'allowances');
|
|
112
|
+
return items;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Query fee grants issued BY an address (where addr is the granter).
|
|
117
|
+
* @param {string} lcdUrl
|
|
118
|
+
* @param {string} granter - Address that issued the grants
|
|
119
|
+
* @returns {Promise<Array>}
|
|
120
|
+
*/
|
|
121
|
+
export async function queryFeeGrantsIssued(lcdUrl, granter) {
|
|
122
|
+
const { items } = await lcdPaginatedSafe(lcdUrl, `/cosmos/feegrant/v1beta1/issued/${granter}`, 'allowances');
|
|
123
|
+
return items;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Query a specific fee grant between granter and grantee.
|
|
128
|
+
* @returns {Promise<object|null>} Allowance object or null
|
|
129
|
+
*/
|
|
130
|
+
export async function queryFeeGrant(lcdUrl, granter, grantee) {
|
|
131
|
+
try {
|
|
132
|
+
const data = await lcd(lcdUrl, `/cosmos/feegrant/v1beta1/allowance/${granter}/${grantee}`);
|
|
133
|
+
return data.allowance || null;
|
|
134
|
+
} catch { return null; } // 404 = no grant
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ─── Fee Grant Workflow Helpers (v25b) ────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Grant fee allowance to all plan subscribers who don't already have one.
|
|
141
|
+
* Filters out self-grants (granter === grantee) and already-granted addresses.
|
|
142
|
+
*
|
|
143
|
+
* @param {number|string} planId
|
|
144
|
+
* @param {object} opts
|
|
145
|
+
* @param {string} opts.granterAddress - Who pays fees (typically plan owner)
|
|
146
|
+
* @param {string} opts.lcdUrl - LCD endpoint
|
|
147
|
+
* @param {object} [opts.grantOpts] - Options for buildFeeGrantMsg (spendLimit, expiration, allowedMessages)
|
|
148
|
+
* @returns {Promise<{ msgs: Array, skipped: string[], newGrants: string[] }>} Messages ready for broadcast
|
|
149
|
+
*/
|
|
150
|
+
export async function grantPlanSubscribers(planId, opts = {}) {
|
|
151
|
+
const { granterAddress, lcdUrl, grantOpts = {} } = opts;
|
|
152
|
+
if (!granterAddress) throw new ValidationError(ErrorCodes.INVALID_OPTIONS, 'granterAddress is required');
|
|
153
|
+
|
|
154
|
+
// Get subscribers
|
|
155
|
+
const { subscribers } = await queryPlanSubscribers(planId, { lcdUrl });
|
|
156
|
+
|
|
157
|
+
// Get existing grants ISSUED BY granter (not grants received)
|
|
158
|
+
const existingGrants = await queryFeeGrantsIssued(lcdUrl || LCD_ENDPOINTS[0].url, granterAddress);
|
|
159
|
+
const alreadyGranted = new Set(existingGrants.map(g => g.grantee));
|
|
160
|
+
|
|
161
|
+
const msgs = [];
|
|
162
|
+
const skipped = [];
|
|
163
|
+
const newGrants = [];
|
|
164
|
+
|
|
165
|
+
const now = new Date();
|
|
166
|
+
// Deduplicate by address and filter active+non-expired
|
|
167
|
+
const seen = new Set();
|
|
168
|
+
for (const sub of subscribers) {
|
|
169
|
+
const addr = sub.acc_address || sub.address;
|
|
170
|
+
if (!addr || seen.has(addr)) continue;
|
|
171
|
+
seen.add(addr);
|
|
172
|
+
// Skip self-grant (chain rejects granter === grantee)
|
|
173
|
+
if (addr === granterAddress || isSameKey(addr, granterAddress)) { skipped.push(addr); continue; }
|
|
174
|
+
// Skip inactive or expired
|
|
175
|
+
if (sub.status && sub.status !== 'active') { skipped.push(addr); continue; }
|
|
176
|
+
if (sub.inactive_at && new Date(sub.inactive_at) <= now) { skipped.push(addr); continue; }
|
|
177
|
+
// Skip already granted
|
|
178
|
+
if (alreadyGranted.has(addr)) { skipped.push(addr); continue; }
|
|
179
|
+
msgs.push(buildFeeGrantMsg(granterAddress, addr, grantOpts));
|
|
180
|
+
newGrants.push(addr);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return { msgs, skipped, newGrants };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Find fee grants expiring within N days.
|
|
188
|
+
*
|
|
189
|
+
* @param {string} lcdUrl - LCD endpoint
|
|
190
|
+
* @param {string} granteeOrGranter - Address to check grants for
|
|
191
|
+
* @param {number} withinDays - Check grants expiring within this many days (default: 7)
|
|
192
|
+
* @param {'grantee'|'granter'} [role='grantee'] - Whether to check as grantee or granter
|
|
193
|
+
* @returns {Promise<Array<{ granter: string, grantee: string, expiresAt: Date|null, daysLeft: number|null }>>}
|
|
194
|
+
*/
|
|
195
|
+
export async function getExpiringGrants(lcdUrl, granteeOrGranter, withinDays = 7, role = 'grantee') {
|
|
196
|
+
const grants = role === 'grantee'
|
|
197
|
+
? await queryFeeGrants(lcdUrl, granteeOrGranter)
|
|
198
|
+
: await queryFeeGrantsIssued(lcdUrl, granteeOrGranter);
|
|
199
|
+
|
|
200
|
+
const now = Date.now();
|
|
201
|
+
const cutoff = now + withinDays * 24 * 60 * 60_000;
|
|
202
|
+
const expiring = [];
|
|
203
|
+
|
|
204
|
+
for (const g of grants) {
|
|
205
|
+
// Fee grant allowances have complex nested @type structures:
|
|
206
|
+
// BasicAllowance: { expiration }
|
|
207
|
+
// PeriodicAllowance: { basic: { expiration } }
|
|
208
|
+
// AllowedMsgAllowance: { allowance: { expiration } or allowance: { basic: { expiration } } }
|
|
209
|
+
const a = g.allowance || {};
|
|
210
|
+
const inner = a.allowance || a; // unwrap AllowedMsgAllowance
|
|
211
|
+
const expStr = inner.expiration || inner.basic?.expiration || a.expiration || a.basic?.expiration;
|
|
212
|
+
if (!expStr) continue; // no expiry set
|
|
213
|
+
const expiresAt = new Date(expStr);
|
|
214
|
+
if (expiresAt.getTime() <= cutoff) {
|
|
215
|
+
expiring.push({
|
|
216
|
+
granter: g.granter,
|
|
217
|
+
grantee: g.grantee,
|
|
218
|
+
expiresAt,
|
|
219
|
+
daysLeft: Math.max(0, Math.round((expiresAt.getTime() - now) / (24 * 60 * 60_000))),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return expiring;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Revoke and re-grant expiring fee grants.
|
|
228
|
+
*
|
|
229
|
+
* @param {string} lcdUrl
|
|
230
|
+
* @param {string} granterAddress
|
|
231
|
+
* @param {number} withinDays - Renew grants expiring within N days
|
|
232
|
+
* @param {object} [grantOpts] - Options for new grants (spendLimit, expiration, allowedMessages)
|
|
233
|
+
* @returns {Promise<{ msgs: Array, renewed: string[] }>} Messages ready for broadcast
|
|
234
|
+
*/
|
|
235
|
+
export async function renewExpiringGrants(lcdUrl, granterAddress, withinDays = 7, grantOpts = {}) {
|
|
236
|
+
const expiring = await getExpiringGrants(lcdUrl, granterAddress, withinDays, 'granter');
|
|
237
|
+
const msgs = [];
|
|
238
|
+
const renewed = [];
|
|
239
|
+
|
|
240
|
+
for (const g of expiring) {
|
|
241
|
+
if (g.grantee === granterAddress) continue; // skip self
|
|
242
|
+
msgs.push(buildRevokeFeeGrantMsg(granterAddress, g.grantee));
|
|
243
|
+
msgs.push(buildFeeGrantMsg(granterAddress, g.grantee, grantOpts));
|
|
244
|
+
renewed.push(g.grantee);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return { msgs, renewed };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ─── Fee Grant Monitoring (v25b) ─────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Monitor fee grants for expiry. Returns an EventEmitter that checks grants on interval.
|
|
254
|
+
*
|
|
255
|
+
* @param {object} opts
|
|
256
|
+
* @param {string} opts.lcdUrl - LCD endpoint
|
|
257
|
+
* @param {string} opts.address - Address to monitor (as granter)
|
|
258
|
+
* @param {number} [opts.checkIntervalMs] - Check interval (default: 6 hours)
|
|
259
|
+
* @param {number} [opts.warnDays] - Emit 'expiring' when grant expires within N days (default: 7)
|
|
260
|
+
* @param {boolean} [opts.autoRenew] - Auto-revoke+re-grant expiring grants (default: false)
|
|
261
|
+
* @param {object} [opts.grantOpts] - Options for renewed grants
|
|
262
|
+
* @returns {EventEmitter} Emits 'expiring' and 'expired' events. Call .stop() to stop monitoring.
|
|
263
|
+
*/
|
|
264
|
+
export function monitorFeeGrants(opts = {}) {
|
|
265
|
+
const { lcdUrl, address, checkIntervalMs = 6 * 60 * 60_000, warnDays = 7, autoRenew = false, grantOpts = {} } = opts;
|
|
266
|
+
if (!lcdUrl || !address) throw new ValidationError(ErrorCodes.INVALID_OPTIONS, 'monitorFeeGrants requires lcdUrl and address');
|
|
267
|
+
|
|
268
|
+
const emitter = new EventEmitter();
|
|
269
|
+
let timer = null;
|
|
270
|
+
|
|
271
|
+
const check = async () => {
|
|
272
|
+
try {
|
|
273
|
+
const expiring = await getExpiringGrants(lcdUrl, address, warnDays, 'granter');
|
|
274
|
+
const now = Date.now();
|
|
275
|
+
|
|
276
|
+
for (const g of expiring) {
|
|
277
|
+
if (g.expiresAt.getTime() <= now) {
|
|
278
|
+
emitter.emit('expired', g);
|
|
279
|
+
} else {
|
|
280
|
+
emitter.emit('expiring', g);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (autoRenew && expiring.length > 0) {
|
|
285
|
+
const { msgs, renewed } = await renewExpiringGrants(lcdUrl, address, warnDays, grantOpts);
|
|
286
|
+
if (msgs.length > 0) {
|
|
287
|
+
emitter.emit('renew', { msgs, renewed });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
} catch (err) {
|
|
291
|
+
emitter.emit('error', err);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
// Start checking
|
|
296
|
+
check();
|
|
297
|
+
timer = setInterval(check, checkIntervalMs);
|
|
298
|
+
if (timer.unref) timer.unref(); // Don't prevent process exit
|
|
299
|
+
|
|
300
|
+
emitter.stop = () => {
|
|
301
|
+
if (timer) { clearInterval(timer); timer = null; }
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
return emitter;
|
|
305
|
+
}
|