PrivatePool

Git Source

Inherits: ERC721TokenReceiver

Author: out.eth (@outdoteth)

A private pool is a an NFT AMM controlled by a single owner with concentrated liquidity, custom fee rates, stolen NFT filtering, custom NFT weightings, royalty support, and flash loans. You can create a pool and change these parameters to your liking. Deposit NFTs and base tokens (or ETH) into the pool to enable trading. Earn fees on each trade.

State Variables

baseToken

The address of the base ERC20 token.

address public baseToken;

nft

The address of the nft.

address public nft;

changeFee

The change/flash fee to 4 decimals of precision. For example, 0.0025 ETH = 25. 500 USDC = 5_000_000.

uint56 public changeFee;

feeRate

The buy/sell fee rate (in basis points) 200 = 2%

uint16 public feeRate;

initialized

Whether or not the pool has been initialized.

bool public initialized;

payRoyalties

Whether or not the pool pays royalties to the NFT creator on each trade.

bool public payRoyalties;

useStolenNftOracle

Whether or not the pool uses the stolen NFT oracle to check if an NFT is stolen.

bool public useStolenNftOracle;

virtualBaseTokenReserves

The virtual base token reserves used in the xy=k invariant. Changing this will change the liquidity depth and price of the pool.

uint128 public virtualBaseTokenReserves;

virtualNftReserves

The virtual nft reserves used in the xy=k invariant. Changing this will change the liquidity depth and price of the pool.

The virtual NFT reserves that a user sets. If it's desired to set the reserves to match 16 NFTs then the virtual reserves should be set to 16e18. If weights are enabled by setting the merkle root to be non-zero then the virtual reserves should be set to the sum of the weights of the NFTs; where floor NFTs all have a weight of 1e18. A rarer NFT may have a weight of 2.3e18 if it's 2.3x more valuable than a floor.

uint128 public virtualNftReserves;

merkleRoot

The merkle root of all the token weights in the pool. If the merkle root is set to bytes32(0) then all NFTs are set to have a weight of 1e18.

bytes32 public merkleRoot;

stolenNftOracle

The NFT oracle to check if an NFT is stolen.

address public immutable stolenNftOracle;

factory

The factory contract that created this pool.

address payable public immutable factory;

royaltyRegistry

The royalty registry from manifold.xyz.

address public immutable royaltyRegistry;

Functions

onlyOwner

modifier onlyOwner() virtual;

receive

receive() external payable;

constructor

*This is only called when the base implementation contract is deployed. The following immutable parameters are set:

  • factory: The address of the factory contract

  • royaltyRegistry: The address of the royalty registry from manifold.xyz

  • stolenNftOracle: The address of the stolen NFT oracle These are all stored in immutable storage, which enables all minimal proxy contracts to read them without incurring additional deployment costs and re-initializing them at point of creation in the factory contract.*

constructor(address _factory, address _royaltyRegistry, address _stolenNftOracle);

initialize

Initializes the private pool and sets the initial parameters. Should only be called once by the factory.

function initialize(
    address _baseToken,
    address _nft,
    uint128 _virtualBaseTokenReserves,
    uint128 _virtualNftReserves,
    uint56 _changeFee,
    uint16 _feeRate,
    bytes32 _merkleRoot,
    bool _useStolenNftOracle,
    bool _payRoyalties
) public;

Parameters

Name
Type
Description

_baseToken

address

The address of the base token

_nft

address

The address of the NFT

_virtualBaseTokenReserves

uint128

The virtual base token reserves

_virtualNftReserves

uint128

The virtual NFT reserves

_changeFee

uint56

_feeRate

uint16

The fee rate (in basis points) 200 = 2%

_merkleRoot

bytes32

The merkle root

_useStolenNftOracle

bool

Whether or not the pool uses the stolen NFT oracle to check if an NFT is stolen

_payRoyalties

bool

buy

Buys NFTs from the pool, paying with base tokens from the caller. Then transfers the bought NFTs to the caller. The net cost depends on the current price, fee rate and assigned NFT weights.

DO NOT call this function directly unless you know what you are doing. Instead, use a wrapper contract that will check the max input amount and revert if the slippage is too high.

function buy(uint256[] calldata tokenIds, uint256[] calldata tokenWeights, MerkleMultiProof calldata proof)
    public
    payable
    returns (uint256 netInputAmount, uint256 feeAmount, uint256 protocolFeeAmount);

Parameters

Name
Type
Description

tokenIds

uint256[]

The token IDs of the NFTs to buy.

tokenWeights

uint256[]

The weights of the NFTs to buy.

