PIP-10: SBT base Voting Power Integration
ID 1
ID 1
Proposed on: Jun 3rd, 2025
Proposed on: Jun 3rd, 2025
Votes
Proposal
Proposal
PIP-10: SBT Governance Token with Delegation per Token ID
- Title: SBT-Based Voting Power Integration
- PIP Number: PIP-10
- Author: pwollemi
- Date of Submission: 2025-05-22
- Status: Draft
- Category: Core
Motivation
This proposal introduces a governance model based on the ERC-1155 standard, enabling Soulbound Token (SBT)-like functionality by tracking voting delegation on a per-token-ID basis. It allows multiple governance classes, each represented by a different token ID, and supports granular delegation, enhancing DAO tooling and composability.
Specification / Details
Goals
- Track
totalSupplyper token ID viaERC1155Supply. - Enable delegation of voting power per token ID.
- Provide
getVotes(account)view function for governance integration. - Expose token metadata URIs for each token ID.
Public Interface
FunctionDescriptionmint(address to, uint256 id, uint256 amount, string memory uri)Mint governance tokensburn(address from, uint256 id, uint256 amount)Burn governance tokensdelegate(address to, uint256 tokenId)Delegate voting power of a specific token IDgetVotes(address account)Get full voting power of an accountgetDelegates(address account, uint256 tokenId)Get delegate for an account/token combinationgetGovernanceTokenIds()List all token IDs used for votinguri(uint256 tokenId)Return metadata URI for a token ID
Storage Layout
mapping(uint256 => string) private _tokenURIs; uint256[] public governanceTokenIds;
mapping(address => mapping(uint256 => address)) public delegates; mapping(uint256 => mapping(address => uint256)) public delegatedVotes;
Delegation Logic
function delegate(address to, uint256 tokenId) external { address prevDelegate = delegates[msg.sender][tokenId]; uint256 balance = balanceOf(msg.sender, tokenId);
// Revoke previous delegate if (prevDelegate != address(0)) { delegatedVotes[tokenId][prevDelegate] -= balance; }
// Assign new delegate delegates[msg.sender][tokenId] = to;
if (to != address(0)) { delegatedVotes[tokenId][to] += balance; }
emit Delegated(msg.sender, to, tokenId); }
Voting Power Logic
function getVotes(address account) public view returns (uint256 totalVotes) { for (uint256 i = 0; i < governanceTokenIds.length; i++) { uint256 id = governanceTokenIds[i];
// Own balance (if not delegated) if (delegates[account][id] == address(0)) { totalVotes += balanceOf(account, id); }
// Votes delegated to them totalVotes += delegatedVotes[id][account]; } }
Transfer Hook for Delegation Tracking
function _afterTokenTransfer( address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) internal override { super._afterTokenTransfer(operator, from, to, ids, amounts, data);
for (uint256 i = 0; i < ids.length; i++) { uint256 id = ids[i]; uint256 amount = amounts[i];
if (from != address(0)) { address fromDelegate = delegates[from][id]; if (fromDelegate != address(0)) { delegatedVotes[id][fromDelegate] -= amount; } }
if (to != address(0)) { address toDelegate = delegates[to][id]; if (toDelegate != address(0)) { delegatedVotes[id][toDelegate] += amount; } } } }
SBT Evaluation Required?
Yes — delegation is tracked and non-transferable voting logic is advisable for some DAO configurations.
Scope of Impact / Potential Risks and Mitigation
- Risk: Delegation tracking can become inconsistent if transfer logic is overridden or misused.
- Mitigation: Override
_afterTokenTransfercarefully and test edge cases with proxy/multi-delegate scenarios.
Alternatives and Rationale
- ERC-20 Voting: Simpler, but lacks per-ID granularity.
- ERC-721 Governance: Cannot batch mint or transfer efficiently, less suited for use cases with many token classes.
- Soulbound ERC-1155: Stronger alignment with identity-bound voting, especially with per-ID delegation.