
Privacy is Decency: a Closer Look at Our Protocols
"Connected humans are commodified & surveilled 24/7 without even realizing it." – Mykola Siusko Web3, in the name of user sovereignty, has constructed platforms that unwittingly extend the now-ubiquitous machinery of surveillance capitalism into the on-chain economy. Ethereum and other public blockchains provide data and metadata that, with the help of AI, make nearly all activity on these networks traceable and identifiable. Fortunately, there is an incredible movement of people and projects...
Data Storage Showdown: Arweave, IPFS, or Filecoin?
TL;DR This article analyzes three data storage protocols: Arweave, IPFS, and Filecoin. Sarcophagus chose Arweave as its data storage protocol because Arweave guarantees data permanence without relying on another party or service. Note:IPFS provides temporary data storage with "pinning" services. The data access address remains constant.Filecoin provides data storage for a fixed amount of time and is built on IPFS-like storage technology. Users pay fees regularly for continued storage.Arweave ...

Road to Regeneration
IntroductionThe world of cryptocurrency has seen its growth accelerate tremendously in recent years. Significant advancements have come in the fields of decentralized finance, digital assets, and blockchain gaming, putting cryptocurrency near the forefront of public discourse and bringing interest and energy into the blockchain space. This phase of growth has created an exciting atmosphere that bodes well for continued innovation and the advancement of decentralization as an organizational ph...
DAOs and onchain orgs, it's time to accelerate. Govern at startup speed.

Privacy is Decency: a Closer Look at Our Protocols
"Connected humans are commodified & surveilled 24/7 without even realizing it." – Mykola Siusko Web3, in the name of user sovereignty, has constructed platforms that unwittingly extend the now-ubiquitous machinery of surveillance capitalism into the on-chain economy. Ethereum and other public blockchains provide data and metadata that, with the help of AI, make nearly all activity on these networks traceable and identifiable. Fortunately, there is an incredible movement of people and projects...
Data Storage Showdown: Arweave, IPFS, or Filecoin?
TL;DR This article analyzes three data storage protocols: Arweave, IPFS, and Filecoin. Sarcophagus chose Arweave as its data storage protocol because Arweave guarantees data permanence without relying on another party or service. Note:IPFS provides temporary data storage with "pinning" services. The data access address remains constant.Filecoin provides data storage for a fixed amount of time and is built on IPFS-like storage technology. Users pay fees regularly for continued storage.Arweave ...

Road to Regeneration
IntroductionThe world of cryptocurrency has seen its growth accelerate tremendously in recent years. Significant advancements have come in the fields of decentralized finance, digital assets, and blockchain gaming, putting cryptocurrency near the forefront of public discourse and bringing interest and energy into the blockchain space. This phase of growth has created an exciting atmosphere that bodes well for continued innovation and the advancement of decentralization as an organizational ph...
DAOs and onchain orgs, it's time to accelerate. Govern at startup speed.

Subscribe to Decent DAO