proof

MerkleMultiProof

The merkle proof for the weights of each NFT to buy.

Returns

Name
Type
Description

netInputAmount

uint256

The amount of base tokens spent inclusive of fees.

feeAmount

uint256

The amount of base tokens spent on fees.

protocolFeeAmount

uint256

sell

Sells NFTs into the pool and transfers base tokens to the caller. NFTs are transferred from the caller to the pool. The net sale amount depends on the current price, fee rate and assigned NFT weights.

DO NOT call this function directly unless you know what you are doing. Instead, use a wrapper contract that will check the min output amount and revert if the slippage is too high.

function sell(
    uint256[] calldata tokenIds,
    uint256[] calldata tokenWeights,
    MerkleMultiProof calldata proof,
    IStolenNftOracle.Message[] memory stolenNftProofs
) public returns (uint256 netOutputAmount, uint256 feeAmount, uint256 protocolFeeAmount);

Parameters

Name
Type
Description

tokenIds

uint256[]

The token IDs of the NFTs to sell.

tokenWeights

uint256[]

The weights of the NFTs to sell.

proof

MerkleMultiProof

The merkle proof for the weights of each NFT to sell.

stolenNftProofs

Message.IStolenNftOracle[]

The proofs that show each NFT is not stolen.

Returns

Name
Type
Description

netOutputAmount

uint256

The amount of base tokens received inclusive of fees.

feeAmount

uint256

The amount of base tokens to pay in fees.

protocolFeeAmount

uint256

change

Changes a set of NFTs that the caller owns for another set of NFTs in the pool. The caller must approve the pool to transfer the NFTs. The sum of the caller's NFT weights must be less than or equal to the sum of the output pool NFTs weights. The caller must also pay a fee depending the net input weight and change fee amount.

function change(
    uint256[] memory inputTokenIds,
    uint256[] memory inputTokenWeights,
    MerkleMultiProof memory inputProof,
    IStolenNftOracle.Message[] memory stolenNftProofs,
    uint256[] memory outputTokenIds,
    uint256[] memory outputTokenWeights,
    MerkleMultiProof memory outputProof
) public payable returns (uint256 feeAmount, uint256 protocolFeeAmount);

Parameters

Name
Type
Description

inputTokenIds

uint256[]

The token IDs of the NFTs to change.

inputTokenWeights

uint256[]

The weights of the NFTs to change.

inputProof

MerkleMultiProof

The merkle proof for the weights of each NFT to change.

stolenNftProofs

Message.IStolenNftOracle[]

The proofs that show each input NFT is not stolen.

outputTokenIds

uint256[]

The token IDs of the NFTs to receive.

outputTokenWeights

uint256[]

The weights of the NFTs to receive.

outputProof

MerkleMultiProof

The merkle proof for the weights of each NFT to receive.

execute

Executes a transaction from the pool account to a target contract. The caller must be the owner of the pool. This allows for use cases such as claiming airdrops.

function execute(address target, bytes memory data) public payable onlyOwner returns (bytes memory);

Parameters

Name
Type
Description

target

address

The address of the target contract.

data

bytes

The data to send to the target contract.

Returns

Name
Type
Description

<none>

bytes

returnData The return data of the transaction.

deposit

Deposits base tokens and NFTs into the pool. The caller must approve the pool to transfer their NFTs and base tokens.

DO NOT call this function directly unless you know what you are doing. Instead, use a wrapper contract that will check the current price is within the desired bounds.

function deposit(uint256[] calldata tokenIds, uint256 baseTokenAmount) public payable;

Parameters

Name
Type
Description

tokenIds

uint256[]

The token IDs of the NFTs to deposit.

baseTokenAmount

uint256

The amount of base tokens to deposit.

withdraw

Withdraws NFTs and tokens from the pool. Can only be called by the owner of the pool.

function withdraw(address _nft, uint256[] calldata tokenIds, address token, uint256 tokenAmount) public onlyOwner;

Parameters

Name
Type
Description

_nft

address

The address of the NFT.

tokenIds

uint256[]

The token IDs of the NFTs to withdraw.

token

address

The address of the token to withdraw.

tokenAmount

uint256

The amount of tokens to withdraw.

setVirtualReserves

Sets the virtual base token reserves and virtual NFT reserves. Can only be called by the owner of the pool. These parameters affect the price and liquidity depth of the pool.

function setVirtualReserves(uint128 newVirtualBaseTokenReserves, uint128 newVirtualNftReserves) public onlyOwner;

Parameters

Name
Type
Description

newVirtualBaseTokenReserves

