IP-NFTs are building blocks for the DeSci economy.
IPNFT
IP-NFTs allow their users to tokenize intellectual property. This repo contains code for IP-NFT smart contracts and compatible subgraphs. Details on how IP-NFTs are minted, their purpose and applications can be found here
Deployments
Mainnet
| Contract | Address | Actions | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | IP-NFT | 0xcaD88677CA87a7815728C72D74B4ff4982d54Fc1 | | | SignedMintAuthorizer | 0xBc5FbB45A2bbB64d9B2EeBFa327284a35d5C5865 |
| | SchmackoSwap | 0xc09b8577c762b5e97a7d640f242e1d9bfaa7eb9d |
| | Tokenizer | 0x58EB89C69CB389DBef0c130C6296ee271b82f436 |
| | Permissioner | 0xC837E02982992B701A1B5e4E21fA01cEB0a628fA |
| | Crowdsale | 0xf0a8d23f38e9cbbe01c4ed37f23bd519b65bc6c2 |
| | Locking Crowdsale | 0xfbfd266bf3b49Db8746155AA318D4533Cc66DB26 |
| | StakedLockingCrowdSale | 0x35Bce29F52f51f547998717CD598068Afa2B29B7 |
|
timelocked token implementation=0x625ed621d814645AA81C50c4f333D4a407576e8F
Subgraph
API: https://subgraph.satsuma-prod.com/742d8952ab24/molecule--4039244/ip-nft-mainnet/api Playground: https://subgraph.satsuma-prod.com/molecule--4039244/ip-nft-mainnet/playground
tokenizer implementation 1.4: 0x0d781edf9c75cf9136aac6600873d0a20a6dd43f tokenizer implementation 1.3: 0x6517DD48908F4C1FF4eD74FfD780908241a3654C tokenizer implementation 1.2: 0xE8701330F196FeFe415b28dAA767AB076F42557A tokenizer implementation 1.1: 0x9C70FA8c87D7e94Fd63eeCCcA657D5c4224a36f3
iptoken implementation 1.4: 0xd79fe2c4879b3a3d732df11294329a60cff3a0a9 iptoken implementation 1.3: 0x89a14Be8f7824d4775053Edad0f2fA2d6767b72B iptoken implementation: 0x9E4fc6E6d1A64e3429aB852d3CB31AD7aa06997A
wrapped iptoken implementation: 0x0ca5f50a8a59a59ef8c8d610f5ebf99e41f1352f
ipnft implementation 2.4: 0x6B179Dffac5E190c670176606f552cB792847f80
Defender Relayer
Deprecated after migrating to Defender 2 (was 0x3D30452c48F2448764d5819a9A2b684Ae2CC5AcF). We're using a key signoff with 0x8626c6293B5101E5E534B5B60F411a37294D8cBE.
Sepolia
| Contract | Address | Explorer | | ------------------ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | IPNFT | 0x152B444e60C526fe4434C721561a077269FcF61a | | | Swap | 0x9e4c638e703d0Af3a3B9eb488dE79A16d402698f |
| | Authorizer | 0x7a9F3773352e4ee0Da6307Cd32C45fE89602129A |
| | Terms Permissioner | 0xC05D649368d8A5e2E98CAa205d47795de5fCB599 |
| | Tokenizer | 0xca63411FF5187431028d003eD74B57531408d2F9 |
| | Crowdsale | 0x8cA737E2cdaE1Ceb332bEf7ba9eA711a3a2f8037 |
| | Locking Crowdsale | 0x0Da77f361bB56f065Aa21647d885685eb7cAE10F |
| | Staked Crowdsale | 0xd1cE2EA7d3b0C9cAB025A4aD762FC00315141ad7 |
|
timelocked token implementation=0xF8F79c1E02387b0Fc9DE0945cD9A2c06F127D851
tokenizer implementation 1.4: 0x4166362c3b9fb7d43c79ae8668e4517799aed0e0
iptoken implementation 1.4: 0xacadd6dd9e7af053f42425a03f68da9920287d5b
wrapped iptoken implementation: 0xa3b844450e31e541e604217b11d48c111419a6a6
new SLCS with support for verifiable timelocks & distinctly configurable staking / locking periods: https://sepolia.etherscan.io/address/0x2d309CF13dC3872f9c9B1B06Ebf6F60caDe08d55#code
Subgraphs
on Satsuma, Techprod Account
API: https://subgraph.satsuma-prod.com/742d8952ab24/molecule--4039244/ip-nft-sepolia/api Playground: https://subgraph.satsuma-prod.com/molecule--4039244/ip-nft-sepolia/playground
Defender Relayer
Deprecated after migrating to Defender 2 (was 0xd7B298c9fB0377124d01D4E826d9D5beFB7CD6FE). We're using a key signoff with 0x8626c6293B5101E5E534B5B60F411a37294D8cBE.
Tokens
| Contract | Address | | | ----------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | USDC (6 decimals) | 0xC7B1b8BEA20d559040928FA1e6a23a3c221286B1 | | | MOL Dao | 0xe0D404C22228b03D5b8a715Cb569C4944BC5A27A |
| | vested MOL Dao | 0x8f80d1183CD983B01B0C9AC6777cC732Ec9800de |
|
old Plain Crowdsale 0xc272b3e980ee3c1e52a9814b1a1d6c48295e8d91 https://sepolia.etherscan.io/address/0xc272b3e980ee3c1e52a9814b1a1d6c48295e8d91
IPNFT_ADDRESS=0x152B444e60C526fe4434C721561a077269FcF61a
ipnft impl 0x67881bbE2d58f5eeb2f2cad3a1FB7Bb6CB834A5A
SOS_ADDRESS=0x9e4c638e703d0Af3a3B9eb488dE79A16d402698f
AUTHORIZER_ADDRESS=0x7a9F3773352e4ee0Da6307Cd32C45fE89602129A
TERMSACCEPTEDPERMISSIONER_ADDRESS=0xC05D649368d8A5e2E98CAa205d47795de5fCB599 TOKENIZER_ADDRESS=0xca63411FF5187431028d003eD74B57531408d2F9 CROWDSALE_ADDRESS=0x8cA737E2cdaE1Ceb332bEf7ba9eA711a3a2f8037 STAKEDLOCKINGCROWDSALE_ADDRESS=0xd1cE2EA7d3b0C9cAB025A4aD762FC00315141ad7
initial IP Token implementation=0xB16e92029De283800df9030De2F255DcB99F19e9 tokenizer imple 0x672d3389b5c5a050ad93100d548817d87edc8597
USDC_ADDRESS=0x309EFD49752803D0B3Ddba2B66A7A900F99B4E70 DAOTOKENADDRESS=0x62f3cBab2C84fbA31DEc50CD21dbb5577333C69a VDAOTOKENADDRESS=0x19A3036b828bffB5E14da2659E950E76f8e6BAA2
upgrading to Tokenizer 1.4
forge script --private-key=$PRIVATEKEY --rpc-url=$RPCURL script/prod/RolloutTokenizerV14.s.sol --broadcast
// 0xTokenizer (address, bytes)(0xNewImpl, 0xNewWrappedIPTokenImpl 0xNewIPTokenImpl) cast send --rpc-url=$RPCURL --private-key=$PRIVATEKEY 0x58EB89C69CB389DBef0c130C6296ee271b82f436 "upgradeToAndCall(address,bytes)" 0x4166362c3b9fb7d43c79ae8668e4517799aed0e0 0x8b3d19bb0000000000000000000000000ca5f50a8a59a59ef8c8d610f5ebf99e41f1352f000000000000000000000000d79fe2c4879b3a3d732df11294329a60cff3a0a9
Timelocked Tokens
originally the "timelocked token" was an inline concept of the slcs. Timelock contracts weren't reusable among cs impls. This changes as of beginning of 2025. As a rather simple but not very elegant (and certainly not correct) solution we decided to "trust" external locking contracts so you can reuse them among crowdsale instances. This was needed for the VitaRNA crowdsale that's supposed to just support locks, no stakes - and hence required another crowdsale instance. During this upgrade we decided to externalize the timelock token template so upcoming instances can be verified on chain.
Prerequisites
To work with this repository you have to install Foundry (
curl -L https://foundry.paradigm.xyz | bash
The above command will install foundryup. Then install Foundry by running foundryup in your terminal.
(Check out the Foundry book for a Windows installation guide:
Usage
install dependencies and build
Run forge install. This will clone dependency repos as submodules into the lib folder.
Run forge build
Testing
Run forge test
Run forge test --gas-report for gas usage reports
Run forge test --match-contract IPNFTV2 -vvv -w to watch only relevant tests an include meaningful output
Hardhat tests
We also added a basic hardhat environment to this project. While foundry stays our primary tool for contract development, hardhat allows us to test e.g. JSON / metadata related features of the contracts. After installing all js dependencies (yarn), you can execute the hardhat tests like:
yarn hardhat test --network hardhat
Deployment
General config
- The deploy scripts are located in
script - Copy
.env.exampleto.env - Set the
ETHERSCAN_KEYif you want to verify deployed contracts on Etherscan. - Set a moderator address that's going to be enabled to issue and revoke mintpasses (only needed for "real" deployments)
.env file and run source .env to get them into your current terminal session or provide them when invoking the command.
Deployment scripts
- a fresh, proxied IPNFT deployment can be created by
forge script script/IPNFT.sol - to rollout a new upgrade on a live network without calling the proxy's upgrade function, you can use
forge script script/UpgradeImplementation.s.sol:DeployImplementationand invoke the upgrade function manually (e.g. from your multisig) - for the "real" thing you'll need to add
-fand--private-keyand finally--broadcastparams .
Deploying everything locally
You need Docker.
Automatically
yarn localenvsets up everything- use
./setupLocal.shto deploy all contracts. Add the optional-for--fixtureflag to also run the fixture scripts to tokenize one IPNFT or-fxto create two crowdsale instances.
Manual
- the dev scripts are supposed to run on your local environment and depend on contract addresses on your local environment. Use
source .envto pull deterministic local contract addresses to your local session.
- Anvil is a local testnet node shipped with Foundry. You can use it for testing your contracts from frontends or for interacting over RPC. You can also use the anvil node from docker, see the accompanying README in the
subgraphfolder. - Run
anvil -h 0.0.0.0in a terminal window and keep it running
forge script script/dev/Ipnft.s.sol:DeployIpnft -f $RPC_URL --broadcast
To issue a mintpass, reserve and mint a test IPNFT for the 1st user, run forge script script/dev/Ipnft.s.sol:FixtureIpnft -f $RPC_URL --broadcast. This requires you to have executed Dev.s.sol before. This also creates a listing on Schmackoswap but doesn't accept it.
To deploy the Synthesizer, run forge script script/dev/Synthesizer.s.sol:DeploySynthesizer -f $RPC_URL --broadcast To synthesize the test IPNFT, run forge script script/dev/Synthesizer.s.sol:FixtureSynthesizer -f $RPC_URL --broadcast
To deploy the StakedLockingCrowdSale contract, run forge script script/dev/CrowdSale.s.sol:DeployCrowdSale -f $RPC_URL --broadcast To test a simple StakedLockingCrowdSale with Molecules, run forge script script/dev/CrowdSale.s.sol:FixtureCrowdSale -f $RPC_URL --broadcast
To approve and finalize the sales listing, run forge script script/dev/ApproveAndBuy.s.sol -f $RPC_URL --broadcast. See the inline comment on why this is a separate script.
Deploy to a live network
The easiest way to deploy contracts without exposing a local private key is the thirdweb. Here's how you initialize the process from the root folder: npx thirdweb@latest deploy
To manually broadcast a bundle of deploy transactions, you can use Deploy.s.sol. It deploys all three relevant contracts (IPNFT, Schmackoswap and Mintpass) and sets up a first moderator (defined by the MODERATOR_ADDRESS env var). Make sure that you're using the correct moderator address for the network you're deploying to.
- Make sure you have the private key for your deployer account at hand and that it has ETH on the target network on it.
- Run
forge script script/Deploy.s.sol:DeployScript -f $RPC_URL --interactives 1 --sender <deployer address> --broadcast -vvvv - Paste the private key for the deployer account
- to verify the contract during deployment, get an Etherscan API key and add
--verify --etherscan-api-key $ETHERSCANAPIKEYto the command.
Deploying the Synthesizer suite
You can deploy the Synthesizer individually, but we created a deployment script that deploys all relevant contracts in the recommended order. These are
- BioPriceFeed
- TermsAcceptedPermissioner
- Synthesizer
- StakedLockingCrowdSale
IPNFTADDRESS=... forge script script/DeploySynthesizer.s.sol:DeploySynthesizerInfrastructure --private-key $PRIVATEKEY --rpc-url $RPC_URL --broadcast
The crowdsale computation model can be tried out here:
Deploying and verifying a single contract without the help of any script forge create --rpc-url $RPCURL --private-key $PRIVATEKEY --chain 5 --etherscan-api-key $ETHERSCANAPIKEY --verify src/crowdsale/StakedLockingCrowdSale.sol:StakedLockingCrowdSale
Verifying the staked crowdsale forge verify-contract --chain-id=11155111 --etherscan-api-key=$ETHERSCANAPIKEY --constructor-args $(cast abi-encode "constructor(address)" 0xF8F79c1E02387b0Fc9DE0945cD9A2c06F127D851) 0x7eeb7113f90893fb95c6666e3930235850f2bc6A src/crowdsale/StakedLockingCrowdSale.sol:StakedLockingCrowdSale
Deploying (vested) test tokens
To test staked / vested token interactions, you need some test tokens. Here are 2 convenient script to get them running:
NAME=Vita SYMBOL=VITA SUPPLYETH=10000000 forge script script/Tokens.s.sol:DeployTestTokensManually --private-key $PRIVATEKEY --rpc-url $RPC_URL --broadcast
and to create the vested tokens counterpart:
TOKEN=0xaddress forge script script/Tokens.s.sol:DeployTokenVesting --private-key $PRIVATEKEY --rpc-url $RPCURL --broadcast
Testing a manual upgrade
deploy the old version
forge script script/IPNFT.s.sol -f $RPC_URL -vvvv --broadcast --private-key ...
switch your branch or get the new contract impl at hand
PROXYADDRESS=<the proxy address> forge script script/UpgradeImplementation.s.sol -f $RPCURL --sender <proxy-owner-address>
(or use your pk and --broadcast to submit it)
Manually verify contracts on Etherscan
full docs:
forge verify-contract --chain-id 5 <address> IPNFT
or, if you need to verify with constructor arguments:
forge verify-contract --chain-id 5 <address> Mintpass --constructor-args $(cast abi-encode "constructor(address)" "0xabcdef")
ERC1967 Proxies are verified using their implementation contstructor call
forge verify-contract --chain-id 5 <proxyaddress> ERC1967Proxy --constructor-args $(cast abi-encode "constructor(address,bytes)" "<impladdress>" "")
checking with mythril on docker
docker run -m 6G --cpus=8 -w /tmp -v $(pwd):/tmp mythril/myth analyze /tmp/src/IPNFT.sol --solc-json /tmp/mythril.config.json
Creating coverage reports
requires the lcov suite installed on your machine
forge coverage --report lcov && genhtml lcov.info -o report --branch-coverage
Interacting with cast
cast is another CLI command installed by Foundry and allows you to query/manipulate your deployed contracts easily. Find out more here:
When having an RPC_URL in your local env, you e.g. can simply call view functions like this: cast call $IPNFT_ADDRESS "tokenURI(uint256)" 1 | cast --to-ascii
manual interaction playbook
Here are some helpful interaction examples with the contracts that you can execute from your command line. Ensure your local environment contains all contract addresses and is sourced to your terminal. We're using your local PRIVATE_KEY here
Manually issue 2 mintpasses to anvil address #0
cast send -i $MINTPASSADDRESS --private-key $PRIVATEKEY "batchMint(address,uint256)" 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 2
Create a reservation
cast send -i $IPNFTADDRESS --private-key $PRIVATEKEY "reserve()(uint256)"
mint an IP-NFT to the first account
cast send --private-key $PRIVATEKEY -i $IPNFTADDRESS --value 0.001ether --broadcast "mintReservation(address,uint256,uint256,string)(uint256)" 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 1 1 "ipfs://test"
approve SchmackoSwap to spend token 0
cast send -i $IPNFTADDRESS --private-key $PRIVATEKEY "approve(address, uint256)()" $SOS_ADDRESS 0
Create a Listing for 10 sample tokens
cast send -i $SOSADDRESS --private-key $PRIVATEKEY "list(address, uint256, address, uint256)(uint256)" $IPNFTADDRESS 0 $ERC20ADDRESS 10
take note of the resulting listing id
Cancel a listing
cast send -i $SOSADDRESS --private-key $PRIVATEKEY "cancel(uint256)()" <listingid>
Create a new Listing (take down id)
cast send -i $SOSADDRESS --private-key $PRIVATEKEY "list(address, uint256, address, uint256)(uint256)" $IPNFTADDRESS 0 $ERC20ADDRESS 10
allow Account(1)
cast send -i $SOS_ADDRESS "changeBuyerAllowance(uint256, address, bool)()" <listingid> 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 true
supply Account(1) with ERC20
cast send -i $ERC20ADDRESS --private-key $PRIVATEKEY "mint(address, uint256)()" 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 10
allow SOS to spend ERC20
cast send --i $ERC20ADDRESS --private-key <account1 private key> "increaseAllowance(address, uint256)()" $SOSADDRESS 10
let account(1) fulfill the listing
cast send -i \$SOS_ADDRESS --private-key <account1 private key> "fulfill(uint256)()" <listingid>
grant read access to another party
cast send --private-key $PRIVATEKEY -i $IPNFTADDRESS "grantReadAccess(address,uint256,uint256)" 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 1 1680265071
Actions
We are using Tenderly Web3Actions to trigger actions based on emitted Events from our deployed Contracts.
These are setup under the moleculeprotocol organization on Tenderly. The QueryIds and API-KEY are stored in the Tenderly context and can be accessed via the Tenderly Frontend. To update these actions you need the Tenderly login credentials.
- StakedLockingCrowdSale (Mainnet & Goerli): BidEvent => Triggers a POST request that executes Dune Queries to update the Dune Visualizations.