Welcome to your new nfo project and to the internet computer development community. By default, creating a new project adds this README and some template files to your project directory. You can edit these template files to customize your project and to include your own code to speed up the development cycle.
To get started, you might want to explore the project directory structure and the default configuration file. Working with this project in your development environment will not affect any production deployment or identity tokens.
To learn more before you start working with nfo, see the following documentation available online:
- Quick Start
- SDK Developer Tools
- Rust Canister Devlopment Guide
- ic-cdk
- ic-cdk-macros
- Candid Introduction
- JavaScript API Reference
If you want to start working on your project right away, you might want to try the following commands:
cd nfo/
dfx help
dfx config --help
If you want to test your project locally, you can use the following commands:
# Starts the replica, running in the background
dfx start --background
# Deploys your canisters to the replica and generates your candid interface
dfx deploy
Once the job completes, your application will be available at http://localhost:8000?canisterId={asset_canister_id}
.
Level 1 policy: Sword: can be transferred by owner Potion: can be transferred by owner
====================================================== Level 2 policy: dynamic, can be added/changed by users
Use case:
- Ognjen owns two sword NFOs
- Satya owns 3 potion NFOs
- Ognjen wants to trade the two swords for 3 potions
- The trade should happen only if Satya agrees
Basic objects: (object_id: #1, type: #sword, metadata: { owner: Ognjen }, #operations: {name: owner.write, authorizer: owner}) (object_id: #2, type: #sword, metadata: { owner: Ognjen }, #operations: {name: owner.write, authorizer: owner}) (object_id: #3, type: #potion, metadata: { owner: Satya }, #operations: {name: owner.write, authorizer: owner}) (object_id: #4, type: #potion, metadata: { owner: Satya }, #operations: {name: owner.write, authorizer: owner}) (object_id: #5, type: #potion, metadata: { owner: Satya }, #operations: {name: owner.write, authorizer: owner})
Ognjen calls create_proposal on the ledger: object(#1).owner.write(Satya); (needs to be approved by: Ognjen) object(#2).owner.write(Satya); (needs to be approved by: Ognjen) object(#3).owner.write(Ognjen); (needs to be approved by: Satya) object(#4).owner.write(Ognjen); (needs to be approved by: Satya) object(#5).owner.write(Ognjen); (needs to be approved by: Satya)
Result: proposal #15 (authorized by Ognjen)
Satya calls accept_proposal(#15)
- The ledger checks that all basic operations are approved by either creator or the acceptor of the proposal
- The ledger transfers the NFOs
Use case #2:
- NFT creator can mint new objects as they want
- Ognjen owns an egg
Basic object: (object_id: #1, type: egg, metadata: { owner: Ognjen }, operations: {name: burn, authorizer: owner } )
NFT creator calls create_proposal on the ledger: object(#1).burn(); (needs to be approved by Ognjen) mint({ type: baby_dino, metadata: { owner: Ognjen }, operations: {name: burn, authorizer: owner } } ); (needs to be approved by NFT creator)
Result: proposal #42 (authorized by NFT creator)
Ognjen calls accept_proposal(#42)
- The ledger checks that all basic operations are approved by either the creator or the acceptor of the proposal
- The ledger executes the rule; as a result, egg is burnt, baby dino is minted
- The proposal #42 is deleted
Use case #3: Ognjen wants to sell all 3 of his swords, but only if he can get both 2 bottles of potion from Satya and 1 magic hat from Jan
(object_id: #1, type: #sword, metadata: { owner: Ognjen }, #operations: {name: owner.write, authorizer: owner}) (object_id: #2, type: #sword, metadata: { owner: Ognjen }, #operations: {name: owner.write, authorizer: owner}) (object_id: #3, type: #sword, metadata: { owner: Ognjen }, #operations: {name: owner.write, authorizer: owner}) (object_id: #4, type: #potion, metadata: { owner: Satya }, #operations: {name: owner.write, authorizer: owner}) (object_id: #5, type: #potion, metadata: { owner: Satya }, #operations: {name: owner.write, authorizer: owner}) (object_id: #6, type: #magic_hat, metadata: { owner: Jan }, #operations: {name: owner.write, authorizer: owner})
Satya calls create_proposal for Ognjen with object(#1).owner.write(Satya); (needs to be approved by: Ognjen) object(#2).owner.write(Satya); (needs to be approved by: Ognjen) object(#4).owner.write(Ognjen); (needs to be approved by: Satya) object(#5).owner.write(Ognjen); (needs to be approved by: Satya)
Result: proposal #1
Ognjen calls create_proposal for Jan with: object(#3).owner.write(Jan); (needs to be approved by: Ognjen) object(#6).owner.write(Ognjen); (needs to be approved by: Jan) accept_proposal(#1)
Result: proposal #2
Jan calls accept_proposal(#2); this results in the full trade. With proposal #2, Ognjen (conditionally) delegated his right to accept proposal #1 to Jan.
Use case #4: like #2, but we want a "standing order" by the NFT creator that is applicable to all eggs
Level 3 policy (with parameters, assertions and so on)
NFT creator create_proposal on the ledger:
-
the proposal takes a parameter, object_id
require(object(object_id).type = "egg"); object(object_id).burn(); mint({ type: baby_dino, metadata: { owner: object(object_id).owner }, operations: {name: burn, authorizer: owner } }
========================================================= Level 3.5: programmatic policies with joint authorization
Use case #5: limiting transfer of swords to users with level > 50
Idea:
- Instead of a single owner of an NFO, or a single authorizer of changing a field, we add joint owners/authorizers
- We use "self", the Principal of the NFO Canister as the co-owner of NFOs
- When creating the object type, the NFO canister author can add "persistent proposals" that are authorized by "self"
Objects: (object_id: #1, type: #sword, metadata: { owner: {self, Ognjen}, user: Ognjen }, #operations: {name: owner.write, authorizer: owner}) (object_id: #2, type: #level, metadata: { owner: self, user: Ognjen, value: 100 }, #operations: {name: value.write, authorizer: {self, game_canister}) (object_id: #2, type: #level, metadata: { owner: self, user: Satya, value: 500 }, #operations: {name: value.write, authorizer: {self, game_canister})
Permanent proposals, authorized by "self":
transfer_sword(object_id, to_level_id): require(object(object_id).type = "sword") require(object(to_level_id).type = "level") require(object(to_level_id).value > 50) object(object_id).owner = {self, object(to_level_id).user }
Bonus example: force that the user level can only increase
increase_level(object_id, amount): require(object(object_id).type = "level") object(object_id).level += amount (needs to be approved by: self, game_canister)