uint128

The new virtual base token reserves.

newVirtualNftReserves

uint128

The new virtual NFT reserves.

setMerkleRoot

Sets the merkle root. Can only be called by the owner of the pool. The merkle root is used to validate the NFT weights.

function setMerkleRoot(bytes32 newMerkleRoot) public onlyOwner;

Parameters

Name
Type
Description

newMerkleRoot

bytes32

The new merkle root.

setFeeRate

Sets the fee rate. Can only be called by the owner of the pool. The fee rate is used to calculate the fee amount when swapping or changing NFTs. The fee rate is in basis points (1/100th of a percent). For example, 10_000 == 100%, 200 == 2%, 1 == 0.01%.

function setFeeRate(uint16 newFeeRate) public onlyOwner;

Parameters

Name
Type
Description

newFeeRate

uint16

The new fee rate (in basis points)

setUseStolenNftOracle

Sets the whether or not to use the stolen NFT oracle. Can only be called by the owner of the pool. The stolen NFT oracle is used to check if an NFT is stolen.

function setUseStolenNftOracle(bool newUseStolenNftOracle) public onlyOwner;

Parameters

Name
Type
Description

newUseStolenNftOracle

bool

The new use stolen NFT oracle flag.

setPayRoyalties

Sets the pay royalties flag. Can only be called by the owner of the pool. If royalties are enabled then the pool will pay royalties when buying or selling NFTs.

function setPayRoyalties(bool newPayRoyalties) public onlyOwner;

Parameters

Name
Type
Description

newPayRoyalties

bool

The new pay royalties flag.

setAllParameters

Updates all parameter settings in one go.

function setAllParameters(
    uint128 newVirtualBaseTokenReserves,
    uint128 newVirtualNftReserves,
    bytes32 newMerkleRoot,
    uint16 newFeeRate,
    bool newUseStolenNftOracle,
    bool newPayRoyalties
) public;

Parameters

Name
Type
Description

newVirtualBaseTokenReserves

uint128

The new virtual base token reserves.

newVirtualNftReserves

uint128

The new virtual NFT reserves.

newMerkleRoot

bytes32

The new merkle root.

newFeeRate

uint16

The new fee rate (in basis points)

newUseStolenNftOracle

bool

The new use stolen NFT oracle flag.

newPayRoyalties

bool

The new pay royalties flag.

flashLoan

Executes a flash loan.

function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 tokenId, bytes calldata data)
    external
    payable
    returns (bool);

Parameters

Name
Type
Description

receiver

IERC3156FlashBorrower

The receiver of the flash loan.

token

address

The address of the NFT contract.

tokenId

uint256

The ID of the NFT.

data

bytes

The data to pass to the receiver.

Returns

Name
Type
Description

<none>

bool

success Whether or not the flash loan was successful.

sumWeightsAndValidateProof

Sums the weights of each NFT and validates that the weights are correct by verifying the merkle proof.

function sumWeightsAndValidateProof(
    uint256[] memory tokenIds,
    uint256[] memory tokenWeights,
    MerkleMultiProof memory proof
) public view returns (uint256);

Parameters

Name
Type
Description

tokenIds

uint256[]

The token IDs of the NFTs to sum the weights for.

tokenWeights

uint256[]

The weights of each NFT in the token IDs array.

proof

MerkleMultiProof

The merkle proof for the weights of each NFT.

Returns

Name
Type
Description

<none>

uint256

sum The sum of the weights of each NFT.

_getRoyalty

Gets the royalty and recipient for a given NFT and sale price. Looks up the royalty info from the manifold registry.

function _getRoyalty(uint256 tokenId, uint256 salePrice)
    internal
    view
    returns (uint256 royaltyFee, address recipient);

Parameters

Name
Type
Description

tokenId

uint256

The token ID of the NFT.

salePrice

uint256

The sale price of the NFT.

Returns

Name
Type
Description

royaltyFee

uint256

The royalty fee to pay.

recipient

address

The address to pay the royalty fee to.

buyQuote

Returns the required input of buying a given amount of NFTs inclusive of the fee which is dependent on the currently set fee rate.

function buyQuote(uint256 outputAmount)
    public
    view
    returns (uint256 netInputAmount, uint256 feeAmount, uint256 protocolFeeAmount);

Parameters

Name
Type
Description

outputAmount

uint256

The amount of NFTs to buy multiplied by 1e18.

Returns

Name
Type
Description

netInputAmount

uint256

The required input amount of base tokens inclusive of the fee.

feeAmount

uint256

The fee amount.

protocolFeeAmount

