Repository overview:
/contract
- an NFT smart contract (the primary component and purpose of this repo)/market
- a marketplace smart contract. This is not used in production; rather, it is a basic market contract that implements NEAR's NFT standards, used for the purposes of testing the NFT contract (e.g. listing, sale & payouts functionality)./neardev
- a folder created bynear dev-deploy
command during the testing phase, which contains the name of a testnet account (more info here and here)/out
- contains compiled WebAssembly ("wasm") for both NFT contract (/out/main.wasm
) and market contract (/out/market.wasm
)- NB:
/out/main.wasm
is the file that Spearmint reads to deploy contract bytecode to an account
- NB:
/test
- contains Mocha tests and related configuration and utils/utils
- lightweight utility code that reads the current contract name (aka account ID) from/neardev
and propogates this ID to/test/config.js
package.json
- contains scripts to easily build, deploy (testnet) and test contracts with a single command 🙌yarn test:deploy
is the grandmaster of these commands; it builds both contracts, deploys them, patches the config, and runs tests.
-
Build NFT contract:
yarn build:contract
-
Build Marketplace contract:
yarn build:market
(no need to do this unless you have made changes to the marketplace contract) -
Build all contracts (NFT & Marketplace):
yarn build:all
-
Deploy NFT contract (testnet):
yarn dev:deploy
- this command deletes
/neardev
and deploys NFT contract to a new testnet account. The new account ID is saved in/neardev
andtest/config.js
. - NB:
yarn dev:deploy
deploys only the NFT contract, not the marketplace contract. Whenyarn test
is run, a marketplace contract is deployed to a derivation of the current NFT contract address if no marketplace contract has been deployed since the most recent NFT contract deployment.
- this command deletes
-
Run tests on NFT contract:
yarn test
- this runs mocha tests against the deployed NFT contract and the deployed Marketplace contract
-
Build contracts, deploy and test in a single command:
yarn test:deploy
TBC
TBC
- When making a change to the NFT contract, whether it is a bugfix or a new feature, tests must be added to
/test/api.test.js
to fully test the new functionality and any possible side-effects.
TBC
TBC
- Think about and solve for security in EVERY function. E.g. "Should anyone be able to call this function and do this action, or should it be restricted to the owner of the contract?"
- Be aware of computation and storage costs. Read more about NEAR's gas and storage staking models if you aren't familiar with them.
- Rust is a strongly typed language! This means that if any of your data shapes change between deployments and you are NOT using enums to version your data structures, you will need to add a migration step in your upgrade process. Generally speaking, it's MUCH easier to upgrade contracts where no data migration is involved or it can be accomplished using enums, so bear this in mind when planning and implementing features and bugfixes.
- Sometimes migrations are unavoidable. In this case, check out this Medium article [COMING SOON] which goes into detail on various migration patterns and how you can use
Versioned
data structures to facilitate data shape changes without having to run a migration step.
https://docs.near.org/docs/tutorials/contracts/nfts/upgrade-contract (basic logic update and redeployment; no state migration)
https://www.near-sdk.io/upgrading/production-basics (helpful info about migrate, ignore_state; intro to enums but code breaks; a note about the need for contributions)
https://github.com/evgenykuzyakov/berryclub/commit/d78491b88cbb16a79c15dfc3901e5cfb7df39fe8 (super helpful)
https://nomicon.io/ChainSpec/Upgradability.html (a helpful note on Versioned data structures, but not easily findable for someone looking for contract best practices)
https://github.com/mikedotexe/rust-contract-upgrades/pulls (also very helpful)
https://docs.near.org/concepts/storage/storage-staking
https://docs.near.org/concepts/basics/transactions/gas
// Mappings pseudo code
TokenTypeString -> TokenTypeInt
TokenTypeInt -> TokenTypeStruct (data)
TokenId.split(":")[0] -> TokenTypeInt
TokenId.split(":")[1] -> TokenEditionInt (unique token in type)
In Rust:
// getting owner of token
let owner_id = self.tokens.owner_by_id.get(&token_id)
// getting metadata for token (TokenTypeStruct)
let mut token_id_iter = token_id.split(TOKEN_DELIMETER);
let token_type_id = token_id_iter.next().unwrap().parse().unwrap();
let metadata = self.token_type_by_id.get(&token_type_id).unwrap().metadata;
yarn && yarn test:deploy
Rust, cargo, near-cli, etc... Everything should work if you have NEAR development env for Rust contracts set up.