@opendatalabs/vana-sdk 2.2.3-canary.046cb7e → 2.2.3-canary.07f70f7

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.
@@ -350,22 +350,49 @@ class StakingController extends import_base.BaseController {
350
350
  * ```
351
351
  */
352
352
  async getStakerSummary(staker, entityId) {
353
- const stakingContract = this.getStakingContract();
354
- const entityContract = this.getEntityContract();
355
- const position = await stakingContract.read.stakerEntities([
356
- staker,
357
- entityId
358
- ]);
359
- const earnedRewards = await stakingContract.read.getEarnedRewards([
360
- staker,
361
- entityId
362
- ]);
353
+ const chainId = this.getChainId();
354
+ const stakingAddress = (0, import_addresses.getContractAddress)(chainId, "VanaPoolStaking");
355
+ const entityAddress = (0, import_addresses.getContractAddress)(chainId, "VanaPoolEntity");
356
+ const stakingAbi = (0, import_abi.getAbi)("VanaPoolStaking");
357
+ const entityAbi = (0, import_abi.getAbi)("VanaPoolEntity");
363
358
  const block = await this.context.publicClient.getBlock();
359
+ const blockNumber = block.number;
360
+ const multicallResults = await this.context.publicClient.multicall({
361
+ contracts: [
362
+ {
363
+ address: stakingAddress,
364
+ abi: stakingAbi,
365
+ functionName: "stakerEntities",
366
+ args: [staker, entityId]
367
+ },
368
+ {
369
+ address: stakingAddress,
370
+ abi: stakingAbi,
371
+ functionName: "getEarnedRewards",
372
+ args: [staker, entityId]
373
+ },
374
+ {
375
+ address: entityAddress,
376
+ abi: entityAbi,
377
+ functionName: "entityShareToVana",
378
+ args: [entityId]
379
+ }
380
+ ],
381
+ blockNumber
382
+ });
383
+ const [positionResult, earnedRewardsResult, shareToVanaResult] = multicallResults;
384
+ if (positionResult.status === "failure" || earnedRewardsResult.status === "failure" || shareToVanaResult.status === "failure") {
385
+ throw new import_errors.BlockchainError(
386
+ "Failed to fetch staker summary: one or more contract calls failed"
387
+ );
388
+ }
389
+ const position = positionResult.result;
390
+ const earnedRewards = earnedRewardsResult.result;
391
+ const shareToVana = shareToVanaResult.result;
364
392
  const currentTimestamp = block.timestamp;
365
393
  const eligibilityTimestamp = position.rewardEligibilityTimestamp;
366
394
  const remainingBondingTime = eligibilityTimestamp > currentTimestamp ? eligibilityTimestamp - currentTimestamp : 0n;
367
395
  const isInBondingPeriod = remainingBondingTime > 0n;
368
- const shareToVana = await entityContract.read.entityShareToVana([entityId]);
369
396
  const currentValue = position.shares * shareToVana / 10n ** 18n;
370
397
  const unvestedRewards = currentValue > position.costBasis ? currentValue - position.costBasis : 0n;
371
398
  return {
@@ -477,6 +504,106 @@ class StakingController extends import_base.BaseController {
477
504
  isInBondingPeriod: result[3]
478
505
  };
479
506
  }
507
+ /**
508
+ * Computes the new bonding period end timestamp after adding stake.
509
+ *
510
+ * @remarks
511
+ * When a staker adds more stake to an existing position, the reward eligibility timestamp
512
+ * is recalculated as a weighted average of the existing and new positions. This function
513
+ * allows you to preview what the new eligibility timestamp would be without executing
514
+ * the stake transaction.
515
+ *
516
+ * The formula used (matching the contract):
517
+ * ```
518
+ * newEligibility = (existingShares * existingEligibility + newShares * newEligibility) / totalShares
519
+ * ```
520
+ *
521
+ * Where `newEligibility` for the incoming stake is `currentTimestamp + bondingPeriod`.
522
+ *
523
+ * @param params - The parameters for computing the new bonding period
524
+ * @param params.staker - The address of the staker
525
+ * @param params.entityId - The ID of the entity
526
+ * @param params.stakeAmount - The amount of VANA to stake (in wei)
527
+ * @returns Object containing the new eligibility timestamp and related info
528
+ *
529
+ * @example
530
+ * ```typescript
531
+ * // Preview bonding period after staking 100 VANA
532
+ * const preview = await vana.staking.computeNewBondingPeriod({
533
+ * staker: '0x742d35...',
534
+ * entityId: 1n,
535
+ * stakeAmount: parseEther("100"),
536
+ * });
537
+ * console.log(`New eligibility: ${new Date(Number(preview.newEligibilityTimestamp) * 1000)}`);
538
+ * console.log(`Remaining bonding time: ${Number(preview.newRemainingBondingTime) / 86400} days`);
539
+ * ```
540
+ */
541
+ async computeNewBondingPeriod(params) {
542
+ const chainId = this.getChainId();
543
+ const stakingAddress = (0, import_addresses.getContractAddress)(chainId, "VanaPoolStaking");
544
+ const entityAddress = (0, import_addresses.getContractAddress)(chainId, "VanaPoolEntity");
545
+ const stakingAbi = (0, import_abi.getAbi)("VanaPoolStaking");
546
+ const entityAbi = (0, import_abi.getAbi)("VanaPoolEntity");
547
+ const block = await this.context.publicClient.getBlock();
548
+ const blockNumber = block.number;
549
+ const currentTimestamp = block.timestamp;
550
+ const multicallResults = await this.context.publicClient.multicall({
551
+ contracts: [
552
+ {
553
+ address: stakingAddress,
554
+ abi: stakingAbi,
555
+ functionName: "stakerEntities",
556
+ args: [params.staker, params.entityId]
557
+ },
558
+ {
559
+ address: stakingAddress,
560
+ abi: stakingAbi,
561
+ functionName: "bondingPeriod",
562
+ args: []
563
+ },
564
+ {
565
+ address: entityAddress,
566
+ abi: entityAbi,
567
+ functionName: "vanaToEntityShare",
568
+ args: [params.entityId]
569
+ }
570
+ ],
571
+ blockNumber
572
+ });
573
+ const [positionResult, bondingPeriodResult, vanaToShareResult] = multicallResults;
574
+ if (positionResult.status === "failure" || bondingPeriodResult.status === "failure" || vanaToShareResult.status === "failure") {
575
+ throw new import_errors.BlockchainError(
576
+ "Failed to compute new bonding period: one or more contract calls failed"
577
+ );
578
+ }
579
+ const position = positionResult.result;
580
+ const bondingPeriodDuration = bondingPeriodResult.result;
581
+ const vanaToShare = vanaToShareResult.result;
582
+ const currentShares = position.shares;
583
+ const currentEligibilityTimestamp = position.rewardEligibilityTimestamp;
584
+ const currentRemainingBondingTime = currentEligibilityTimestamp > currentTimestamp ? currentEligibilityTimestamp - currentTimestamp : 0n;
585
+ const estimatedNewShares = params.stakeAmount * vanaToShare / 10n ** 18n;
586
+ const totalSharesAfter = currentShares + estimatedNewShares;
587
+ const newStakeEligibility = currentTimestamp + bondingPeriodDuration;
588
+ let newEligibilityTimestamp;
589
+ if (currentShares === 0n) {
590
+ newEligibilityTimestamp = newStakeEligibility;
591
+ } else {
592
+ newEligibilityTimestamp = (currentShares * currentEligibilityTimestamp + estimatedNewShares * newStakeEligibility) / totalSharesAfter;
593
+ }
594
+ const newRemainingBondingTime = newEligibilityTimestamp > currentTimestamp ? newEligibilityTimestamp - currentTimestamp : 0n;
595
+ return {
596
+ newEligibilityTimestamp,
597
+ newRemainingBondingTime,
598
+ currentEligibilityTimestamp,
599
+ currentRemainingBondingTime,
600
+ currentShares,
601
+ estimatedNewShares,
602
+ totalSharesAfter,
603
+ bondingPeriodDuration,
604
+ currentTimestamp
605
+ };
606
+ }
480
607
  /**
481
608
  * Unstakes VANA from an entity in the VanaPool protocol.
482
609
  *
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/controllers/staking.ts"],"sourcesContent":["/**\n * Provides staking functionality for the VanaPool protocol.\n *\n * @remarks\n * This controller handles interactions with VanaPool staking contracts,\n * allowing users to query staking information such as total VANA staked,\n * entity information, and staker positions.\n *\n * @category Controllers\n * @module StakingController\n */\n\nimport type { ControllerContext } from \"../types/controller-context\";\nimport type { TransactionOptions } from \"../types/operations\";\nimport { BaseController } from \"./base\";\nimport { getContract, parseEther } from \"viem\";\nimport type { Address, Hash } from \"viem\";\nimport { getContractAddress } from \"../generated/addresses\";\nimport { getAbi } from \"../generated/abi\";\nimport { BlockchainError } from \"../errors\";\n\n/**\n * Information about a staking entity in the VanaPool protocol.\n */\nexport interface EntityInfo {\n entityId: bigint;\n ownerAddress: Address;\n status: number;\n name: string;\n maxAPY: bigint;\n lockedRewardPool: bigint;\n activeRewardPool: bigint;\n totalShares: bigint;\n lastUpdateTimestamp: bigint;\n}\n\n/**\n * Information about a staker's position in an entity.\n */\nexport interface StakerEntityInfo {\n shares: bigint;\n costBasis: bigint;\n rewardEligibilityTimestamp: bigint;\n realizedRewards: bigint;\n vestedRewards: bigint;\n}\n\n/**\n * Comprehensive staking summary for a staker in an entity.\n */\nexport interface StakerEntitySummary {\n /** Number of shares owned by the staker */\n shares: bigint;\n /** Cost basis - the original VANA amount staked */\n costBasis: bigint;\n /** Current value of the staker's shares in VANA */\n currentValue: bigint;\n /** Timestamp when rewards become eligible */\n rewardEligibilityTimestamp: bigint;\n /** Remaining bonding time in seconds (0 if bonding period has passed) */\n remainingBondingTime: bigint;\n /** Whether the staker is still in the bonding period */\n isInBondingPeriod: boolean;\n /** Vested rewards that can be claimed without penalty */\n vestedRewards: bigint;\n /** Unvested rewards (pending interest = currentValue - costBasis) */\n unvestedRewards: bigint;\n /** Realized/withdrawn rewards (already claimed) */\n realizedRewards: bigint;\n /** Total earned rewards (includes vested, unvested, and realized) */\n earnedRewards: bigint;\n}\n\n/**\n * Controller for VanaPool staking operations.\n *\n * @remarks\n * Provides methods to query staking information from the VanaPool contracts.\n * This includes total VANA staked across the protocol, entity information,\n * and individual staker positions.\n *\n * @example\n * ```typescript\n * // Get total VANA staked in the protocol\n * const totalStaked = await vana.staking.getTotalVanaStaked();\n * console.log(`Total staked: ${totalStaked} wei`);\n *\n * // Get entity information\n * const entity = await vana.staking.getEntity(1n);\n * console.log(`Entity name: ${entity.name}`);\n * ```\n *\n * @category Controllers\n */\nexport class StakingController extends BaseController {\n constructor(context: ControllerContext) {\n super(context);\n }\n\n /**\n * Gets the chain ID from context.\n */\n private getChainId(): number {\n const chainId =\n this.context.walletClient?.chain?.id ??\n this.context.publicClient.chain?.id;\n if (!chainId) {\n throw new Error(\"Chain ID not available\");\n }\n return chainId;\n }\n\n /**\n * Gets the VanaPoolEntity contract instance.\n */\n private getEntityContract() {\n const chainId = this.getChainId();\n return getContract({\n address: getContractAddress(chainId, \"VanaPoolEntity\"),\n abi: getAbi(\"VanaPoolEntity\"),\n client: this.context.publicClient,\n });\n }\n\n /**\n * Gets the VanaPoolStaking contract instance.\n */\n private getStakingContract() {\n const chainId = this.getChainId();\n return getContract({\n address: getContractAddress(chainId, \"VanaPoolStaking\"),\n abi: getAbi(\"VanaPoolStaking\"),\n client: this.context.publicClient,\n });\n }\n\n /**\n * Gets the total amount of VANA staked in the VanaPool protocol or a specific entity.\n *\n * @remarks\n * When called without an entityId, this retrieves the sum of activeRewardPool\n * across all active entities in the VanaPool protocol.\n * When called with an entityId, this returns the activeRewardPool for that specific entity.\n * The value is returned in wei (10^18 = 1 VANA).\n *\n * @param entityId - Optional entity ID to get staked amount for a specific entity\n * @returns The total amount of VANA staked in wei\n *\n * @example\n * ```typescript\n * // Get total staked across all entities\n * const totalStaked = await vana.staking.getTotalVanaStaked();\n * console.log(`Total staked: ${Number(totalStaked) / 1e18} VANA`);\n *\n * // Get staked amount for a specific entity\n * const entityStaked = await vana.staking.getTotalVanaStaked(1n);\n * console.log(`Entity 1 staked: ${Number(entityStaked) / 1e18} VANA`);\n * ```\n */\n async getTotalVanaStaked(entityId?: bigint): Promise<bigint> {\n const entityContract = this.getEntityContract();\n\n // If entityId is provided, return the activeRewardPool for that specific entity\n if (entityId !== undefined) {\n const entity = await entityContract.read.entities([entityId]);\n return entity.activeRewardPool;\n }\n\n // Otherwise, sum up the activeRewardPool from all active entities\n const activeEntityIds = await entityContract.read.activeEntitiesValues();\n\n let totalStaked = 0n;\n for (const id of activeEntityIds) {\n const entity = await entityContract.read.entities([id]);\n totalStaked += entity.activeRewardPool;\n }\n\n return totalStaked;\n }\n\n /**\n * Gets information about a specific staking entity.\n *\n * @param entityId - The ID of the entity to query\n * @returns The entity information including name, APY, shares, and reward pools\n *\n * @example\n * ```typescript\n * const entity = await vana.staking.getEntity(1n);\n * console.log(`Entity: ${entity.name}`);\n * console.log(`Total shares: ${entity.totalShares}`);\n * console.log(`Max APY: ${Number(entity.maxAPY) / 100}%`);\n * ```\n */\n async getEntity(entityId: bigint): Promise<EntityInfo> {\n const entityContract = this.getEntityContract();\n\n const result = await entityContract.read.entities([entityId]);\n\n return {\n entityId: result.entityId,\n ownerAddress: result.ownerAddress as Address,\n status: result.status,\n name: result.name,\n maxAPY: result.maxAPY,\n lockedRewardPool: result.lockedRewardPool,\n activeRewardPool: result.activeRewardPool,\n totalShares: result.totalShares,\n lastUpdateTimestamp: result.lastUpdateTimestamp,\n };\n }\n\n /**\n * Gets the total number of staking entities in the protocol.\n *\n * @returns The count of entities\n *\n * @example\n * ```typescript\n * const count = await vana.staking.getEntitiesCount();\n * console.log(`Total entities: ${count}`);\n * ```\n */\n async getEntitiesCount(): Promise<bigint> {\n const entityContract = this.getEntityContract();\n return entityContract.read.entitiesCount();\n }\n\n /**\n * Gets the IDs of all active staking entities.\n *\n * @returns Array of active entity IDs\n *\n * @example\n * ```typescript\n * const activeIds = await vana.staking.getActiveEntities();\n * console.log(`Active entities: ${activeIds.join(', ')}`);\n * ```\n */\n async getActiveEntities(): Promise<readonly bigint[]> {\n const entityContract = this.getEntityContract();\n return entityContract.read.activeEntitiesValues();\n }\n\n /**\n * Gets a staker's position in a specific entity.\n *\n * @param staker - The address of the staker\n * @param entityId - The ID of the entity\n * @returns The staker's position information\n *\n * @example\n * ```typescript\n * const position = await vana.staking.getStakerPosition(\n * '0x742d35...',\n * 1n\n * );\n * console.log(`Shares: ${position.shares}`);\n * console.log(`Rewards: ${position.realizedRewards}`);\n * ```\n */\n async getStakerPosition(\n staker: Address,\n entityId: bigint,\n ): Promise<StakerEntityInfo> {\n const stakingContract = this.getStakingContract();\n\n const result = await stakingContract.read.stakerEntities([\n staker,\n entityId,\n ]);\n\n return {\n shares: result.shares,\n costBasis: result.costBasis,\n rewardEligibilityTimestamp: result.rewardEligibilityTimestamp,\n realizedRewards: result.realizedRewards,\n vestedRewards: result.vestedRewards,\n };\n }\n\n /**\n * Gets the earned rewards for a staker in an entity.\n *\n * @param staker - The address of the staker\n * @param entityId - The ID of the entity\n * @returns The earned rewards amount in wei\n *\n * @example\n * ```typescript\n * const rewards = await vana.staking.getEarnedRewards(\n * '0x742d35...',\n * 1n\n * );\n * console.log(`Earned: ${Number(rewards) / 1e18} VANA`);\n * ```\n */\n async getEarnedRewards(staker: Address, entityId: bigint): Promise<bigint> {\n const stakingContract = this.getStakingContract();\n return stakingContract.read.getEarnedRewards([staker, entityId]);\n }\n\n /**\n * Gets the minimum stake amount required to stake.\n *\n * @returns The minimum stake amount in wei\n *\n * @example\n * ```typescript\n * const minStake = await vana.staking.getMinStakeAmount();\n * console.log(`Minimum stake: ${Number(minStake) / 1e18} VANA`);\n * ```\n */\n async getMinStakeAmount(): Promise<bigint> {\n const stakingContract = this.getStakingContract();\n return stakingContract.read.minStakeAmount();\n }\n\n /**\n * Gets the count of active stakers in the protocol.\n *\n * @returns The number of active stakers\n *\n * @example\n * ```typescript\n * const count = await vana.staking.getActiveStakersCount();\n * console.log(`Active stakers: ${count}`);\n * ```\n */\n async getActiveStakersCount(): Promise<bigint> {\n const stakingContract = this.getStakingContract();\n return stakingContract.read.activeStakersListCount();\n }\n\n /**\n * Gets the bonding period for staking.\n *\n * @returns The bonding period in seconds\n *\n * @example\n * ```typescript\n * const bondingPeriod = await vana.staking.getBondingPeriod();\n * console.log(`Bonding period: ${bondingPeriod / 86400n} days`);\n * ```\n */\n async getBondingPeriod(): Promise<bigint> {\n const stakingContract = this.getStakingContract();\n return stakingContract.read.bondingPeriod();\n }\n\n /**\n * Gets the total distributed rewards for a specific entity from the subgraph.\n *\n * @remarks\n * This queries the VanaPool subgraph to retrieve the cumulative `totalDistributedRewards`\n * for a staking entity. This value represents the sum of all rewards that have been\n * processed (moved from lockedRewardPool to activeRewardPool) minus any forfeited\n * rewards that were returned.\n *\n * Requires a configured `subgraphUrl` in the Vana constructor options.\n *\n * @param entityId - The ID of the entity to query\n * @param options - Optional configuration including custom subgraph URL\n * @returns The total distributed rewards in wei\n * @throws {BlockchainError} When subgraph URL is not configured or query fails\n *\n * @example\n * ```typescript\n * const totalRewards = await vana.staking.getTotalDistributedRewards(1n);\n * const totalRewardsVana = Number(totalRewards) / 1e18;\n * console.log(`Total distributed rewards: ${totalRewardsVana.toLocaleString()} VANA`);\n * ```\n */\n async getTotalDistributedRewards(\n entityId: bigint,\n options: { subgraphUrl?: string } = {},\n ): Promise<bigint> {\n const graphqlEndpoint = options.subgraphUrl ?? this.context.subgraphUrl;\n\n if (!graphqlEndpoint) {\n throw new BlockchainError(\n \"subgraphUrl is required. Please provide a valid subgraph endpoint or configure it in Vana constructor.\",\n );\n }\n\n const query = `\n query GetStakingEntityRewards($entityId: ID!) {\n stakingEntity(id: $entityId) {\n totalDistributedRewards\n }\n }\n `;\n\n const response = await fetch(graphqlEndpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n query,\n variables: {\n entityId: entityId.toString(),\n },\n }),\n });\n\n if (!response.ok) {\n throw new BlockchainError(\n `Subgraph query failed with status ${response.status}`,\n );\n }\n\n const result = (await response.json()) as {\n data?: { stakingEntity?: { totalDistributedRewards: string } };\n errors?: Array<{ message: string }>;\n };\n\n if (result.errors && result.errors.length > 0) {\n throw new BlockchainError(\n `Subgraph query error: ${result.errors[0].message}`,\n );\n }\n\n if (!result.data?.stakingEntity) {\n throw new BlockchainError(`Staking entity ${entityId} not found`);\n }\n\n return BigInt(result.data.stakingEntity.totalDistributedRewards);\n }\n\n /**\n * Gets a comprehensive staking summary for a staker in an entity.\n *\n * @remarks\n * This method aggregates all relevant staking information for a staker in a single call,\n * including staked amount, current value, bonding status, and all reward types.\n *\n * Reward breakdown:\n * - `earnedRewards` = pendingInterest + vestedRewards + realizedRewards\n * - `pendingInterest` = currentValue - costBasis (unvested appreciation)\n * - `vestedRewards` = rewards that have vested but not yet withdrawn\n * - `realizedRewards` = rewards already withdrawn during unstakes\n *\n * @param staker - The address of the staker\n * @param entityId - The ID of the entity\n * @returns A comprehensive summary of the staker's position\n *\n * @example\n * ```typescript\n * const summary = await vana.staking.getStakerSummary('0x742d35...', 1n);\n * console.log(`Total staked: ${Number(summary.totalStaked) / 1e18} VANA`);\n * console.log(`Current value: ${Number(summary.currentValue) / 1e18} VANA`);\n * console.log(`In bonding period: ${summary.isInBondingPeriod}`);\n * console.log(`Remaining bonding time: ${Number(summary.remainingBondingTime) / 86400} days`);\n * console.log(`Vested rewards: ${Number(summary.vestedRewards) / 1e18} VANA`);\n * console.log(`Unvested rewards: ${Number(summary.unvestedRewards) / 1e18} VANA`);\n * console.log(`Realized rewards: ${Number(summary.realizedRewards) / 1e18} VANA`);\n * console.log(`Total earned: ${Number(summary.earnedRewards) / 1e18} VANA`);\n * ```\n */\n async getStakerSummary(\n staker: Address,\n entityId: bigint,\n ): Promise<StakerEntitySummary> {\n const stakingContract = this.getStakingContract();\n const entityContract = this.getEntityContract();\n\n // Get staker's position data\n const position = await stakingContract.read.stakerEntities([\n staker,\n entityId,\n ]);\n\n // Get earned rewards from contract\n const earnedRewards = await stakingContract.read.getEarnedRewards([\n staker,\n entityId,\n ]);\n\n // Get current timestamp from the blockchain\n const block = await this.context.publicClient.getBlock();\n const currentTimestamp = block.timestamp;\n\n // Calculate remaining bonding time\n const eligibilityTimestamp = position.rewardEligibilityTimestamp;\n const remainingBondingTime =\n eligibilityTimestamp > currentTimestamp\n ? eligibilityTimestamp - currentTimestamp\n : 0n;\n const isInBondingPeriod = remainingBondingTime > 0n;\n\n // Calculate current value of shares\n // entityShareToVana returns the VANA value of 1 share (scaled by 1e18)\n const shareToVana = await entityContract.read.entityShareToVana([entityId]);\n const currentValue = (position.shares * shareToVana) / 10n ** 18n;\n\n // Calculate pending interest (unvested rewards)\n // pendingInterest = currentValue - costBasis (if positive)\n const unvestedRewards =\n currentValue > position.costBasis\n ? currentValue - position.costBasis\n : 0n;\n\n return {\n shares: position.shares,\n costBasis: position.costBasis,\n currentValue,\n rewardEligibilityTimestamp: eligibilityTimestamp,\n remainingBondingTime,\n isInBondingPeriod,\n vestedRewards: position.vestedRewards,\n unvestedRewards,\n realizedRewards: position.realizedRewards,\n earnedRewards,\n };\n }\n\n /**\n * Stakes VANA to an entity in the VanaPool protocol.\n *\n * @remarks\n * This method stakes native VANA tokens to a specified entity. The staker will receive\n * shares in proportion to their stake amount. A bonding period applies during which\n * rewards cannot be fully claimed without penalty.\n *\n * Requires a wallet client to be configured in the Vana constructor.\n *\n * @param params - The staking parameters\n * @param params.entityId - The ID of the entity to stake to\n * @param params.amount - The amount of VANA to stake (in wei, or as a string like \"1.5\" for 1.5 VANA)\n * @param params.recipient - Optional recipient address for the shares (defaults to the sender)\n * @param params.minShares - Optional minimum shares to receive (slippage protection, defaults to 0)\n * @returns The transaction hash\n * @throws {BlockchainError} When wallet client is not configured\n *\n * @example\n * ```typescript\n * // Stake 100 VANA to entity 1\n * const txHash = await vana.staking.stake({\n * entityId: 1n,\n * amount: \"100\", // 100 VANA\n * });\n * console.log(`Staked! Transaction: ${txHash}`);\n *\n * // Stake with slippage protection\n * const txHash2 = await vana.staking.stake({\n * entityId: 1n,\n * amount: parseEther(\"50\"), // 50 VANA in wei\n * minShares: parseEther(\"49\"), // Expect at least 49 shares\n * });\n * ```\n */\n async stake(\n params: {\n entityId: bigint;\n amount: bigint | string;\n recipient?: Address;\n minShares?: bigint;\n },\n options?: TransactionOptions,\n ): Promise<Hash> {\n this.assertWallet();\n\n const chainId = this.getChainId();\n const stakingAddress = getContractAddress(chainId, \"VanaPoolStaking\");\n const stakingAbi = getAbi(\"VanaPoolStaking\");\n\n // Convert amount to bigint if it's a string (e.g., \"1.5\" -> 1.5 VANA in wei)\n const amountWei =\n typeof params.amount === \"string\"\n ? parseEther(params.amount)\n : params.amount;\n\n // Get account with fallback to userAddress\n const account =\n this.context.walletClient.account ?? this.context.userAddress;\n const accountAddress =\n typeof account === \"string\" ? account : account.address;\n\n // Default recipient to the sender's address\n const recipient = params.recipient ?? accountAddress;\n\n // Default minShares to 0 (no slippage protection)\n const minShares = params.minShares ?? 0n;\n\n const txHash = await this.context.walletClient.writeContract({\n address: stakingAddress,\n abi: stakingAbi,\n functionName: \"stake\",\n args: [params.entityId, recipient, minShares],\n value: amountWei,\n account,\n chain: this.context.walletClient.chain,\n ...this.spreadTransactionOptions(options),\n });\n\n return txHash;\n }\n\n /**\n * Gets the maximum amount of VANA that can be unstaked in a single transaction.\n *\n * @remarks\n * This calls the contract's getMaxUnstakeAmount which returns the minimum of:\n * 1. The withdrawable VANA (costBasis if in bonding period, shareValue if eligible)\n * 2. The entity's activeRewardPool (what the entity has available)\n * 3. The treasury balance (what can be paid out)\n *\n * The limiting factor indicates what's constraining the unstake:\n * - 0 = user shares/costBasis\n * - 1 = activeRewardPool\n * - 2 = treasury\n *\n * @param staker - The address of the staker\n * @param entityId - The ID of the entity\n * @returns Object containing maxVana, maxShares, limitingFactor, and isInBondingPeriod\n *\n * @example\n * ```typescript\n * const result = await vana.staking.getMaxUnstakeAmount('0x742d35...', 1n);\n * console.log(`Max unstake: ${Number(result.maxVana) / 1e18} VANA`);\n * console.log(`Max shares: ${result.maxShares}`);\n * console.log(`In bonding period: ${result.isInBondingPeriod}`);\n * ```\n */\n async getMaxUnstakeAmount(\n staker: Address,\n entityId: bigint,\n ): Promise<{\n maxVana: bigint;\n maxShares: bigint;\n limitingFactor: number;\n isInBondingPeriod: boolean;\n }> {\n const stakingContract = this.getStakingContract();\n\n const result = await stakingContract.read.getMaxUnstakeAmount([\n staker,\n entityId,\n ]);\n\n return {\n maxVana: result[0],\n maxShares: result[1],\n limitingFactor: Number(result[2]),\n isInBondingPeriod: result[3],\n };\n }\n\n /**\n * Unstakes VANA from an entity in the VanaPool protocol.\n *\n * @remarks\n * This method unstakes native VANA tokens from a specified entity. The amount\n * that can be unstaked depends on whether the staker is in the bonding period:\n *\n * - **During bonding period**: Only cost basis can be withdrawn; rewards are forfeited\n * - **After bonding period**: Full current value (cost basis + rewards) can be withdrawn\n *\n * Use `getMaxUnstakeAmount` to determine the maximum amount that can be unstaked.\n *\n * Requires a wallet client to be configured in the Vana constructor.\n *\n * @param params - The unstaking parameters\n * @param params.entityId - The ID of the entity to unstake from\n * @param params.amount - The amount of VANA to unstake (in wei, or as a string like \"1.5\" for 1.5 VANA)\n * @param params.maxShares - Maximum shares to burn for slippage protection (defaults to 0, no protection)\n * @returns The transaction hash\n * @throws {BlockchainError} When wallet client is not configured\n *\n * @example\n * ```typescript\n * // Get max unstake amount first\n * const maxUnstake = await vana.staking.getMaxUnstakeAmount(address, 1n);\n *\n * // Unstake the maximum amount with slippage protection\n * const txHash = await vana.staking.unstake({\n * entityId: 1n,\n * amount: maxUnstake.maxVana,\n * maxShares: maxUnstake.maxShares,\n * });\n * console.log(`Unstaked! Transaction: ${txHash}`);\n * ```\n */\n async unstake(\n params: {\n entityId: bigint;\n amount: bigint | string;\n maxShares?: bigint;\n },\n options?: TransactionOptions,\n ): Promise<Hash> {\n this.assertWallet();\n\n const chainId = this.getChainId();\n const stakingAddress = getContractAddress(chainId, \"VanaPoolStaking\");\n const stakingAbi = getAbi(\"VanaPoolStaking\");\n\n // Convert amount to bigint if it's a string (e.g., \"1.5\" -> 1.5 VANA in wei)\n const amountWei =\n typeof params.amount === \"string\"\n ? parseEther(params.amount)\n : params.amount;\n\n // Default maxShares to 0 (no slippage protection)\n const maxShares = params.maxShares ?? 0n;\n\n // Get account with fallback to userAddress\n const account =\n this.context.walletClient.account ?? this.context.userAddress;\n\n const txHash = await this.context.walletClient.writeContract({\n address: stakingAddress,\n abi: stakingAbi,\n functionName: \"unstakeVana\",\n args: [params.entityId, amountWei, maxShares],\n account,\n chain: this.context.walletClient.chain,\n ...this.spreadTransactionOptions(options),\n });\n\n return txHash;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/controllers/staking.ts"],"sourcesContent":["/**\n * Provides staking functionality for the VanaPool protocol.\n *\n * @remarks\n * This controller handles interactions with VanaPool staking contracts,\n * allowing users to query staking information such as total VANA staked,\n * entity information, and staker positions.\n *\n * @category Controllers\n * @module StakingController\n */\n\nimport type { ControllerContext } from \"../types/controller-context\";\nimport type { TransactionOptions } from \"../types/operations\";\nimport { BaseController } from \"./base\";\nimport { getContract, parseEther } from \"viem\";\nimport type { Address, Hash } from \"viem\";\nimport { getContractAddress } from \"../generated/addresses\";\nimport { getAbi } from \"../generated/abi\";\nimport { BlockchainError } from \"../errors\";\n\n/**\n * Information about a staking entity in the VanaPool protocol.\n */\nexport interface EntityInfo {\n entityId: bigint;\n ownerAddress: Address;\n status: number;\n name: string;\n maxAPY: bigint;\n lockedRewardPool: bigint;\n activeRewardPool: bigint;\n totalShares: bigint;\n lastUpdateTimestamp: bigint;\n}\n\n/**\n * Information about a staker's position in an entity.\n */\nexport interface StakerEntityInfo {\n shares: bigint;\n costBasis: bigint;\n rewardEligibilityTimestamp: bigint;\n realizedRewards: bigint;\n vestedRewards: bigint;\n}\n\n/**\n * Comprehensive staking summary for a staker in an entity.\n */\nexport interface StakerEntitySummary {\n /** Number of shares owned by the staker */\n shares: bigint;\n /** Cost basis - the original VANA amount staked */\n costBasis: bigint;\n /** Current value of the staker's shares in VANA */\n currentValue: bigint;\n /** Timestamp when rewards become eligible */\n rewardEligibilityTimestamp: bigint;\n /** Remaining bonding time in seconds (0 if bonding period has passed) */\n remainingBondingTime: bigint;\n /** Whether the staker is still in the bonding period */\n isInBondingPeriod: boolean;\n /** Vested rewards that can be claimed without penalty */\n vestedRewards: bigint;\n /** Unvested rewards (pending interest = currentValue - costBasis) */\n unvestedRewards: bigint;\n /** Realized/withdrawn rewards (already claimed) */\n realizedRewards: bigint;\n /** Total earned rewards (includes vested, unvested, and realized) */\n earnedRewards: bigint;\n}\n\n/**\n * Controller for VanaPool staking operations.\n *\n * @remarks\n * Provides methods to query staking information from the VanaPool contracts.\n * This includes total VANA staked across the protocol, entity information,\n * and individual staker positions.\n *\n * @example\n * ```typescript\n * // Get total VANA staked in the protocol\n * const totalStaked = await vana.staking.getTotalVanaStaked();\n * console.log(`Total staked: ${totalStaked} wei`);\n *\n * // Get entity information\n * const entity = await vana.staking.getEntity(1n);\n * console.log(`Entity name: ${entity.name}`);\n * ```\n *\n * @category Controllers\n */\nexport class StakingController extends BaseController {\n constructor(context: ControllerContext) {\n super(context);\n }\n\n /**\n * Gets the chain ID from context.\n */\n private getChainId(): number {\n const chainId =\n this.context.walletClient?.chain?.id ??\n this.context.publicClient.chain?.id;\n if (!chainId) {\n throw new Error(\"Chain ID not available\");\n }\n return chainId;\n }\n\n /**\n * Gets the VanaPoolEntity contract instance.\n */\n private getEntityContract() {\n const chainId = this.getChainId();\n return getContract({\n address: getContractAddress(chainId, \"VanaPoolEntity\"),\n abi: getAbi(\"VanaPoolEntity\"),\n client: this.context.publicClient,\n });\n }\n\n /**\n * Gets the VanaPoolStaking contract instance.\n */\n private getStakingContract() {\n const chainId = this.getChainId();\n return getContract({\n address: getContractAddress(chainId, \"VanaPoolStaking\"),\n abi: getAbi(\"VanaPoolStaking\"),\n client: this.context.publicClient,\n });\n }\n\n /**\n * Gets the total amount of VANA staked in the VanaPool protocol or a specific entity.\n *\n * @remarks\n * When called without an entityId, this retrieves the sum of activeRewardPool\n * across all active entities in the VanaPool protocol.\n * When called with an entityId, this returns the activeRewardPool for that specific entity.\n * The value is returned in wei (10^18 = 1 VANA).\n *\n * @param entityId - Optional entity ID to get staked amount for a specific entity\n * @returns The total amount of VANA staked in wei\n *\n * @example\n * ```typescript\n * // Get total staked across all entities\n * const totalStaked = await vana.staking.getTotalVanaStaked();\n * console.log(`Total staked: ${Number(totalStaked) / 1e18} VANA`);\n *\n * // Get staked amount for a specific entity\n * const entityStaked = await vana.staking.getTotalVanaStaked(1n);\n * console.log(`Entity 1 staked: ${Number(entityStaked) / 1e18} VANA`);\n * ```\n */\n async getTotalVanaStaked(entityId?: bigint): Promise<bigint> {\n const entityContract = this.getEntityContract();\n\n // If entityId is provided, return the activeRewardPool for that specific entity\n if (entityId !== undefined) {\n const entity = await entityContract.read.entities([entityId]);\n return entity.activeRewardPool;\n }\n\n // Otherwise, sum up the activeRewardPool from all active entities\n const activeEntityIds = await entityContract.read.activeEntitiesValues();\n\n let totalStaked = 0n;\n for (const id of activeEntityIds) {\n const entity = await entityContract.read.entities([id]);\n totalStaked += entity.activeRewardPool;\n }\n\n return totalStaked;\n }\n\n /**\n * Gets information about a specific staking entity.\n *\n * @param entityId - The ID of the entity to query\n * @returns The entity information including name, APY, shares, and reward pools\n *\n * @example\n * ```typescript\n * const entity = await vana.staking.getEntity(1n);\n * console.log(`Entity: ${entity.name}`);\n * console.log(`Total shares: ${entity.totalShares}`);\n * console.log(`Max APY: ${Number(entity.maxAPY) / 100}%`);\n * ```\n */\n async getEntity(entityId: bigint): Promise<EntityInfo> {\n const entityContract = this.getEntityContract();\n\n const result = await entityContract.read.entities([entityId]);\n\n return {\n entityId: result.entityId,\n ownerAddress: result.ownerAddress as Address,\n status: result.status,\n name: result.name,\n maxAPY: result.maxAPY,\n lockedRewardPool: result.lockedRewardPool,\n activeRewardPool: result.activeRewardPool,\n totalShares: result.totalShares,\n lastUpdateTimestamp: result.lastUpdateTimestamp,\n };\n }\n\n /**\n * Gets the total number of staking entities in the protocol.\n *\n * @returns The count of entities\n *\n * @example\n * ```typescript\n * const count = await vana.staking.getEntitiesCount();\n * console.log(`Total entities: ${count}`);\n * ```\n */\n async getEntitiesCount(): Promise<bigint> {\n const entityContract = this.getEntityContract();\n return entityContract.read.entitiesCount();\n }\n\n /**\n * Gets the IDs of all active staking entities.\n *\n * @returns Array of active entity IDs\n *\n * @example\n * ```typescript\n * const activeIds = await vana.staking.getActiveEntities();\n * console.log(`Active entities: ${activeIds.join(', ')}`);\n * ```\n */\n async getActiveEntities(): Promise<readonly bigint[]> {\n const entityContract = this.getEntityContract();\n return entityContract.read.activeEntitiesValues();\n }\n\n /**\n * Gets a staker's position in a specific entity.\n *\n * @param staker - The address of the staker\n * @param entityId - The ID of the entity\n * @returns The staker's position information\n *\n * @example\n * ```typescript\n * const position = await vana.staking.getStakerPosition(\n * '0x742d35...',\n * 1n\n * );\n * console.log(`Shares: ${position.shares}`);\n * console.log(`Rewards: ${position.realizedRewards}`);\n * ```\n */\n async getStakerPosition(\n staker: Address,\n entityId: bigint,\n ): Promise<StakerEntityInfo> {\n const stakingContract = this.getStakingContract();\n\n const result = await stakingContract.read.stakerEntities([\n staker,\n entityId,\n ]);\n\n return {\n shares: result.shares,\n costBasis: result.costBasis,\n rewardEligibilityTimestamp: result.rewardEligibilityTimestamp,\n realizedRewards: result.realizedRewards,\n vestedRewards: result.vestedRewards,\n };\n }\n\n /**\n * Gets the earned rewards for a staker in an entity.\n *\n * @param staker - The address of the staker\n * @param entityId - The ID of the entity\n * @returns The earned rewards amount in wei\n *\n * @example\n * ```typescript\n * const rewards = await vana.staking.getEarnedRewards(\n * '0x742d35...',\n * 1n\n * );\n * console.log(`Earned: ${Number(rewards) / 1e18} VANA`);\n * ```\n */\n async getEarnedRewards(staker: Address, entityId: bigint): Promise<bigint> {\n const stakingContract = this.getStakingContract();\n return stakingContract.read.getEarnedRewards([staker, entityId]);\n }\n\n /**\n * Gets the minimum stake amount required to stake.\n *\n * @returns The minimum stake amount in wei\n *\n * @example\n * ```typescript\n * const minStake = await vana.staking.getMinStakeAmount();\n * console.log(`Minimum stake: ${Number(minStake) / 1e18} VANA`);\n * ```\n */\n async getMinStakeAmount(): Promise<bigint> {\n const stakingContract = this.getStakingContract();\n return stakingContract.read.minStakeAmount();\n }\n\n /**\n * Gets the count of active stakers in the protocol.\n *\n * @returns The number of active stakers\n *\n * @example\n * ```typescript\n * const count = await vana.staking.getActiveStakersCount();\n * console.log(`Active stakers: ${count}`);\n * ```\n */\n async getActiveStakersCount(): Promise<bigint> {\n const stakingContract = this.getStakingContract();\n return stakingContract.read.activeStakersListCount();\n }\n\n /**\n * Gets the bonding period for staking.\n *\n * @returns The bonding period in seconds\n *\n * @example\n * ```typescript\n * const bondingPeriod = await vana.staking.getBondingPeriod();\n * console.log(`Bonding period: ${bondingPeriod / 86400n} days`);\n * ```\n */\n async getBondingPeriod(): Promise<bigint> {\n const stakingContract = this.getStakingContract();\n return stakingContract.read.bondingPeriod();\n }\n\n /**\n * Gets the total distributed rewards for a specific entity from the subgraph.\n *\n * @remarks\n * This queries the VanaPool subgraph to retrieve the cumulative `totalDistributedRewards`\n * for a staking entity. This value represents the sum of all rewards that have been\n * processed (moved from lockedRewardPool to activeRewardPool) minus any forfeited\n * rewards that were returned.\n *\n * Requires a configured `subgraphUrl` in the Vana constructor options.\n *\n * @param entityId - The ID of the entity to query\n * @param options - Optional configuration including custom subgraph URL\n * @returns The total distributed rewards in wei\n * @throws {BlockchainError} When subgraph URL is not configured or query fails\n *\n * @example\n * ```typescript\n * const totalRewards = await vana.staking.getTotalDistributedRewards(1n);\n * const totalRewardsVana = Number(totalRewards) / 1e18;\n * console.log(`Total distributed rewards: ${totalRewardsVana.toLocaleString()} VANA`);\n * ```\n */\n async getTotalDistributedRewards(\n entityId: bigint,\n options: { subgraphUrl?: string } = {},\n ): Promise<bigint> {\n const graphqlEndpoint = options.subgraphUrl ?? this.context.subgraphUrl;\n\n if (!graphqlEndpoint) {\n throw new BlockchainError(\n \"subgraphUrl is required. Please provide a valid subgraph endpoint or configure it in Vana constructor.\",\n );\n }\n\n const query = `\n query GetStakingEntityRewards($entityId: ID!) {\n stakingEntity(id: $entityId) {\n totalDistributedRewards\n }\n }\n `;\n\n const response = await fetch(graphqlEndpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n query,\n variables: {\n entityId: entityId.toString(),\n },\n }),\n });\n\n if (!response.ok) {\n throw new BlockchainError(\n `Subgraph query failed with status ${response.status}`,\n );\n }\n\n const result = (await response.json()) as {\n data?: { stakingEntity?: { totalDistributedRewards: string } };\n errors?: Array<{ message: string }>;\n };\n\n if (result.errors && result.errors.length > 0) {\n throw new BlockchainError(\n `Subgraph query error: ${result.errors[0].message}`,\n );\n }\n\n if (!result.data?.stakingEntity) {\n throw new BlockchainError(`Staking entity ${entityId} not found`);\n }\n\n return BigInt(result.data.stakingEntity.totalDistributedRewards);\n }\n\n /**\n * Gets a comprehensive staking summary for a staker in an entity.\n *\n * @remarks\n * This method aggregates all relevant staking information for a staker in a single call,\n * including staked amount, current value, bonding status, and all reward types.\n *\n * Reward breakdown:\n * - `earnedRewards` = pendingInterest + vestedRewards + realizedRewards\n * - `pendingInterest` = currentValue - costBasis (unvested appreciation)\n * - `vestedRewards` = rewards that have vested but not yet withdrawn\n * - `realizedRewards` = rewards already withdrawn during unstakes\n *\n * @param staker - The address of the staker\n * @param entityId - The ID of the entity\n * @returns A comprehensive summary of the staker's position\n *\n * @example\n * ```typescript\n * const summary = await vana.staking.getStakerSummary('0x742d35...', 1n);\n * console.log(`Total staked: ${Number(summary.totalStaked) / 1e18} VANA`);\n * console.log(`Current value: ${Number(summary.currentValue) / 1e18} VANA`);\n * console.log(`In bonding period: ${summary.isInBondingPeriod}`);\n * console.log(`Remaining bonding time: ${Number(summary.remainingBondingTime) / 86400} days`);\n * console.log(`Vested rewards: ${Number(summary.vestedRewards) / 1e18} VANA`);\n * console.log(`Unvested rewards: ${Number(summary.unvestedRewards) / 1e18} VANA`);\n * console.log(`Realized rewards: ${Number(summary.realizedRewards) / 1e18} VANA`);\n * console.log(`Total earned: ${Number(summary.earnedRewards) / 1e18} VANA`);\n * ```\n */\n async getStakerSummary(\n staker: Address,\n entityId: bigint,\n ): Promise<StakerEntitySummary> {\n const chainId = this.getChainId();\n const stakingAddress = getContractAddress(chainId, \"VanaPoolStaking\");\n const entityAddress = getContractAddress(chainId, \"VanaPoolEntity\");\n const stakingAbi = getAbi(\"VanaPoolStaking\");\n const entityAbi = getAbi(\"VanaPoolEntity\");\n\n // Get latest block first to ensure all reads are from the same block\n const block = await this.context.publicClient.getBlock();\n const blockNumber = block.number;\n\n // Batch all contract reads into a single multicall RPC request at the same block\n const multicallResults = await this.context.publicClient.multicall({\n contracts: [\n {\n address: stakingAddress,\n abi: stakingAbi,\n functionName: \"stakerEntities\",\n args: [staker, entityId],\n },\n {\n address: stakingAddress,\n abi: stakingAbi,\n functionName: \"getEarnedRewards\",\n args: [staker, entityId],\n },\n {\n address: entityAddress,\n abi: entityAbi,\n functionName: \"entityShareToVana\",\n args: [entityId],\n },\n ],\n blockNumber,\n });\n\n const [positionResult, earnedRewardsResult, shareToVanaResult] =\n multicallResults;\n\n if (\n positionResult.status === \"failure\" ||\n earnedRewardsResult.status === \"failure\" ||\n shareToVanaResult.status === \"failure\"\n ) {\n throw new BlockchainError(\n \"Failed to fetch staker summary: one or more contract calls failed\",\n );\n }\n\n const position = positionResult.result as {\n shares: bigint;\n costBasis: bigint;\n rewardEligibilityTimestamp: bigint;\n realizedRewards: bigint;\n vestedRewards: bigint;\n };\n const earnedRewards = earnedRewardsResult.result as bigint;\n const shareToVana = shareToVanaResult.result as bigint;\n const currentTimestamp = block.timestamp;\n\n // Calculate remaining bonding time\n const eligibilityTimestamp = position.rewardEligibilityTimestamp;\n const remainingBondingTime =\n eligibilityTimestamp > currentTimestamp\n ? eligibilityTimestamp - currentTimestamp\n : 0n;\n const isInBondingPeriod = remainingBondingTime > 0n;\n\n // Calculate current value of shares\n // entityShareToVana returns the VANA value of 1 share (scaled by 1e18)\n const currentValue = (position.shares * shareToVana) / 10n ** 18n;\n\n // Calculate pending interest (unvested rewards)\n // pendingInterest = currentValue - costBasis (if positive)\n const unvestedRewards =\n currentValue > position.costBasis\n ? currentValue - position.costBasis\n : 0n;\n\n return {\n shares: position.shares,\n costBasis: position.costBasis,\n currentValue,\n rewardEligibilityTimestamp: eligibilityTimestamp,\n remainingBondingTime,\n isInBondingPeriod,\n vestedRewards: position.vestedRewards,\n unvestedRewards,\n realizedRewards: position.realizedRewards,\n earnedRewards,\n };\n }\n\n /**\n * Stakes VANA to an entity in the VanaPool protocol.\n *\n * @remarks\n * This method stakes native VANA tokens to a specified entity. The staker will receive\n * shares in proportion to their stake amount. A bonding period applies during which\n * rewards cannot be fully claimed without penalty.\n *\n * Requires a wallet client to be configured in the Vana constructor.\n *\n * @param params - The staking parameters\n * @param params.entityId - The ID of the entity to stake to\n * @param params.amount - The amount of VANA to stake (in wei, or as a string like \"1.5\" for 1.5 VANA)\n * @param params.recipient - Optional recipient address for the shares (defaults to the sender)\n * @param params.minShares - Optional minimum shares to receive (slippage protection, defaults to 0)\n * @returns The transaction hash\n * @throws {BlockchainError} When wallet client is not configured\n *\n * @example\n * ```typescript\n * // Stake 100 VANA to entity 1\n * const txHash = await vana.staking.stake({\n * entityId: 1n,\n * amount: \"100\", // 100 VANA\n * });\n * console.log(`Staked! Transaction: ${txHash}`);\n *\n * // Stake with slippage protection\n * const txHash2 = await vana.staking.stake({\n * entityId: 1n,\n * amount: parseEther(\"50\"), // 50 VANA in wei\n * minShares: parseEther(\"49\"), // Expect at least 49 shares\n * });\n * ```\n */\n async stake(\n params: {\n entityId: bigint;\n amount: bigint | string;\n recipient?: Address;\n minShares?: bigint;\n },\n options?: TransactionOptions,\n ): Promise<Hash> {\n this.assertWallet();\n\n const chainId = this.getChainId();\n const stakingAddress = getContractAddress(chainId, \"VanaPoolStaking\");\n const stakingAbi = getAbi(\"VanaPoolStaking\");\n\n // Convert amount to bigint if it's a string (e.g., \"1.5\" -> 1.5 VANA in wei)\n const amountWei =\n typeof params.amount === \"string\"\n ? parseEther(params.amount)\n : params.amount;\n\n // Get account with fallback to userAddress\n const account =\n this.context.walletClient.account ?? this.context.userAddress;\n const accountAddress =\n typeof account === \"string\" ? account : account.address;\n\n // Default recipient to the sender's address\n const recipient = params.recipient ?? accountAddress;\n\n // Default minShares to 0 (no slippage protection)\n const minShares = params.minShares ?? 0n;\n\n const txHash = await this.context.walletClient.writeContract({\n address: stakingAddress,\n abi: stakingAbi,\n functionName: \"stake\",\n args: [params.entityId, recipient, minShares],\n value: amountWei,\n account,\n chain: this.context.walletClient.chain,\n ...this.spreadTransactionOptions(options),\n });\n\n return txHash;\n }\n\n /**\n * Gets the maximum amount of VANA that can be unstaked in a single transaction.\n *\n * @remarks\n * This calls the contract's getMaxUnstakeAmount which returns the minimum of:\n * 1. The withdrawable VANA (costBasis if in bonding period, shareValue if eligible)\n * 2. The entity's activeRewardPool (what the entity has available)\n * 3. The treasury balance (what can be paid out)\n *\n * The limiting factor indicates what's constraining the unstake:\n * - 0 = user shares/costBasis\n * - 1 = activeRewardPool\n * - 2 = treasury\n *\n * @param staker - The address of the staker\n * @param entityId - The ID of the entity\n * @returns Object containing maxVana, maxShares, limitingFactor, and isInBondingPeriod\n *\n * @example\n * ```typescript\n * const result = await vana.staking.getMaxUnstakeAmount('0x742d35...', 1n);\n * console.log(`Max unstake: ${Number(result.maxVana) / 1e18} VANA`);\n * console.log(`Max shares: ${result.maxShares}`);\n * console.log(`In bonding period: ${result.isInBondingPeriod}`);\n * ```\n */\n async getMaxUnstakeAmount(\n staker: Address,\n entityId: bigint,\n ): Promise<{\n maxVana: bigint;\n maxShares: bigint;\n limitingFactor: number;\n isInBondingPeriod: boolean;\n }> {\n const stakingContract = this.getStakingContract();\n\n const result = await stakingContract.read.getMaxUnstakeAmount([\n staker,\n entityId,\n ]);\n\n return {\n maxVana: result[0],\n maxShares: result[1],\n limitingFactor: Number(result[2]),\n isInBondingPeriod: result[3],\n };\n }\n\n /**\n * Computes the new bonding period end timestamp after adding stake.\n *\n * @remarks\n * When a staker adds more stake to an existing position, the reward eligibility timestamp\n * is recalculated as a weighted average of the existing and new positions. This function\n * allows you to preview what the new eligibility timestamp would be without executing\n * the stake transaction.\n *\n * The formula used (matching the contract):\n * ```\n * newEligibility = (existingShares * existingEligibility + newShares * newEligibility) / totalShares\n * ```\n *\n * Where `newEligibility` for the incoming stake is `currentTimestamp + bondingPeriod`.\n *\n * @param params - The parameters for computing the new bonding period\n * @param params.staker - The address of the staker\n * @param params.entityId - The ID of the entity\n * @param params.stakeAmount - The amount of VANA to stake (in wei)\n * @returns Object containing the new eligibility timestamp and related info\n *\n * @example\n * ```typescript\n * // Preview bonding period after staking 100 VANA\n * const preview = await vana.staking.computeNewBondingPeriod({\n * staker: '0x742d35...',\n * entityId: 1n,\n * stakeAmount: parseEther(\"100\"),\n * });\n * console.log(`New eligibility: ${new Date(Number(preview.newEligibilityTimestamp) * 1000)}`);\n * console.log(`Remaining bonding time: ${Number(preview.newRemainingBondingTime) / 86400} days`);\n * ```\n */\n async computeNewBondingPeriod(params: {\n staker: Address;\n entityId: bigint;\n stakeAmount: bigint;\n }): Promise<{\n /** The new reward eligibility timestamp after staking */\n newEligibilityTimestamp: bigint;\n /** Remaining bonding time in seconds after staking */\n newRemainingBondingTime: bigint;\n /** Current eligibility timestamp (before staking) */\n currentEligibilityTimestamp: bigint;\n /** Current remaining bonding time (before staking) */\n currentRemainingBondingTime: bigint;\n /** Current shares held by staker */\n currentShares: bigint;\n /** Estimated new shares to be received */\n estimatedNewShares: bigint;\n /** Total shares after staking */\n totalSharesAfter: bigint;\n /** The bonding period duration in seconds */\n bondingPeriodDuration: bigint;\n /** Current block timestamp used for calculation */\n currentTimestamp: bigint;\n }> {\n const chainId = this.getChainId();\n const stakingAddress = getContractAddress(chainId, \"VanaPoolStaking\");\n const entityAddress = getContractAddress(chainId, \"VanaPoolEntity\");\n const stakingAbi = getAbi(\"VanaPoolStaking\");\n const entityAbi = getAbi(\"VanaPoolEntity\");\n\n // Get latest block first to ensure all reads are from the same block\n const block = await this.context.publicClient.getBlock();\n const blockNumber = block.number;\n const currentTimestamp = block.timestamp;\n\n // Batch all contract reads into a single multicall RPC request at the same block\n const multicallResults = await this.context.publicClient.multicall({\n contracts: [\n {\n address: stakingAddress,\n abi: stakingAbi,\n functionName: \"stakerEntities\",\n args: [params.staker, params.entityId],\n },\n {\n address: stakingAddress,\n abi: stakingAbi,\n functionName: \"bondingPeriod\",\n args: [],\n },\n {\n address: entityAddress,\n abi: entityAbi,\n functionName: \"vanaToEntityShare\",\n args: [params.entityId],\n },\n ],\n blockNumber,\n });\n\n const [positionResult, bondingPeriodResult, vanaToShareResult] =\n multicallResults;\n\n if (\n positionResult.status === \"failure\" ||\n bondingPeriodResult.status === \"failure\" ||\n vanaToShareResult.status === \"failure\"\n ) {\n throw new BlockchainError(\n \"Failed to compute new bonding period: one or more contract calls failed\",\n );\n }\n\n const position = positionResult.result as {\n shares: bigint;\n costBasis: bigint;\n rewardEligibilityTimestamp: bigint;\n realizedRewards: bigint;\n vestedRewards: bigint;\n };\n const bondingPeriodDuration = bondingPeriodResult.result as bigint;\n const vanaToShare = vanaToShareResult.result as bigint;\n\n const currentShares = position.shares;\n const currentEligibilityTimestamp = position.rewardEligibilityTimestamp;\n\n // Calculate current remaining bonding time\n const currentRemainingBondingTime =\n currentEligibilityTimestamp > currentTimestamp\n ? currentEligibilityTimestamp - currentTimestamp\n : 0n;\n\n // Estimate new shares: newShares = (stakeAmount * vanaToShare) / 1e18\n const estimatedNewShares = (params.stakeAmount * vanaToShare) / 10n ** 18n;\n\n // Calculate new eligibility timestamp using weighted average formula\n // newEligibility = (existingShares * existingEligibility + newShares * (currentTime + bondingPeriod)) / totalShares\n const totalSharesAfter = currentShares + estimatedNewShares;\n const newStakeEligibility = currentTimestamp + bondingPeriodDuration;\n\n let newEligibilityTimestamp: bigint;\n if (currentShares === 0n) {\n // First stake: eligibility is simply current time + bonding period\n newEligibilityTimestamp = newStakeEligibility;\n } else {\n // Weighted average of existing and new positions\n newEligibilityTimestamp =\n (currentShares * currentEligibilityTimestamp +\n estimatedNewShares * newStakeEligibility) /\n totalSharesAfter;\n }\n\n // Calculate new remaining bonding time\n const newRemainingBondingTime =\n newEligibilityTimestamp > currentTimestamp\n ? newEligibilityTimestamp - currentTimestamp\n : 0n;\n\n return {\n newEligibilityTimestamp,\n newRemainingBondingTime,\n currentEligibilityTimestamp,\n currentRemainingBondingTime,\n currentShares,\n estimatedNewShares,\n totalSharesAfter,\n bondingPeriodDuration,\n currentTimestamp,\n };\n }\n\n /**\n * Unstakes VANA from an entity in the VanaPool protocol.\n *\n * @remarks\n * This method unstakes native VANA tokens from a specified entity. The amount\n * that can be unstaked depends on whether the staker is in the bonding period:\n *\n * - **During bonding period**: Only cost basis can be withdrawn; rewards are forfeited\n * - **After bonding period**: Full current value (cost basis + rewards) can be withdrawn\n *\n * Use `getMaxUnstakeAmount` to determine the maximum amount that can be unstaked.\n *\n * Requires a wallet client to be configured in the Vana constructor.\n *\n * @param params - The unstaking parameters\n * @param params.entityId - The ID of the entity to unstake from\n * @param params.amount - The amount of VANA to unstake (in wei, or as a string like \"1.5\" for 1.5 VANA)\n * @param params.maxShares - Maximum shares to burn for slippage protection (defaults to 0, no protection)\n * @returns The transaction hash\n * @throws {BlockchainError} When wallet client is not configured\n *\n * @example\n * ```typescript\n * // Get max unstake amount first\n * const maxUnstake = await vana.staking.getMaxUnstakeAmount(address, 1n);\n *\n * // Unstake the maximum amount with slippage protection\n * const txHash = await vana.staking.unstake({\n * entityId: 1n,\n * amount: maxUnstake.maxVana,\n * maxShares: maxUnstake.maxShares,\n * });\n * console.log(`Unstaked! Transaction: ${txHash}`);\n * ```\n */\n async unstake(\n params: {\n entityId: bigint;\n amount: bigint | string;\n maxShares?: bigint;\n },\n options?: TransactionOptions,\n ): Promise<Hash> {\n this.assertWallet();\n\n const chainId = this.getChainId();\n const stakingAddress = getContractAddress(chainId, \"VanaPoolStaking\");\n const stakingAbi = getAbi(\"VanaPoolStaking\");\n\n // Convert amount to bigint if it's a string (e.g., \"1.5\" -> 1.5 VANA in wei)\n const amountWei =\n typeof params.amount === \"string\"\n ? parseEther(params.amount)\n : params.amount;\n\n // Default maxShares to 0 (no slippage protection)\n const maxShares = params.maxShares ?? 0n;\n\n // Get account with fallback to userAddress\n const account =\n this.context.walletClient.account ?? this.context.userAddress;\n\n const txHash = await this.context.walletClient.writeContract({\n address: stakingAddress,\n abi: stakingAbi,\n functionName: \"unstakeVana\",\n args: [params.entityId, amountWei, maxShares],\n account,\n chain: this.context.walletClient.chain,\n ...this.spreadTransactionOptions(options),\n });\n\n return txHash;\n }\n}\n"],"mappings":"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","names":[]}
@@ -361,6 +361,64 @@ export declare class StakingController extends BaseController {
361
361
  limitingFactor: number;
362
362
  isInBondingPeriod: boolean;
363
363
  }>;
364
+ /**
365
+ * Computes the new bonding period end timestamp after adding stake.
366
+ *
367
+ * @remarks
368
+ * When a staker adds more stake to an existing position, the reward eligibility timestamp
369
+ * is recalculated as a weighted average of the existing and new positions. This function
370
+ * allows you to preview what the new eligibility timestamp would be without executing
371
+ * the stake transaction.
372
+ *
373
+ * The formula used (matching the contract):
374
+ * ```
375
+ * newEligibility = (existingShares * existingEligibility + newShares * newEligibility) / totalShares
376
+ * ```
377
+ *
378
+ * Where `newEligibility` for the incoming stake is `currentTimestamp + bondingPeriod`.
379
+ *
380
+ * @param params - The parameters for computing the new bonding period
381
+ * @param params.staker - The address of the staker
382
+ * @param params.entityId - The ID of the entity
383
+ * @param params.stakeAmount - The amount of VANA to stake (in wei)
384
+ * @returns Object containing the new eligibility timestamp and related info
385
+ *
386
+ * @example
387
+ * ```typescript
388
+ * // Preview bonding period after staking 100 VANA
389
+ * const preview = await vana.staking.computeNewBondingPeriod({
390
+ * staker: '0x742d35...',
391
+ * entityId: 1n,
392
+ * stakeAmount: parseEther("100"),
393
+ * });
394
+ * console.log(`New eligibility: ${new Date(Number(preview.newEligibilityTimestamp) * 1000)}`);
395
+ * console.log(`Remaining bonding time: ${Number(preview.newRemainingBondingTime) / 86400} days`);
396
+ * ```
397
+ */
398
+ computeNewBondingPeriod(params: {
399
+ staker: Address;
400
+ entityId: bigint;
401
+ stakeAmount: bigint;
402
+ }): Promise<{
403
+ /** The new reward eligibility timestamp after staking */
404
+ newEligibilityTimestamp: bigint;
405
+ /** Remaining bonding time in seconds after staking */
406
+ newRemainingBondingTime: bigint;
407
+ /** Current eligibility timestamp (before staking) */
408
+ currentEligibilityTimestamp: bigint;
409
+ /** Current remaining bonding time (before staking) */
410
+ currentRemainingBondingTime: bigint;
411
+ /** Current shares held by staker */
412
+ currentShares: bigint;
413
+ /** Estimated new shares to be received */
414
+ estimatedNewShares: bigint;
415
+ /** Total shares after staking */
416
+ totalSharesAfter: bigint;
417
+ /** The bonding period duration in seconds */
418
+ bondingPeriodDuration: bigint;
419
+ /** Current block timestamp used for calculation */
420
+ currentTimestamp: bigint;
421
+ }>;
364
422
  /**
365
423
  * Unstakes VANA from an entity in the VanaPool protocol.
366
424
  *
@@ -327,22 +327,49 @@ class StakingController extends BaseController {
327
327
  * ```
328
328
  */
329
329
  async getStakerSummary(staker, entityId) {
330
- const stakingContract = this.getStakingContract();
331
- const entityContract = this.getEntityContract();
332
- const position = await stakingContract.read.stakerEntities([
333
- staker,
334
- entityId
335
- ]);
336
- const earnedRewards = await stakingContract.read.getEarnedRewards([
337
- staker,
338
- entityId
339
- ]);
330
+ const chainId = this.getChainId();
331
+ const stakingAddress = getContractAddress(chainId, "VanaPoolStaking");
332
+ const entityAddress = getContractAddress(chainId, "VanaPoolEntity");
333
+ const stakingAbi = getAbi("VanaPoolStaking");
334
+ const entityAbi = getAbi("VanaPoolEntity");
340
335
  const block = await this.context.publicClient.getBlock();
336
+ const blockNumber = block.number;
337
+ const multicallResults = await this.context.publicClient.multicall({
338
+ contracts: [
339
+ {
340
+ address: stakingAddress,
341
+ abi: stakingAbi,
342
+ functionName: "stakerEntities",
343
+ args: [staker, entityId]
344
+ },
345
+ {
346
+ address: stakingAddress,
347
+ abi: stakingAbi,
348
+ functionName: "getEarnedRewards",
349
+ args: [staker, entityId]
350
+ },
351
+ {
352
+ address: entityAddress,
353
+ abi: entityAbi,
354
+ functionName: "entityShareToVana",
355
+ args: [entityId]
356
+ }
357
+ ],
358
+ blockNumber
359
+ });
360
+ const [positionResult, earnedRewardsResult, shareToVanaResult] = multicallResults;
361
+ if (positionResult.status === "failure" || earnedRewardsResult.status === "failure" || shareToVanaResult.status === "failure") {
362
+ throw new BlockchainError(
363
+ "Failed to fetch staker summary: one or more contract calls failed"
364
+ );
365
+ }
366
+ const position = positionResult.result;
367
+ const earnedRewards = earnedRewardsResult.result;
368
+ const shareToVana = shareToVanaResult.result;
341
369
  const currentTimestamp = block.timestamp;
342
370
  const eligibilityTimestamp = position.rewardEligibilityTimestamp;
343
371
  const remainingBondingTime = eligibilityTimestamp > currentTimestamp ? eligibilityTimestamp - currentTimestamp : 0n;
344
372
  const isInBondingPeriod = remainingBondingTime > 0n;
345
- const shareToVana = await entityContract.read.entityShareToVana([entityId]);
346
373
  const currentValue = position.shares * shareToVana / 10n ** 18n;
347
374
  const unvestedRewards = currentValue > position.costBasis ? currentValue - position.costBasis : 0n;
348
375
  return {
@@ -454,6 +481,106 @@ class StakingController extends BaseController {
454
481
  isInBondingPeriod: result[3]
455
482
  };
456
483
  }
484
+ /**
485
+ * Computes the new bonding period end timestamp after adding stake.
486
+ *
487
+ * @remarks
488
+ * When a staker adds more stake to an existing position, the reward eligibility timestamp
489
+ * is recalculated as a weighted average of the existing and new positions. This function
490
+ * allows you to preview what the new eligibility timestamp would be without executing
491
+ * the stake transaction.
492
+ *
493
+ * The formula used (matching the contract):
494
+ * ```
495
+ * newEligibility = (existingShares * existingEligibility + newShares * newEligibility) / totalShares
496
+ * ```
497
+ *
498
+ * Where `newEligibility` for the incoming stake is `currentTimestamp + bondingPeriod`.
499
+ *
500
+ * @param params - The parameters for computing the new bonding period
501
+ * @param params.staker - The address of the staker
502
+ * @param params.entityId - The ID of the entity
503
+ * @param params.stakeAmount - The amount of VANA to stake (in wei)
504
+ * @returns Object containing the new eligibility timestamp and related info
505
+ *
506
+ * @example
507
+ * ```typescript
508
+ * // Preview bonding period after staking 100 VANA
509
+ * const preview = await vana.staking.computeNewBondingPeriod({
510
+ * staker: '0x742d35...',
511
+ * entityId: 1n,
512
+ * stakeAmount: parseEther("100"),
513
+ * });
514
+ * console.log(`New eligibility: ${new Date(Number(preview.newEligibilityTimestamp) * 1000)}`);
515
+ * console.log(`Remaining bonding time: ${Number(preview.newRemainingBondingTime) / 86400} days`);
516
+ * ```
517
+ */
518
+ async computeNewBondingPeriod(params) {
519
+ const chainId = this.getChainId();
520
+ const stakingAddress = getContractAddress(chainId, "VanaPoolStaking");
521
+ const entityAddress = getContractAddress(chainId, "VanaPoolEntity");
522
+ const stakingAbi = getAbi("VanaPoolStaking");
523
+ const entityAbi = getAbi("VanaPoolEntity");
524
+ const block = await this.context.publicClient.getBlock();
525
+ const blockNumber = block.number;
526
+ const currentTimestamp = block.timestamp;
527
+ const multicallResults = await this.context.publicClient.multicall({
528
+ contracts: [
529
+ {
530
+ address: stakingAddress,
531
+ abi: stakingAbi,
532
+ functionName: "stakerEntities",
533
+ args: [params.staker, params.entityId]
534
+ },
535
+ {
536
+ address: stakingAddress,
537
+ abi: stakingAbi,
538
+ functionName: "bondingPeriod",
539
+ args: []
540
+ },
541
+ {
542
+ address: entityAddress,
543
+ abi: entityAbi,
544
+ functionName: "vanaToEntityShare",
545
+ args: [params.entityId]
546
+ }
547
+ ],
548
+ blockNumber
549
+ });
550
+ const [positionResult, bondingPeriodResult, vanaToShareResult] = multicallResults;
551
+ if (positionResult.status === "failure" || bondingPeriodResult.status === "failure" || vanaToShareResult.status === "failure") {
552
+ throw new BlockchainError(
553
+ "Failed to compute new bonding period: one or more contract calls failed"
554
+ );
555
+ }
556
+ const position = positionResult.result;
557
+ const bondingPeriodDuration = bondingPeriodResult.result;
558
+ const vanaToShare = vanaToShareResult.result;
559
+ const currentShares = position.shares;
560
+ const currentEligibilityTimestamp = position.rewardEligibilityTimestamp;
561
+ const currentRemainingBondingTime = currentEligibilityTimestamp > currentTimestamp ? currentEligibilityTimestamp - currentTimestamp : 0n;
562
+ const estimatedNewShares = params.stakeAmount * vanaToShare / 10n ** 18n;
563
+ const totalSharesAfter = currentShares + estimatedNewShares;
564
+ const newStakeEligibility = currentTimestamp + bondingPeriodDuration;
565
+ let newEligibilityTimestamp;
566
+ if (currentShares === 0n) {
567
+ newEligibilityTimestamp = newStakeEligibility;
568
+ } else {
569
+ newEligibilityTimestamp = (currentShares * currentEligibilityTimestamp + estimatedNewShares * newStakeEligibility) / totalSharesAfter;
570
+ }
571
+ const newRemainingBondingTime = newEligibilityTimestamp > currentTimestamp ? newEligibilityTimestamp - currentTimestamp : 0n;
572
+ return {
573
+ newEligibilityTimestamp,
574
+ newRemainingBondingTime,
575
+ currentEligibilityTimestamp,
576
+ currentRemainingBondingTime,
577
+ currentShares,
578
+ estimatedNewShares,
579
+ totalSharesAfter,
580
+ bondingPeriodDuration,
581
+ currentTimestamp
582
+ };
583
+ }
457
584
  /**
458
585
  * Unstakes VANA from an entity in the VanaPool protocol.
459
586
  *