Subscribe to Decent DAO
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers
written by wen_me
As you know, there is no right path; there is only your path. Finding that path depends on what tools are available and what outcomes you hope to achieve.
You can index events on every transfer and create a Merkle root consisting of user addresses and user allocations.
To achieve this, you need an indexing service to make this information readily available. Or, you need to generate an exhaustive list of transfer events up to a certain block height, which may be resource intensive.
Pros:
Gas-less
Merkle Roots
Cons:
Centralized indexing
Centralized Merkle Root Creation
If you want to support the tracking of every transfer natively within your ERC20, simply include the ERC20Snapshot.sol ERC20 extension. When capturing current user balances, call captureSnapshot to reference the balances at a later point in time.
The downside of this approach is that it requires additional state management on every transfer function, which increases the gas costs for every transfer transaction.
Pros:
Contract Layer Integration
Decentralized
Cons:
Expensive Creation & Activation
Each path has tradeoffs depending on whether the goal is decentralization or cost. This tutorial focuses on OpenZeppelin Snapshot.
ERC20 Knowledge
OZ Integrations / Smart Contracts
Hardhat
Create a contract called SnaphotToken.sol.
SnaphotToken.sol inherits from IERC20.sol and ERC20Snapshot.sol.
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol";
contract SnapshotToken is IERC20, ERC20Snapshot {
}
Define the constructor and mint tokens to an array of holders.
constructor(
string memory _name,
string memory _symbol,
address[] memory _hodlers,
uint256[] memory _allocations
) ERC20(_name, _symbol) {
for (uint256 i = 0; i < _hodlers.length; i++) {
_mint(_hodlers[i], _allocations[i]);
}
}
Create an external function that calls the internal _snapshot function. This is the function that assigns user balances/total supply to a specific point in -time and may be referenced later when you are looking to fork/mirror token allocations.
function captureSnapShot() external returns (uint256 snapId) {
snapId = _snapshot();
}
Finally, add the internal _beforeTokenTransfer function, which is called to update balance and/or total supply snapshots before the values are modified.
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual override(ERC20Snapshot) {
super._beforeTokenTransfer(from, to, amount);
}
If your code looks similar to the code below, you now have the ability to natively record users’ balances and quickly reference them by calling balanceOfAt(address account, uint256 snapshotID) or reference the totalSupply by calling totalSupplyAt(uint256 snapshotId).
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol";
contract SnapshotToken is IERC20, ERC20Snapshot {
constructor(
string memory _name,
string memory _symbol,
address[] memory _hodlers,
uint256[] memory _allocations
) ERC20(_name, _symbol) {
for (uint256 i = 0; i < _hodlers.length; i++) {
_mint(_hodlers[i], _allocations[i]);
}
}
function captureSnapShot() external returns (uint256 snapId) {
snapId = _snapshot();
}
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual override(ERC20Snapshot) {
super._beforeTokenTransfer(from, to, amount);
}
}
Let’s connect this alpha to a business case we have at Decent DAO.
Decent’s goal was to mirror the token holders’ balances which denote their voting power within a DAO to a subsidiary organization owned by the parent token holders.
So, we utilized ERC20Snapshot.sol to record parent organization balances, mint a subsidiary token allocation for the parent allocation, and then split this allocation based on the recorded parent allocations.
/// @notice Calculate a users cToken allocation
/// @param claimer Address which is being claimed for
/// @return cTokenAllocation Users cToken allocation
function calculateClaimAmount(address claimer)
public
view
returns (uint256 cTokenAllocation)
{
cTokenAllocation = isSnapClaimed[claimer] ? 0 :
(VotesToken(pToken).balanceOfAt(claimer, snapId) * pAllocation)
/
VotesToken(pToken).totalSupplyAt(snapId);
}
written by wen_me
As you know, there is no right path; there is only your path. Finding that path depends on what tools are available and what outcomes you hope to achieve.
You can index events on every transfer and create a Merkle root consisting of user addresses and user allocations.
To achieve this, you need an indexing service to make this information readily available. Or, you need to generate an exhaustive list of transfer events up to a certain block height, which may be resource intensive.
Pros:
Gas-less
Merkle Roots
Cons:
Centralized indexing
Centralized Merkle Root Creation
If you want to support the tracking of every transfer natively within your ERC20, simply include the ERC20Snapshot.sol ERC20 extension. When capturing current user balances, call captureSnapshot to reference the balances at a later point in time.
The downside of this approach is that it requires additional state management on every transfer function, which increases the gas costs for every transfer transaction.
Pros:
Contract Layer Integration
Decentralized
Cons:
Expensive Creation & Activation
Each path has tradeoffs depending on whether the goal is decentralization or cost. This tutorial focuses on OpenZeppelin Snapshot.
ERC20 Knowledge
OZ Integrations / Smart Contracts
Hardhat
Create a contract called SnaphotToken.sol.
SnaphotToken.sol inherits from IERC20.sol and ERC20Snapshot.sol.
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol";
contract SnapshotToken is IERC20, ERC20Snapshot {
}
Define the constructor and mint tokens to an array of holders.
constructor(
string memory _name,
string memory _symbol,
address[] memory _hodlers,
uint256[] memory _allocations
) ERC20(_name, _symbol) {
for (uint256 i = 0; i < _hodlers.length; i++) {
_mint(_hodlers[i], _allocations[i]);
}
}
Create an external function that calls the internal _snapshot function. This is the function that assigns user balances/total supply to a specific point in -time and may be referenced later when you are looking to fork/mirror token allocations.
function captureSnapShot() external returns (uint256 snapId) {
snapId = _snapshot();
}
Finally, add the internal _beforeTokenTransfer function, which is called to update balance and/or total supply snapshots before the values are modified.
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual override(ERC20Snapshot) {
super._beforeTokenTransfer(from, to, amount);
}
If your code looks similar to the code below, you now have the ability to natively record users’ balances and quickly reference them by calling balanceOfAt(address account, uint256 snapshotID) or reference the totalSupply by calling totalSupplyAt(uint256 snapshotId).
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol";
contract SnapshotToken is IERC20, ERC20Snapshot {
constructor(
string memory _name,
string memory _symbol,
address[] memory _hodlers,
uint256[] memory _allocations
) ERC20(_name, _symbol) {
for (uint256 i = 0; i < _hodlers.length; i++) {
_mint(_hodlers[i], _allocations[i]);
}
}
function captureSnapShot() external returns (uint256 snapId) {
snapId = _snapshot();
}
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual override(ERC20Snapshot) {
super._beforeTokenTransfer(from, to, amount);
}
}
Let’s connect this alpha to a business case we have at Decent DAO.
Decent’s goal was to mirror the token holders’ balances which denote their voting power within a DAO to a subsidiary organization owned by the parent token holders.
So, we utilized ERC20Snapshot.sol to record parent organization balances, mint a subsidiary token allocation for the parent allocation, and then split this allocation based on the recorded parent allocations.
/// @notice Calculate a users cToken allocation
/// @param claimer Address which is being claimed for
/// @return cTokenAllocation Users cToken allocation
function calculateClaimAmount(address claimer)
public
view
returns (uint256 cTokenAllocation)
{
cTokenAllocation = isSnapClaimed[claimer] ? 0 :
(VotesToken(pToken).balanceOfAt(claimer, snapId) * pAllocation)
/
VotesToken(pToken).totalSupplyAt(snapId);
}
No activity yet