uint256

sellQuote

Returns the output amount of selling a given amount of NFTs inclusive of the fee which is dependent on the currently set fee rate.

function sellQuote(uint256 inputAmount)
    public
    view
    returns (uint256 netOutputAmount, uint256 feeAmount, uint256 protocolFeeAmount);

Parameters

Name
Type
Description

inputAmount

uint256

The amount of NFTs to sell multiplied by 1e18.

Returns

Name
Type
Description

netOutputAmount

uint256

The output amount of base tokens inclusive of the fee.

feeAmount

uint256

The fee amount.

protocolFeeAmount

uint256

changeFeeQuote

Returns the fee required to change a given amount of NFTs. The fee is based on the current changeFee (which contains 4 decimals of precision) multiplied by some exponent depending on the base token decimals.

function changeFeeQuote(uint256 inputAmount) public view returns (uint256 feeAmount, uint256 protocolFeeAmount);

Parameters

Name
Type
Description

inputAmount

uint256

The amount of NFTs to change multiplied by 1e18.

Returns

Name
Type
Description

feeAmount

uint256

The fee amount.

protocolFeeAmount

uint256

The protocol fee amount.

price

Returns the price of the pool to 18 decimals of accuracy.

function price() public view returns (uint256);

Returns

Name
Type
Description

<none>

uint256

price The price of the pool.

flashFee

Returns the fee required to flash swap a given NFT.

function flashFee(address, uint256) public view returns (uint256);

Returns

Name
Type
Description

<none>

uint256

feeAmount The fee amount.

flashFeeToken

Returns the token that is used to pay the flash fee.

function flashFeeToken() public view returns (address);

availableForFlashLoan

Returns whether or not an NFT is available for a flash loan.

function availableForFlashLoan(address token, uint256 tokenId) public view returns (bool);

Parameters

Name
Type
Description

token

address

The address of the NFT contract.

tokenId

uint256

The ID of the NFT.

Returns

Name
Type
Description

<none>

bool

available Whether or not the NFT is available for a flash loan.

Events

Initialize

event Initialize(
    address indexed baseToken,
    address indexed nft,
    uint128 virtualBaseTokenReserves,
    uint128 virtualNftReserves,
    uint56 changeFee,
    uint16 feeRate,
    bytes32 merkleRoot,
    bool useStolenNftOracle,
    bool payRoyalties
);

Buy

event Buy(
    uint256[] tokenIds,
    uint256[] tokenWeights,
    uint256 inputAmount,
    uint256 feeAmount,
    uint256 protocolFeeAmount,
    uint256 royaltyFeeAmount
);

Sell

event Sell(
    uint256[] tokenIds,
    uint256[] tokenWeights,
    uint256 outputAmount,
    uint256 feeAmount,
    uint256 protocolFeeAmount,
    uint256 royaltyFeeAmount
);

Deposit

event Deposit(uint256[] tokenIds, uint256 baseTokenAmount);

Withdraw

event Withdraw(address indexed nft, uint256[] tokenIds, address token, uint256 amount);

Change

event Change(
    uint256[] inputTokenIds,
    uint256[] inputTokenWeights,
    uint256[] outputTokenIds,
    uint256[] outputTokenWeights,
    uint256 feeAmount,
    uint256 protocolFeeAmount
);

SetVirtualReserves

event SetVirtualReserves(uint128 virtualBaseTokenReserves, uint128 virtualNftReserves);

SetMerkleRoot

event SetMerkleRoot(bytes32 merkleRoot);

SetFeeRate

event SetFeeRate(uint16 feeRate);

SetUseStolenNftOracle

event SetUseStolenNftOracle(bool useStolenNftOracle);

SetPayRoyalties

event SetPayRoyalties(bool payRoyalties);

Errors

AlreadyInitialized

error AlreadyInitialized();

Unauthorized

error Unauthorized();

InvalidEthAmount

error InvalidEthAmount();

InvalidMerkleProof

error InvalidMerkleProof();

InsufficientInputWeight

error InsufficientInputWeight();

FeeRateTooHigh

error FeeRateTooHigh();

NotAvailableForFlashLoan

error NotAvailableForFlashLoan();

FlashLoanFailed

error FlashLoanFailed();

InvalidRoyaltyFee

error InvalidRoyaltyFee();

Structs

MerkleMultiProof

Merkle proof input for a sparse merkle multi proof. It can be generated with a library like: https://github.com/OpenZeppelin/merkle-tree#treegetmultiproof

struct MerkleMultiProof {
    bytes32[] proof;
    bool[] flags;
}

Last updated