{"_id":"@1inch/token-hooks","_rev":"6-a605addb81a694636bae57b53e757b98","name":"@1inch/token-hooks","dist-tags":{"latest":"2.0.0"},"versions":{"2.0.0":{"name":"@1inch/token-hooks","version":"2.0.0","author":{"name":"1inch"},"license":"MIT","_id":"@1inch/token-hooks@2.0.0","maintainers":[{"name":"krboktv-inch","email":"kirill@1inch.io"},{"name":"sevenswen","email":"tihonenko.v.v@gmail.com"},{"name":"zzomrot","email":"d.bukov@1inch.io"},{"name":"zumzoom","email":"by.zumzoom@gmail.com"},{"name":"k06a","email":"k06aaa@gmail.com"},{"name":"1inch-robot","email":"devops@1inch.io"}],"homepage":"https://github.com/1inch/token-hooks#readme","bugs":{"url":"https://github.com/1inch/token-hooks/issues"},"dist":{"shasum":"d3b7e02f65b076eea8275bd149980f750ebb4872","tarball":"https://registry.npmjs.org/@1inch/token-hooks/-/token-hooks-2.0.0.tgz","fileCount":15,"integrity":"sha512-fn+nXwYWM1gX8m/iwD8brI7M/qZXzn/6SO+WtnV1+plUBMvoWKUa6y43XV3wUAlY+K+B5tQdzR5kPOWVb4j0yg==","signatures":[{"sig":"MEUCIH/aVJLB152fPcmhaOFI0pd/2j5T6YjpFqB5H/84ILMmAiEA9eOFcLtlwjPihge1xVcgFJhw4SXf4satgiO4ze4kAOk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":59335},"gitHead":"d46dbd9223f38875a9fff6ada53022680729cccb","scripts":{"lint":"yarn run lint:js && yarn run lint:sol","test":"hardhat test --parallel","clean":"rimraf artifacts cache coverage coverage.json contracts/hardhat-dependency-compiler","deploy":"hardhat deploy --network","lint:js":"eslint .","test:ci":"hardhat test","coverage":"hardhat coverage","lint:fix":"yarn run lint:js:fix && yarn run lint:sol:fix","lint:sol":"solhint --max-warnings 0 \"contracts/**/*.sol\"","lint:js:fix":"eslint . --fix","lint:sol:fix":"solhint --max-warnings 0 \"contracts/**/*.sol\" --fix"},"_npmUser":{"name":"k06a","actor":{"name":"k06a","type":"user","email":"k06aaa@gmail.com"},"email":"k06aaa@gmail.com"},"repository":{"url":"git+ssh://git@github.com/1inch/token-hooks.git","type":"git"},"_npmVersion":"10.9.2","description":"ERC20 extension enabling external smart contract based hooks to track balances of those users who opted-in to those hooks","directories":{},"resolutions":{"elliptic":"6.6.1"},"_nodeVersion":"23.7.0","dependencies":{"@1inch/solidity-utils":"6.6.0","@openzeppelin/contracts":"5.0.1"},"_hasShrinkwrap":false,"devDependencies":{"chai":"4.5.0","dotenv":"16.3.1","eslint":"8.56.0","ethers":"6.9.0","rimraf":"5.0.5","hardhat":"2.23.0","solhint":"3.6.2","hardhat-deploy":"0.11.45","hardhat-tracer":"^3.2.1","eslint-plugin-n":"16.4.0","solidity-coverage":"0.8.5","eslint-plugin-import":"2.29.1","hardhat-gas-reporter":"1.0.9","eslint-plugin-promise":"6.1.1","eslint-config-standard":"17.1.0","@openzeppelin/test-helpers":"0.5.16","@nomicfoundation/hardhat-ethers":"3.0.5","@nomicfoundation/hardhat-verify":"2.0.2","@nomicfoundation/hardhat-chai-matchers":"2.0.8"},"_npmOperationalInternal":{"tmp":"tmp/token-hooks_2.0.0_1751446834337_0.7535877296438613","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2025-07-02T09:00:34.279Z","modified":"2026-09-15T14:08:35.417Z","2.0.0":"2025-07-02T09:00:34.609Z"},"bugs":{"url":"https://github.com/1inch/token-hooks/issues"},"author":{"name":"1inch"},"license":"MIT","homepage":"https://github.com/1inch/token-hooks#readme","repository":{"url":"git+ssh://git@github.com/1inch/token-hooks.git","type":"git"},"description":"ERC20 extension enabling external smart contract based hooks to track balances of those users who opted-in to those hooks","maintainers":[{"email":"kirill@1inch.io","name":"krboktv-inch"},{"email":"tihonenko.v.v@gmail.com","name":"sevenswen"},{"email":"devops@1inch.io","name":"1inch-robot"},{"email":"ext.a.babkina@1inch.io","name":"ababkina-1inch"}],"readme":"[![Build Status](https://github.com/1inch/token-hooks/workflows/CI/badge.svg)](https://github.com/1inch/token-hooks/actions)\n[![Coverage Status](https://codecov.io/gh/1inch/token-hooks/branch/master/graph/badge.svg?token=Z3D5O3XUYV)](https://codecov.io/gh/1inch/token-hooks)\n[![NPM Package](https://img.shields.io/npm/v/@1inch/token-hooks.svg)](https://www.npmjs.org/package/@1inch/token-hooks)\n\n# 1inch Token Hooks: A Comprehensive Guide for Extending ERC20 Functionalities\n\n[Overview](#overview)\n\n[Primary Benefits](#primary-benefits)\n\n[Implementation](#implementation)\n\n[Generic Examples](#generic-examples)\n     \n[Deployed Examples](#deployed-examples)\n\n[Helpful Links](#other-helpful-links)\n\n## Overview\n> **Note:** Token Hooks were previously known as Token Plugins. The name was changed to better reflect the nature of the functionality.\n\nToken hooks are smart contracts that extend the capabilities of ERC20 tokens and wrappers by adding custom accounting features to the original token. Inspired by the hook concept widely used in the web 2.0 world, these hooks enable users to **dynamically increase the functionality of their tokens on-demand** without the need to transfer tokens to a special smart contract.\n\nThe major benefit, and a key difference from existing solutions, is that these do not require token transfers to a special smart contract, as is commonly seen in farming or delegating protocols. Another beneficial point is that once an ERC20 hook code is deployed, it can be reused by any tokens that support the 1inch hook standard.\n\nSupport for hooks on the token side is similar to the implementation of classic ERC20 extensions (i.e., OpenZeppelin ERC20 extensions). The deployment and usage are permissionless from the perspective of a token contract owner, since the holder is the actor who decides which hook to subscribe to.\n\nTechnically, hooks are a collection of smart contracts that track changes in ERC20 token balances and perform supplementary accounting tasks for those balances. They are particularly useful when you need to track, for example, token shares without actually transferring tokens to a dedicated accounting contract.\n\nThe token hooks standard is designed to be secure and to prevent asset loss, gas, and DoS attacks on transfers.\n\n***Note: ERC721 (NFT) and ERC1155 (Multi-token) support is coming soon!***\n\n## Primary Benefits\n- **100% permissionless from the token contract owner**: Open to all participants.\n- **Risk-free participation**: Token hooks do not require any approval, deposit, or transfer of funds into an external contract for participation.\n- **Multiple hook connections**: Users can connect with multiple hooks, allowing for simultaneous involvement in multiple incentive programs or governance systems, etc. (subject to a predefined limit, set at deployment).\n- **On-demand functionality**: Users can add or remove hooks as needed, enabling dynamic feature activation without token migrations or protocol upgrades.\n- **Simple to adopt**: Implementation is only 150 lines of code.\n- **High security**: 1inch Token Hooks have gone through extensive [audits](https://github.com/1inch/1inch-audits/tree/master/Fusion%20mode%20and%20Token-hooks) by multiple top-tier companies.\n- **Built-in reentrancy protection**: This feature ensures that the balances cannot be tampered with by manipulating hook accounting. \n- **Custom ERC20 representation**: A hook can be represented by its own associated ERC20 (custom inheritance), enabling building complex and multi-layered accounting systems like 1inch Fusion.\n\n## Use-Cases\nHere are some examples of how Token Hooks is currently being (or could be used) today, showcasing its ability to power automated governance systems and on-demand token functionality:\n\n- **st1INCH resolver delegation**\n  Through staking 1INCH, token holders receive Unicorn Power (UP), and can earn rewards from Resolvers in the Intent Swap system. In order to earn these rewards, the UP received from staking can be delegated (see contract) to a specific Resolver. The resolver is incentivized to have UP delegated to them, so they will reward delegators with some amount of funds. The delegation of st1INCH is done with a token hook, so there is no need to transfer the tokens to another contract. ([see dst1inch contract](https://etherscan.io/token/0xAccfAc2339e16DC80c50d2fa81b5c2B049B4f947#code))\n\n- **Weighted voting power & Automated governance**\n  VE governance models like veCRV require the user to lock tokens for a certain amount of time to earn voting rights. This signals to the protocol a long-term vested interest and greatly reduces the surface area for governance attacks. With Token Hooks, the VE token model can be replaced with logic that gives the wallet ramping voting power by simply holding the base governance token for long periods of time. When a wallet first holds the governance token, its voting power will be nearly zero, but over time (e.g. 2 years), it will increase until it reaches a set maximum. This creates an automated governance system where voting power evolves dynamically based on holding patterns without requiring manual lock-ups.\n\n- **LP-Token farming**\n  Some protocols incentivize LP token holders with additional rewards beyond swap fees through an additional yield contract that holds the LP tokens and distributes the rewards proportionally to the participating LPs. With token hooks, these extra rewards for LP holders can continue to be opt-in without the need to deposit those LP tokens into a secondary contract. ([See 1inch Fusion pods](https://etherscan.io/address/0x1A87c0F9CCA2f0926A155640e8958a8A6B0260bE#code))\n\n- **Shadow staking**\n  If a protocol wanted to simply reward holders of their token, they could reward them similarly to the weighted voting power method, but instead of increasing voting power over time, the APR of holding the token can increase. Long-term holders will receive rewards and short-term holders/traders would not receive the same benefit.\n\n- **Borrow/lending rewards**\n  In traditional lending protocols, users must transfer assets and hold both lending and debt tokens in their wallets, limiting farming opportunities. With 1inch Token Hooks, users are able to maintain custody of their assets while a hook tracks balances, distributing rewards seamlessly and securely without ever having to move the assets.\n\n## Limitations\n- Any hook's processing logic consumes additional gas, with external operations that change an account balance incurring higher costs. To mitigate this, the hook extension sets a limit on the gas consumption per hook and caps the maximum amount of gas that can be spent.\n- **Hook Quantity**: The contract deployer should establish a limit on the number of hooks managed under the hook management contract.\n- **Maximum gas usage**: The hook management contract limits the amount of gas any hook can use to avoid overspent and gas attacks. It is highly recommended not to change beyond the recommended amount of 140,000.\n- **Only works with transferrable tokens**: By fundamental design, hooks are unable to integrate with tokens whose balances can update without transfers (such as rebase tokens).\n\n## Implementation\n\n![ERC20Hooks](/src/img/HooksDiagram.png)\n\nConnecting a token contract with the 1inch Token Hooks is a straightforward process. If you're creating a brand new token contract or migrating an existing one, you can simply inherit from the hook-enabled ERC20 contract OR wrap an existing token and inherit hook functionality within the wrapper (`contract MyWrapper is ERC20Wrapper, ERC20Hooks { ... }`). Subsequently, any hook (deployed as a separate contract) can be connected to your hook-enabled ERC20, enabling it to track balance updates of the underlying asset efficiently.\n\nIn other words, 1inch Token Hooks require inheritance from an independent, \"hook-enabled\" ERC20 contract, which manages all related dependent hook contracts. The hook-enabled ERC20 contract is responsible for calling the `updateBalance` function with every change in an account's balance.\n\nAll hooks will only track the balances of participating accounts. So all non-participants are represented as \"0 addresses\". If an account is not participating in a hook and receives a hook-enabled token, the `From` and `To` amounts under `_updateBalances` will be represented as 0. Therefore, if a non-participant sends a hook-enabled token to an existing participant, it will effectively \"mint\" the tracked balance. If a participant sends a hook-enabled token to a non-participant, it will effectively \"burn\" the tracked balance.\n\n![Token Transfers](/src/img/TokenTransferDiagram.png)\n\nFor security purposes, hooks are designed with several fail-safes, including a maximum number of usable hooks, custom gas limits, a reentrancy guard, and native isolation from the main contract state. The maximum hooks and gas limit can be initialized as state variables using `MAX_HOOKS_PER_ACCOUNT` and `HOOK_CALL_GAS_LIMIT`, respectively. For reentrancy prevention, `ReentrancyGuardExt` is included from OpenZeppelin's library. Finally, for native isolation from the token contract, a single method with only three arguments (`To`, `From`, and `Amount`) is used. This simple architecture results in a dynamic (and risk-free!) enhancement of any ERC20 contract's capabilities.\n\n## Integrating hook support in your token implementation\nTo integrate hooks in a smart contract, a \"mothership\" or parent contract must be used to manage all related hooks. This includes adding, removing, and viewing hooks, as well as connecting multiple hooks. The parent contract calls the `updateBalance` function for each hook on every update of an account's balance. The hook then executes its logic based on the updated balance information. An account must connect a hook to utilize its logic.\n\n- **Inherit token**: `contract MyToken is ERC20Hooks { ... }`\n- **Or wrap it**: `contract MyWrapper is ERC20Wrapper, ERC20Hooks { ... }`\n\nThis will add support for the hook infrastructure.\n\n- **Wallets can hook**: `MyToken.addHook(hook)`, where `hook` is the address of your or a third-party deployed hook.\n  - Now every time a wallet balance changes, the hook will know about it.\n\n## How to create your own hook\nTo create your own hook, it is necessary to inherit the Hook contract and implement its abstract function `_updateBalances`.\n\n- **Inherit hook**: `contract MyHook is Hook { ... }`\n- **Implement _updateBalances** function to process wallet balance changes.\n\n## Generic Examples\n\nBelow are examples of implementing Token Hooks through either creating a new token with built-in hook support, or wrapping an existing token to add hook functionality on-demand.\n\n### Example 1: Creating a new token with hook support\n\n```solidity\n// SPDX-License-Identifier: MIT\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { ERC20Hooks } from \"@1inch/token-hooks/contracts/ERC20Hooks.sol\";\n\n// Minimalistic inherited token example\ncontract MyToken is ERC20, ERC20Hooks {\n    constructor(string memory name, string memory symbol)\n        ERC20(name, symbol)\n        ERC20Hooks(10, 140000) // Max 10 hooks, 140k gas limit per hook\n    {}\n    \n    function mint(address account, uint256 amount) external {\n        _mint(account, amount);\n    }\n}\n```\n\n### Example 2: Wrapping an existing token to add hook functionality\n\n```solidity\n// SPDX-License-Identifier: MIT\nimport { ERC20Wrapper, IERC20, ERC20 } from \"@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol\";\nimport { ERC20Hooks } from \"@1inch/token-hooks/contracts/ERC20Hooks.sol\";\n\n// Minimalistic token wrapper example\ncontract TokenWrapper is ERC20Wrapper, ERC20Hooks {\n    constructor(IERC20 underlyingToken)\n        ERC20(\"Wrapped Token\", \"wTKN\")\n        ERC20Wrapper(underlyingToken)\n        ERC20Hooks(10, 140000) // Max 10 hooks, 140k gas limit per hook\n    {}\n}\n```\n\n### Example 3: A hook that mints/burns based on token balances\n\n```solidity\n// SPDX-License-Identifier: MIT\nimport { ERC20 } from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport { Hook } from \"@1inch/token-hooks/contracts/Hook.sol\";\nimport { IERC20Hooks } from \"@1inch/token-hooks/contracts/interfaces/IERC20Hooks.sol\";\n\ncontract MyHook is ERC20, Hook {\n    constructor(string memory name, string memory symbol, IERC20Hooks token_)\n        ERC20(name, symbol)\n        Hook(token_)\n    {} // solhint-disable-line no-empty-blocks\n\n    function _updateBalances(address from, address to, uint256 amount) internal override {\n        if (from == address(0)) {\n            _mint(to, amount);\n        } else if (to == address(0)) {\n            _burn(from, amount);\n        } else {\n            _transfer(from, to, amount);\n        }\n    }\n}\n```\n\nThe dynamic nature of Token Hooks makes them perfect for building automated governance systems. Users can opt in to governance mechanisms on-demand, and governance parameters can automatically adjust based on token activity without requiring manual interactions.\n\n## Deployed Examples\n- [Hook-enabled ERC20 contract](https://arbiscan.io/token/0x36a8747fc5F09cDE48e7b8Eb073Ae911b2cBa933#code)\n- [Simple Hook contract](https://arbiscan.io/address/0x7f75495bf9a3f20b253a68a34a152c5f5587a742#code)\n- [1inch Fusion (Delegated Staked 1INCH) Hook Contract](https://etherscan.io/address/0x806d9073136c8A4A3fD21E0e708a9e17C87129e8#code)\n- [1inch Fusion Staking Farm](https://etherscan.io/address/0x1A87c0F9CCA2f0926A155640e8958a8A6B0260bE#code)\n\n## Other Helpful Links\n- [Hook-enabled ERC20 Token contract (abstract)](https://github.com/1inch/token-hooks/blob/master/contracts/ERC20Hooks.sol)\n- [Hook contract (abstract)](https://github.com/1inch/token-hooks/blob/master/contracts/Hook.sol)\n- [Anton Bukov speech at ETHCC](https://youtu.be/Is-T5Q2E0A8?feature=shared)\n- [Kirill Kuznetcov speech at Nethermind Summit, Istanbul](https://youtu.be/BwehZHhR8Z4?feature=shared)\n","readmeFilename":"README.md"}