{"_id":"@anastasia-labs/single-asset-staking-offchain","name":"@anastasia-labs/single-asset-staking-offchain","dist-tags":{"latest":"0.3.7"},"versions":{"0.3.7":{"name":"@anastasia-labs/single-asset-staking-offchain","version":"0.3.7","description":"https://docs.github.com/en/packages/quickstart","main":"./dist/index.js","types":"./dist/index.d.ts","type":"module","keywords":[],"author":"","license":"ISC","devDependencies":{"@sinclair/typebox":"^0.25.13","@types/node":"^20.4.9","@typescript-eslint/eslint-plugin":"^5.59.1","@typescript-eslint/parser":"^5.59.1","eslint":"^8.52.0","eslint-config-prettier":"^8.8.0","prettier":"^3.1.0","prettier-eslint":"^16.1.2","ts-node":"^10.9.1","tsup":"^6.7.0","typescript":"^5.1.3","vitest":"0.34.6"},"dependencies":{"@lucid-evolution/lucid":"0.3.47","@noble/hashes":"^1.5.0","ts-pattern":"^5.0.5"},"directories":{"test":"test"},"scripts":{"test":"export NODE_ENV='emulator' && vitest run","build":"tsup src/index.ts --minify --format esm,cjs --clean","lint":"eslint","repack":"pnpm run build  && pnpm pack","ts-node":"ts-node"},"_id":"@anastasia-labs/single-asset-staking-offchain@0.3.7","_integrity":"sha512-/KuHIVCRiZa4DaMf7Usi28+yQi0qOE2egZ7uRWJ9ghcxThc6pjUBXrFxLgysL1FAA3J3CBSEdfUPbDlJ1TAfQg==","_resolved":"/tmp/cdca5be3af6641c57c0bc896951d613f/anastasia-labs-single-asset-staking-offchain-0.3.7.tgz","_from":"file:anastasia-labs-single-asset-staking-offchain-0.3.7.tgz","_nodeVersion":"18.20.4","_npmVersion":"10.7.0","dist":{"integrity":"sha512-/KuHIVCRiZa4DaMf7Usi28+yQi0qOE2egZ7uRWJ9ghcxThc6pjUBXrFxLgysL1FAA3J3CBSEdfUPbDlJ1TAfQg==","shasum":"5ba85dd62c35511f33136222540c0d51d27c9863","tarball":"https://registry.npmjs.org/@anastasia-labs/single-asset-staking-offchain/-/single-asset-staking-offchain-0.3.7.tgz","fileCount":4,"unpackedSize":129367,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCI3XbwUFQTfzolWL1NiaQQ4m1KSgP1ylsrkPTSzvPv1AIhAIIdjY0TCUWuOt4JIgLOm9Vjuq4TZ5qzQAsa9XUoyDa1"}]},"_npmUser":{"name":"anastasia-labs","email":"info@anastasialabs.com"},"maintainers":[{"name":"anastasia-labs","email":"info@anastasialabs.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/single-asset-staking-offchain_0.3.7_1729016910429_0.0004492171819512336"},"_hasShrinkwrap":false}},"time":{"created":"2024-10-15T18:28:30.333Z","0.3.7":"2024-10-15T18:28:30.672Z","modified":"2024-10-15T18:28:30.938Z"},"maintainers":[{"name":"anastasia-labs","email":"info@anastasialabs.com"}],"description":"https://docs.github.com/en/packages/quickstart","keywords":[],"license":"ISC","readme":"# Single Asset Staking Offchain\n\n## Table of Contents\n\n- [Introduction](#introduction)\n- [Overview](#overview)\n- [Details](#details)\n  - [Deployment](#deployment)\n    - [Build Scripts](#build-scripts-buildscriptsts)\n    - [Deploy Reference Scripts](#deploy-reference-scripts-deployrefscriptsts)\n  - [Setup](#setup)\n    - [Create Config UTxO](#create-config-utxo-createconfigts)\n    - [Initialize Staking](#initialize-staking-initstakingts)\n  - [User Participation](#user-participation)\n  - [Active Staking](#active-staking)\n  - [Rewards Processing](#rewards-processing-processrewardsts)\n    - [Initialize Commit Fold](#initialize-commit-fold-initfoldts)\n    - [Complete Commit Fold](#complete-commit-fold-multifoldts)\n    - [Initialize Reward Fold](#initialize-reward-fold-initrewardfoldts)\n    - [Complete Reward Fold](#complete-reward-fold-rewardfoldnodests)\n    - [Project Reclaims Reward](#project-reclaims-reward-reclaimrewardts)\n    - [Deinitialize Head Node](#deinitialize-head-node-dinitnodets)\n  - [Claim](#claim-reclaimnodets)\n- [Important Notes](#important-notes)\n- [Local Build](#local-build)\n- [Tests](#tests)\n  - [Test Framework](#testing-framework)\n  - [Running Tests](#running-tests)\n\n# Introduction\n\n\"Single Asset Staking Offchain\" project provides the necessary SDK to interact with \"Single Asset Staking Contracts\". These contracts facilitate collective staking of digital assets and distributing rewards among participants in a completely on-chain and trustless manner.\n\nAs the name suggests, it allows for a single asset, which can be any Cardano Native Fungible Token, to be staked to earn rewards. The reward itself can be any Cardano Native Fungible Token. The contracts are parameterized with `policyId` and `tokenName` (in addition to a few others) of stake token and reward token. This allows different projects conducting the Staking event to configure the contracts accordingly.\n\nInstead of a fixed percentage based return, the staking reward obtained is not known beforehand. Because its determined by the total amount of assets staked till the end of the staking period and the total rewards locked before staking begins. Eligible participants are then given rewards propotional to their share of stake (`(userStake * totalRewards) / totalStake`).\n\nThe contracts are available at [Single Asset Staking](https://github.com/Anastasia-Labs/single-asset-staking).\n\n# Overview\n\nAn interesting technical detail about this protocol is the use of an [on-chain association list](https://github.com/Plutonomicon/plutonomicon/blob/main/assoc.md). It maintains every unique public key's stake in a separate UTxO which points to the next stake UTxO. Every UTxO in the list will have `StakingSetNode` in its datum.\n\n```hs\ndata StakingSetNode = MkSetNode\n  { key :: StakingNodeKey  -- owner wallet's PaymentPubKeyHash\n  , next :: StakingNodeKey -- next PaymentPubKeyHash in a list of lexicographically sorted key hashes\n  {- This field tells us which Staking Campaign this node belongs to.\n     Each Staking Campaign is uniquely identified by a Config UTxO\n     containing an NFT (configCS.configTN) -}\n  , configTN :: TokenName\n  }\n\ndata StakingNodeKey = Key BuiltinByteString | Empty\n```\n\nThis sections provides you with the timeline of different phases involved in Single Asset Staking. With each phase further listing the order of actions which comprises it.\n\n```mermaid\ntimeline\n\n    title Single Asset Staking Phases\n\n    Deployment : Build Scripts\n               : Deploy Reference Scripts\n\n    Setup : Create Config UTxO\n          : Initialize Staking\n\n    User Participation : Register Stake\n                       : Modify Stake*\n                       : Remove Stake* (w/o penalty)\n\n    Active Staking : Remove Stake* (w/ 25% penalty)\n\n    Rewards Processing : Initialize Commit Fold\n                       : Complete Commit Fold\n                       : Initialize Reward Fold\n                       : Complete Reward Fold\n                       : Project Reclaims Reward\n                       : Deinitialize Head Node\n\n    Claim : Users Claim Stake & Reward\n```\n\n> Note: [*] - Actions which can be performed if user wishes to.\n\n# Details\n\n## Deployment\n\nEverything begins here with _Anastasia Labs_ configuring and providing the Smart Contracts, on-chain as Reference Scripts. Once these are available, Projects can reuse the same contracts for any number of new Staking Campaigns. The deployment phase comprises of below two actions.\n\n### **Build Scripts** `buildScripts.ts`\n\nThe contracts available from Single Asset Staking repository, require certain paramters to be applied. These parameters include `configCS` (policyId of ConfigPolicy) and script credentials in case of dependant scripts. This step involves providing contracts with the required parameters.\n\n### **Deploy Reference Scripts** `deployRefScripts.ts`\n\nThis step uses the applied validators obtained above to create a [Reference Script UTxO](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0033) for every validator. In order to easily identify a particular validator on-chain, a native minting policy is used in conjuction. It mints an NFT with the validator name and is made available inside the RefUTxO. This native minting policy allows minting for a very short duration of _thirty mintues_ within which all the RefUTxOs must be created. All the RefUTxOs are sent to an \"Always Fail Script\" address ensuring they are immutable and locked forever.\n\n```mermaid\n---\ntitle: Deploy Reference Scripts\n---\ngraph LR\n    I1(Input UTxO)\n    TX[ Transaction ]\n    subgraph Always Fails Script\n    O1((\"UTxO\n        $deployId.ConfigPolicy\n        ref_script: configPolicy \"))\n    O2((\"UTxO\n        $deployId.NodeValidator\n        ref_script: nodeValidator \"))\n    O3((\"UTxO\n        $deployId.NodePolicy\n        ref_script: nodePolicy\"))\n    ..\n    end\n    I1 --> TX\n    MP{Native Minting Policy} -.-o TX\n    TX --> O3\n```\n\n## Setup\n\nEvery Project which wants to create a new Staking Campaign will start from here. This phases consists of below three actions. Its only after this phase is completed that users can begin staking.\n\n### **Create Config UTxO** `createConfig.ts`\n\nEvery individual Staking Campaign begins by first creating a Config UTxO for it. It works like this:\n\n- Every Staking Campaign's configuration, instead of being configured in the contract as parameters, is obtained from a Config UTxO's datum. This datum (of type `StakingConfig`) contains all the event related details. Thereby leaving the same set of contracts to work for multiple campaigns.\n\n```hs\ndata StakingConfig = StakingConfig\n  { stakingInitUTxO :: TxOutRef\n  , freezeStake :: POSIXTime\n  , endStaking :: POSIXTime\n  , penaltyAddress :: Address\n  , stakeCS :: CurrencySymbol\n  , stakeTN :: TokenName\n  , minimumStake :: Integer\n  , rewardCS :: CurrencySymbol\n  , rewardTN :: TokenName\n  }\n```\n\n- All the deployed smart contracts require this Config UTxO as a reference input to validate every transaction.\n- With the help of a Config Policy, this UTxO is uniquely identified by an NFT (`configCS.configTN`), minted in the same transaction.\n- This ouput is then sent to an Always Fails Script address, guaranteeing no changes to the camapaign parameters.\n- All the UTxOs belonging to a particular campaign will have the same `configTN` field value in their datum to avoid mixing UTxOs from different campaigns.\n\n```mermaid\n---\ntitle: Create Config UTxO\n---\ngraph LR\n    I1(Config Init UTxO)\n    TX[ Transaction ]\n    subgraph Always Fails Script\n    O1((\"Output\n        $ConfigPolicy.abc: 1n\n        datum: StakingConfig\"))\n    end\n    I1 --> TX\n    MP{\"Ref Input\n      $deployId.ConfigPolicy\"}  -.-o|Mint $ConfigPolicy.configTN| TX\n    TX --> O1\n```\n\n### **Initialize Staking** `initStaking.ts`\n\nHere project locks the entire staking reward in `tokenHolderValidator`. The total reward amount will be distributed among participants in proportion to their stake. Locking of rewards beforehand gives high assurance to all the participants before they can begin staking. Additional one percent of total reward amount is paid as protocol fees for facilitating staking to Anastasia Labs.\n\n> Note: The wallet containing the `stakingInitUTxO` must have enough reward tokens to cover the total reward amount plus 1% protocol fees along with\n> sufficient lovelaces to cover mininum ADA costs and transaction fees.\n\nThis transaction also marks the beginning of the association list which will contain all the stake by different participants as separate UTxOs. The first node of the list know as head node is created in this step.\n\nHead node differs from all the nodes in that its key is null. Every valid stake UTxO in the list has a unique \"Node Token\" which is minted by `nodePolicy` at the time of its insertion. The token name is derived as NODE_PREFIX (\"FSN\") + PaymentPubKeyHash thereby making every node token unique. Head node just has NODE_PREFIX as the token name.\n\n```mermaid\n---\ntitle: Initialize Staking\n---\ngraph LR\n    I1(Staking Init UTxO)\n    TX[ Transaction ]\n    subgraph Token Holder Validator\n    O1((\"Output 1\n        $rewardCS.rewardTN: totalReward\n        $TokenHolderPolicy.RTHolder: 1n\n        datum: configTN = abc\"))\n    end\n    subgraph Staking Validator\n    O2((\"Head Node\n        $stakeCS.stakeTN: minStake\n        $NodePolicy.FSN: 1n\n        datum: key = null, next = null,\n        configTN = abc\"))\n    end\n    O3((\"Output 3\"))\n    I1 --> TX\n    MP1{\"Ref Input\n      $deployId.TokenHolderPolicy\"}  -.-o|Mint $TokenHolderPolicy.RTHolder| TX\n    TX --> O1\n    TX --> O2\n    MP2{\"Ref Input\n      $deployId.NodePolicy\"}  -.-o|Mint $NodePolicy.FSN| TX\n    TX -->|1% Protocol Fees| O3\n    R1((\"Ref Input\n        $ConfigPolicy.abc: 1n\n        datum: StakingConfig\")) -.-o TX\n```\n\n## User Participation\n\nNow the Staking event is opened and users can participate by locking their stake in `nodeValidator` by updating the linked list. Before the stake is frozen, participants can choose to increase, decrease or remove their stake altogether.\n\n## Active Staking (`insertNode.ts`, `modifyNode.ts`, `removeNode.ts`)\n\nOnce stake is frozen (configured by parameter `freezeStake :: POSIXTime`), the active staking phase begins for which the participants will be earning rewards. This phase lasts till `endStaking :: POSIXTime` as decided by the project. During this period, new participants cannot enter nor can the old ones modify their stake. However, existing stakers can still get their stake back if they choose to, by paying 25% of their stake as penalty fee.\n\n## Rewards Processing `processRewards.ts`\n\nAfter the active staking phase has ended (after `endStaking :: POSIXTime`) comes the part where project processes and allocates rewards to its participants who staked till now.\n\nIts done by first calculating and saving the total amount staked on-chain. Then every participant's stake UTxO is updated to include rewards in it, in proportion to their stake. Reward calculation is given by the formula `(userStake * totalRewards) / totalStake`.\n\nFollowing sequence of on-chain actions elaborate further on how rewards processing mechanism works. The `processRewards.ts` endpoint carries out all\nthe below actions for the project in a sequential manner.\n\n### **Initialize Commit Fold** `initFold.ts`\n\nCommit Fold carries out the computation of total staked amount by going over all the linked list UTxOs one after the other in order. The current state of the computation, i.e. how far along the linked list it has summed and the current sum, is stored in a UTxO at `foldValidator`. This UTxO is uniquely identified with the presence of an NFT ($FoldPolicy.CFold) minted using `foldPolicy`. This initialization of commit UTxO is perfomed in this step.\n\n```mermaid\n---\ntitle: Initialize Commit Fold\n---\ngraph LR\n    I1(Input UTxO)\n    TX[ Transaction ]\n    subgraph Staking Validator\n    N1((\"Ref Input\n        $stakeCS.stakeTN: minStake\n        $NodePolicy.FSN: 1n\n        datum:\n        key = null, next = aa1,\n        configTN = abc \"))\n    N2((\"UTxO\n        $stakeCS.stakeTN: minStake\n        $NodePolicy.FSNaa1 : 1n\n        datum:\n        key = aa1, next = bb2,\n        configTN = abc \"))\n    N3((\"UTxO\n        $stakeCS.stakeTN: minStake\n        $NodePolicy.FSNbb2 : 1n\n        datum:\n        key = bb2, next = null,\n        configTN = abc \"))\n    end\n    subgraph Fold Validator\n    O1((\"Output\n        $FoldPolicy.CFold: 1n\n        datum:\n        key = null, next = aa1,\n        totalStake = 0n,\n        configTN = abc\n        \"))\n    end\n    N1 -.-o TX\n    I1 --> TX\n    MP{\"Ref Input\n      $deployId.FoldPolicy\"}  -.-o|Mint $FoldPolicy.CFold| TX\n    TX --> O1\n    R1((\"Ref Input\n        $ConfigPolicy.abc: 1n\n        datum: StakingConfig\")) -.-o TX\n```\n\n### **Complete Commit Fold** `multiFold.ts`\n\nHere one stake UTxO after another is used as reference input to calculate and update `totalStake` value in Commit Fold UTxO's datum. This is done till the end of list is not reached, at which point `next = null` in fold datum and `totalStake` is finally determined. This endpoint needs to be called repeatedly till `fetchCampaignState` in `fetchState.ts` does not return `CapaignStatus.StakeCalculationEnded` in its CampaignStatus field of CampaignState.\n\n```mermaid\n---\ntitle: Complete Commit Fold\n---\ngraph LR\n    I1(Input UTxO)\n    TX[ Transaction ]\n    subgraph Staking Validator\n    N1((\"UTxO\n        $stakeCS.stakeTN: minStake\n        $NodePolicy.FSN: 1n\n        datum:\n        key = null, next = aa1,\n        configTN = abc \"))\n    N2((\"Ref Input\n        $stakeCS.stakeTN: minStake\n        $NodePolicy.FSNaa1 : 1n\n        datum:\n        key = aa1, next = bb2,\n        configTN = abc \"))\n    N3((\"Ref Input\n        $stakeCS.stakeTN: minStake\n        $NodePolicy.FSNbb2 : 1n\n        datum:\n        key = bb2, next = null,\n        configTN = abc \"))\n    end\n    subgraph Fold Validator\n    F1((\"Input\n        $FoldPolicy.CFold: 1n\n        datum:\n        key = null, next = aa1,\n        totalStake = 0n,\n        configTN = abc\n        \"))\n    F2((\"Output\n        $FoldPolicy.CFold: 1n\n        datum:\n        key = null, next = null,\n        totalStake = 2 * minStake,\n        configTN = abc\n        \"))\n    end\n    F1 --> TX\n    N2 -.-o TX\n    N3 -.-o TX\n    I1 --> TX\n    TX --> F2\n    R1((\"Ref Input\n        $ConfigPolicy.abc: 1n\n        datum: StakingConfig\")) -.-o TX\n```\n\n> Note: Head Node's stake is never taken into account.\n\n### **Initialize Reward Fold** `initRewardFold.ts`\n\nNow that we have total staked amount available on-chain, we initialize the reward fold wherein a UTxO to `rewardFoldValidator` is sent. This contains total reward amount obtained from UTxO locked at \"Token Holder Validator\" along with `totalRewardTokens` and `totalStake` in its datum. Additionally, it has `$RewardPolicy.RFold` NFT minted from \"Reward Policy\" which validates that the initialization is carried out accurately.\n\n```mermaid\n---\ntitle: Initialize Reward Fold\n---\ngraph LR\n    I1(Input UTxO)\n    TX[ Transaction ]\n    subgraph Staking Validator\n    N1((\"Head Node Input\n        $ADA : 3\n        $stakeCS.stakeTN: minStake\n        $NodePolicy.FSN: 1n\n        datum:\n        key = null, next = aa1,\n        configTN = abc \"))\n    N2((\"Head Node Output\n        $ADA : 2\n        $stakeCS.stakeTN: minStake\n        $NodePolicy.FSN: 1n\n        datum:\n        key = null, next = aa1,\n        configTN = abc \"))\n    end\n    subgraph Commit Fold Validator\n    F1((\"Input\n        $FoldPolicy.CFold: 1n\n        datum:\n        totalStake = 2 * minStake\n        key = null, next = null,\n        configTN = abc\n        \"))\n    end\n    subgraph Reward Fold Validator\n    O1((\"Output\n        $RewardPolicy.RFold: 1n\n        $rewardCS.rewardTN: totalReward\n        datum:\n        totalRewardTokens = totalReward\n        totalStake = 2 * minStake\n        key = null, next = aa1,\n        configTN = abc\n        \"))\n    end\n    subgraph Token Holder Validator\n    T1((\"Input\n        $rewardCS.rewardTN: totalReward\n        $TokenHolderPolicy.RTHolder: 1n\n        datum: configTN = abc \"))\n    end\n    MP1{\"Ref Input\n      $deployId.TokenHolderPolicy\"}  -.-o|Burn $TokenHolderPolicy.RTHolder| TX\n    MP2{\"Ref Input\n      $deployId.FoldPolicy\"}  -.-o|Burn $FoldPolicy.CFold| TX\n    MP3{\"Ref Input\n      $deployId.RewardPolicy\"}  -.-o|Mint $RewardPolicy.RFold| TX\n\n    T1 --> TX\n    F1 --> TX\n    N1 --> TX\n    I1 --> TX\n    TX --> O1\n    TX --> N2\n    R1((\"Ref Input\n        $ConfigPolicy.abc: 1n\n        datum: StakingConfig\")) -.-o TX\n```\n\n> Note: Upon undergoing rewards fold a UTxO has to pay 1 ADA folding fee.\n\n### **Complete Reward Fold** `rewardFoldNodes.ts`\n\nWith Reward Fold UTxO initialized with rewards and other essential information, rewards can be distributed into individual stake UTxO. This is similar to commit fold, with UTxO after head node being processed first and other UTxOs in the order they appear in list. Rewards fold gets concluded when `next = null` on processing the last UTxO of the list. Upon undergoing rewards fold a UTxO has to pay 1 ADA folding fee. This endpoint needs to be called repeatedly till `fetchCampaignState` in `fetchState.ts` does not return `CapaignStatus.UserClaimsAllowed` in its CampaignStatus field of CampaignState.\n\n```mermaid\n---\ntitle: Complete Reward Fold\n---\ngraph LR\n    I1(Input UTxO)\n    TX[ Transaction ]\n    subgraph Staking Validator\n    N1((\"UTxO\n        $stakeCS.stakeTN: minStake\n        $NodePolicy.FSN: 1n\n        datum:\n        key = null, next = aa1,\n        configTN = abc \"))\n    N2((\"Input\n        $stakeCS.stakeTN: minStake\n        $NodePolicy.FSNaa1 : 1n\n        datum:\n        key = aa1, next = bb2,\n        configTN = abc \"))\n    N3((\"Input\n        $stakeCS.stakeTN: minStake\n        $NodePolicy.FSNbb2 : 1n\n        datum:\n        key = bb2, next = null,\n        configTN = abc \"))\n    N4((\"Output\n        $stakeCS.stakeTN: minStake\n        $NodePolicy.FSNaa1 : 1n\n        $rewardCS.rewardTN: totalReward/2\n        datum:\n        key = aa1, next = bb2,\n        configTN = abc \"))\n    N5((\"Output\n        $stakeCS.stakeTN: minStake\n        $NodePolicy.FSNbb2 : 1n\n        $rewardCS.rewardTN: totalReward/2\n        datum:\n        key = bb2, next = null,\n        configTN = abc \"))\n    end\n    subgraph Reward Fold Validator\n    O1((\"Output\n        $RewardPolicy.RFold: 1n\n        $rewardCS.rewardTN: totalReward\n        datum:\n        totalRewardTokens = totalReward\n        totalStake = 2 * minStake\n        key = null, next = aa1,\n        configTN = abc\n        \"))\n    O2((\"Output\n        $RewardPolicy.RFold: 1n\n        $rewardCS.rewardTN: rewardsLeft*\n        datum:\n        totalRewardTokens = totalReward\n        totalStake = 2 * minStake\n        key = null, next = null,\n        configTN = abc\n        \"))\n    end\n    O1 --> TX\n    N2 --> TX\n    N3 --> TX\n    I1 --> TX\n    TX --> O2\n    TX --> N4\n    TX --> N5\n    R1((\"Ref Input\n        $ConfigPolicy.abc: 1n\n        datum: StakingConfig\")) -.-o TX\n```\n\n> Note: [*] - If any reward tokens are left due to remainder from integer division in `(userStake * totalRewards) / totalStake`\n\n### **Project Reclaims Reward** `reclaimReward.ts`\n\nOnce the rewards are processed, project is free to claim any remaining project tokens left in \"Reward Fold UTxO\" along with any lovelaces present. They'll have to additionally burn \"$RewardPolicy.RFold\" token for this, which is only allowed when `next = null` i.e. all rewards are processed.\n\n### **Deinitialize Head Node** `dinitNode.ts`\n\nThe project is also free to reclaim the Head Node with the \"minStake\" and lovelaces present in it. It can only be done after the reward fold is initiated (Reward Fold Token datum has `next == *head node's next*`), therefore ensuring no information is lost.\n\n## Claim `reclaimNode.ts`\n\nOnly after rewards are processed can the participants claim their stake and reward. They can do so by spending their stake UTxO from \"Staking Validator\" after signing transaction with private key belonging to the PaymentPubKeyHash as `key` in UTxO's datum.\n\n# Important Notes\n\n1. Its advisable to use two different wallets, each containing one Init UTxO (`configInitUTxO` & `stakingInitUTxO`). So that they aren’t spent before their respective initialization transactions. The wallet containing the `stakingInitUTxO` must have enough reward tokens to cover the total reward amount plus 1% protocol fees, minimum stake requirement worth of stake tokens and sufficient lovelaces to cover mininum ADA costs and transaction fees.\n2. Only `stakeTN` and `rewardTN` fields are expected to be UTF-8 encoded strings. All the other Currency Symbols/ Policy Ids and Token name strings are expected to Hex encoded strings.\n3. The offchain expects Cardano Native Token amounts in their lowest denomination/unit. For example, if the Stake Token is [MIN](https://cardanoscan.io/token/29d222ce763455e3d7a09a665ce554f00ac89d2e99a1a83d267170c64d494e) which has 6 decimal places and you want 10 MIN to be the minimum stake. You will have to configure `minimumStake`(field in `StakingConfig` and other Config objects) to be `10 * 10^6` (`Amount * 10 ^ Decimals`) i.e `10_000_000`. Similarly, the reward amount the project wants to lock as total staking reward, specified by `rewardsAmount` must be in its lowest unit for e.g. if total reward is 1000 MIN then `rewardsAmount == 1_000_000_000`. Likewise, the reponses obtained from the endpoint will provide CNT amount values in their lowest unit. Fields like `totalStake` & `totalReward` (in `CampaignState`), `rewardAmount` in `InitStakingConfig` and `toStake` used while staking or modifying stake, adhere to this representation.\n4. Once stake is frozen (configured by parameter `freezeStake :: POSIXTime`), the active staking phase begins for which the participants will be earning rewards. This phase lasts till `endStaking :: POSIXTime` as decided by the project. During this period, new participants cannot enter nor can the old ones modify their stake. However, existing stakers can still get their stake back if they choose to, by paying 25% of their stake as penalty fee.\n5. All the transactions provided will have a validity range of 6 minutes. Attempting to perform any action using the SDK endpoints with less than 3 minutes of difference from either the `freezeStake` or `endStaking` will result in an error i.e. `|Date.now() - freezeStakeOrEndStaking| > 3 minutes`.\n6. Using an on-chain association list for managing stake provides increased throughput with increased number of stakers. However, it\n   is susceptible to contention due to multiple users updating the list concurrently. Hence, it is recommended that the project user of Maestro APIs (adding, modifying and withdrawing stake) implement a retry mechanism (with some delay) to handle failures encountered after submitting the signed transactions.\n7. Every wallet that stakes need to provide 3 ADA along with the intended stake amount, greater than minimum stake that is configured by the\n   project. Out of this 3 ADA, a variable folding fee ranging from ~1 to 1.5 ADA will be taken. The remaining ADA (minimum ADA requirement) will be returned to the wallet along with its stake and reward, after claims are open.\n\n# Local Build\n\nIn the main directory\n\n```\npnpm run build\n```\n\n# Tests\n\n## Testing Framework\n\nhttps://github.com/vitest-dev/vitest\n\n## Running Tests\n\n```sh\npnpm test\n```\n\n![single-asset-staking-offchain](/assets/gifs/single-asset-staking-offchain.gif)\n","readmeFilename":"README.md"}