blue-js-sdk 2.0.1 → 2.1.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/ai-path/index.js +13 -4
- package/batch.js +2 -2
- package/chain/broadcast.js +113 -3
- package/chain/client.js +54 -6
- package/chain/queries.js +21 -0
- package/connection/connect.js +4 -4
- package/cosmjs-setup.js +52 -7
- package/docs/CHAIN-PROTOCOL-UPGRADE-PROPOSAL.md +662 -0
- package/docs/ON-CHAIN-FUNCTIONS.md +1310 -0
- package/index.js +12 -0
- package/package.json +2 -1
- package/plan-operations.js +18 -11
- package/protocol/encoding.js +38 -24
- package/protocol/messages.js +19 -19
- package/protocol/plans.js +18 -11
- package/protocol/v3.js +11 -7
- package/test-subscription-flows.js +457 -0
- package/v3protocol.js +38 -24
- package/test-all-chain-ops.js +0 -493
- package/test-feegrant-connect.js +0 -98
- package/test-logic.js +0 -148
package/ai-path/index.js
CHANGED
|
@@ -58,8 +58,17 @@ export {
|
|
|
58
58
|
rpcQueryBalance,
|
|
59
59
|
rpcQueryNode,
|
|
60
60
|
// v1.5.2: Session recovery (referenced in docs but was missing from exports)
|
|
61
|
-
recoverOrphans,
|
|
61
|
+
recoverOrphans as recoverSession,
|
|
62
|
+
// v2.0.2: Plan operations for AI agents using subscription-based access
|
|
63
|
+
connectViaSubscription,
|
|
64
|
+
connectViaPlan,
|
|
65
|
+
subscribeToPlan,
|
|
66
|
+
hasActiveSubscription,
|
|
67
|
+
querySubscriptions,
|
|
68
|
+
queryPlanNodes,
|
|
69
|
+
queryFeeGrants,
|
|
70
|
+
buildFeeGrantMsg,
|
|
71
|
+
broadcastWithFeeGrant,
|
|
72
|
+
rpcQueryNodesForPlan,
|
|
73
|
+
rpcQuerySubscriptionsForAccount,
|
|
62
74
|
} from '../index.js';
|
|
63
|
-
|
|
64
|
-
// Re-export recoverOrphans as recoverSession (the name used in ai-path docs)
|
|
65
|
-
export { recoverOrphans as recoverSession };
|
package/batch.js
CHANGED
|
@@ -142,10 +142,10 @@ async function _processBatch(client, account, batch, gigabytes, denom, ctx) {
|
|
|
142
142
|
typeUrl: MSG_TYPES.START_SESSION,
|
|
143
143
|
value: {
|
|
144
144
|
from: account.address,
|
|
145
|
-
|
|
145
|
+
nodeAddress: node.address,
|
|
146
146
|
gigabytes,
|
|
147
147
|
hours: 0,
|
|
148
|
-
|
|
148
|
+
maxPrice: {
|
|
149
149
|
denom: priceEntry.denom,
|
|
150
150
|
base_value: priceEntry.base_value,
|
|
151
151
|
quote_value: priceEntry.quote_value,
|
package/chain/broadcast.js
CHANGED
|
@@ -338,10 +338,10 @@ export function buildBatchStartSession(from, nodes) {
|
|
|
338
338
|
typeUrl: '/sentinel.node.v3.MsgStartSessionRequest',
|
|
339
339
|
value: {
|
|
340
340
|
from,
|
|
341
|
-
|
|
341
|
+
nodeAddress: n.nodeAddress,
|
|
342
342
|
gigabytes: n.gigabytes || 1,
|
|
343
343
|
hours: 0,
|
|
344
|
-
|
|
344
|
+
maxPrice: n.maxPrice,
|
|
345
345
|
},
|
|
346
346
|
}));
|
|
347
347
|
}
|
|
@@ -382,7 +382,7 @@ export function buildBatchSend(fromAddress, recipients) {
|
|
|
382
382
|
export function buildBatchLink(provAddress, planId, nodeAddresses) {
|
|
383
383
|
return nodeAddresses.map(addr => ({
|
|
384
384
|
typeUrl: '/sentinel.plan.v3.MsgLinkNodeRequest',
|
|
385
|
-
value: { from: provAddress, id: BigInt(planId),
|
|
385
|
+
value: { from: provAddress, id: BigInt(planId), nodeAddress: addr },
|
|
386
386
|
}));
|
|
387
387
|
}
|
|
388
388
|
|
|
@@ -432,6 +432,116 @@ export async function subscribeToPlan(client, fromAddress, planId, denom = 'udvp
|
|
|
432
432
|
return { subscriptionId: BigInt(subId), txHash: result.transactionHash };
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Share a subscription's bandwidth with another address.
|
|
437
|
+
* The chain only supports bytes-based sharing — there is NO time/duration field.
|
|
438
|
+
* For time-based plans (e.g. 1 month), the operator must manually remove the user
|
|
439
|
+
* when the period expires using cancelSubscription or by not renewing.
|
|
440
|
+
*
|
|
441
|
+
* @param {SigningStargateClient} client
|
|
442
|
+
* @param {string} ownerAddress - Subscription owner (sent1...)
|
|
443
|
+
* @param {number|string|bigint} subscriptionId - Subscription to share
|
|
444
|
+
* @param {string} recipientAddress - User to add (sent1...)
|
|
445
|
+
* @param {number|string|bigint} bytes - Bandwidth quota in bytes (e.g. 1073741824 = 1 GB)
|
|
446
|
+
* @returns {Promise<{ txHash: string }>}
|
|
447
|
+
*/
|
|
448
|
+
export async function shareSubscription(client, ownerAddress, subscriptionId, recipientAddress, bytes) {
|
|
449
|
+
const msg = {
|
|
450
|
+
typeUrl: '/sentinel.subscription.v3.MsgShareSubscriptionRequest',
|
|
451
|
+
value: { from: ownerAddress, id: BigInt(subscriptionId), accAddress: recipientAddress, bytes: String(bytes) },
|
|
452
|
+
};
|
|
453
|
+
const result = await broadcast(client, ownerAddress, [msg]);
|
|
454
|
+
return { txHash: result.transactionHash };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Share a subscription with fee grant — operator pays gas on behalf of user.
|
|
459
|
+
* Same as shareSubscription() but uses broadcastWithFeeGrant for the TX fee.
|
|
460
|
+
*
|
|
461
|
+
* @param {SigningStargateClient} client - Client with owner's wallet
|
|
462
|
+
* @param {string} ownerAddress - Subscription owner (sent1...)
|
|
463
|
+
* @param {number|string|bigint} subscriptionId - Subscription to share
|
|
464
|
+
* @param {string} recipientAddress - User to add (sent1...)
|
|
465
|
+
* @param {number|string|bigint} bytes - Bandwidth quota in bytes
|
|
466
|
+
* @param {string} granterAddress - Fee granter address (sent1...)
|
|
467
|
+
* @returns {Promise<{ txHash: string }>}
|
|
468
|
+
*/
|
|
469
|
+
export async function shareSubscriptionWithFeeGrant(client, ownerAddress, subscriptionId, recipientAddress, bytes, granterAddress) {
|
|
470
|
+
const msg = {
|
|
471
|
+
typeUrl: '/sentinel.subscription.v3.MsgShareSubscriptionRequest',
|
|
472
|
+
value: { from: ownerAddress, id: BigInt(subscriptionId), accAddress: recipientAddress, bytes: String(bytes) },
|
|
473
|
+
};
|
|
474
|
+
const result = await broadcastWithFeeGrant(client, ownerAddress, [msg], granterAddress);
|
|
475
|
+
return { txHash: result.transactionHash };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// ─── Plan User Onboarding (composite operation) ────────────────────────────
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Onboard a user to a plan subscription: subscribe → share bandwidth → optional fee grant.
|
|
482
|
+
* This is the complete "add user to plan" operation for plan operators.
|
|
483
|
+
*
|
|
484
|
+
* IMPORTANT: The chain only supports bytes-based sharing. There is NO time/duration
|
|
485
|
+
* field in MsgShareSubscriptionRequest. For time-based plans (e.g. 1 month), the
|
|
486
|
+
* operator must track expiry externally and remove/not-renew the user when time expires.
|
|
487
|
+
*
|
|
488
|
+
* Steps:
|
|
489
|
+
* 1. Subscribe to plan (operator pays, creates subscription)
|
|
490
|
+
* 2. Share the subscription with the user (allocate bytes)
|
|
491
|
+
* 3. Optionally grant fee allowance (so user's TX gas is paid by operator)
|
|
492
|
+
*
|
|
493
|
+
* @param {SigningStargateClient} client - Operator's signing client
|
|
494
|
+
* @param {string} operatorAddress - Plan operator/owner address (sent1...)
|
|
495
|
+
* @param {object} opts
|
|
496
|
+
* @param {number|string|bigint} opts.planId - Plan to subscribe to
|
|
497
|
+
* @param {string} opts.userAddress - User to onboard (sent1...)
|
|
498
|
+
* @param {number|string|bigint} opts.bytes - Bandwidth quota in bytes (e.g. 1073741824 = 1 GB)
|
|
499
|
+
* @param {string} [opts.denom='udvpn'] - Payment denomination
|
|
500
|
+
* @param {boolean} [opts.grantFee=false] - Also grant fee allowance to user
|
|
501
|
+
* @param {number} [opts.feeSpendLimit] - Max spend for fee grant in udvpn (default: 500000 = 0.5 P2P)
|
|
502
|
+
* @param {Date|string} [opts.feeExpiration] - Fee grant expiry date
|
|
503
|
+
* @param {Function} [opts.buildFeeGrant] - Custom buildFeeGrantMsg function (to avoid circular import)
|
|
504
|
+
* @returns {Promise<{ subscriptionId: bigint, shareTxHash: string, grantTxHash?: string }>}
|
|
505
|
+
*/
|
|
506
|
+
export async function onboardPlanUser(client, operatorAddress, opts) {
|
|
507
|
+
const {
|
|
508
|
+
planId, userAddress, bytes,
|
|
509
|
+
denom = 'udvpn',
|
|
510
|
+
grantFee = false,
|
|
511
|
+
feeSpendLimit = 500_000,
|
|
512
|
+
feeExpiration,
|
|
513
|
+
buildFeeGrant,
|
|
514
|
+
} = opts;
|
|
515
|
+
|
|
516
|
+
// Step 1: Subscribe to plan
|
|
517
|
+
const { subscriptionId, txHash: subTxHash } = await subscribeToPlan(client, operatorAddress, planId, denom);
|
|
518
|
+
|
|
519
|
+
// Step 2: Share subscription with user (bytes-based — no time/duration on chain)
|
|
520
|
+
const shareMsg = {
|
|
521
|
+
typeUrl: '/sentinel.subscription.v3.MsgShareSubscriptionRequest',
|
|
522
|
+
value: { from: operatorAddress, id: BigInt(subscriptionId), accAddress: userAddress, bytes: String(bytes) },
|
|
523
|
+
};
|
|
524
|
+
const shareResult = await broadcast(client, operatorAddress, [shareMsg]);
|
|
525
|
+
|
|
526
|
+
const result = {
|
|
527
|
+
subscriptionId,
|
|
528
|
+
subscribeTxHash: subTxHash,
|
|
529
|
+
shareTxHash: shareResult.transactionHash,
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
// Step 3: Optional fee grant
|
|
533
|
+
if (grantFee && buildFeeGrant) {
|
|
534
|
+
const grantMsg = buildFeeGrant(operatorAddress, userAddress, {
|
|
535
|
+
spendLimit: feeSpendLimit,
|
|
536
|
+
expiration: feeExpiration,
|
|
537
|
+
});
|
|
538
|
+
const grantResult = await broadcast(client, operatorAddress, [grantMsg]);
|
|
539
|
+
result.grantTxHash = grantResult.transactionHash;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return result;
|
|
543
|
+
}
|
|
544
|
+
|
|
435
545
|
/**
|
|
436
546
|
* Estimate the cost of starting a session with a node.
|
|
437
547
|
* Supports both gigabyte and hourly pricing. When preferHourly is true and
|
package/chain/client.js
CHANGED
|
@@ -43,7 +43,8 @@ import {
|
|
|
43
43
|
encodeMsgEndLease,
|
|
44
44
|
} from '../plan-operations.js';
|
|
45
45
|
|
|
46
|
-
import { GAS_PRICE } from '../defaults.js';
|
|
46
|
+
import { GAS_PRICE, RPC_ENDPOINTS } from '../defaults.js';
|
|
47
|
+
import { ValidationError, ChainError, ErrorCodes } from '../errors.js';
|
|
47
48
|
|
|
48
49
|
// ─── CosmJS Registry ─────────────────────────────────────────────────────────
|
|
49
50
|
|
|
@@ -107,12 +108,59 @@ export function buildRegistry() {
|
|
|
107
108
|
/**
|
|
108
109
|
* Create a SigningStargateClient connected to Sentinel RPC.
|
|
109
110
|
* Gas price: from defaults.js GAS_PRICE (chain minimum).
|
|
111
|
+
*
|
|
112
|
+
* Signatures:
|
|
113
|
+
* createClient(rpcUrl, wallet) — classic: connect to specific RPC with existing wallet
|
|
114
|
+
* createClient(mnemonic) — convenience: create wallet from mnemonic, try RPC endpoints with failover
|
|
115
|
+
*
|
|
116
|
+
* @param {string} rpcUrlOrMnemonic - Either an RPC URL (https://...) or a BIP39 mnemonic
|
|
117
|
+
* @param {DirectSecp256k1HdWallet} [wallet] - Wallet object (required when first arg is RPC URL)
|
|
118
|
+
* @returns {Promise<SigningStargateClient>} Connected signing client with full Sentinel registry
|
|
110
119
|
*/
|
|
111
|
-
export async function createClient(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
export async function createClient(rpcUrlOrMnemonic, wallet) {
|
|
121
|
+
// Classic call: createClient(rpcUrl, wallet)
|
|
122
|
+
if (wallet) {
|
|
123
|
+
return SigningStargateClient.connectWithSigner(rpcUrlOrMnemonic, wallet, {
|
|
124
|
+
gasPrice: GasPrice.fromString(GAS_PRICE),
|
|
125
|
+
registry: buildRegistry(),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// If first arg looks like a URL, it's a missing wallet — throw helpful error
|
|
130
|
+
if (typeof rpcUrlOrMnemonic === 'string' && /^(https?|wss?):\/\//i.test(rpcUrlOrMnemonic)) {
|
|
131
|
+
throw new ValidationError(ErrorCodes.INVALID_MNEMONIC,
|
|
132
|
+
'createClient(rpcUrl, wallet): wallet parameter is required when passing an RPC URL. ' +
|
|
133
|
+
'Use createClient(mnemonic) for convenience, or createClient(rpcUrl, wallet) with an existing wallet.',
|
|
134
|
+
{ value: rpcUrlOrMnemonic });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Convenience call: createClient(mnemonic) — create wallet + try RPC endpoints
|
|
138
|
+
// Lazy import to avoid circular dependency
|
|
139
|
+
const { createWallet, validateMnemonic } = await import('./wallet.js');
|
|
140
|
+
validateMnemonic(rpcUrlOrMnemonic, 'createClient');
|
|
141
|
+
const { wallet: derivedWallet } = await createWallet(rpcUrlOrMnemonic);
|
|
142
|
+
const registry = buildRegistry();
|
|
143
|
+
const gasPrice = GasPrice.fromString(GAS_PRICE);
|
|
144
|
+
|
|
145
|
+
// Try each RPC endpoint until one connects
|
|
146
|
+
const errors = [];
|
|
147
|
+
for (const ep of RPC_ENDPOINTS) {
|
|
148
|
+
try {
|
|
149
|
+
const client = await SigningStargateClient.connectWithSigner(ep.url, derivedWallet, {
|
|
150
|
+
gasPrice,
|
|
151
|
+
registry,
|
|
152
|
+
});
|
|
153
|
+
return client;
|
|
154
|
+
} catch (err) {
|
|
155
|
+
errors.push({ endpoint: ep.url, name: ep.name, error: err.message });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// All endpoints failed
|
|
160
|
+
const tried = errors.map(e => ` ${e.name} (${e.endpoint}): ${e.error}`).join('\n');
|
|
161
|
+
throw new ChainError('ALL_ENDPOINTS_FAILED',
|
|
162
|
+
`createClient(mnemonic): failed to connect to all ${RPC_ENDPOINTS.length} RPC endpoints:\n${tried}`,
|
|
163
|
+
{ endpoints: errors });
|
|
116
164
|
}
|
|
117
165
|
|
|
118
166
|
// ─── All Type URL Constants ──────────────────────────────────────────────────
|
package/chain/queries.js
CHANGED
|
@@ -394,6 +394,27 @@ export async function hasActiveSubscription(address, planId, lcdUrl) {
|
|
|
394
394
|
return { has: false };
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
+
/**
|
|
398
|
+
* Query allocations for a subscription (who has how many bytes).
|
|
399
|
+
* NOTE: Uses v2 endpoint because this specific v3 query hasn't been implemented yet
|
|
400
|
+
* (returns 501). The v2 path returns the same allocation data. Same situation as /plan/v3/plans/{id}.
|
|
401
|
+
*
|
|
402
|
+
* @param {string|number|bigint} subscriptionId
|
|
403
|
+
* @param {string} [lcdUrl]
|
|
404
|
+
* @returns {Promise<Array<{ id: string, address: string, grantedBytes: string, utilisedBytes: string }>>}
|
|
405
|
+
*/
|
|
406
|
+
export async function querySubscriptionAllocations(subscriptionId, lcdUrl) {
|
|
407
|
+
try {
|
|
408
|
+
const data = await lcdQuery(`/sentinel/subscription/v2/subscriptions/${subscriptionId}/allocations`, { lcdUrl });
|
|
409
|
+
return (data.allocations || []).map(a => ({
|
|
410
|
+
id: a.id,
|
|
411
|
+
address: a.address,
|
|
412
|
+
grantedBytes: a.granted_bytes || '0',
|
|
413
|
+
utilisedBytes: a.utilised_bytes || '0',
|
|
414
|
+
}));
|
|
415
|
+
} catch { return []; }
|
|
416
|
+
}
|
|
417
|
+
|
|
397
418
|
// ─── Plan Subscriber Helpers (v25b) ──────────────────────────────────────────
|
|
398
419
|
|
|
399
420
|
/**
|
package/connection/connect.js
CHANGED
|
@@ -440,10 +440,10 @@ export async function connectDirect(opts) {
|
|
|
440
440
|
typeUrl: MSG_TYPES.START_SESSION,
|
|
441
441
|
value: {
|
|
442
442
|
from: account.address,
|
|
443
|
-
|
|
443
|
+
nodeAddress: opts.nodeAddress,
|
|
444
444
|
gigabytes: sessionGigabytes,
|
|
445
445
|
hours: sessionHours,
|
|
446
|
-
|
|
446
|
+
maxPrice: { denom: 'udvpn', base_value: sessionMaxPrice.base_value, quote_value: sessionMaxPrice.quote_value },
|
|
447
447
|
},
|
|
448
448
|
};
|
|
449
449
|
|
|
@@ -477,10 +477,10 @@ export async function connectDirect(opts) {
|
|
|
477
477
|
typeUrl: MSG_TYPES.START_SESSION,
|
|
478
478
|
value: {
|
|
479
479
|
from: account.address,
|
|
480
|
-
|
|
480
|
+
nodeAddress: opts.nodeAddress,
|
|
481
481
|
gigabytes: retryGigabytes,
|
|
482
482
|
hours: retryHours,
|
|
483
|
-
|
|
483
|
+
maxPrice: { denom: 'udvpn', base_value: retryMaxPrice.base_value, quote_value: retryMaxPrice.quote_value },
|
|
484
484
|
},
|
|
485
485
|
};
|
|
486
486
|
checkAborted(signal);
|
package/cosmjs-setup.js
CHANGED
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
encodeMsgStartLease,
|
|
51
51
|
encodeMsgEndLease,
|
|
52
52
|
} from './plan-operations.js';
|
|
53
|
-
import { GAS_PRICE, LCD_ENDPOINTS, tryWithFallback } from './defaults.js';
|
|
53
|
+
import { GAS_PRICE, RPC_ENDPOINTS, LCD_ENDPOINTS, tryWithFallback } from './defaults.js';
|
|
54
54
|
import { ValidationError, NodeError, ChainError, ErrorCodes } from './errors.js';
|
|
55
55
|
import path from 'path';
|
|
56
56
|
import os from 'os';
|
|
@@ -216,12 +216,57 @@ export function buildRegistry() {
|
|
|
216
216
|
/**
|
|
217
217
|
* Create a SigningStargateClient connected to Sentinel RPC.
|
|
218
218
|
* Gas price: from defaults.js GAS_PRICE (chain minimum).
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
219
|
+
*
|
|
220
|
+
* Signatures:
|
|
221
|
+
* createClient(rpcUrl, wallet) — classic: connect to specific RPC with existing wallet
|
|
222
|
+
* createClient(mnemonic) — convenience: create wallet from mnemonic, try RPC endpoints with failover
|
|
223
|
+
*
|
|
224
|
+
* @param {string} rpcUrlOrMnemonic - Either an RPC URL (https://...) or a BIP39 mnemonic
|
|
225
|
+
* @param {DirectSecp256k1HdWallet} [wallet] - Wallet object (required when first arg is RPC URL)
|
|
226
|
+
* @returns {Promise<SigningStargateClient>} Connected signing client with full Sentinel registry
|
|
227
|
+
*/
|
|
228
|
+
export async function createClient(rpcUrlOrMnemonic, wallet) {
|
|
229
|
+
// Classic call: createClient(rpcUrl, wallet)
|
|
230
|
+
if (wallet) {
|
|
231
|
+
return SigningStargateClient.connectWithSigner(rpcUrlOrMnemonic, wallet, {
|
|
232
|
+
gasPrice: GasPrice.fromString(GAS_PRICE),
|
|
233
|
+
registry: buildRegistry(),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// If first arg looks like a URL, it's a missing wallet — throw helpful error
|
|
238
|
+
if (typeof rpcUrlOrMnemonic === 'string' && /^(https?|wss?):\/\//i.test(rpcUrlOrMnemonic)) {
|
|
239
|
+
throw new ValidationError(ErrorCodes.INVALID_MNEMONIC,
|
|
240
|
+
'createClient(rpcUrl, wallet): wallet parameter is required when passing an RPC URL. ' +
|
|
241
|
+
'Use createClient(mnemonic) for convenience, or createClient(rpcUrl, wallet) with an existing wallet.',
|
|
242
|
+
{ value: rpcUrlOrMnemonic });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Convenience call: createClient(mnemonic) — create wallet + try RPC endpoints
|
|
246
|
+
validateMnemonic(rpcUrlOrMnemonic, 'createClient');
|
|
247
|
+
const { wallet: derivedWallet } = await createWallet(rpcUrlOrMnemonic);
|
|
248
|
+
const registry = buildRegistry();
|
|
249
|
+
const gasPrice = GasPrice.fromString(GAS_PRICE);
|
|
250
|
+
|
|
251
|
+
// Try each RPC endpoint until one connects
|
|
252
|
+
const errors = [];
|
|
253
|
+
for (const ep of RPC_ENDPOINTS) {
|
|
254
|
+
try {
|
|
255
|
+
const client = await SigningStargateClient.connectWithSigner(ep.url, derivedWallet, {
|
|
256
|
+
gasPrice,
|
|
257
|
+
registry,
|
|
258
|
+
});
|
|
259
|
+
return client;
|
|
260
|
+
} catch (err) {
|
|
261
|
+
errors.push({ endpoint: ep.url, name: ep.name, error: err.message });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// All endpoints failed
|
|
266
|
+
const tried = errors.map(e => ` ${e.name} (${e.endpoint}): ${e.error}`).join('\n');
|
|
267
|
+
throw new ChainError('ALL_ENDPOINTS_FAILED',
|
|
268
|
+
`createClient(mnemonic): failed to connect to all ${RPC_ENDPOINTS.length} RPC endpoints:\n${tried}`,
|
|
269
|
+
{ endpoints: errors });
|
|
225
270
|
}
|
|
226
271
|
|
|
227
272
|
// ─── TX Helpers ──────────────────────────────────────────────────────────────
|