Platform ArtBlock is a way to create a creator based community that focuses on specific type of art. These communities are a form of DAO.
- Users can purchase platform native ERC20 ABX Tokens with a fixed price of ethers.
- User can create new communities by speending ABX Token.
- Each community will have their own native ERC20 token which can be exchanged with ABX Token.
- Each community will have a Decentralized EXchange (DEX) which will have two assets - ABX Token & the community native token.
- The liquidity pool follows Constant Product Automated Market Maker (CPAMM). The price curve for these AMMs abides by the x*y=k equation, where X and Y are the quantities of assets 1 and 2, and K is a constant.
- Creators can publish their art in respective communities for approval by staking certain community native tokens. The arts can be of two category - exclusive & general.
- Community members can be vote on the approval of the product with options for upvote & downvote. The voting is weighted.
- Artists can host Dutch auctions for approved exclusive items.
- Auctioned exclusive arts will be minted as Non-Transferable Token.
- Approved general arts will be minted as NFT.
- There is an ArtBlock marketplace where creators can sell their general art products in community native tokens & current owners can resell general arts.
- There is a dynamic royalty system for original creators allowing them to set a percentage for each subsequent rsell.
- 👷🏽♂️ Hardhat
- 🌈 RainbowKit
- ➬ WAGMI
- 🌐 Next JS
- [🗺 Web3.storage]
- 🗺 Etherscan
- 🕹 Typechain
- TailwindCSS – Utility-first CSS framework for rapid UI development
- TypeScript – Static type checker for end-to-end typesafety
- Prettier – Opinionated code formatter for consistent code style
- ESLint – Pluggable linter for Next.js and TypeScript
Node js
npm
yarn
Install yarn:
npm i -g yarn
For contract dev: Run this command on the root folder:
yarn
For Frontend dev:
Go to frontend
folder and install node modules:
cd frontend
yarn
- Install a wallet like Metamask
- Copy
.env.example
to.env
- Mac or Linux
cp .env.example .env
- Windows
copy .env.example .env
- Mac or Linux
- Set the env variable in
.env
file on root level and onfrontend
folder:
Variable descriptions:
RPC_NODE_API_KEY
: Get from Alchemy site after sign up and loginPRIVATE_KEY
: Export private key from metamask, follow these instructionsETHERSCAN_API_KEY
: Get from etherscan
Frontend ENV Variable:
4. NEXT_PUBLIC_ALCHEMY_API_KEY
: Same as RPC_NODE_API_KEY
- Compile Contract:
npm run compile
- Run test:
npm run test
- Deploy
npm run deploy:<network>
- Verify on etherscan
npx hardhat verify --network sepolia <YOUR_CONTRACT_ADDRESS> <Paramaters>
For example for ArtBlockPlatform
contract:
npx hardhat verify --network sepolia 0xAECD7dFD9d5ED08EA916B052D90A75366B963A61 "Hello world"
The voting weight is determined by the amount of community native tokens held by the voter.
function upvote(uint proposalId) external {
ArtProposal storage _artProposal = artProposals[proposalId];
require(_artProposal.closingTime < block.timestamp);
uint weight = (1 ether * comToken.balanceOf(msg.sender) / comToken.totalSupply());
_artProposal.upvotes += weight;
}
A Dutch auction initially offers an item at a price in excess of the amount the seller expects to receive. The price lowers in steps until a bidder accepts the current price. That bidder wins the auction and pays that price for the item.
function createDutchAuction(Artwork memory _artwork, uint _start, uint _decrement, uint _interval, uint _minprice) external {
Auction storage auction = auctions.push();
auction.artwork = _artwork;
auction.start = _start;
auction.decrement = _decrement * 60; //decrement is in minutes, multiplied by 60 to convert to seconds
auction.interval = _interval;
auction.minprice = _minprice;
auction.artist = msg.sender;
auction.startTime = block.timestamp;
auctionMap[auctionIndex++] = auction;
}
mapping (uint => Artwork) exclusiveArtIndex;
function placeBid(uint _auctionId) external {
Auction storage auction = auctionMap[_auctionId];
uint currentPrice = auction.start - (auction.decrement * (block.timestamp - auction.start) / auction.interval);
require (
comToken.transferFrom(msg.sender, auction.artwork.artist, currentPrice)
);
uint id = exclusiveNTT.safeMint(msg.sender);
exclusiveArtIndex[id] = auction.artwork;
artNFT.burn(auction.artwork.nftTokenId);
}
Before transferring a minted NFT, it will check that their is no current owner. If there is a current owner, transfer will not occur.
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId,
uint256 batchSize
) internal virtual override {
require(from == address(0), "Err: token transfer is BLOCKED");
super._beforeTokenTransfer(from, to, tokenId, batchSize);
}
function buyFromMarket (uint _id) external {
Sellpost storage _sellpost = sellposts[getSellpost(_id)];
require(comToken.transferFrom(msg.sender, artNFT.ownerOf(_sellpost.artwork.nftTokenId), _sellpost.price - (_sellpost.price * _sellpost.artwork.royalty / 100 )), "Community Token transfer to owner
failed!");
require(comToken.transferFrom(msg.sender, _sellpost.artwork.artist, _sellpost.price * _sellpost.artwork.royalty / 100 ), "Community Token transfer to artist failed!");
_sellpost.isSold = true;
}
How much dx, dy to add?
xy = k
(x + dx)(y + dy) = k'
No price change, before and after adding liquidity
x / y = (x + dx) / (y + dy)
x(y + dy) = y(x + dx)
x * dy = y * dx
x / y = dx / dy
dy = y / x * dx
How much shares to mint?
f(x, y) = value of liquidity
We will define f(x, y) = sqrt(xy)
L0 = f(x, y)
L1 = f(x + dx, y + dy)
T = total shares
s = shares to mint
Total shares should increase proportional to increase in liquidity
L1 / L0 = (T + s) / T
L1 * T = L0 * (T + s)
(L1 - L0) * T / L0 = s
Claim
(L1 - L0) / L0 = dx / x = dy / y
Proof
--- Equation 1 ---
(L1 - L0) / L0 = (sqrt((x + dx)(y + dy)) - sqrt(xy)) / sqrt(xy)
dx / dy = x / y so replace dy = dx * y / x
--- Equation 2 ---
Equation 1 = (sqrt(xy + 2ydx + dx^2 * y / x) - sqrt(xy)) / sqrt(xy)
Multiply by sqrt(x) / sqrt(x)
Equation 2 = (sqrt(x^2y + 2xydx + dx^2 * y) - sqrt(x^2y)) / sqrt(x^2y)
= (sqrt(y)(sqrt(x^2 + 2xdx + dx^2) - sqrt(x^2)) / (sqrt(y)sqrt(x^2))
sqrt(y) on top and bottom cancels out
--- Equation 3 ---
Equation 2 = (sqrt(x^2 + 2xdx + dx^2) - sqrt(x^2)) / (sqrt(x^2)
= (sqrt((x + dx)^2) - sqrt(x^2)) / sqrt(x^2)
= ((x + dx) - x) / x
= dx / x
Since dx / dy = x / y,
dx / x = dy / y
Finally
(L1 - L0) / L0 = dx / x = dy / y
function addLiquidity(uint256 _abxAmount, uint256 _comAmount)
external
returns (uint256 shares)
{
uint256 abxReserve = getAbxReserve();
uint256 comReserve = getComReserve();
if (abxReserve > 0 || comReserve > 0) {
require(
abxReserve * _comAmount == comReserve * _abxAmount
);
}
abxToken.transferFrom(msg.sender, address(this), _abxAmount);
comToken.transferFrom(msg.sender, address(this), _comAmount);
if (totalSupply == 0) {
shares = _sqrt(_abxAmount * _comAmount);
} else {
shares = _min(
(_abxAmount * totalSupply) / abxReserve,
(_comAmount * totalSupply) / comReserve
);
}
require(shares > 0);
_mint(msg.sender, shares);
}
How much dy for dx?
xy = k
(x + dx)(y - dy) = k
y - dy = k / (x + dx)
y - k / (x + dx) = dy
y - xy / (x + dx) = dy
(yx + ydx - xy) / (x + dx) = dy
ydx / (x + dx) = dy
There will be a 0.3% exchange fee.
function swap(address _tokenIn, uint256 _amountIn)
internal
returns (uint256 amountOut)
{
require(
_tokenIn == address(abxToken) || _tokenIn == address(comToken)
);
require(_amountIn > 0, "amount in = 0");
bool isAbxToken = _tokenIn == address(abxToken);
(
IERC20 tokenIn,
IERC20 tokenOut,
uint256 reserveIn,
uint256 reserveOut
) = isAbxToken
? (abxToken, comToken, getAbxReserve(), getComReserve())
: (comToken, abxToken, getComReserve(), getAbxReserve());
tokenIn.transferFrom(msg.sender, address(this), _amountIn);
uint256 amountInWithFee = (_amountIn * 997) / 1000;
amountOut =
(reserveOut * amountInWithFee) /
(reserveIn + amountInWithFee);
tokenOut.transfer(msg.sender, amountOut);
}