blue-js-sdk 2.0.3 → 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/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
- node_address: node.address,
145
+ nodeAddress: node.address,
146
146
  gigabytes,
147
147
  hours: 0,
148
- max_price: {
148
+ maxPrice: {
149
149
  denom: priceEntry.denom,
150
150
  base_value: priceEntry.base_value,
151
151
  quote_value: priceEntry.quote_value,
@@ -338,10 +338,10 @@ export function buildBatchStartSession(from, nodes) {
338
338
  typeUrl: '/sentinel.node.v3.MsgStartSessionRequest',
339
339
  value: {
340
340
  from,
341
- node_address: n.nodeAddress,
341
+ nodeAddress: n.nodeAddress,
342
342
  gigabytes: n.gigabytes || 1,
343
343
  hours: 0,
344
- max_price: n.maxPrice,
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), node_address: addr },
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/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
  /**
@@ -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
- node_address: opts.nodeAddress,
443
+ nodeAddress: opts.nodeAddress,
444
444
  gigabytes: sessionGigabytes,
445
445
  hours: sessionHours,
446
- max_price: { denom: 'udvpn', base_value: sessionMaxPrice.base_value, quote_value: sessionMaxPrice.quote_value },
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
- node_address: opts.nodeAddress,
480
+ nodeAddress: opts.nodeAddress,
481
481
  gigabytes: retryGigabytes,
482
482
  hours: retryHours,
483
- max_price: { denom: 'udvpn', base_value: retryMaxPrice.base_value, quote_value: retryMaxPrice.quote_value },
483
+ maxPrice: { denom: 'udvpn', base_value: retryMaxPrice.base_value, quote_value: retryMaxPrice.quote_value },
484
484
  },
485
485
  };
486
486
  checkAborted(signal);