← Protocols
ERC-3525 — Semi-Fungible Token
Standard / EIP·EVM

ERC-3525 — Semi-Fungible Token

01Description

Final ERC standard for semi-fungible tokens that combines ERC-721 unique IDs with an ERC-20-style quantitative `value` and a `slot` attribute. Tokens sharing the same slot are fungible by value, enabling unified representation of bonds, vesting schedules, and structured financial instruments.

02Best for
  • 01tokenized bonds and vesting
  • 02structured financial instruments
  • 03fractional NFT-like assets
  • 04RWA primitives
  • 05tokens with quantitative + identity dimensions
03Install
  • pnpm add @solvprotocol/erc-3525
  • # reference impl: https://github.com/solv-finance/erc-3525
05Prompt snippet
Implement ERC-3525 by inheriting Solv Protocol's reference `ERC3525` contract (which itself extends ERC-721 + ERC-165). Each token carries a `<tokenId, slot, value>` triple: `slotOf(uint256 tokenId) returns (uint256)`, `balanceOf(uint256 tokenId) returns (uint256)` (overloaded — returns the token's `value`, distinct from ERC-721 `balanceOf(address)`), and `transferFrom(uint256 fromTokenId, address to, uint256 value)` / `transferFrom(uint256 fromTokenId, uint256 toTokenId, uint256 value)` for value transfers between tokens of the same slot. Emit `TransferValue`, `ApprovalValue`, and `SlotChanged` events. Use `valueDecimals()` (returns uint8) to expose value precision the way ERC-20 `decimals()` does. Tokens in different slots are non-fungible with each other; tokens in the same slot can split/merge value freely.
06Gotchas
  • `balanceOf` is OVERLOADED — `balanceOf(address)` returns NFT count (ERC-721 semantics) while `balanceOf(uint256 tokenId)` returns the token's `value`. Integrators that auto-resolve overloads (ethers v5 vs v6, viem) often pick the wrong one and read garbage.
  • Two tokens with different `slot` values are NOT interchangeable — value transfers across slots MUST revert. Validate slot equality on every value-level transfer or you'll silently break accounting.
  • Splitting via `transferFrom(fromTokenId, toAddress, value)` mints a NEW tokenId on the receiver — wallets that subscribe to ERC-721 `Transfer` events will see unexpected mints; index `SlotChanged` and `TransferValue` separately.
  • Approval is two-tier: ERC-721 token-level approvals AND a separate value-level `approve(uint256 tokenId, address operator, uint256 value)`. Forgetting the value-level approve is the #1 integration bug.
  • Most NFT marketplaces (OpenSea, Blur) treat ERC-3525 as plain ERC-721 and ignore `value` — listings will trade the entire `value` payload of a tokenId, which is rarely what users expect.
07Alternatives