├── .editorconfig ├── .envrc ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .vscode └── tasks.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── Protocol.rs ├── README.md ├── docker-compose.yml ├── docs ├── .nojekyll ├── README.md ├── _sidebar.md ├── index.html ├── pallets │ ├── rmrk-core-calls.md │ ├── rmrk-core-overview.md │ ├── rmrk-core-storages.md │ ├── rmrk-equip.md │ └── rmrk-market.md └── rpc.md ├── node ├── Cargo.toml ├── build.rs └── src │ ├── benchmarking.rs │ ├── chain_spec.rs │ ├── cli.rs │ ├── command.rs │ ├── lib.rs │ ├── main.rs │ ├── rpc.rs │ └── service.rs ├── pallets ├── rmrk-core │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── benchmarking.rs │ │ ├── functions.rs │ │ ├── lib.rs │ │ ├── mock.rs │ │ ├── tests.rs │ │ ├── types.rs │ │ └── weights.rs ├── rmrk-equip │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── benchmarking.rs │ │ ├── functions.rs │ │ ├── lib.rs │ │ ├── mock.rs │ │ ├── tests.rs │ │ └── weights.rs ├── rmrk-market │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── benchmarking.rs │ │ ├── lib.rs │ │ ├── mock.rs │ │ ├── tests.rs │ │ ├── types.rs │ │ └── weights.rs └── template │ ├── Cargo.toml │ ├── README.md │ └── src │ ├── benchmarking.rs │ ├── lib.rs │ ├── mock.rs │ └── tests.rs ├── rpc-runtime-api ├── Cargo.toml └── src │ └── lib.rs ├── rpc ├── Cargo.toml └── src │ └── lib.rs ├── runtime ├── Cargo.toml ├── build.rs └── src │ ├── constants.rs │ └── lib.rs ├── rust-setup.md ├── rust-toolchain.toml ├── rustfmt.toml ├── scripts ├── docker_run.sh └── init.sh ├── shell.nix ├── tests ├── package.json ├── src │ ├── acceptNft.test.ts │ ├── addResource.test.ts │ ├── addTheme.test.ts │ ├── burnNft.test.ts │ ├── changeCollectionIssuer.test.ts │ ├── config.ts │ ├── createBase.test.ts │ ├── createCollection.test.ts │ ├── deleteCollection.test.ts │ ├── equipNft.test.ts │ ├── getOwnedNfts.test.ts │ ├── getOwnedNftsInCollection.test.ts │ ├── getPropertiesOfOwnedNfts.test.ts │ ├── interfaces │ │ ├── augment-api-consts.ts │ │ ├── augment-api-errors.ts │ │ ├── augment-api-events.ts │ │ ├── augment-api-query.ts │ │ ├── augment-api-rpc.ts │ │ ├── augment-api-tx.ts │ │ ├── augment-api.ts │ │ ├── augment-types.ts │ │ ├── definitions.ts │ │ ├── index.ts │ │ ├── lookup.ts │ │ ├── metadata.json │ │ ├── registry.ts │ │ ├── rmrk │ │ │ ├── definitions.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── types-lookup.ts │ │ └── types.ts │ ├── lockCollection.test.ts │ ├── mintNft.test.ts │ ├── rejectNft.test.ts │ ├── removeResource.test.ts │ ├── replaceResource.test.ts │ ├── sendNft.test.ts │ ├── setCollectionProperty.test.ts │ ├── setEquippableList.test.ts │ ├── setNftProperty.test.ts │ ├── setResourcePriorities.test.ts │ ├── substrate │ │ ├── privateKey.ts │ │ ├── promisify-substrate.ts │ │ └── substrate-api.ts │ └── util │ │ ├── fetch.ts │ │ ├── helpers.ts │ │ └── tx.ts ├── tsconfig.json └── yarn.lock └── traits ├── Cargo.toml └── src ├── base.rs ├── budget.rs ├── collection.rs ├── lib.rs ├── misc.rs ├── nft.rs ├── part.rs ├── phantom_type.rs ├── priority.rs ├── property.rs ├── resource.rs ├── serialize.rs └── theme.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style=space 5 | indent_size=2 6 | tab_width=2 7 | end_of_line=lf 8 | charset=utf-8 9 | trim_trailing_whitespace=true 10 | insert_final_newline = true 11 | 12 | [*.{rs,toml}] 13 | indent_style=tab 14 | indent_size=tab 15 | tab_width=4 16 | max_line_length=100 17 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | # If lorri exists, better try it first. 2 | if has lorri; then 3 | eval "$(lorri direnv)" 4 | else 5 | # Otherwise fall back to pure nix 6 | use nix 7 | fi 8 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | name: Test 4 | 5 | on: 6 | pull_request: 7 | branches: 8 | - main 9 | push: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Cache cargo 19 | uses: actions/cache@v2 20 | with: 21 | path: | 22 | ~/.cargo/registry 23 | ~/.cargo/git 24 | target 25 | key: ${{ runner.os }}-cargo 26 | 27 | - name: Set-Up 28 | run: sudo apt install -y git clang curl libssl-dev llvm libudev-dev protobuf-compiler 29 | 30 | - name: Install toolchain 31 | # hacky way to install rust. Rustup is pre-installed on runners. Calling rustup show will detect the rust-toolchain.toml, and subsequently 32 | # download the needed toolchain and components. 33 | run: | 34 | rustup show 35 | 36 | - name: Check 37 | run: cargo check --workspace --all-features 38 | 39 | - name: Test 40 | run: cargo test --workspace --all-features 41 | 42 | - name: Lint 43 | run: cargo clippy --workspace --all-features 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | **/target/ 4 | # These are backup files generated by rustfmt 5 | **/*.rs.bk 6 | 7 | .DS_Store 8 | .vscode 9 | 10 | tests/node_modules 11 | 12 | # The cache for docker container dependency 13 | .cargo 14 | 15 | # The cache for chain data in container 16 | .local 17 | 18 | # direnv cache 19 | .direnv 20 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | // Refer https://pre-commit.com/#install 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v4.0.1 5 | hooks: 6 | - id: trailing-whitespace 7 | args: [--markdown-linebreak-ext=md] 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | - id: check-added-large-files 11 | - id: check-json 12 | - id: pretty-format-json 13 | args: [--autofix] 14 | - id: check-merge-conflict 15 | - id: check-symlinks 16 | - id: detect-private-key 17 | - id: no-commit-to-branch 18 | args: [--branch, main] 19 | - id: mixed-line-ending 20 | args: [--fix=lf] 21 | - id: check-toml 22 | - repo: https://github.com/doublify/pre-commit-rust 23 | rev: master 24 | hooks: 25 | - id: cargo-check 26 | - id: clippy 27 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "args": [ 5 | "run", 6 | "--release", 7 | "--", 8 | "--dev" 9 | ], 10 | "command": "cargo", 11 | "group": { 12 | "isDefault": true, 13 | "kind": "build" 14 | }, 15 | "label": "Run ", 16 | "presentation": { 17 | "panel": "new", 18 | "reveal": "always" 19 | }, 20 | "problemMatcher": [ 21 | { 22 | "fileLocation": [ 23 | "relative", 24 | "${workspaceRoot}" 25 | ], 26 | "owner": "rust", 27 | "pattern": { 28 | "column": 3, 29 | "endColumn": 5, 30 | "endLine": 4, 31 | "file": 1, 32 | "line": 2, 33 | "message": 7, 34 | "regexp": "^(.*):(\\d+):(\\d+):\\s+(\\d+):(\\d+)\\s+(warning|error):\\s+(.*)$", 35 | "severity": 6 36 | } 37 | } 38 | ], 39 | "type": "shell" 40 | } 41 | ], 42 | "version": "2.0.0" 43 | } 44 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | edition = "2021" 3 | members = [ 4 | 'node', 5 | 'pallets/template', 6 | 'pallets/rmrk-core', 7 | 'pallets/rmrk-equip', 8 | 'pallets/rmrk-market', 9 | 'rpc-runtime-api', 10 | 'runtime', 11 | ] 12 | [profile.release] 13 | panic = 'unwind' 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | run-debug: 2 | cargo run -- --dev 3 | 4 | run: 5 | cargo run --release -- --dev 6 | 7 | toolchain: 8 | ./scripts/init.sh 9 | 10 | build: 11 | cargo build --release 12 | 13 | check: 14 | SKIP_WASM_BUILD= cargo check --all --tests 15 | 16 | test: 17 | SKIP_WASM_BUILD= cargo test --all 18 | 19 | t: 20 | cargo test -p pallet-rmrk-core -- --nocapture && \ 21 | cargo test -p pallet-rmrk-market -- --nocapture && \ 22 | cargo test -p pallet-rmrk-equip -- --nocapture 23 | 24 | purge: 25 | cargo run -- purge-chain --dev -y 26 | 27 | restart: purge run 28 | 29 | init: toolchain build-full 30 | 31 | benchmark-output-core: 32 | cargo run --manifest-path node/Cargo.toml --release --features runtime-benchmarks -- benchmark --extrinsic '*' --pallet pallet_rmrk_core --output runtime/src/weights/pallet_rmrk_core.rs --execution=wasm --wasm-execution=compiled 33 | 34 | test-benchmark-core: 35 | cargo test --manifest-path pallets/rmrk-core/Cargo.toml --features runtime-benchmarks -- --nocapture -------------------------------------------------------------------------------- /Protocol.rs: -------------------------------------------------------------------------------- 1 | //! Bounty Chain 2 | //! 3 | //! This rough protocol sketch uses the Bounty and Treasury pallets from Substrate's FRAME. 4 | //! 5 | //! ### Terminology: Treasury 6 | //! 7 | //! - **Proposal:** A suggestion to allocate funds from the pot to a beneficiary. 8 | //! - **Beneficiary:** An account who will receive the funds from a proposal iff the proposal is 9 | //! approved. 10 | //! - **Deposit:** Funds that a proposer must lock when making a proposal. The deposit will be 11 | //! returned or slashed if the proposal is approved or rejected respectively. 12 | //! - **Pot:** Unspent funds accumulated by the treasury pallet. 13 | //! 14 | //! ### Terminology: Bounty 15 | //! 16 | //! Bounty: 17 | //! - **Bounty spending proposal:** A proposal to reward a predefined body of work upon completion 18 | //! by the Treasury. 19 | //! - **Proposer:** An account proposing a bounty spending. 20 | //! - **Curator:** An account managing the bounty and assigning a payout address receiving the 21 | //! reward for the completion of work. 22 | //! - **Deposit:** The amount held on deposit for placing a bounty proposal plus the amount held on 23 | //! deposit per byte within the bounty description. 24 | //! - **Curator deposit:** The payment from a candidate willing to curate an approved bounty. The 25 | //! deposit is returned when/if the bounty is completed. 26 | //! - **Bounty value:** The total amount that should be paid to the Payout Address if the bounty is 27 | //! rewarded. 28 | //! - **Payout address:** The account to which the total or part of the bounty is assigned to. 29 | //! - **Payout Delay:** The delay period for which a bounty beneficiary needs to wait before 30 | //! claiming. 31 | //! - **Curator fee:** The reserved upfront payment for a curator for work related to the bounty. 32 | //! 33 | //! Reviewers and Contributors have reputations and can openly participate in the network. 34 | //! 35 | //! The governance system has: 36 | //! - A reviewing committee: these are experts whose task is to review bounty submissions and approve or reject them. 37 | //! These members earn a reputation and are voted to retain their seat on the committee. 38 | //! 39 | //! - A creators committee: this can be any bounty hunter looking to earn rewards for the bounties and participate in the network. 40 | //! 41 | //! - A council: these members have been creators or reviewers for some time. 42 | //! Their job is to scout experts in specific knowledge domains. 43 | //! 44 | //! Participation protocol: 45 | //! - `request_participation` - Get tokens to get started from Treasury, temporary to kickstart adoption. 46 | //! - `vote_for_reviewer` - Hunters can vote on reviewers they submitted to as part of the governance. 47 | //! 48 | //! ## Treasury Interface 49 | //! 50 | //! ### Dispatchable Functions 51 | //! 52 | //! General spending/proposal protocol: 53 | //! - `propose_spend` - Make a spending proposal and stake the required deposit. 54 | //! - `reject_proposal` - Reject a proposal, slashing the deposit. 55 | //! - `approve_proposal` - Accept the proposal, returning the deposit. 56 | //! 57 | //! ## GenesisConfig 58 | //! 59 | //! The Treasury pallet depends on the [`GenesisConfig`]. 60 | //! 61 | //! Bounty protocol: 62 | //! - `propose_bounty` - Propose a specific treasury amount to be earmarked for a predefined set of 63 | //! tasks and stake the required deposit. 64 | //! - `approve_bounty` - Accept a specific treasury amount to be earmarked for a predefined body of 65 | //! work. 66 | //! 67 | //! - `propose_curator` - Assign an account to a bounty as candidate curator. 68 | //! - `accept_curator` - Accept a bounty assignment from the Council, setting a curator deposit. 69 | //! 70 | //! - `extend_bounty_expiry` - Extend the expiry block number of the bounty and stay active. 71 | //! - `award_bounty` - Close and pay out the specified amount for the completed work. 72 | //! - `claim_bounty` - Claim a specific bounty amount from the Payout Address. 73 | //! - `unassign_curator` - Unassign an accepted curator from a specific earmark. 74 | //! - `close_bounty` - Cancel the earmark for a specific treasury amount and close the bounty. 75 | //! 76 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.2" 2 | 3 | services: 4 | dev: 5 | container_name: rmrk-substrate 6 | image: paritytech/ci-linux:974ba3ac-20201006 7 | working_dir: /var/www/rmrk-substrate 8 | ports: 9 | - "9944:9944" 10 | environment: 11 | - CARGO_HOME=/var/www/rmrk-substrate/.cargo 12 | volumes: 13 | - .:/var/www/rmrk-substrate 14 | - type: bind 15 | source: ./.local 16 | target: /root/.local 17 | command: bash -c "cargo build --release && ./target/release/rmrk-substrate --dev --ws-external" 18 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmrk-team/rmrk-substrate/8c74166627c496435e2dff5dbea03a55d8fec764/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # RMRK Substrate Pallets 2 | 3 | ## Main features 4 | 5 | - NFTs owning NFTs 6 | - NFTs having multiple priority-ordered client-dependent resources (a book with a cover and an audio 7 | version) 8 | - NFTs with conditional rendering (on-NFT logic allowing different visuals and resources to trigger 9 | depending on on-chain and off-chain conditions). 10 | - NFTs governed as DAOs via shareholder tokens (issue commands to NFTs democratically) 11 | - NFTs, accounts, and other on-chain entities being emoted to (on-chain emotes), allowing early 12 | price discovery without listing, selling, bidding. 13 | 14 | ## Overview 15 | 16 | Initial implementation extends [Substrate Uniques](https://github.com/paritytech/substrate/tree/master/frame/uniques) pallet as 'low level' NFT dependency. 17 | Mechanics and interactions are based on [RMRK 2 standard](https://github.com/rmrk-team/rmrk-spec/tree/master/standards/rmrk2.0.0) 18 | 19 | ![](https://camo.githubusercontent.com/1202d3852b7eba4ae73a6e90021e2006984e349f392665c34897fda846fe5b57/68747470733a2f2f7374617469632e7377696d6c616e65732e696f2f36383731663161343233386533663637363265623738343132663062383363322e706e67) 20 | 21 | ## Pallets 22 | 23 | - [Core](/pallets/rmrk-core-overview) 24 | - [Equip](/pallets/rmrk-equip) 25 | - [Market](/pallets/rmrk-market) 26 | - Auctions (coming soon) 27 | - Emotes (coming soon) 28 | - Fractionalization (coming later) 29 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | - Overview 2 | 3 | - [RMRK Standard](README.md) 4 | - [RPC](rpc.md) 5 | 6 | - RMRK Core pallet 7 | 8 | - [Overview](pallets/rmrk-core-overview.md) 9 | - [Calls](pallets/rmrk-core-calls.md) 10 | - [Storages/Events](pallets/rmrk-core-storages.md) 11 | 12 | - Other Pallets 13 | - [Equip](pallets/rmrk-equip.md) 14 | - [Market](pallets/rmrk-market.md) 15 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /docs/pallets/rmrk-core-calls.md: -------------------------------------------------------------------------------- 1 | ### **create_collection** 2 | 3 | Create a collection of assets. 4 | 5 | ```rust 6 | metadata: BoundedVec, // e.g. IPFS hash 7 | max: Option, // How many NFTs will ever belong to this collection. 0 for infinite. 8 | symbol: BoundedVec // Ticker symbol by which to represent the token in wallets and UIs, e.g. ZOMB 9 | ``` 10 | 11 | ### **mint_nft** 12 | 13 | Mints an NFT in the specified collection. Sets metadata and the royalty attribute. 14 | 15 | ```rust 16 | owner: T::AccountId, 17 | collection_id: CollectionId, // The collection of the asset to be minted. 18 | royalty_recipient: Option, // Receiver of the royalty 19 | royalty: Option, // Permillage reward from each trade for the Recipient 20 | metadata: BoundedVec // Arbitrary data about an nft, e.g. IPFS hash 21 | transferable: bool // Non transferable NFT (aka "Soulbound"), 22 | resources: Option> // Add resources during mint 23 | ``` 24 | 25 | ### **mint_nft_directly_to_nft** 26 | 27 | Mints an NFT in the specified collection directly to another NFT. Sets metadata and the royalty attribute. 28 | 29 | ```rust 30 | owner: (CollectionId, NftId), // Owner is a tuple of CollectionId, NftId 31 | collection_id: CollectionId, // The collection of the asset to be minted. 32 | royalty_recipient: Option, // Receiver of the royalty 33 | royalty: Option, // Permillage reward from each trade for the Recipient 34 | metadata: BoundedVec // Arbitrary data about an nft, e.g. IPFS hash 35 | transferable: bool // Non transferable NFT (aka "Soulbound"), 36 | resources: Option> // Add resources during mint 37 | ``` 38 | 39 | ### **burn_nft** 40 | 41 | Burn a NFT 42 | 43 | ```rust 44 | collection_id: CollectionId, 45 | nft_id: NftId, 46 | max_burns: u32, // Max depth of nested assets recursive burning 47 | ``` 48 | 49 | ### **destroy_collection** 50 | 51 | destroy a collection 52 | 53 | ```rust 54 | collection_id: CollectionId 55 | ``` 56 | 57 | ### **send** 58 | 59 | Transfers a NFT from an Account or NFT A to another Account or NFT B 60 | 61 | ```rust 62 | collection_id: CollectionId, // collection id of the nft to be transferred 63 | nft_id: NftId, // nft id of the nft to be transferred 64 | new_owner: AccountIdOrCollectionNftTuple // new owner of the nft which can be either an account or a NFT 65 | ``` 66 | 67 | ### **accept_nft** 68 | 69 | Accepts an NFT sent from another account to self or owned NFT. 70 | 71 | ```rust 72 | collection_id: CollectionId, // collection id of the nft to be accepted 73 | nft_id: NftId, // nft id of the nft to be accepted 74 | new_owner: AccountIdOrCollectionNftTuple // either origin's account ID or origin-owned NFT, whichever the NFT was sent to 75 | ``` 76 | 77 | ### **reject_nft** 78 | 79 | Rejects an NFT sent from another account to self or owned NFT 80 | 81 | ```rust 82 | collection_id: CollectionId, // collection id of the nft to be accepted 83 | nft_id: NftId // nft id of the nft to be accepted 84 | ``` 85 | 86 | ### **change_collection_issuer** 87 | 88 | Change the issuer of a collection. 89 | 90 | ```rust 91 | collection_id: CollectionId, // collection id of the nft to change issuer of 92 | new_issuer: ::Source // Collection's new issuer 93 | ``` 94 | 95 | ### **set_property** 96 | 97 | Set a custom value on an NFT 98 | 99 | ```rust 100 | collection_id: CollectionId, 101 | maybe_nft_id: Option, 102 | key: KeyLimitOf, 103 | value: ValueLimitOf 104 | ``` 105 | 106 | ### **lock_collection** 107 | 108 | Lock collection 109 | 110 | ```rust 111 | collection_id: CollectionId 112 | ``` 113 | 114 | --- 115 | 116 | ### **add_basic_resource** 117 | 118 | Create a basic resource. [BasicResource](https://github.com/rmrk-team/rmrk-substrate/blob/3f4f1a7613be81828697347d3e297a460fca5ec5/traits/src/resource.rs#L25) 119 | 120 | ```rust 121 | collection_id: CollectionId, 122 | nft_id: NftId, 123 | resource: BasicResource>, 124 | ``` 125 | 126 | ### **add_composable_resource** 127 | 128 | Create s composable resource. [ComposableResource](https://github.com/rmrk-team/rmrk-substrate/blob/3f4f1a7613be81828697347d3e297a460fca5ec5/traits/src/resource.rs#L60) 129 | 130 | ```rust 131 | collection_id: CollectionId, 132 | nft_id: NftId, 133 | resource: ComposableResource, BoundedVec>, 134 | ``` 135 | 136 | ### **add_slot_resource** 137 | 138 | Create a slot resource. [SlotResource](https://github.com/rmrk-team/rmrk-substrate/blob/3f4f1a7613be81828697347d3e297a460fca5ec5/traits/src/resource.rs#L107) 139 | 140 | ```rust 141 | collection_id: CollectionId, 142 | nft_id: NftId, 143 | resource: SlotResource>, 144 | ``` 145 | 146 | ### **accept_resource** 147 | 148 | Accept the addition of a new resource to an existing NFT. 149 | 150 | ```rust 151 | collection_id: CollectionId, 152 | nft_id: NftId, 153 | resource_id: ResourceId 154 | ``` 155 | 156 | ### **remove_resource** 157 | 158 | Remove a resource. 159 | 160 | ```rust 161 | collection_id: CollectionId, 162 | nft_id: NftId, 163 | resource_id: ResourceId 164 | ``` 165 | 166 | ### **accept_resource_removal** 167 | 168 | Accept the removal of a resource of an existing NFT. 169 | 170 | ```rust 171 | collection_id: CollectionId, 172 | nft_id: NftId, 173 | resource_id: ResourceId 174 | ``` 175 | 176 | ### **set_priority** 177 | 178 | set a different order of resource priority 179 | 180 | ```rust 181 | collection_id: CollectionId, 182 | nft_id: NftId, 183 | priorities: BoundedVec, 184 | ``` 185 | -------------------------------------------------------------------------------- /docs/pallets/rmrk-core-overview.md: -------------------------------------------------------------------------------- 1 | Essential functionality for nested and multi-resourced NFTs. 2 | 3 | Typical use cases are: 4 | 5 | - Nested NFTs include anything non-stackably-visual: bundles of NFTs, mortgage NFT with photos of the house, music albums with songs, user-created curated collections for galleries, and more. 6 | - A set of NFTs can be combined as a single object that can be send and sell in an auction as a whole. 7 | - By following some special rules (defined in BASE), some NFTs can be combined in a meaningful way that produce some special effects. E.g. glasses can be equipped to a Kanaria bird and can be rendered as a complete portrait. 8 | 9 | Ownership model for nested NFTs ( NFT owning another NFT ) is based on [this](https://github.com/rmrk-team/rmrk-substrate/issues/27) proposal using `pallet-unique` to trace hierarchy of the NFTs and virtual accounts trick. 10 | 11 | ![](https://static.swimlanes.io/15201cbf30d5a669d71beee38813e5a5.png) 12 | -------------------------------------------------------------------------------- /docs/pallets/rmrk-core-storages.md: -------------------------------------------------------------------------------- 1 | **Events** implementation [rmrk-core/src/lib.rs#L67-L149](https://github.com/rmrk-team/rmrk-substrate/blob/main/pallets/rmrk-core/src/lib.rs#L67-L149) 2 | 3 | - CollectionCreated 4 | - NftMinted 5 | - NFTBurned 6 | - CollectionDestroyed 7 | - NFTSent 8 | - NFTAccepted 9 | - NFTRejected 10 | - IssuerChanged 11 | - PropertySet 12 | - CollectionLocked 13 | - ResourceAdded 14 | - ResourceAccepted 15 | - PrioritySet 16 | 17 | --- 18 | 19 | **Storages** implementation [rmrk-core/src/lib.rs#L159-L223](https://github.com/rmrk-team/rmrk-substrate/blob/main/pallets/rmrk-core/src/lib.rs#L159-L223) 20 | 21 | ### NextNftId 22 | 23 | Get next NFT id 24 | 25 | ```rust 26 | pub type NextNftId = StorageMap<_, Twox64Concat, CollectionId, NftId, ValueQuery>; 27 | ``` 28 | 29 | ### CollectionIndex 30 | 31 | Get next Collection index 32 | 33 | ```rust 34 | pub type CollectionIndex = StorageValue<_, CollectionId, ValueQuery>; 35 | ``` 36 | 37 | ### NextResourceId 38 | 39 | Get next Resource id 40 | 41 | ```rust 42 | pub type NextResourceId = StorageDoubleMap< 43 | _, 44 | Twox64Concat, 45 | CollectionId, 46 | Twox64Concat, 47 | NftId, 48 | ResourceId, 49 | ValueQuery, 50 | >; 51 | ``` 52 | 53 | ### Collections 54 | 55 | Stores collections info 56 | 57 | ```rust 58 | pub type Collections = StorageMap< 59 | _, 60 | Twox64Concat, 61 | CollectionId, 62 | CollectionInfo, BoundedCollectionSymbolOf, T::AccountId>, 63 | >; 64 | ``` 65 | 66 | ### Nfts 67 | 68 | Stores nft info 69 | 70 | ```rust 71 | pub type Nfts = 72 | StorageDoubleMap<_, Twox64Concat, CollectionId, Twox64Concat, NftId, InstanceInfoOf>; 73 | ``` 74 | 75 | ### Children 76 | 77 | Stores nft children nesting info. Allows NFT to own other NFT 78 | 79 | ```rust 80 | pub type Children = StorageDoubleMap< 81 | _, 82 | Twox64Concat, 83 | (CollectionId, NftId), 84 | Twox64Concat, 85 | (CollectionId, NftId), 86 | (), 87 | >; 88 | ``` 89 | 90 | ### Resources 91 | 92 | Stores resource info. Allows NFTs to contain multiple resources. 93 | 94 | ```rust 95 | pub type Resources = StorageNMap< 96 | _, 97 | ( 98 | NMapKey, 99 | NMapKey, 100 | NMapKey, 101 | ), 102 | ResourceInfoOf, 103 | OptionQuery, 104 | >; 105 | ``` 106 | 107 | ### EquippableBases 108 | 109 | Stores the existence of a base for a particular NFT. This is populated on `add_composable_resource`, and is used in the rmrk-equip pallet when equipping a resource. 110 | 111 | ```rust 112 | pub type EquippableBases = StorageNMap< 113 | _, 114 | ( 115 | NMapKey, 116 | NMapKey, 117 | NMapKey, 118 | ), 119 | (), 120 | >; 121 | ``` 122 | 123 | ### EquippableSlots 124 | 125 | Stores the existence of a Base + Slot for a particular NFT's particular resource. This is populated on `add_slot_resource`, and is used in the rmrk-equip pallet when equipping a resource. 126 | 127 | ```rust 128 | pub type EquippableSlots = StorageNMap< 129 | _, 130 | ( 131 | NMapKey, 132 | NMapKey, 133 | NMapKey, 134 | NMapKey, 135 | NMapKey, 136 | ), 137 | (), 138 | >; 139 | ``` 140 | 141 | ### Properties 142 | 143 | Arbitrary properties / metadata of an asset. 144 | 145 | ```rust 146 | pub(super) type Properties = StorageNMap< 147 | _, 148 | ( 149 | NMapKey, 150 | NMapKey>, 151 | NMapKey>, 152 | ), 153 | ValueLimitOf, 154 | OptionQuery, 155 | >; 156 | ``` 157 | -------------------------------------------------------------------------------- /docs/pallets/rmrk-equip.md: -------------------------------------------------------------------------------- 1 | # Equip NFT Pallet Design 2 | 3 | ![](https://static.swimlanes.io/9b72cd84a9ca752cbb7f2c6348d6a50b.png) 4 | 5 | Equips an owned NFT into a slot on its parent, or unequips it. 6 | * You can only EQUIP an existing NFT (one that has not been BURNed yet). 7 | * You can only EQUIP an NFT into its immediate parent. 8 | * You cannot equip across ancestors, or even across other NFTs. 9 | * You can only unequip an equipped NFT. 10 | * You can only equip a non-pending child NFT. 11 | * You can equip/unequip a non-transferable NFT. As an example, putting a helmet on or taking it off does not change the ownership of the helmet. 12 | 13 | Equip logic has many implications - per-slot limits, nested rendering across multiple depths (ie configurable cap on configurable kanaria bird), unequip on sale, burn, etc. Equip "addon lego" not useful by itself, it needs the core. 14 | Should extend RMRK Core pallet. 15 | 16 | Full RMRK2 spec for [Equip](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/interactions/equip.md) and [Base](https://github.com/rmrk-team/rmrk-spec/blob/master/standards/rmrk2.0.0/entities/base.md) 17 | 18 | ## Calls 19 | 20 | 21 | 22 | ### **equip** 23 | Equip a child NFT into a parent's slot, or unequip 24 | ```rust 25 | item: (CollectionId, NftId), 26 | equipper: (CollectionId, NftId), 27 | base: BaseId, 28 | slot: SlotId 29 | ``` 30 | 31 | ### **equippable** 32 | Changes the list of equippable collections on a base's part 33 | 34 | ```rust 35 | base_id: BaseId, 36 | slot_id: SlotId, 37 | equippables: EquippableList 38 | ``` 39 | ### **theme_add** 40 | Add a new theme to a base 41 | 42 | ```rust 43 | base_id: BaseId, 44 | theme: Theme> 45 | ``` 46 | 47 | ### **create_base** 48 | Create a base. catalogue of parts. It is not an NFT 49 | 50 | ```rust 51 | base_type: BoundedVec, 52 | symbol: BoundedVec, 53 | parts: Vec>> 54 | ``` 55 | 56 | 57 | ## Storages 58 | Current implementation [here](https://github.com/rmrk-team/rmrk-substrate/blob/main/pallets/rmrk-equip/src/lib.rs#L51-L83) 59 | 60 | * Bases 61 | * Parts 62 | * NextBaseId 63 | * NextPartId 64 | * Equippings 65 | * Themes 66 | 67 | ## Events 68 | Current implementation [here](https://github.com/rmrk-team/rmrk-substrate/blob/main/pallets/rmrk-equip/src/lib.rs#L105-L126) 69 | * BaseCreated 70 | * SlotEquipped 71 | * SlotUnequipped 72 | * EquippablesUpdated 73 | 74 | ## Traits / Types 75 | Set of re-usable traits describing the total interface located [here](https://github.com/rmrk-team/rmrk-substrate/tree/main/traits/src) 76 | 77 | ### Primitives 78 | ```rust 79 | type BaseId = u32; 80 | type SlotId = u32; 81 | type PartId = u32; 82 | type ZIndex = u32; 83 | ``` 84 | 85 | ### BaseInfo 86 | ```rust 87 | pub struct BaseInfo { 88 | /// Original creator of the Base 89 | pub issuer: AccountId, 90 | /// Specifies how an NFT should be rendered, ie "svg" 91 | pub base_type: BoundedString, 92 | /// User provided symbol during Base creation 93 | pub symbol: BoundedString, 94 | /// Parts, full list of both Fixed and Slot parts 95 | pub parts: Vec>, 96 | } 97 | ``` 98 | 99 | ### Part 100 | ```rust 101 | pub enum PartType { 102 | FixedPart(FixedPart), 103 | SlotPart(SlotPart), 104 | } 105 | 106 | pub struct FixedPart { 107 | pub id: PartId, 108 | pub z: ZIndex, 109 | pub src: BoundedString, 110 | } 111 | 112 | pub struct SlotPart { 113 | pub id: PartId, 114 | pub equippable: EquippableList, 115 | pub src: BoundedString, 116 | pub z: ZIndex, 117 | } 118 | 119 | pub enum EquippableList { 120 | All, 121 | Empty, 122 | Custom(Vec), 123 | } 124 | ``` 125 | 126 | ### Theme 127 | 128 | ```rust 129 | pub struct Theme { 130 | /// Name of the theme 131 | pub name: BoundedString, 132 | /// Theme properties 133 | pub properties: Vec>, 134 | } 135 | 136 | pub struct ThemeProperty { 137 | /// Key of the property 138 | pub key: BoundedString, 139 | /// Value of the property 140 | pub value: BoundedString, 141 | /// Inheritability 142 | pub inherit: Option, 143 | } 144 | ``` -------------------------------------------------------------------------------- /docs/pallets/rmrk-market.md: -------------------------------------------------------------------------------- 1 | # Market Pallet Design 2 | 3 | Marketplace pallet. Should extend RMRK Core pallet. 4 | ## Calls 5 | 6 | ### **buy** 7 | Buy a listed NFT. Ensure that the NFT is available for purchase and has not recently been purchased, sent, or burned. 8 | 9 | ```rust 10 | collection_id: CollectionId, 11 | nft_id: NftId 12 | amount: Option> 13 | ``` 14 | 15 | ### **list** 16 | List a RMRK NFT on the Marketplace for purchase. A listing can be cancelled, and is 17 | automatically considered cancelled when a `buy` is executed on top of a given listing. 18 | An NFT that has another NFT as its owner CANNOT be listed. An NFT owned by a NFT must 19 | first be sent to an account before being listed. 20 | 21 | ```rust 22 | collection_id: CollectionId, 23 | nft_id: NftId, 24 | amount: BalanceOf, 25 | expires: Option 26 | ``` 27 | 28 | 29 | ### **unlist** 30 | Unlist a RMRK NFT on the Marketplace and remove from storage in `Listings`. 31 | 32 | ```rust 33 | collection_id: CollectionId, 34 | nft_id: NftId 35 | ``` 36 | 37 | ### **make_offer** 38 | Make an offer on a RMRK NFT for purchase. An offer can be set with an expiration where the offer can no longer be accepted by the RMRK NFT owner. 39 | 40 | ```rust 41 | collection_id: CollectionId, 42 | nft_id: NftId, 43 | amount: BalanceOf, 44 | expires: Option 45 | ``` 46 | 47 | ### **withdraw_offer** 48 | Withdraw an offer on a RMRK NFT, such that it is no longer available to be accepted by the NFT owner. 49 | ```rust 50 | collection_id: CollectionId, 51 | nft_id: NftId 52 | ``` 53 | 54 | ### **accept_offer** 55 | Accept an offer on a RMRK NFT from a potential buyer. 56 | 57 | ```rust 58 | collection_id: CollectionId, 59 | nft_id: NftId, 60 | offerer: T::AccountId // Account that made the offer 61 | ``` 62 | 63 | ## Storages 64 | Current implementation [here](https://github.com/rmrk-team/rmrk-substrate/blob/main/pallets/rmrk-market/src/lib.rs#L74-L98) 65 | 66 | * ListedNfts 67 | * Offers 68 | 69 | ## Events 70 | Current implementation [here](https://github.com/rmrk-team/rmrk-substrate/blob/main/pallets/rmrk-market/src/lib.rs#L102-L151) 71 | * TokenPriceUpdated 72 | * TokenSold 73 | * TokenListed 74 | * TokenUnlisted 75 | * OfferPlaced 76 | * OfferWithdrawn 77 | * OfferAccepted 78 | 79 | ## Types 80 | 81 | ### ListInfo 82 | ```rust 83 | pub struct ListInfo { 84 | /// Owner who listed the NFT at the time 85 | pub(super) listed_by: AccountId, 86 | /// Listed amount 87 | pub(super) amount: Balance, 88 | /// After this block the listing can't be bought 89 | pub(super) expires: Option, 90 | } 91 | ``` 92 | 93 | ### Offer 94 | ```rust 95 | pub struct Offer { 96 | /// User who made the offer 97 | pub(super) maker: AccountId, 98 | /// Offered amount 99 | pub(super) amount: Balance, 100 | /// After this block the offer can't be accepted 101 | pub(super) expires: Option, 102 | } 103 | ``` -------------------------------------------------------------------------------- /docs/rpc.md: -------------------------------------------------------------------------------- 1 | # RMRK RPC 2 | 3 | > NOTE: All of the RMRK types can be found at `rmrk-substrate/traits/src` 4 | 5 | - `CollectionInfo`: `traits/src/collection.rs` 6 | - `NftInfo`: `traits/src/nft.rs` 7 | - `ResourceInfo`: `traits/src/resource.rs` 8 | - `BaseInfo`: `traits/src/base.rs` 9 | - `PartType`: `traits/src/part.rs` 10 | - `Theme`: `traits/src/theme.rs` 11 | - `PropertyInfo`: `traits/src/property.rs` 12 | - `Bytes == Vec` 13 | 14 | ### Get last collection index (get collections count) 15 | 16 | The frontend can fetch and show the overall collection's count 17 | 18 | ```rust 19 | lastCollectionIdx() -> CollectionId 20 | ``` 21 | 22 | ### Get collection by id 23 | 24 | The frontend can fetch and show the collection info 25 | 26 | ```rust 27 | collectionById(collectionId: CollectionId) -> Option 28 | ``` 29 | 30 | ### Get owned NFTs within a collection 31 | 32 | The frontend can fetch all NFTs within a collection owned by a specific user 33 | 34 | ```rust 35 | accountTokens(accountId: AccountId, collectionId: CollectionId) -> Vec 36 | ``` 37 | 38 | ### Get NFT info by id 39 | 40 | The frontent can fetch and show NFT info 41 | 42 | ```rust 43 | nftById(collectionId: CollectionId, nftId: NftId) -> Option 44 | ``` 45 | 46 | ### Get all of the NFTs owned by user 47 | 48 | The frontend can fetch several NFTs at once. Pagination is supported. 49 | 50 | ```rust 51 | fn nfts_owned_by(account_id: AccountId, start_index: Option, count: Option) -> Result>; 52 | ``` 53 | 54 | ### Get the properties of all of the NFTs owned by user 55 | 56 | The frontend can fetch several properties of multiple NFTs at once. Pagination is supported. 57 | 58 | ```rust 59 | fn properties_of_nfts_owned_by( 60 | account_id: AccountId, 61 | start_index: Option, 62 | count: Option 63 | ) -> Result)>>; 64 | ``` 65 | 66 | ### Get property keys' values 67 | 68 | The frontend can fetch several properties at once 69 | 70 | ```rust 71 | collectionProperties(collectionId: CollectionId, filterKeys: Option>) -> Vec 72 | 73 | nftProperties(collectionId: CollectionId, nftId: NftId, filterKeys: Option>) -> Vec 74 | ``` 75 | 76 | ### Get NFT children 77 | 78 | The frotnend can fetch chlidren of an NFT 79 | 80 | ```rust 81 | nftChildren(collectionId: CollectionId, nftId: NftId) -> Vec 82 | ``` 83 | 84 | ### Get NFT Resources 85 | 86 | The frontend can fetch NFT resources 87 | 88 | ```rust 89 | nftResources(collectionId: CollectionId, nftId: NftId) -> Vec 90 | ``` 91 | 92 | ### Get NFT Resource Priority 93 | 94 | The frontend can fetch NFT resource priorities 95 | 96 | ```rust 97 | nftResourcePriority(collectionId: CollectionId, nftId: NftId, resourceId: ResourceId) -> Option /* resource priority */ 98 | ``` 99 | 100 | ### Get NFT Base 101 | 102 | The frotnend can fetch the NFT Base info 103 | 104 | ```rust 105 | base(baseId: BaseId) -> Option 106 | ``` 107 | 108 | ### Get Base parts 109 | 110 | The frontend can fetch all Base's parts 111 | 112 | ```rust 113 | baseParts(baseId: BaseId) -> Vec 114 | ``` 115 | 116 | ### Get Base Theme names 117 | 118 | The frontend can fetch all Base's theme names 119 | 120 | ```rust 121 | themeNames(baseId: BaseId) -> Vec 122 | ``` 123 | 124 | ### Get Base Theme 125 | 126 | The frontend can fetch Base's Theme info -- name, properties, and inherit flag 127 | 128 | ```rust 129 | theme(baseId: BaseId, themeName: Bytes, filterKeys: Option>) -> Option 130 | ``` 131 | -------------------------------------------------------------------------------- /node/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rmrk-substrate" 3 | version = "4.0.0-dev" 4 | description = "Eternally liquid. Forward compatible. Nested, conditional, & Multi-resourced NFTs." 5 | authors = ["RMRK Team"] 6 | homepage = "https://rmrk.app" 7 | edition = "2021" 8 | license = "Apache-2.0" 9 | publish = false 10 | repository = "https://github.com/rmrk-team/rmrk-substrate" 11 | build = "build.rs" 12 | 13 | [[bin]] 14 | name = "rmrk-substrate" 15 | 16 | [package.metadata.docs.rs] 17 | targets = ["x86_64-unknown-linux-gnu"] 18 | 19 | [dependencies] 20 | clap = { version = "4.0.9", features = ["derive"] } 21 | futures = { version = "0.3.21", features = ["thread-pool"]} 22 | 23 | sc-cli = { version = "0.10.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 24 | sp-core = { version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 25 | sc-executor = { version = "0.10.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 26 | sc-service = { version = "0.10.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 27 | sc-telemetry = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 28 | sc-keystore = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 29 | sc-transaction-pool = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 30 | sc-transaction-pool-api = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 31 | sc-consensus-aura = { version = "0.10.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 32 | sp-consensus-aura = { version = "0.10.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 33 | sp-consensus = { version = "0.10.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 34 | sc-consensus = { version = "0.10.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 35 | sc-finality-grandpa = { version = "0.10.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 36 | sp-finality-grandpa = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 37 | sc-client-api = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 38 | sp-runtime = { version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 39 | sp-io = { version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 40 | sp-timestamp = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 41 | sp-inherents = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 42 | sp-keyring = { version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 43 | frame-system = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 44 | pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 45 | 46 | # These dependencies are used for the node template"s RPCs 47 | jsonrpsee = { version = "0.16.2", features = ["server"] } 48 | sc-rpc = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 49 | sp-api = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 50 | sc-rpc-api = { version = "0.10.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 51 | sp-blockchain = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 52 | sp-block-builder = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 53 | sc-basic-authorship = { version = "0.10.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 54 | substrate-frame-rpc-system = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 55 | pallet-transaction-payment-rpc = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 56 | 57 | # These dependencies are used for runtime benchmarking 58 | frame-benchmarking = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 59 | frame-benchmarking-cli = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 60 | 61 | # Local Dependencies 62 | rmrk-substrate-runtime = { version = "4.0.0-dev", path = "../runtime" } 63 | pallet-rmrk-rpc-runtime-api = { version = "0.0.1", path = "../rpc-runtime-api" } 64 | pallet-rmrk-rpc = { version = "0.0.1", path = "../rpc" } 65 | rmrk-traits = { version = "0.0.1", default-features = false, path = "../traits" } 66 | pallet-rmrk-core = { version = "0.0.1", path = "../pallets/rmrk-core" } 67 | pallet-rmrk-equip = { version = "0.0.1", path = "../pallets/rmrk-equip" } 68 | 69 | # CLI-specific dependencies 70 | try-runtime-cli = { version = "0.10.0-dev", optional = true, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 71 | 72 | [build-dependencies] 73 | substrate-build-script-utils = { version = "3.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 74 | 75 | [features] 76 | default = [] 77 | # Dependencies that are only required if runtime benchmarking should be build. 78 | runtime-benchmarks = [ 79 | "rmrk-substrate-runtime/runtime-benchmarks", 80 | "frame-benchmarking/runtime-benchmarks", 81 | "frame-benchmarking-cli/runtime-benchmarks", 82 | ] 83 | # Enable features that allow the runtime to be tried and debugged. Name might be subject to change 84 | # in the near future. 85 | try-runtime = ["try-runtime-cli/try-runtime"] 86 | -------------------------------------------------------------------------------- /node/build.rs: -------------------------------------------------------------------------------- 1 | use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; 2 | 3 | fn main() { 4 | generate_cargo_keys(); 5 | 6 | rerun_if_git_head_changed(); 7 | } 8 | -------------------------------------------------------------------------------- /node/src/benchmarking.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) 2022 Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 5 | 6 | // This program is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | 16 | // You should have received a copy of the GNU General Public License 17 | // along with this program. If not, see . 18 | 19 | //! Setup code for [`super::command`] which would otherwise bloat that module. 20 | //! 21 | //! Should only be used for benchmarking as it may break in other contexts. 22 | 23 | use crate::service::FullClient; 24 | 25 | use rmrk_substrate_runtime as runtime; 26 | use runtime::{AccountId, Balance, BalancesCall, SystemCall}; 27 | use sc_cli::Result; 28 | use sc_client_api::BlockBackend; 29 | use sp_core::{Encode, Pair}; 30 | use sp_inherents::{InherentData, InherentDataProvider}; 31 | use sp_keyring::Sr25519Keyring; 32 | use sp_runtime::{OpaqueExtrinsic, SaturatedConversion}; 33 | 34 | // use rmrk_substrate_runtime::EXISTENTIAL_DEPOSIT; 35 | use std::{sync::Arc, time::Duration}; 36 | 37 | /// Generates extrinsics for the `benchmark overhead` command. 38 | /// 39 | /// Note: Should only be used for benchmarking. 40 | pub struct RemarkBuilder { 41 | client: Arc, 42 | } 43 | 44 | impl RemarkBuilder { 45 | /// Creates a new [`Self`] from the given client. 46 | pub fn new(client: Arc) -> Self { 47 | Self { client } 48 | } 49 | } 50 | 51 | impl frame_benchmarking_cli::ExtrinsicBuilder for RemarkBuilder { 52 | fn pallet(&self) -> &str { 53 | "system" 54 | } 55 | 56 | fn extrinsic(&self) -> &str { 57 | "remark" 58 | } 59 | 60 | fn build(&self, nonce: u32) -> std::result::Result { 61 | let acc = Sr25519Keyring::Bob.pair(); 62 | let extrinsic: OpaqueExtrinsic = create_benchmark_extrinsic( 63 | self.client.as_ref(), 64 | acc, 65 | SystemCall::remark { remark: vec![] }.into(), 66 | nonce, 67 | ) 68 | .into(); 69 | 70 | Ok(extrinsic) 71 | } 72 | } 73 | 74 | /// Generates `Balances::TransferKeepAlive` extrinsics for the benchmarks. 75 | /// 76 | /// Note: Should only be used for benchmarking. 77 | pub struct TransferKeepAliveBuilder { 78 | client: Arc, 79 | dest: AccountId, 80 | value: Balance, 81 | } 82 | 83 | impl TransferKeepAliveBuilder { 84 | /// Creates a new [`Self`] from the given client. 85 | pub fn new(client: Arc, dest: AccountId, value: Balance) -> Self { 86 | Self { client, dest, value } 87 | } 88 | } 89 | 90 | impl frame_benchmarking_cli::ExtrinsicBuilder for TransferKeepAliveBuilder { 91 | fn pallet(&self) -> &str { 92 | "balances" 93 | } 94 | 95 | fn extrinsic(&self) -> &str { 96 | "transfer_keep_alive" 97 | } 98 | 99 | fn build(&self, nonce: u32) -> std::result::Result { 100 | let acc = Sr25519Keyring::Bob.pair(); 101 | let extrinsic: OpaqueExtrinsic = create_benchmark_extrinsic( 102 | self.client.as_ref(), 103 | acc, 104 | BalancesCall::transfer_keep_alive { 105 | dest: self.dest.clone().into(), 106 | value: self.value.into(), 107 | } 108 | .into(), 109 | nonce, 110 | ) 111 | .into(); 112 | 113 | Ok(extrinsic) 114 | } 115 | } 116 | 117 | /// Create a transaction using the given `call`. 118 | /// 119 | /// Note: Should only be used for benchmarking. 120 | pub fn create_benchmark_extrinsic( 121 | client: &FullClient, 122 | sender: sp_core::sr25519::Pair, 123 | call: runtime::RuntimeCall, 124 | nonce: u32, 125 | ) -> runtime::UncheckedExtrinsic { 126 | let genesis_hash = client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"); 127 | let best_hash = client.chain_info().best_hash; 128 | let best_block = client.chain_info().best_number; 129 | 130 | let period = runtime::BlockHashCount::get() 131 | .checked_next_power_of_two() 132 | .map(|c| c / 2) 133 | .unwrap_or(2) as u64; 134 | let extra: runtime::SignedExtra = ( 135 | frame_system::CheckSpecVersion::::new(), 136 | frame_system::CheckTxVersion::::new(), 137 | frame_system::CheckGenesis::::new(), 138 | frame_system::CheckEra::::from(sp_runtime::generic::Era::mortal( 139 | period, 140 | best_block.saturated_into(), 141 | )), 142 | frame_system::CheckNonce::::from(nonce), 143 | frame_system::CheckWeight::::new(), 144 | pallet_transaction_payment::ChargeTransactionPayment::::from(0), 145 | ); 146 | 147 | let raw_payload = runtime::SignedPayload::from_raw( 148 | call.clone(), 149 | extra.clone(), 150 | ( 151 | runtime::VERSION.spec_version, 152 | runtime::VERSION.transaction_version, 153 | genesis_hash, 154 | best_hash, 155 | (), 156 | (), 157 | (), 158 | ), 159 | ); 160 | let signature = raw_payload.using_encoded(|e| sender.sign(e)); 161 | 162 | runtime::UncheckedExtrinsic::new_signed( 163 | call.clone(), 164 | sp_runtime::AccountId32::from(sender.public()).into(), 165 | runtime::Signature::Sr25519(signature.clone()), 166 | extra.clone(), 167 | ) 168 | } 169 | 170 | /// Generates inherent data for the `benchmark overhead` command. 171 | /// 172 | /// Note: Should only be used for benchmarking. 173 | pub fn inherent_benchmark_data() -> Result { 174 | let mut inherent_data = InherentData::new(); 175 | let d = Duration::from_millis(0); 176 | let timestamp = sp_timestamp::InherentDataProvider::new(d.into()); 177 | 178 | futures::executor::block_on(timestamp.provide_inherent_data(&mut inherent_data)) 179 | .map_err(|e| format!("creating inherent data: {:?}", e))?; 180 | Ok(inherent_data) 181 | } 182 | -------------------------------------------------------------------------------- /node/src/chain_spec.rs: -------------------------------------------------------------------------------- 1 | use rmrk_substrate_runtime::{ 2 | AccountId, AuraConfig, BalancesConfig, GenesisConfig, GrandpaConfig, Signature, SudoConfig, 3 | SystemConfig, WASM_BINARY, 4 | }; 5 | use sc_service::ChainType; 6 | use sp_consensus_aura::sr25519::AuthorityId as AuraId; 7 | use sp_core::{sr25519, Pair, Public}; 8 | use sp_finality_grandpa::AuthorityId as GrandpaId; 9 | use sp_runtime::traits::{IdentifyAccount, Verify}; 10 | 11 | // The URL for the telemetry server. 12 | // const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; 13 | 14 | /// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type. 15 | pub type ChainSpec = sc_service::GenericChainSpec; 16 | 17 | /// Generate a crypto pair from seed. 18 | pub fn get_from_seed(seed: &str) -> ::Public { 19 | TPublic::Pair::from_string(&format!("//{}", seed), None) 20 | .expect("static values are valid; qed") 21 | .public() 22 | } 23 | 24 | type AccountPublic = ::Signer; 25 | 26 | /// Helper function to generate an account ID from seed 27 | pub fn get_account_id_from_seed(seed: &str) -> AccountId 28 | where 29 | AccountPublic: From<::Public>, 30 | { 31 | AccountPublic::from(get_from_seed::(seed)).into_account() 32 | } 33 | 34 | /// Generate an Aura authority key. 35 | pub fn authority_keys_from_seed(s: &str) -> (AuraId, GrandpaId) { 36 | (get_from_seed::(s), get_from_seed::(s)) 37 | } 38 | 39 | pub fn development_config() -> Result { 40 | let wasm_binary = WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?; 41 | 42 | Ok(ChainSpec::from_genesis( 43 | // Name 44 | "Development", 45 | // ID 46 | "dev", 47 | ChainType::Development, 48 | move || { 49 | testnet_genesis( 50 | wasm_binary, 51 | // Initial PoA authorities 52 | vec![authority_keys_from_seed("Alice")], 53 | // Sudo account 54 | get_account_id_from_seed::("Alice"), 55 | // Pre-funded accounts 56 | vec![ 57 | get_account_id_from_seed::("Alice"), 58 | get_account_id_from_seed::("Bob"), 59 | get_account_id_from_seed::("Eve"), 60 | get_account_id_from_seed::("Dave"), 61 | get_account_id_from_seed::("Alice//stash"), 62 | get_account_id_from_seed::("Bob//stash"), 63 | ], 64 | true, 65 | ) 66 | }, 67 | // Bootnodes 68 | vec![], 69 | // Telemetry 70 | None, 71 | // Protocol ID 72 | None, 73 | None, 74 | // Properties 75 | None, 76 | // Extensions 77 | None, 78 | )) 79 | } 80 | 81 | pub fn local_testnet_config() -> Result { 82 | let wasm_binary = WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?; 83 | 84 | Ok(ChainSpec::from_genesis( 85 | // Name 86 | "Local Testnet", 87 | // ID 88 | "local_testnet", 89 | ChainType::Local, 90 | move || { 91 | testnet_genesis( 92 | wasm_binary, 93 | // Initial PoA authorities 94 | vec![authority_keys_from_seed("Alice"), authority_keys_from_seed("Bob")], 95 | // Sudo account 96 | get_account_id_from_seed::("Alice"), 97 | // Pre-funded accounts 98 | vec![ 99 | get_account_id_from_seed::("Alice"), 100 | get_account_id_from_seed::("Bob"), 101 | get_account_id_from_seed::("Charlie"), 102 | get_account_id_from_seed::("Dave"), 103 | get_account_id_from_seed::("Eve"), 104 | get_account_id_from_seed::("Ferdie"), 105 | get_account_id_from_seed::("Alice//stash"), 106 | get_account_id_from_seed::("Bob//stash"), 107 | get_account_id_from_seed::("Charlie//stash"), 108 | get_account_id_from_seed::("Dave//stash"), 109 | get_account_id_from_seed::("Eve//stash"), 110 | get_account_id_from_seed::("Ferdie//stash"), 111 | ], 112 | true, 113 | ) 114 | }, 115 | // Bootnodes 116 | vec![], 117 | // Telemetry 118 | None, 119 | // Protocol ID 120 | None, 121 | // Properties 122 | None, 123 | None, 124 | // Extensions 125 | None, 126 | )) 127 | } 128 | 129 | /// Configure initial storage state for FRAME modules. 130 | fn testnet_genesis( 131 | wasm_binary: &[u8], 132 | initial_authorities: Vec<(AuraId, GrandpaId)>, 133 | root_key: AccountId, 134 | endowed_accounts: Vec, 135 | _enable_println: bool, 136 | ) -> GenesisConfig { 137 | GenesisConfig { 138 | system: SystemConfig { 139 | // Add Wasm runtime to storage. 140 | code: wasm_binary.to_vec(), 141 | }, 142 | balances: BalancesConfig { 143 | // Configure endowed accounts with initial balance of 1 << 60. 144 | balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(), 145 | }, 146 | aura: AuraConfig { 147 | authorities: initial_authorities.iter().map(|x| (x.0.clone())).collect(), 148 | }, 149 | grandpa: GrandpaConfig { 150 | authorities: initial_authorities.iter().map(|x| (x.1.clone(), 1)).collect(), 151 | }, 152 | sudo: SudoConfig { 153 | // Assign network admin rights. 154 | key: Some(root_key), 155 | }, 156 | transaction_payment: Default::default(), 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /node/src/cli.rs: -------------------------------------------------------------------------------- 1 | use sc_cli::RunCmd; 2 | 3 | #[derive(Debug, clap::Parser)] 4 | pub struct Cli { 5 | #[clap(subcommand)] 6 | pub subcommand: Option, 7 | 8 | #[clap(flatten)] 9 | pub run: RunCmd, 10 | } 11 | 12 | #[derive(Debug, clap::Subcommand)] 13 | pub enum Subcommand { 14 | /// Key management cli utilities 15 | #[clap(subcommand)] 16 | Key(sc_cli::KeySubcommand), 17 | 18 | /// Build a chain specification. 19 | BuildSpec(sc_cli::BuildSpecCmd), 20 | 21 | /// Validate blocks. 22 | CheckBlock(sc_cli::CheckBlockCmd), 23 | 24 | /// Export blocks. 25 | ExportBlocks(sc_cli::ExportBlocksCmd), 26 | 27 | /// Export the state of a given block into a chain spec. 28 | ExportState(sc_cli::ExportStateCmd), 29 | 30 | /// Import blocks. 31 | ImportBlocks(sc_cli::ImportBlocksCmd), 32 | 33 | /// Remove the whole chain. 34 | PurgeChain(sc_cli::PurgeChainCmd), 35 | 36 | /// Revert the chain to a previous state. 37 | Revert(sc_cli::RevertCmd), 38 | 39 | /// The custom benchmark subcommand benchmarking runtime pallets. 40 | #[clap(subcommand)] 41 | Benchmark(frame_benchmarking_cli::BenchmarkCmd), 42 | 43 | /// Try some command against runtime state. 44 | #[cfg(feature = "try-runtime")] 45 | TryRuntime(try_runtime_cli::TryRuntimeCmd), 46 | 47 | /// Try some command against runtime state. Note: `try-runtime` feature must be enabled. 48 | #[cfg(not(feature = "try-runtime"))] 49 | TryRuntime, 50 | 51 | /// Db meta columns information. 52 | ChainInfo(sc_cli::ChainInfoCmd), 53 | } 54 | -------------------------------------------------------------------------------- /node/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod chain_spec; 2 | pub mod rpc; 3 | pub mod service; 4 | -------------------------------------------------------------------------------- /node/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Substrate Node Template CLI library. 2 | #![warn(missing_docs)] 3 | 4 | mod chain_spec; 5 | #[macro_use] 6 | mod service; 7 | mod benchmarking; 8 | mod cli; 9 | mod command; 10 | mod rpc; 11 | 12 | fn main() -> sc_cli::Result<()> { 13 | command::run() 14 | } 15 | -------------------------------------------------------------------------------- /node/src/rpc.rs: -------------------------------------------------------------------------------- 1 | //! A collection of node-specific RPC methods. 2 | //! Substrate provides the `sc-rpc` crate, which defines the core RPC layer 3 | //! used by Substrate nodes. This file extends those RPC definitions with 4 | //! capabilities that are specific to this project's runtime configuration. 5 | 6 | #![warn(missing_docs)] 7 | 8 | use std::sync::Arc; 9 | 10 | use jsonrpsee::RpcModule; 11 | 12 | use rmrk_substrate_runtime::{ 13 | opaque::Block, AccountId, Balance, CollectionSymbolLimit, Index, KeyLimit, 14 | MaxCollectionsEquippablePerPart, MaxPropertiesPerTheme, PartsLimit, UniquesStringLimit, 15 | ValueLimit, 16 | }; 17 | use rmrk_traits::{ 18 | primitives::{CollectionId, NftId, PartId}, 19 | BaseInfo, CollectionInfo, NftInfo, PartType, PropertyInfo, ResourceInfo, Theme, ThemeProperty, 20 | }; 21 | pub use sc_rpc_api::DenyUnsafe; 22 | use sc_transaction_pool_api::TransactionPool; 23 | use sp_api::ProvideRuntimeApi; 24 | use sp_block_builder::BlockBuilder; 25 | use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; 26 | use sp_runtime::{BoundedVec, Permill}; 27 | 28 | /// Full client dependencies. 29 | pub struct FullDeps { 30 | /// The client instance to use. 31 | pub client: Arc, 32 | /// Transaction pool instance. 33 | pub pool: Arc

, 34 | /// Whether to deny unsafe calls 35 | pub deny_unsafe: DenyUnsafe, 36 | } 37 | 38 | /// Instantiate all full RPC extensions. 39 | pub fn create_full( 40 | deps: FullDeps, 41 | ) -> Result, Box> 42 | where 43 | C: ProvideRuntimeApi, 44 | C: HeaderBackend + HeaderMetadata + 'static, 45 | C: Send + Sync + 'static, 46 | C::Api: substrate_frame_rpc_system::AccountNonceApi, 47 | C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, 48 | C::Api: BlockBuilder, 49 | C::Api: pallet_rmrk_rpc_runtime_api::RmrkApi< 50 | Block, 51 | AccountId, 52 | CollectionInfo< 53 | BoundedVec, 54 | BoundedVec, 55 | AccountId, 56 | >, 57 | NftInfo, CollectionId, NftId>, 58 | ResourceInfo, BoundedVec>, 59 | PropertyInfo, BoundedVec>, 60 | BaseInfo>, 61 | PartType< 62 | BoundedVec, 63 | BoundedVec, 64 | >, 65 | Theme< 66 | BoundedVec, 67 | BoundedVec>, MaxPropertiesPerTheme>, 68 | >, 69 | >, 70 | P: TransactionPool + 'static, 71 | { 72 | use pallet_rmrk_rpc::{Rmrk, RmrkApiServer}; 73 | use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; 74 | use substrate_frame_rpc_system::{System, SystemApiServer}; 75 | 76 | let mut module = RpcModule::new(()); 77 | let FullDeps { client, pool, deny_unsafe } = deps; 78 | 79 | module.merge(System::new(client.clone(), pool.clone(), deny_unsafe).into_rpc())?; 80 | module.merge(TransactionPayment::new(client.clone()).into_rpc())?; 81 | module.merge(Rmrk::new(client.clone()).into_rpc())?; 82 | 83 | Ok(module) 84 | } 85 | -------------------------------------------------------------------------------- /pallets/rmrk-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pallet-rmrk-core" 3 | version = "0.0.1" 4 | description = "RMRK Core" 5 | authors = ["RMRK Team"] 6 | homepage = "" 7 | edition = "2021" 8 | license = "Apache-2.0" 9 | repository = "https://github.com/rmrk-team/rmrk-substrate" 10 | 11 | [package.metadata.docs.rs] 12 | targets = ["x86_64-unknown-linux-gnu"] 13 | 14 | [dependencies] 15 | serde = { version = "1.0.111", default-features = false, features = ["derive"] } 16 | sp-runtime = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 17 | sp-std = { default-features = false, version = "5.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 18 | codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ 19 | "derive", 20 | ] } 21 | scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } 22 | frame-support = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 23 | frame-system = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 24 | frame-benchmarking = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", optional = true, branch = "polkadot-v0.9.36" } 25 | 26 | pallet-uniques = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 27 | pallet-balances = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 28 | 29 | 30 | # Local Dependencies 31 | rmrk-traits = { default-features = false, version = "0.0.1", path = "../../traits" } 32 | 33 | [dev-dependencies] 34 | sp-core = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 35 | sp-io = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 36 | sp-runtime = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 37 | sp-std = { default-features = false, version = "5.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 38 | 39 | [features] 40 | default = ["std"] 41 | std = [ 42 | "pallet-uniques/std", 43 | "pallet-balances/std", 44 | "serde/std", 45 | "codec/std", 46 | "scale-info/std", 47 | "frame-support/std", 48 | "frame-system/std", 49 | "frame-benchmarking/std", 50 | ] 51 | 52 | runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] 53 | try-runtime = ["frame-support/try-runtime"] 54 | -------------------------------------------------------------------------------- /pallets/rmrk-core/README.md: -------------------------------------------------------------------------------- 1 | # RMRK Core Pallet 2 | 3 | Documentation [https://rmrk-team.github.io/rmrk-substrate/#/pallets/rmrk-core](https://rmrk-team.github.io/rmrk-substrate/#/pallets/rmrk-core) 4 | 5 | License: Apache-2.0 6 | -------------------------------------------------------------------------------- /pallets/rmrk-core/src/types.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021-2022 RMRK 2 | // This file is part of rmrk-core. 3 | // License: Apache 2.0 modified by RMRK, see LICENSE.md 4 | 5 | use frame_support::pallet_prelude::*; 6 | use sp_runtime::Permill; 7 | 8 | use scale_info::TypeInfo; 9 | #[cfg(feature = "std")] 10 | use serde::{Deserialize, Serialize}; 11 | 12 | #[derive(Encode, Decode, Eq, Copy, PartialEq, Clone, RuntimeDebug, TypeInfo)] 13 | #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] 14 | pub struct ClassInfo { 15 | /// Arbitrary data about a class, e.g. IPFS hash 16 | pub issuer: AccountId, 17 | pub metadata: BoundedString, 18 | pub max: u32, 19 | pub symbol: BoundedString, 20 | } 21 | 22 | #[derive(Encode, Decode, Eq, Copy, PartialEq, Clone, RuntimeDebug, TypeInfo)] 23 | pub struct InstanceInfo { 24 | /// The user account which receives the royalty 25 | pub recipient: AccountId, 26 | /// Royalty in per mille (1/1000) 27 | pub royalty: Permill, 28 | /// Arbitrary data about an instance, e.g. IPFS hash 29 | pub metadata: BoundedString, 30 | } 31 | -------------------------------------------------------------------------------- /pallets/rmrk-equip/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pallet-rmrk-equip" 3 | version = "0.0.1" 4 | description = "RMRK Equip" 5 | authors = ["RMRK Team"] 6 | homepage = "" 7 | edition = "2021" 8 | license = "Apache 2.0" 9 | repository = "https://github.com/rmrk-team/rmrk-substrate" 10 | 11 | [package.metadata.docs.rs] 12 | targets = ["x86_64-unknown-linux-gnu"] 13 | 14 | [dependencies] 15 | serde = { version = "1.0.111", default-features = false, features = ["derive"] } 16 | sp-runtime = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 17 | sp-std = { default-features = false, version = "5.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 18 | codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ 19 | "derive", 20 | ] } 21 | scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } 22 | frame-support = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 23 | frame-system = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 24 | frame-benchmarking = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", optional = true, branch = "polkadot-v0.9.36" } 25 | 26 | pallet-uniques = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 27 | pallet-balances = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 28 | 29 | # Local Dependencies 30 | pallet-rmrk-core = { default-features = false, version = "0.0.1", path = "../rmrk-core" } 31 | rmrk-traits = { default-features = false, version = "0.0.1", path = "../../traits" } 32 | 33 | 34 | [dev-dependencies] 35 | sp-core = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 36 | sp-io = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 37 | sp-runtime = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 38 | sp-std = { default-features = false, version = "5.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 39 | 40 | [features] 41 | default = ["std"] 42 | std = [ 43 | "pallet-uniques/std", 44 | "serde/std", 45 | "codec/std", 46 | "scale-info/std", 47 | "frame-support/std", 48 | "frame-system/std", 49 | "frame-benchmarking/std", 50 | "pallet-rmrk-core/std", 51 | "pallet-balances/std", 52 | ] 53 | 54 | runtime-benchmarks = [ 55 | "frame-benchmarking/runtime-benchmarks", 56 | "pallet-rmrk-core/runtime-benchmarks" 57 | ] 58 | try-runtime = ["frame-support/try-runtime"] 59 | -------------------------------------------------------------------------------- /pallets/rmrk-equip/README.md: -------------------------------------------------------------------------------- 1 | # Equip Pallet 2 | 3 | Documentation [https://rmrk-team.github.io/rmrk-substrate/#/pallets/rmrk-equip](https://rmrk-team.github.io/rmrk-substrate/#/pallets/rmrk-equip) 4 | 5 | License: Apache-2.0 -------------------------------------------------------------------------------- /pallets/rmrk-equip/src/weights.rs: -------------------------------------------------------------------------------- 1 | 2 | //! Autogenerated weights for `pallet_rmrk_equip` 3 | //! 4 | //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev 5 | //! DATE: 2022-12-26, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` 6 | //! HOSTNAME: `Sergejs-MacBook-Air.local`, CPU: `` 7 | //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 8 | 9 | // Executed Command: 10 | // ./target/release/rmrk-substrate 11 | // benchmark 12 | // pallet 13 | // --chain 14 | // dev 15 | // --execution=wasm 16 | // --wasm-execution=compiled 17 | // --pallet 18 | // pallet_rmrk_equip 19 | // --extrinsic=* 20 | // --steps 21 | // 50 22 | // --repeat 23 | // 20 24 | // --output 25 | // pallets/rmrk-equip/src/weights.rs 26 | 27 | #![cfg_attr(rustfmt, rustfmt_skip)] 28 | #![allow(unused_parens)] 29 | #![allow(unused_imports)] 30 | 31 | use frame_support::{traits::Get, weights::{Weight}}; 32 | use sp_std::marker::PhantomData; 33 | 34 | /// Weight functions needed for `pallet_rmrk_equip`. 35 | pub trait WeightInfo { 36 | fn change_base_issuer() -> Weight; 37 | fn equip() -> Weight; 38 | fn unequip() -> Weight; 39 | fn equippable() -> Weight; 40 | fn equippable_add() -> Weight; 41 | fn equippable_remove() -> Weight; 42 | fn theme_add() -> Weight; 43 | fn create_base() -> Weight; 44 | } 45 | 46 | /// Weight functions for `pallet_rmrk_equip`. 47 | pub struct SubstrateWeight(PhantomData); 48 | impl WeightInfo for SubstrateWeight { 49 | // Storage: RmrkEquip Bases (r:1 w:1) 50 | fn change_base_issuer() -> Weight { 51 | Weight::from_ref_time(16_000_000 as u64) 52 | .saturating_add(T::DbWeight::get().reads(1 as u64)) 53 | .saturating_add(T::DbWeight::get().writes(1 as u64)) 54 | } 55 | // Storage: RmrkCore Nfts (r:2 w:1) 56 | // Storage: RmrkCore Lock (r:2 w:0) 57 | // Storage: RmrkEquip Equippings (r:1 w:1) 58 | // Storage: Uniques Asset (r:2 w:0) 59 | // Storage: RmrkCore EquippableBases (r:1 w:0) 60 | // Storage: RmrkCore EquippableSlots (r:1 w:0) 61 | // Storage: RmrkEquip Parts (r:1 w:0) 62 | fn equip() -> Weight { 63 | Weight::from_ref_time(47_000_000 as u64) 64 | .saturating_add(T::DbWeight::get().reads(10 as u64)) 65 | .saturating_add(T::DbWeight::get().writes(2 as u64)) 66 | } 67 | // Storage: RmrkCore Lock (r:2 w:0) 68 | // Storage: RmrkEquip Equippings (r:1 w:1) 69 | // Storage: RmrkCore Nfts (r:1 w:1) 70 | // Storage: Uniques Asset (r:2 w:0) 71 | fn unequip() -> Weight { 72 | Weight::from_ref_time(35_000_000 as u64) 73 | .saturating_add(T::DbWeight::get().reads(6 as u64)) 74 | .saturating_add(T::DbWeight::get().writes(2 as u64)) 75 | } 76 | // Storage: RmrkEquip Bases (r:1 w:0) 77 | // Storage: RmrkEquip Parts (r:1 w:1) 78 | fn equippable() -> Weight { 79 | Weight::from_ref_time(16_000_000 as u64) 80 | .saturating_add(T::DbWeight::get().reads(2 as u64)) 81 | .saturating_add(T::DbWeight::get().writes(1 as u64)) 82 | } 83 | // Storage: RmrkEquip Bases (r:1 w:0) 84 | // Storage: RmrkEquip Parts (r:1 w:1) 85 | fn equippable_add() -> Weight { 86 | Weight::from_ref_time(16_000_000 as u64) 87 | .saturating_add(T::DbWeight::get().reads(2 as u64)) 88 | .saturating_add(T::DbWeight::get().writes(1 as u64)) 89 | } 90 | // Storage: RmrkEquip Bases (r:1 w:0) 91 | // Storage: RmrkEquip Parts (r:1 w:1) 92 | fn equippable_remove() -> Weight { 93 | Weight::from_ref_time(17_000_000 as u64) 94 | .saturating_add(T::DbWeight::get().reads(2 as u64)) 95 | .saturating_add(T::DbWeight::get().writes(1 as u64)) 96 | } 97 | // Storage: RmrkEquip Bases (r:1 w:0) 98 | // Storage: RmrkEquip Themes (r:1 w:2) 99 | fn theme_add() -> Weight { 100 | Weight::from_ref_time(16_000_000 as u64) 101 | .saturating_add(T::DbWeight::get().reads(2 as u64)) 102 | .saturating_add(T::DbWeight::get().writes(2 as u64)) 103 | } 104 | // Storage: RmrkEquip NextBaseId (r:1 w:1) 105 | // Storage: RmrkEquip Bases (r:0 w:1) 106 | fn create_base() -> Weight { 107 | Weight::from_ref_time(13_000_000 as u64) 108 | .saturating_add(T::DbWeight::get().reads(1 as u64)) 109 | .saturating_add(T::DbWeight::get().writes(2 as u64)) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /pallets/rmrk-market/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pallet-rmrk-market" 3 | version = "0.0.1" 4 | description = "RMRK Market" 5 | authors = ["RMRK Team"] 6 | homepage = "" 7 | edition = "2021" 8 | license = "Apache-2.0" 9 | repository = "https://github.com/rmrk-team/rmrk-substrate" 10 | 11 | [package.metadata.docs.rs] 12 | targets = ["x86_64-unknown-linux-gnu"] 13 | 14 | [dependencies] 15 | serde = { version = "1.0.111", default-features = false, features = ["derive"] } 16 | sp-runtime = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 17 | sp-std = { default-features = false, version = "5.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 18 | codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ 19 | "derive", 20 | ] } 21 | scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } 22 | frame-support = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 23 | frame-system = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 24 | frame-benchmarking = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", optional = true, branch = "polkadot-v0.9.36" } 25 | 26 | pallet-uniques = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 27 | pallet-balances = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 28 | 29 | 30 | # Local Dependencies 31 | pallet-rmrk-core = { default-features = false, version = "0.0.1", path = "../rmrk-core" } 32 | rmrk-traits = { default-features = false, version = "0.0.1", path = "../../traits" } 33 | 34 | [dev-dependencies] 35 | sp-core = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 36 | sp-io = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 37 | sp-runtime = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 38 | sp-std = { default-features = false, version = "5.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 39 | 40 | [features] 41 | default = ["std"] 42 | std = [ 43 | "pallet-uniques/std", 44 | "pallet-balances/std", 45 | "serde/std", 46 | "codec/std", 47 | "scale-info/std", 48 | "frame-support/std", 49 | "frame-system/std", 50 | "frame-benchmarking/std", 51 | ] 52 | 53 | runtime-benchmarks = [ 54 | "frame-benchmarking/runtime-benchmarks", 55 | "pallet-rmrk-core/runtime-benchmarks", 56 | ] 57 | try-runtime = ["frame-support/try-runtime"] 58 | -------------------------------------------------------------------------------- /pallets/rmrk-market/README.md: -------------------------------------------------------------------------------- 1 | # Market Pallet 2 | 3 | Documentations [https://rmrk-team.github.io/rmrk-substrate/#/pallets/rmrk-market](https://rmrk-team.github.io/rmrk-substrate/#/pallets/rmrk-market) 4 | 5 | License: Apache-2.0 -------------------------------------------------------------------------------- /pallets/rmrk-market/src/types.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021-2022 RMRK 2 | // This file is part of rmrk-market. 3 | // License: Apache 2.0 modified by RMRK, see LICENSE.md 4 | 5 | use frame_support::pallet_prelude::*; 6 | 7 | #[cfg(feature = "std")] 8 | use serde::{Deserialize, Serialize}; 9 | 10 | use scale_info::TypeInfo; 11 | 12 | #[derive(Encode, Decode, Eq, Copy, PartialEq, Clone, RuntimeDebug, TypeInfo, MaxEncodedLen)] 13 | #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] 14 | pub struct ListInfo { 15 | /// Owner who listed the NFT at the time 16 | pub(super) listed_by: AccountId, 17 | /// Listed amount 18 | pub(super) amount: Balance, 19 | /// After this block the listing can't be bought 20 | pub(super) expires: Option, 21 | } 22 | 23 | #[derive(Encode, Decode, Eq, Copy, PartialEq, Clone, RuntimeDebug, TypeInfo, MaxEncodedLen)] 24 | #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] 25 | pub struct Offer { 26 | /// User who made the offer 27 | pub(super) maker: AccountId, 28 | /// Offered amount 29 | pub(super) amount: Balance, 30 | /// After this block the offer can't be accepted 31 | pub(super) expires: Option, 32 | } 33 | -------------------------------------------------------------------------------- /pallets/rmrk-market/src/weights.rs: -------------------------------------------------------------------------------- 1 | 2 | //! Autogenerated weights for `pallet_rmrk_market` 3 | //! 4 | //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev 5 | //! DATE: 2022-11-14, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` 6 | //! HOSTNAME: `Sergejs-MacBook-Air.local`, CPU: `` 7 | //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 8 | 9 | // Executed Command: 10 | // ./target/release/rmrk-substrate 11 | // benchmark 12 | // pallet 13 | // --chain 14 | // dev 15 | // --execution=wasm 16 | // --wasm-execution=compiled 17 | // --pallet 18 | // pallet_rmrk_market 19 | // --extrinsic 20 | // * 21 | // --steps 22 | // 50 23 | // --repeat 24 | // 20 25 | // --output 26 | // pallets/rmrk-market/src/weights.rs 27 | 28 | #![cfg_attr(rustfmt, rustfmt_skip)] 29 | #![allow(unused_parens)] 30 | #![allow(unused_imports)] 31 | 32 | use frame_support::{traits::Get, weights::{Weight}}; 33 | use sp_std::marker::PhantomData; 34 | 35 | /// Weight functions needed for pallet_rmrk_market. 36 | pub trait WeightInfo { 37 | fn buy() -> Weight; 38 | fn list() -> Weight; 39 | fn unlist() -> Weight; 40 | fn make_offer() -> Weight; 41 | fn withdraw_offer() -> Weight; 42 | fn accept_offer() -> Weight; 43 | } 44 | 45 | /// Weight functions for `pallet_rmrk_core`. 46 | pub struct SubstrateWeight(PhantomData); 47 | impl WeightInfo for SubstrateWeight { 48 | // Storage: Uniques Asset (r:1 w:1) 49 | // Storage: RmrkMarket ListedNfts (r:1 w:1) 50 | // Storage: RmrkCore Lock (r:1 w:1) 51 | // Storage: System Account (r:1 w:1) 52 | // Storage: RmrkCore Nfts (r:1 w:1) 53 | // Storage: Uniques Class (r:1 w:0) 54 | // Storage: Uniques Account (r:0 w:2) 55 | // Storage: Uniques ItemPriceOf (r:0 w:1) 56 | fn buy() -> Weight { 57 | Weight::from_ref_time(63_000_000 as u64) 58 | .saturating_add(T::DbWeight::get().reads(6 as u64)) 59 | .saturating_add(T::DbWeight::get().writes(8 as u64)) 60 | } 61 | // Storage: Uniques Asset (r:1 w:0) 62 | // Storage: RmrkCore Nfts (r:1 w:0) 63 | // Storage: Uniques Class (r:1 w:0) 64 | // Storage: RmrkCore Lock (r:1 w:1) 65 | // Storage: RmrkMarket ListedNfts (r:0 w:1) 66 | fn list() -> Weight { 67 | Weight::from_ref_time(26_000_000 as u64) 68 | .saturating_add(T::DbWeight::get().reads(4 as u64)) 69 | .saturating_add(T::DbWeight::get().writes(2 as u64)) 70 | } 71 | // Storage: RmrkMarket ListedNfts (r:1 w:1) 72 | // Storage: Uniques Asset (r:1 w:0) 73 | // Storage: RmrkCore Lock (r:1 w:1) 74 | fn unlist() -> Weight { 75 | Weight::from_ref_time(23_000_000 as u64) 76 | .saturating_add(T::DbWeight::get().reads(3 as u64)) 77 | .saturating_add(T::DbWeight::get().writes(2 as u64)) 78 | } 79 | // Storage: Uniques Asset (r:1 w:0) 80 | // Storage: RmrkMarket Offers (r:1 w:1) 81 | fn make_offer() -> Weight { 82 | Weight::from_ref_time(28_000_000 as u64) 83 | .saturating_add(T::DbWeight::get().reads(2 as u64)) 84 | .saturating_add(T::DbWeight::get().writes(1 as u64)) 85 | } 86 | // Storage: RmrkMarket Offers (r:1 w:1) 87 | // Storage: Uniques Asset (r:1 w:0) 88 | fn withdraw_offer() -> Weight { 89 | Weight::from_ref_time(28_000_000 as u64) 90 | .saturating_add(T::DbWeight::get().reads(2 as u64)) 91 | .saturating_add(T::DbWeight::get().writes(1 as u64)) 92 | } 93 | // Storage: Uniques Asset (r:1 w:1) 94 | // Storage: RmrkMarket Offers (r:1 w:1) 95 | // Storage: System Account (r:1 w:1) 96 | // Storage: RmrkCore Lock (r:1 w:1) 97 | // Storage: RmrkCore Nfts (r:1 w:1) 98 | // Storage: Uniques Class (r:1 w:0) 99 | // Storage: Uniques Account (r:0 w:2) 100 | // Storage: Uniques ItemPriceOf (r:0 w:1) 101 | fn accept_offer() -> Weight { 102 | Weight::from_ref_time(74_000_000 as u64) 103 | .saturating_add(T::DbWeight::get().reads(6 as u64)) 104 | .saturating_add(T::DbWeight::get().writes(8 as u64)) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /pallets/template/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pallet-template" 3 | version = "0.0.1" 4 | description = "FRAME pallet template for defining custom runtime logic." 5 | authors = ["Substrate DevHub "] 6 | homepage = "https://substrate.io/" 7 | edition = "2021" 8 | license = "Unlicense" 9 | publish = false 10 | repository = "https://github.com/rmrk-team/rmrk-substrate" 11 | 12 | [package.metadata.docs.rs] 13 | targets = ["x86_64-unknown-linux-gnu"] 14 | 15 | [dependencies] 16 | codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ 17 | "derive", 18 | ] } 19 | scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } 20 | frame-support = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 21 | frame-system = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 22 | frame-benchmarking = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", optional = true, branch = "polkadot-v0.9.36" } 23 | 24 | [dev-dependencies] 25 | sp-core = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 26 | sp-io = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 27 | sp-runtime = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 28 | 29 | [features] 30 | default = ["std"] 31 | std = [ 32 | "codec/std", 33 | "scale-info/std", 34 | "frame-support/std", 35 | "frame-system/std", 36 | "frame-benchmarking/std", 37 | ] 38 | 39 | runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] 40 | try-runtime = ["frame-support/try-runtime"] 41 | -------------------------------------------------------------------------------- /pallets/template/README.md: -------------------------------------------------------------------------------- 1 | License: Unlicense 2 | -------------------------------------------------------------------------------- /pallets/template/src/benchmarking.rs: -------------------------------------------------------------------------------- 1 | //! Benchmarking setup for pallet-template 2 | 3 | use super::*; 4 | 5 | #[allow(unused)] 6 | use crate::Pallet as Template; 7 | use frame_benchmarking::{benchmarks, whitelisted_caller}; 8 | use frame_system::RawOrigin; 9 | 10 | benchmarks! { 11 | do_something { 12 | let s in 0 .. 100; 13 | let caller: T::AccountId = whitelisted_caller(); 14 | }: _(RawOrigin::Signed(caller), s) 15 | verify { 16 | assert_eq!(Something::::get(), Some(s)); 17 | } 18 | 19 | impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test); 20 | } 21 | -------------------------------------------------------------------------------- /pallets/template/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | /// Edit this file to define custom logic or remove it if it is not needed. 4 | /// Learn more about FRAME and the core library of Substrate FRAME pallets: 5 | /// 6 | pub use pallet::*; 7 | 8 | #[cfg(test)] 9 | mod mock; 10 | 11 | #[cfg(test)] 12 | mod tests; 13 | 14 | #[cfg(feature = "runtime-benchmarks")] 15 | mod benchmarking; 16 | 17 | #[frame_support::pallet] 18 | pub mod pallet { 19 | use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; 20 | use frame_system::pallet_prelude::*; 21 | 22 | /// Configure the pallet by specifying the parameters and types on which it depends. 23 | #[pallet::config] 24 | pub trait Config: frame_system::Config { 25 | /// Because this pallet emits events, it depends on the runtime's definition of an event. 26 | type RuntimeEvent: From> + IsType<::RuntimeEvent>; 27 | } 28 | 29 | #[pallet::pallet] 30 | #[pallet::generate_store(pub(super) trait Store)] 31 | pub struct Pallet(_); 32 | 33 | // The pallet's runtime storage items. 34 | // https://docs.substrate.io/v3/runtime/storage 35 | #[pallet::storage] 36 | #[pallet::getter(fn something)] 37 | // Learn more about declaring storage items: 38 | // https://docs.substrate.io/v3/runtime/storage#declaring-storage-items 39 | pub type Something = StorageValue<_, u32>; 40 | 41 | // Pallets use events to inform users when important changes are made. 42 | // https://docs.substrate.io/v3/runtime/events-and-errors 43 | #[pallet::event] 44 | #[pallet::generate_deposit(pub(super) fn deposit_event)] 45 | pub enum Event { 46 | /// Event documentation should end with an array that provides descriptive names for event 47 | /// parameters. [something, who] 48 | SomethingStored(u32, T::AccountId), 49 | } 50 | 51 | // Errors inform users that something went wrong. 52 | #[pallet::error] 53 | pub enum Error { 54 | /// Error names should be descriptive. 55 | NoneValue, 56 | /// Errors should have helpful documentation associated with them. 57 | StorageOverflow, 58 | } 59 | 60 | // Dispatchable functions allows users to interact with the pallet and invoke state changes. 61 | // These functions materialize as "extrinsics", which are often compared to transactions. 62 | // Dispatchable functions must be annotated with a weight and must return a DispatchResult. 63 | #[pallet::call] 64 | impl Pallet { 65 | /// An example dispatchable that takes a singles value as a parameter, writes the value to 66 | /// storage and emits an event. This function must be dispatched by a signed extrinsic. 67 | #[pallet::call_index(0)] 68 | #[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())] 69 | pub fn do_something(origin: OriginFor, something: u32) -> DispatchResult { 70 | // Check that the extrinsic was signed and get the signer. 71 | // This function will return an error if the extrinsic is not signed. 72 | // https://docs.substrate.io/v3/runtime/origins 73 | let who = ensure_signed(origin)?; 74 | 75 | // Update storage. 76 | >::put(something); 77 | 78 | // Emit an event. 79 | Self::deposit_event(Event::SomethingStored(something, who)); 80 | // Return a successful DispatchResultWithPostInfo 81 | Ok(()) 82 | } 83 | 84 | /// An example dispatchable that may throw a custom error. 85 | #[pallet::call_index(1)] 86 | #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1).ref_time())] 87 | pub fn cause_error(origin: OriginFor) -> DispatchResult { 88 | let _who = ensure_signed(origin)?; 89 | 90 | // Read a value from storage. 91 | match >::get() { 92 | // Return an error if the value has not been set. 93 | None => Err(Error::::NoneValue.into()), 94 | Some(old) => { 95 | // Increment the value read from storage; will error in the event of overflow. 96 | let new = old.checked_add(1).ok_or(Error::::StorageOverflow)?; 97 | // Update the value in storage with the incremented result. 98 | >::put(new); 99 | Ok(()) 100 | }, 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /pallets/template/src/mock.rs: -------------------------------------------------------------------------------- 1 | use crate as pallet_template; 2 | use frame_support::{parameter_types, traits::ConstU32}; 3 | use frame_system as system; 4 | use sp_core::H256; 5 | use sp_runtime::{ 6 | testing::Header, 7 | traits::{BlakeTwo256, IdentityLookup}, 8 | }; 9 | 10 | type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; 11 | type Block = frame_system::mocking::MockBlock; 12 | 13 | // Configure a mock runtime to test the pallet. 14 | frame_support::construct_runtime!( 15 | pub enum Test where 16 | Block = Block, 17 | NodeBlock = Block, 18 | UncheckedExtrinsic = UncheckedExtrinsic, 19 | { 20 | System: frame_system::{Pallet, Call, Config, Storage, Event}, 21 | TemplateModule: pallet_template::{Pallet, Call, Storage, Event}, 22 | } 23 | ); 24 | 25 | parameter_types! { 26 | pub const BlockHashCount: u64 = 250; 27 | pub const SS58Prefix: u8 = 42; 28 | } 29 | 30 | impl system::Config for Test { 31 | type BaseCallFilter = frame_support::traits::Everything; 32 | type BlockWeights = (); 33 | type BlockLength = (); 34 | type DbWeight = (); 35 | type RuntimeOrigin = RuntimeOrigin; 36 | type RuntimeCall = RuntimeCall; 37 | type Index = u64; 38 | type BlockNumber = u64; 39 | type Hash = H256; 40 | type Hashing = BlakeTwo256; 41 | type AccountId = u64; 42 | type Lookup = IdentityLookup; 43 | type Header = Header; 44 | type RuntimeEvent = RuntimeEvent; 45 | type BlockHashCount = BlockHashCount; 46 | type Version = (); 47 | type PalletInfo = PalletInfo; 48 | type AccountData = (); 49 | type OnNewAccount = (); 50 | type OnKilledAccount = (); 51 | type SystemWeightInfo = (); 52 | type SS58Prefix = SS58Prefix; 53 | type OnSetCode = (); 54 | type MaxConsumers = ConstU32<2>; 55 | } 56 | 57 | impl pallet_template::Config for Test { 58 | type RuntimeEvent = RuntimeEvent; 59 | } 60 | 61 | // Build genesis storage according to the mock runtime. 62 | pub fn new_test_ext() -> sp_io::TestExternalities { 63 | system::GenesisConfig::default().build_storage::().unwrap().into() 64 | } 65 | -------------------------------------------------------------------------------- /pallets/template/src/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::{mock::*, Error}; 2 | use frame_support::{assert_noop, assert_ok}; 3 | 4 | #[test] 5 | fn it_works_for_default_value() { 6 | new_test_ext().execute_with(|| { 7 | // Dispatch a signed extrinsic. 8 | assert_ok!(TemplateModule::do_something(RuntimeOrigin::signed(1), 42)); 9 | // Read pallet storage and assert an expected result. 10 | assert_eq!(TemplateModule::something(), Some(42)); 11 | }); 12 | } 13 | 14 | #[test] 15 | fn correct_error_for_none_value() { 16 | new_test_ext().execute_with(|| { 17 | // Ensure the expected error is thrown when no value is present. 18 | assert_noop!( 19 | TemplateModule::cause_error(RuntimeOrigin::signed(1)), 20 | Error::::NoneValue 21 | ); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /rpc-runtime-api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pallet-rmrk-rpc-runtime-api" 3 | version = "0.0.1" 4 | license = "" 5 | edition = "2021" 6 | 7 | [dependencies] 8 | serde = { version = "1.0.132", default-features = false, features = ["derive"] } 9 | codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } 10 | scale-info = { version = "2.0", default-features = false } 11 | sp-core = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.36" } 12 | sp-std = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.36" } 13 | sp-api = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.36" } 14 | sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.36" } 15 | 16 | rmrk-traits = { version = "0.0.1", path = "../traits", default-features = false } 17 | 18 | [features] 19 | default = ["std"] 20 | std = [ 21 | "codec/std", 22 | "scale-info/std", 23 | "serde/std", 24 | "sp-core/std", 25 | "sp-std/std", 26 | "sp-api/std", 27 | "sp-runtime/std", 28 | "rmrk-traits/std", 29 | ] 30 | -------------------------------------------------------------------------------- /rpc-runtime-api/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | use rmrk_traits::{ 4 | primitives::{BaseId, CollectionId, NftId, ResourceId}, 5 | NftChild, 6 | }; 7 | use sp_api::{Decode, Encode}; 8 | use sp_runtime::DispatchError; 9 | use sp_std::vec::Vec; 10 | 11 | pub type Result = core::result::Result; 12 | 13 | pub type RpcString = Vec; 14 | 15 | pub type PropertyKey = RpcString; 16 | 17 | pub type ThemeName = RpcString; 18 | 19 | sp_api::decl_runtime_apis! { 20 | pub trait RmrkApi< 21 | AccountId, 22 | CollectionInfo, 23 | NftInfo, 24 | ResourceInfo, 25 | PropertyInfo, 26 | BaseInfo, 27 | PartType, 28 | Theme 29 | > 30 | where 31 | AccountId: Encode, 32 | CollectionInfo: Decode, 33 | NftInfo: Decode, 34 | ResourceInfo: Decode, 35 | PropertyInfo: Decode, 36 | BaseInfo: Decode, 37 | PartType: Decode, 38 | Theme: Decode, 39 | { 40 | /// Get collection by id 41 | fn collection_by_id(id: CollectionId) -> Result>; 42 | 43 | /// Get NFT by collection id and NFT id 44 | fn nft_by_id(collection_id: CollectionId, nft_id: NftId) -> Result>; 45 | 46 | /// Get tokens owned by an account in a collection 47 | fn account_tokens(account_id: AccountId, collection_id: CollectionId) -> Result>; 48 | 49 | /// Get NFT children 50 | fn nft_children(collection_id: CollectionId, nft_id: NftId) -> Result>>; 51 | 52 | /// Get all of the NFTs of the provided account. Supports pagination by 53 | /// specifying an optional `start_index` and `count`. 54 | /// 55 | /// The `start_index` parameter defines the number of collections after 56 | /// which we start reading the NFTs. The collections in which the user 57 | /// doesn't own any NFTs are not counted. 58 | /// 59 | /// The `count` parameter specifies the number of collections to read from. 60 | fn nfts_owned_by(account_id: AccountId, start_index: Option, count: Option) -> Result>; 61 | 62 | /// Get all of the properties of the NFTs owned by the specified 63 | /// account. Supports pagination by specifying an optional `start_index` 64 | /// and `count`. 65 | /// 66 | /// The `start_index` parameter defines the number of collections after 67 | /// which we start reading the NFT properties. The collections in which 68 | /// the user doesn't own any NFTs are not counted. 69 | /// 70 | /// The `count` parameter specifies the number of collections to read from. 71 | fn properties_of_nfts_owned_by( 72 | account_id: AccountId, 73 | start_index: Option, 74 | count: Option 75 | ) -> Result)>>; 76 | 77 | /// Get collection properties 78 | fn collection_properties(collection_id: CollectionId, filter_keys: Option>) -> Result>; 79 | 80 | /// Get NFT properties 81 | fn nft_properties(collection_id: CollectionId, nft_id: NftId, filter_keys: Option>) -> Result>; 82 | 83 | /// Get NFT resources 84 | fn nft_resources(collection_id: CollectionId, nft_id: NftId) -> Result>; 85 | 86 | /// Get NFT resource priority 87 | fn nft_resource_priority(collection_id: CollectionId, nft_id: NftId, resource_id: ResourceId) -> Result>; 88 | 89 | /// Get base info 90 | fn base(base_id: BaseId) -> Result>; 91 | 92 | /// Get all Base's parts 93 | fn base_parts(base_id: BaseId) -> Result>; 94 | 95 | /// Get Base's theme names 96 | fn theme_names(base_id: BaseId) -> Result>; 97 | 98 | /// Get Theme info -- name, properties, and inherit flag 99 | fn theme(base_id: BaseId, theme_name: ThemeName, filter_keys: Option>) -> Result>; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /rpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pallet-rmrk-rpc" 3 | version = "0.0.1" 4 | license = "" 5 | edition = "2021" 6 | 7 | [dependencies] 8 | codec = { package = "parity-scale-codec", version = "3.0" } 9 | scale-info = { version = "2.0" } 10 | jsonrpsee = { version = "0.16.2", features = ["server"] } 11 | 12 | # primitives 13 | sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } 14 | sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } 15 | sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } 16 | 17 | # client dependencies 18 | sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } 19 | sc-rpc = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } 20 | 21 | rmrk-traits = { version = "0.0.1", default-features = false, path = "../traits" } 22 | pallet-rmrk-rpc-runtime-api = { path = "../rpc-runtime-api" } 23 | -------------------------------------------------------------------------------- /runtime/build.rs: -------------------------------------------------------------------------------- 1 | use substrate_wasm_builder::WasmBuilder; 2 | 3 | fn main() { 4 | WasmBuilder::new() 5 | .with_current_project() 6 | .export_heap_base() 7 | .import_memory() 8 | .build() 9 | } 10 | -------------------------------------------------------------------------------- /runtime/src/constants.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Substrate. 2 | 3 | // Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | 18 | //! A set of constant values used in substrate runtime. 19 | /// Type used for expressing timestamp. 20 | 21 | /// Money matters. 22 | pub mod currency { 23 | pub use crate::Balance; 24 | 25 | pub const MILLICENTS: Balance = 1_000_000_000; 26 | pub const CENTS: Balance = 1_000 * MILLICENTS; // assume this is worth about a cent. 27 | pub const DOLLARS: Balance = 100 * CENTS; 28 | pub const UNITS: Balance = 1_000_000_000_000; 29 | 30 | pub const fn deposit(items: u32, bytes: u32) -> Balance { 31 | items as Balance * 15 * CENTS + (bytes as Balance) * 6 * CENTS 32 | } 33 | } 34 | 35 | /// Time. 36 | pub mod time { 37 | use crate::{BlockNumber, Moment}; 38 | 39 | /// Since BABE is probabilistic this is the average expected block time that 40 | /// we are targeting. Blocks will be produced at a minimum duration defined 41 | /// by `SLOT_DURATION`, but some slots will not be allocated to any 42 | /// authority and hence no block will be produced. We expect to have this 43 | /// block time on average following the defined slot duration and the value 44 | /// of `c` configured for BABE (where `1 - c` represents the probability of 45 | /// a slot being empty). 46 | /// This value is only used indirectly to define the unit constants below 47 | /// that are expressed in blocks. The rest of the code should use 48 | /// `SLOT_DURATION` instead (like the Timestamp pallet for calculating the 49 | /// minimum period). 50 | /// 51 | /// If using BABE with secondary slots (default) then all of the slots will 52 | /// always be assigned, in which case `MILLISECS_PER_BLOCK` and 53 | /// `SLOT_DURATION` should have the same value. 54 | /// 55 | /// 56 | pub const MILLISECS_PER_BLOCK: Moment = 3000; 57 | pub const SECS_PER_BLOCK: Moment = MILLISECS_PER_BLOCK / 1000; 58 | 59 | // NOTE: Currently it is not possible to change the slot duration after the chain has started. 60 | // Attempting to do so will brick block production. 61 | pub const SLOT_DURATION: Moment = MILLISECS_PER_BLOCK; 62 | 63 | // 1 in 4 blocks (on average, not counting collisions) will be primary BABE blocks. 64 | pub const PRIMARY_PROBABILITY: (u64, u64) = (1, 4); 65 | 66 | // NOTE: Currently it is not possible to change the epoch duration after the chain has started. 67 | // Attempting to do so will brick block production. 68 | pub const EPOCH_DURATION_IN_BLOCKS: BlockNumber = 10 * MINUTES; 69 | pub const EPOCH_DURATION_IN_SLOTS: u64 = { 70 | const SLOT_FILL_RATE: f64 = MILLISECS_PER_BLOCK as f64 / SLOT_DURATION as f64; 71 | 72 | (EPOCH_DURATION_IN_BLOCKS as f64 * SLOT_FILL_RATE) as u64 73 | }; 74 | 75 | // These time units are defined in number of blocks. 76 | pub const MINUTES: BlockNumber = 60 / (SECS_PER_BLOCK as BlockNumber); 77 | pub const HOURS: BlockNumber = MINUTES * 60; 78 | pub const DAYS: BlockNumber = HOURS * 24; 79 | } 80 | -------------------------------------------------------------------------------- /rust-setup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | --- 4 | 5 | This page will guide you through the steps needed to prepare a computer for development with the 6 | Substrate Node Template. Since Substrate is built with 7 | [the Rust programming language](https://www.rust-lang.org/), the first thing you will need to do is 8 | prepare the computer for Rust development - these steps will vary based on the computer's operating 9 | system. Once Rust is configured, you will use its toolchains to interact with Rust projects; the 10 | commands for Rust's toolchains will be the same for all supported, Unix-based operating systems. 11 | 12 | ## Unix-Based Operating Systems 13 | 14 | Substrate development is easiest on Unix-based operating systems like macOS or Linux. The examples 15 | in the Substrate [Tutorials](https://docs.substrate.io/tutorials/v3) and 16 | [How-to Guides](https://docs.substrate.io/how-to-guides/v3) use Unix-style terminals to demonstrate 17 | how to interact with Substrate from the command line. 18 | 19 | ### macOS 20 | 21 | Open the Terminal application and execute the following commands: 22 | 23 | ```bash 24 | # Install Homebrew if necessary https://brew.sh/ 25 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" 26 | 27 | # Make sure Homebrew is up-to-date, install openssl and cmake 28 | brew update 29 | brew install openssl cmake 30 | ``` 31 | 32 | ### Ubuntu/Debian 33 | 34 | Use a terminal shell to execute the following commands: 35 | 36 | ```bash 37 | sudo apt update 38 | # May prompt for location information 39 | sudo apt install -y cmake pkg-config libssl-dev git build-essential clang libclang-dev curl 40 | ``` 41 | 42 | ### Arch Linux 43 | 44 | Run these commands from a terminal: 45 | 46 | ```bash 47 | pacman -Syu --needed --noconfirm cmake gcc openssl-1.0 pkgconf git clang 48 | export OPENSSL_LIB_DIR="/usr/lib/openssl-1.0" 49 | export OPENSSL_INCLUDE_DIR="/usr/include/openssl-1.0" 50 | ``` 51 | 52 | ### Fedora/RHEL/CentOS 53 | 54 | Use a terminal to run the following commands: 55 | 56 | ```bash 57 | # Update 58 | sudo dnf update 59 | # Install packages 60 | sudo dnf install cmake pkgconfig rocksdb rocksdb-devel llvm git libcurl libcurl-devel curl-devel clang 61 | ``` 62 | 63 | ## Rust Developer Environment 64 | 65 | This project uses [`rustup`](https://rustup.rs/) to help manage the Rust toolchain. First install 66 | and configure `rustup`: 67 | 68 | ```bash 69 | # Install 70 | curl https://sh.rustup.rs -sSf | sh 71 | # Configure 72 | source ~/.cargo/env 73 | ``` 74 | 75 | Finally, configure the Rust toolchain: 76 | 77 | ```bash 78 | rustup default stable 79 | rustup update nightly 80 | rustup update stable 81 | rustup target add wasm32-unknown-unknown --toolchain nightly 82 | ``` 83 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2022-10-25" 3 | components = [ "rustfmt", "rustc", "rust-std", "cargo", "clippy", "llvm-tools-preview"] 4 | targets = [ "wasm32-unknown-unknown" ] 5 | profile = "minimal" -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Basic 2 | hard_tabs = true 3 | max_width = 100 4 | use_small_heuristics = "Max" 5 | # Imports 6 | imports_granularity = "Crate" 7 | reorder_imports = true 8 | # Consistency 9 | newline_style = "Unix" 10 | # Format comments 11 | comment_width = 100 12 | wrap_comments = true 13 | # Misc 14 | chain_width = 80 15 | spaces_around_ranges = false 16 | binop_separator = "Back" 17 | reorder_impl_items = false 18 | match_arm_leading_pipes = "Preserve" 19 | match_arm_blocks = false 20 | match_block_trailing_comma = true 21 | trailing_comma = "Vertical" 22 | trailing_semicolon = false 23 | use_field_init_shorthand = true 24 | -------------------------------------------------------------------------------- /scripts/docker_run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # This script is meant to be run on Unix/Linux based systems 3 | set -e 4 | 5 | echo "*** Start Substrate node template ***" 6 | 7 | cd $(dirname ${BASH_SOURCE[0]})/.. 8 | 9 | docker-compose down --remove-orphans 10 | docker-compose run --rm --service-ports dev $@ 11 | -------------------------------------------------------------------------------- /scripts/init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # This script is meant to be run on Unix/Linux based systems 3 | set -e 4 | 5 | echo "*** Initializing WASM build environment" 6 | 7 | if [ -z $CI_PROJECT_NAME ] ; then 8 | rustup update nightly 9 | rustup update stable 10 | fi 11 | 12 | rustup target add wasm32-unknown-unknown --toolchain nightly 13 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | mozillaOverlay = 3 | import (builtins.fetchGit { 4 | url = "https://github.com/mozilla/nixpkgs-mozilla.git"; 5 | rev = "57c8084c7ef41366993909c20491e359bbb90f54"; 6 | }); 7 | pinned = builtins.fetchGit { 8 | # Descriptive name to make the store path easier to identify 9 | url = "https://github.com/nixos/nixpkgs/"; 10 | # Commit hash for nixos-unstable as of 2020-04-26 11 | # `git ls-remote https://github.com/nixos/nixpkgs nixos-unstable` 12 | ref = "refs/heads/nixos-unstable"; 13 | rev = "1fe6ed37fd9beb92afe90671c0c2a662a03463dd"; 14 | }; 15 | nixpkgs = import pinned { overlays = [ mozillaOverlay ]; }; 16 | toolchain = with nixpkgs; (rustChannelOf { date = "2022-09-08"; channel = "nightly"; }); 17 | rust-wasm = toolchain.rust.override { 18 | targets = [ "wasm32-unknown-unknown" ]; 19 | }; 20 | in 21 | with nixpkgs; pkgs.mkShell { 22 | buildInputs = [ 23 | clang 24 | pkg-config 25 | rust-wasm 26 | ] ++ stdenv.lib.optionals stdenv.isDarwin [ 27 | darwin.apple_sdk.frameworks.Security 28 | ]; 29 | 30 | LIBCLANG_PATH = "${llvmPackages.libclang}/lib"; 31 | PROTOC = "${protobuf}/bin/protoc"; 32 | RUST_SRC_PATH = "${toolchain.rust-src}/lib/rustlib/src/rust/library/"; 33 | ROCKSDB_LIB_DIR = "${rocksdb}/lib"; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rmrk-tests", 3 | "version": "1.0.0", 4 | "description": "Unique Chain RMRK Tests", 5 | "main": "", 6 | "devDependencies": { 7 | "@polkadot/dev": "0.66.36", 8 | "@polkadot/ts": "0.4.22", 9 | "@polkadot/typegen": "8.7.2-13", 10 | "@types/chai": "^4.3.1", 11 | "@types/chai-as-promised": "^7.1.5", 12 | "@types/mocha": "^9.1.1", 13 | "@types/node": "^17.0.35", 14 | "@typescript-eslint/eslint-plugin": "^5.26.0", 15 | "@typescript-eslint/parser": "^5.26.0", 16 | "chai": "^4.3.6", 17 | "eslint": "^8.16.0", 18 | "mocha": "^10.0.0", 19 | "ts-node": "^10.8.0", 20 | "typescript": "^4.7.2" 21 | }, 22 | "mocha": { 23 | "timeout": 9999999, 24 | "require": "ts-node/register" 25 | }, 26 | "scripts": { 27 | "lint": "eslint --ext .ts,.js src/", 28 | "fix": "eslint --ext .ts,.js src/ --fix", 29 | "test": "mocha --timeout 9999999 -r ts-node/register './src/*.test.ts'", 30 | "testAddTheme": "mocha --timeout 9999999 -r ts-node/register './src/addTheme.test.ts'", 31 | "testCreateBase": "mocha --timeout 9999999 -r ts-node/register './src/createBase.test.ts'", 32 | "testCreateCollection": "mocha --timeout 9999999 -r ts-node/register './src/createCollection.test.ts'", 33 | "testDeleteCollection": "mocha --timeout 9999999 -r ts-node/register './src/deleteCollection.test.ts'", 34 | "testChangeCollectionIssuer": "mocha --timeout 9999999 -r ts-node/register './src/changeCollectionIssuer.test.ts'", 35 | "testLockCollection": "mocha --timeout 9999999 -r ts-node/register './src/lockCollection.test.ts'", 36 | "testMintNft": "mocha --timeout 9999999 -r ts-node/register './src/mintNft.test.ts'", 37 | "testBurnNft": "mocha --timeout 9999999 -r ts-node/register './src/burnNft.test.ts'", 38 | "testGetOwnedNftsInCollection": "mocha --timeout 9999999 -r ts-node/register './src/getOwnedNftsInCollection.test.ts'", 39 | "testGetOwnedNfts": "mocha --timeout 9999999 -r ts-node/register './src/getOwnedNfts.test.ts'", 40 | "testGetPropertiesOfOwnedNfts": "mocha --timeout 9999999 -r ts-node/register './src/getPropertiesOfOwnedNfts.test.ts'", 41 | "testSetNftProperty": "mocha --timeout 9999999 -r ts-node/register './src/setNftProperty.test.ts'", 42 | "testSetCollectionProperty": "mocha --timeout 9999999 -r ts-node/register './src/setCollectionProperty.test.ts'", 43 | "testAddResource": "mocha --timeout 9999999 -r ts-node/register './src/addResource.test.ts'", 44 | "testReplaceResource": "mocha --timeout 9999999 -r ts-node/register './src/replaceResource.test.ts'", 45 | "testRemoveResource": "mocha --timeout 9999999 -r ts-node/register './src/removeResource.test.ts'", 46 | "testSetResourcePriorities": "mocha --timeout 9999999 -r ts-node/register './src/setResourcePriorities.test.ts'", 47 | "testSetEquippableList": "mocha --timeout 9999999 -r ts-node/register './src/setEquippableList.test.ts'", 48 | "testSendNft": "mocha --timeout 9999999 -r ts-node/register './src/sendNft.test.ts'", 49 | "testAcceptNft": "mocha --timeout 9999999 -r ts-node/register './src/acceptNft.test.ts'", 50 | "testRejectNft": "mocha --timeout 9999999 -r ts-node/register './src/rejectNft.test.ts'", 51 | "testEquipNft": "mocha --timeout 9999999 -r ts-node/register './src/equipNft.test.ts'", 52 | "polkadot-types-fetch-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/interfaces/metadata.json", 53 | "polkadot-types-from-defs": "ts-node ./node_modules/.bin/polkadot-types-from-defs --endpoint src/interfaces/metadata.json --input src/interfaces/ --package .", 54 | "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint src/interfaces/metadata.json --output src/interfaces/ --package .", 55 | "polkadot-types": "yarn polkadot-types-fetch-metadata && yarn polkadot-types-from-defs && yarn polkadot-types-from-chain" 56 | }, 57 | "author": "", 58 | "license": "SEE LICENSE IN ../LICENSE", 59 | "homepage": "", 60 | "dependencies": { 61 | "@polkadot/api": "8.7.2-13", 62 | "@polkadot/api-contract": "8.7.2-13", 63 | "@polkadot/util-crypto": "9.4.1", 64 | "bignumber.js": "^9.0.2", 65 | "chai-as-promised": "^7.1.1", 66 | "find-process": "^1.4.7", 67 | "solc": "0.8.14-fixed", 68 | "web3": "^1.7.3" 69 | }, 70 | "standard": { 71 | "globals": [ 72 | "it", 73 | "assert", 74 | "beforeEach", 75 | "afterEach", 76 | "describe", 77 | "contract", 78 | "artifacts" 79 | ] 80 | }, 81 | "resolutions": { 82 | "simple-get": "^4.0.1" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/src/acceptNft.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { getApiConnection } from "./substrate/substrate-api"; 3 | import { createCollection, mintNft, sendNft, acceptNft } from "./util/tx"; 4 | import { NftIdTuple } from "./util/fetch"; 5 | import { isNftChildOfAnother, expectTxFailure } from "./util/helpers"; 6 | 7 | describe("integration test: accept NFT", () => { 8 | let api: any; 9 | before(async () => { 10 | api = await getApiConnection(); 11 | }); 12 | 13 | const alice = "//Alice"; 14 | const bob = "//Bob"; 15 | 16 | const createTestCollection = async ( 17 | issuerUri: string, 18 | collectionId: number 19 | ) => { 20 | return await createCollection( 21 | api, 22 | collectionId, 23 | issuerUri, 24 | "accept-metadata", 25 | null, 26 | "acpt" 27 | ); 28 | }; 29 | 30 | it("accept NFT", async () => { 31 | const ownerAlice = alice; 32 | const ownerBob = bob; 33 | 34 | const aliceCollectionId = await createTestCollection(alice, 0); 35 | const bobCollectionId = await createTestCollection(bob, 1); 36 | 37 | const parentNftId = await mintNft( 38 | api, 39 | 0, 40 | alice, 41 | ownerAlice, 42 | aliceCollectionId, 43 | "parent-nft-metadata" 44 | ); 45 | const childNftId = await mintNft( 46 | api, 47 | 0, 48 | bob, 49 | ownerBob, 50 | bobCollectionId, 51 | "child-nft-metadata" 52 | ); 53 | 54 | const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; 55 | 56 | await sendNft( 57 | api, 58 | "pending", 59 | ownerBob, 60 | bobCollectionId, 61 | childNftId, 62 | newOwnerNFT 63 | ); 64 | await acceptNft(api, alice, bobCollectionId, childNftId, newOwnerNFT); 65 | 66 | const isChild = await isNftChildOfAnother( 67 | api, 68 | bobCollectionId, 69 | childNftId, 70 | newOwnerNFT 71 | ); 72 | expect(isChild).to.be.true; 73 | }); 74 | 75 | it("[negative] unable to accept NFT by a not-an-owner", async () => { 76 | const ownerAlice = alice; 77 | const ownerBob = bob; 78 | 79 | const aliceCollectionId = await createTestCollection(alice, 2); 80 | const bobCollectionId = await createTestCollection(bob, 3); 81 | 82 | const parentNftId = await mintNft( 83 | api, 84 | 0, 85 | alice, 86 | ownerAlice, 87 | aliceCollectionId, 88 | "parent-nft-metadata" 89 | ); 90 | const childNftId = await mintNft( 91 | api, 92 | 0, 93 | bob, 94 | ownerBob, 95 | bobCollectionId, 96 | "child-nft-metadata" 97 | ); 98 | 99 | const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; 100 | 101 | await sendNft( 102 | api, 103 | "pending", 104 | ownerBob, 105 | bobCollectionId, 106 | childNftId, 107 | newOwnerNFT 108 | ); 109 | const tx = acceptNft(api, bob, bobCollectionId, childNftId, newOwnerNFT); 110 | 111 | await expectTxFailure(/rmrkCore\.NoPermission/, tx); 112 | }); 113 | 114 | it("[negative] unable to accept non-existing NFT", async () => { 115 | const collectionId = 0; 116 | const maxNftId = 0xffffffff; 117 | 118 | const owner = alice; 119 | const aliceCollectionId = await createTestCollection(alice, 4); 120 | 121 | const parentNftId = await mintNft( 122 | api, 123 | 0, 124 | alice, 125 | owner, 126 | aliceCollectionId, 127 | "parent-nft-metadata" 128 | ); 129 | 130 | const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; 131 | 132 | const tx = acceptNft(api, alice, collectionId, maxNftId, newOwnerNFT); 133 | 134 | await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); 135 | }); 136 | 137 | it("[negative] unable to accept NFT which is not sent", async () => { 138 | const ownerAlice = alice; 139 | const ownerBob = bob; 140 | 141 | const aliceCollectionId = await createTestCollection(alice, 5); 142 | const bobCollectionId = await createTestCollection(bob, 6); 143 | 144 | const parentNftId = await mintNft( 145 | api, 146 | 0, 147 | alice, 148 | ownerAlice, 149 | aliceCollectionId, 150 | "parent-nft-metadata" 151 | ); 152 | const childNftId = await mintNft( 153 | api, 154 | 0, 155 | bob, 156 | ownerBob, 157 | bobCollectionId, 158 | "child-nft-metadata" 159 | ); 160 | 161 | const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; 162 | 163 | const tx = acceptNft(api, alice, bobCollectionId, childNftId, newOwnerNFT); 164 | 165 | await expectTxFailure(/rmrkCore\.NoPermission/, tx); 166 | 167 | const isChild = await isNftChildOfAnother( 168 | api, 169 | bobCollectionId, 170 | childNftId, 171 | newOwnerNFT 172 | ); 173 | expect(isChild).to.be.false; 174 | }); 175 | 176 | it("[negative] accept NFT", async () => { 177 | const ownerAlice = alice; 178 | const ownerBob = bob; 179 | 180 | const aliceCollectionId = await createTestCollection(alice, 7); 181 | const bobCollectionId = await createTestCollection(bob, 8); 182 | 183 | const parentNftId = await mintNft( 184 | api, 185 | 0, 186 | alice, 187 | ownerAlice, 188 | aliceCollectionId, 189 | "parent-nft-metadata" 190 | ); 191 | const childNftId = await mintNft( 192 | api, 193 | 0, 194 | bob, 195 | ownerBob, 196 | bobCollectionId, 197 | "child-nft-metadata" 198 | ); 199 | 200 | const parentNftId2 = await mintNft( 201 | api, 202 | 1, 203 | alice, 204 | ownerAlice, 205 | aliceCollectionId, 206 | "parent-nft-metadata2" 207 | ); 208 | 209 | const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; 210 | const notNewOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId2]; 211 | 212 | await sendNft( 213 | api, 214 | "pending", 215 | ownerBob, 216 | bobCollectionId, 217 | childNftId, 218 | newOwnerNFT 219 | ); 220 | const tx = acceptNft( 221 | api, 222 | alice, 223 | bobCollectionId, 224 | childNftId, 225 | notNewOwnerNFT 226 | ); 227 | 228 | await expectTxFailure(/rmrkCore\.CannotAcceptToNewOwner/, tx); 229 | 230 | const isChild = await isNftChildOfAnother( 231 | api, 232 | bobCollectionId, 233 | childNftId, 234 | notNewOwnerNFT 235 | ); 236 | expect(isChild).to.be.false; 237 | }); 238 | 239 | after(() => { 240 | api.disconnect(); 241 | }); 242 | }); 243 | -------------------------------------------------------------------------------- /tests/src/addTheme.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { getApiConnection } from './substrate/substrate-api'; 3 | import { createBase, addTheme } from "./util/tx"; 4 | import { expectTxFailure } from './util/helpers'; 5 | import { getThemeNames } from './util/fetch'; 6 | 7 | describe("integration test: add Theme to Base", () => { 8 | let api: any; 9 | before(async () => { api = await getApiConnection(); }); 10 | 11 | const alice = "//Alice"; 12 | const bob = "//Bob"; 13 | 14 | it("add default theme", async () => { 15 | const baseId = await createBase(api, alice, "default-themed-base", "DTBase", []); 16 | await addTheme(api, alice, baseId, { 17 | name: "default", 18 | properties: [ 19 | { 20 | key: "some-key", 21 | value: "some-key-value" 22 | }, 23 | { 24 | key: "another-key", 25 | value: "another-key-value" 26 | } 27 | ] 28 | }); 29 | }); 30 | 31 | it("add default theme and a custom one", async () => { 32 | const baseId = await createBase(api, alice, "2-themed-base", "2TBase", []); 33 | await addTheme(api, alice, baseId, { 34 | name: "default", 35 | properties: [ 36 | { 37 | key: "default-key", 38 | value: "default-key-value" 39 | } 40 | ] 41 | }); 42 | await addTheme(api, alice, baseId, { 43 | name: "custom-theme", 44 | properties: [ 45 | { 46 | key: "custom-key-0", 47 | value: "custom-key-value-0" 48 | }, 49 | { 50 | key: "custom-key-1", 51 | value: "custom-key-value-1" 52 | } 53 | ] 54 | }) 55 | }); 56 | 57 | it("fetch filtered theme keys", async () => { 58 | const baseId = await createBase(api, alice, "2-themed-base", "2TBase", []); 59 | await addTheme(api, alice, baseId, { 60 | name: "default", 61 | properties: [ 62 | { 63 | key: "first-key", 64 | value: "first-key-value" 65 | }, 66 | { 67 | key: "second-key", 68 | value: "second-key-value" 69 | } 70 | ] 71 | }, ["second-key"]); 72 | }); 73 | 74 | it("fetch theme names", async() => { 75 | const baseId = await createBase(api, alice, "3-themed-base", "3TBase", []); 76 | const names = [ 77 | "default", 78 | "first-theme", 79 | "second-theme" 80 | ]; 81 | 82 | for (let i = 0; i < names.length; i++) { 83 | await addTheme(api, alice, baseId, { name: names[i], properties: [{ key: 'dummy', value: 'dummy' }] }); 84 | } 85 | 86 | const fetchedNames = await getThemeNames(api, baseId); 87 | 88 | for (let i = 0; i < names.length; i++) { 89 | const isFound = fetchedNames.find( 90 | (name) => name === names[i] 91 | ) !== undefined; 92 | 93 | expect(isFound, "Error: invalid theme names").to.be.true; 94 | } 95 | }); 96 | 97 | it("[negative] unable to add theme to non-existing base", async () => { 98 | const maxBaseId = 0xFFFFFFFF; 99 | const tx = addTheme(api, alice, maxBaseId, { 100 | name: "default", 101 | properties: [] 102 | }); 103 | 104 | await expectTxFailure(/rmrkEquip\.BaseDoesntExist/, tx); 105 | }); 106 | 107 | it("[negative] unable to add custom theme if no default theme", async () => { 108 | const baseId = await createBase(api, alice, "no-default-themed-base", "NDTBase", []); 109 | const tx = addTheme(api, alice, baseId, { 110 | name: "custom-theme", 111 | properties: [] 112 | }); 113 | 114 | await expectTxFailure(/rmrkEquip\.NeedsDefaultThemeFirst/, tx); 115 | }); 116 | 117 | it("[negative] unable to add theme by a not-an-owner", async () => { 118 | const baseId = await createBase(api, alice, "no-default-themed-base", "NDTBase", []); 119 | const tx = addTheme(api, bob, baseId, { 120 | name: "default", 121 | properties: [] 122 | }); 123 | 124 | await expectTxFailure(/rmrkEquip\.PermissionError/, tx); 125 | }); 126 | 127 | after(() => { api.disconnect(); }); 128 | }); 129 | -------------------------------------------------------------------------------- /tests/src/burnNft.test.ts: -------------------------------------------------------------------------------- 1 | import { getApiConnection } from "./substrate/substrate-api"; 2 | import { expectTxFailure } from "./util/helpers"; 3 | import { NftIdTuple, getChildren } from "./util/fetch"; 4 | import { burnNft, createCollection, sendNft, mintNft } from "./util/tx"; 5 | 6 | import chai from "chai"; 7 | import chaiAsPromised from "chai-as-promised"; 8 | 9 | chai.use(chaiAsPromised); 10 | const expect = chai.expect; 11 | 12 | describe("integration test: burn nft", () => { 13 | const Alice = "//Alice"; 14 | const Bob = "//Bob"; 15 | 16 | let api: any; 17 | before(async () => { 18 | api = await getApiConnection(); 19 | }); 20 | 21 | it("burn nft", async () => { 22 | await createCollection( 23 | api, 24 | 20, 25 | Alice, 26 | "test-metadata", 27 | null, 28 | "test-symbol" 29 | ).then(async (collectionId) => { 30 | const nftId = await mintNft( 31 | api, 32 | 0, 33 | Alice, 34 | Alice, 35 | collectionId, 36 | "nft-metadata" 37 | ); 38 | await burnNft(api, Alice, collectionId, nftId); 39 | }); 40 | }); 41 | 42 | it("burn nft with children", async () => { 43 | const collectionId = await createCollection( 44 | api, 45 | 21, 46 | Alice, 47 | "test-metadata", 48 | null, 49 | "test-symbol" 50 | ); 51 | 52 | const parentNftId = await mintNft( 53 | api, 54 | 0, 55 | Alice, 56 | Alice, 57 | collectionId, 58 | "nft-metadata" 59 | ); 60 | 61 | const childNftId = await mintNft( 62 | api, 63 | 1, 64 | Alice, 65 | Alice, 66 | collectionId, 67 | "nft-metadata" 68 | ); 69 | 70 | const newOwnerNFT: NftIdTuple = [collectionId, parentNftId]; 71 | 72 | await sendNft(api, "sent", Alice, collectionId, childNftId, newOwnerNFT); 73 | 74 | const childrenBefore = await getChildren(api, collectionId, parentNftId); 75 | expect( 76 | childrenBefore.length === 1, 77 | "Error: parent NFT should have children" 78 | ).to.be.true; 79 | 80 | let child = childrenBefore[0]; 81 | expect( 82 | child.collectionId.eq(collectionId), 83 | "Error: invalid child collection Id" 84 | ).to.be.true; 85 | 86 | expect(child.nftId.eq(childNftId), "Error: invalid child NFT Id").to.be 87 | .true; 88 | 89 | await burnNft(api, Alice, collectionId, parentNftId); 90 | 91 | const childrenAfter = await getChildren(api, collectionId, parentNftId); 92 | 93 | expect(childrenAfter.length === 0, "Error: children should be burned").to.be 94 | .true; 95 | }); 96 | 97 | it("burn child nft", async () => { 98 | const collectionId = await createCollection( 99 | api, 100 | 22, 101 | Alice, 102 | "test-metadata", 103 | null, 104 | "test-symbol" 105 | ); 106 | 107 | const parentNftId = await mintNft( 108 | api, 109 | 0, 110 | Alice, 111 | Alice, 112 | collectionId, 113 | "nft-metadata" 114 | ); 115 | 116 | const childNftId = await mintNft( 117 | api, 118 | 1, 119 | Alice, 120 | Alice, 121 | collectionId, 122 | "nft-metadata" 123 | ); 124 | 125 | const newOwnerNFT: NftIdTuple = [collectionId, parentNftId]; 126 | 127 | await sendNft(api, "sent", Alice, collectionId, childNftId, newOwnerNFT); 128 | 129 | const childrenBefore = await getChildren(api, collectionId, parentNftId); 130 | expect( 131 | childrenBefore.length === 1, 132 | "Error: parent NFT should have children" 133 | ).to.be.true; 134 | 135 | let child = childrenBefore[0]; 136 | expect( 137 | child.collectionId.eq(collectionId), 138 | "Error: invalid child collection Id" 139 | ).to.be.true; 140 | 141 | expect(child.nftId.eq(childNftId), "Error: invalid child NFT Id").to.be 142 | .true; 143 | 144 | await burnNft(api, Alice, collectionId, childNftId); 145 | 146 | const childrenAfter = await getChildren(api, collectionId, parentNftId); 147 | 148 | expect(childrenAfter.length === 0, "Error: children should be burned").to.be 149 | .true; 150 | }); 151 | 152 | it("[negative] burn non-existing NFT", async () => { 153 | await createCollection( 154 | api, 155 | 23, 156 | Alice, 157 | "test-metadata", 158 | null, 159 | "test-symbol" 160 | ).then(async (collectionId) => { 161 | const tx = burnNft(api, Alice, collectionId, 99999); 162 | await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); 163 | }); 164 | }); 165 | 166 | it("[negative] burn not an owner NFT user", async () => { 167 | await createCollection( 168 | api, 169 | 24, 170 | Alice, 171 | "test-metadata", 172 | null, 173 | "test-symbol" 174 | ).then(async (collectionId) => { 175 | const nftId = await mintNft( 176 | api, 177 | 0, 178 | Alice, 179 | Alice, 180 | collectionId, 181 | "nft-metadata" 182 | ); 183 | const tx = burnNft(api, Bob, collectionId, nftId); 184 | await expectTxFailure(/rmrkCore\.NoPermission/, tx); 185 | }); 186 | }); 187 | 188 | after(() => { 189 | api.disconnect(); 190 | }); 191 | }); 192 | -------------------------------------------------------------------------------- /tests/src/changeCollectionIssuer.test.ts: -------------------------------------------------------------------------------- 1 | import { getApiConnection } from "./substrate/substrate-api"; 2 | import { expectTxFailure } from "./util/helpers"; 3 | import { changeIssuer, createCollection } from "./util/tx"; 4 | 5 | describe("integration test: collection issuer", () => { 6 | const Alice = "//Alice"; 7 | const Bob = "//Bob"; 8 | 9 | let api: any; 10 | before(async () => { 11 | api = await getApiConnection(); 12 | }); 13 | 14 | it("change collection issuer", async () => { 15 | await createCollection( 16 | api, 17 | 30, 18 | Alice, 19 | "test-metadata", 20 | null, 21 | "test-symbol" 22 | ).then(async (collectionId) => { 23 | await changeIssuer(api, Alice, collectionId, Bob); 24 | }); 25 | }); 26 | 27 | it("[negative] change not an owner NFT collection issuer", async () => { 28 | await createCollection( 29 | api, 30 | 31, 31 | Bob, 32 | "test-metadata", 33 | null, 34 | "test-symbol" 35 | ).then(async (collectionId) => { 36 | const tx = changeIssuer(api, Alice, collectionId, Bob); 37 | await expectTxFailure(/rmrkCore\.NoPermission/, tx); 38 | }); 39 | }); 40 | 41 | it("[negative] change non-existigit NFT collection issuer", async () => { 42 | await createCollection( 43 | api, 44 | 32, 45 | Alice, 46 | "test-metadata", 47 | null, 48 | "test-symbol" 49 | ).then(async () => { 50 | const tx = changeIssuer(api, Alice, 99999, Bob); 51 | await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); 52 | }); 53 | }); 54 | 55 | after(() => { 56 | api.disconnect(); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /tests/src/config.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2022 Unique Network (Gibraltar) Ltd. 2 | // This file is part of Unique Network. 3 | 4 | // Unique Network is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Unique Network is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Unique Network. If not, see . 16 | 17 | import process from 'process'; 18 | 19 | const config = { 20 | substrateUrl: process.env.substrateUrl || 'ws://127.0.0.1:9944', 21 | frontierUrl: process.env.frontierUrl || 'http://127.0.0.1:9933', 22 | }; 23 | 24 | export default config; -------------------------------------------------------------------------------- /tests/src/createBase.test.ts: -------------------------------------------------------------------------------- 1 | import { getApiConnection } from "./substrate/substrate-api"; 2 | import { createCollection, createBase } from "./util/tx"; 3 | 4 | describe("integration test: create new Base", () => { 5 | let api: any; 6 | before(async () => { 7 | api = await getApiConnection(); 8 | }); 9 | 10 | const alice = "//Alice"; 11 | 12 | it("create empty Base", async () => { 13 | await createBase(api, alice, "empty-base-type", "EBase", []); 14 | }); 15 | 16 | it("create Base with fixed part", async () => { 17 | await createBase(api, alice, "fixedpart-base-type", "FPBase", [ 18 | { 19 | FixedPart: { 20 | id: 42, 21 | z: 0, 22 | src: "some-fixed-url", 23 | }, 24 | }, 25 | ]); 26 | }); 27 | 28 | it("create Base with slot part (no collection)", async () => { 29 | await createBase(api, alice, "slotpart-base-type", "SPBase", [ 30 | { 31 | SlotPart: { 32 | id: 112, 33 | equippable: "Empty", 34 | z: 0, 35 | src: "some-fallback-slot-url", 36 | }, 37 | }, 38 | ]); 39 | }); 40 | 41 | it("create Base with slot part (any collection)", async () => { 42 | await createBase(api, alice, "slotpartany-base-type", "SPABase", [ 43 | { 44 | SlotPart: { 45 | id: 222, 46 | equippable: "All", 47 | z: 1, 48 | src: "some-fallback-slot-url", 49 | }, 50 | }, 51 | ]); 52 | }); 53 | 54 | it("create Base with slot part (custom collections)", async () => { 55 | const firstCollectionId = await createCollection( 56 | api, 57 | 40, 58 | alice, 59 | "first-collection-meta", 60 | null, 61 | "first-collection" 62 | ); 63 | 64 | const secondCollectionId = await createCollection( 65 | api, 66 | 41, 67 | alice, 68 | "first-collection-meta", 69 | null, 70 | "first-collection" 71 | ); 72 | 73 | await createBase(api, alice, "slotpartcustom-base-type", "SPCBase", [ 74 | { 75 | SlotPart: { 76 | id: 1024, 77 | equippable: { 78 | Custom: [firstCollectionId, secondCollectionId], 79 | }, 80 | z: 2, 81 | src: "some-fallback-slot-url", 82 | }, 83 | }, 84 | ]); 85 | }); 86 | 87 | after(() => { 88 | api.disconnect(); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /tests/src/createCollection.test.ts: -------------------------------------------------------------------------------- 1 | import { getApiConnection } from "./substrate/substrate-api"; 2 | import { createCollection } from "./util/tx"; 3 | 4 | describe("Integration test: create new collection", () => { 5 | let api: any; 6 | before(async () => { 7 | api = await getApiConnection(); 8 | }); 9 | 10 | const alice = "//Alice"; 11 | 12 | it("create NFT collection", async () => { 13 | await createCollection(api, 50, alice, "test-metadata", 42, "test-symbol"); 14 | }); 15 | 16 | it("create NFT collection without token limit", async () => { 17 | await createCollection( 18 | api, 19 | 51, 20 | alice, 21 | "no-limit-metadata", 22 | null, 23 | "no-limit-symbol" 24 | ); 25 | }); 26 | 27 | after(() => { 28 | api.disconnect(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /tests/src/deleteCollection.test.ts: -------------------------------------------------------------------------------- 1 | import { getApiConnection } from "./substrate/substrate-api"; 2 | import { expectTxFailure } from "./util/helpers"; 3 | import { createCollection, deleteCollection } from "./util/tx"; 4 | 5 | describe("integration test: delete collection", () => { 6 | let api: any; 7 | before(async () => { 8 | api = await getApiConnection(); 9 | }); 10 | 11 | const Alice = "//Alice"; 12 | const Bob = "//Bob"; 13 | 14 | it("delete NFT collection", async () => { 15 | await createCollection( 16 | api, 17 | 60, 18 | Alice, 19 | "test-metadata", 20 | null, 21 | "test-symbol" 22 | ).then(async (collectionId) => { 23 | await deleteCollection(api, Alice, collectionId.toString()); 24 | }); 25 | }); 26 | 27 | it("[negative] delete non-existing NFT collection", async () => { 28 | const tx = deleteCollection(api, Alice, "99999"); 29 | await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); 30 | }); 31 | 32 | it("[negative] delete not an owner NFT collection", async () => { 33 | await createCollection( 34 | api, 35 | 61, 36 | Alice, 37 | "test-metadata", 38 | null, 39 | "test-symbol" 40 | ).then(async (collectionId) => { 41 | const tx = deleteCollection(api, Bob, collectionId.toString()); 42 | await expectTxFailure(/uniques.NoPermission/, tx); 43 | }); 44 | }); 45 | 46 | after(() => { 47 | api.disconnect(); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /tests/src/getOwnedNfts.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { getApiConnection } from "./substrate/substrate-api"; 3 | import { getOwnedNfts } from "./util/fetch"; 4 | import { mintNft, createCollection } from "./util/tx"; 5 | 6 | function checkMetadata(nft: any, nftMetadata: string, nftId: number) { 7 | if(nft) { 8 | expect(nft[2].transferable.isTrue, `The nft should be transferable`).to.be 9 | .true; 10 | expect(nft[2].metadata.toUtf8() === (nftMetadata + `-${nftId}`), `The nft metadata should be correct`).to.be 11 | .true; 12 | expect(nft[2].royalty.isNone, `The royalty should be None.`).to.be 13 | .true; 14 | expect(nft[2].equipped.isEmpty, `The nft shouldn't be equipped.`).to.be 15 | .true; 16 | expect(nft[2].pending.isFalse, `The nft shouldn't be pending.`).to.be 17 | .true; 18 | } 19 | } 20 | 21 | describe("integration test: get owned NFTs", () => { 22 | let api: any; 23 | let collections: Array<{id: number, metadata: string, collectionMax: any, symbol: string}>; 24 | let nfts: Array<{nftId: number, collectionId: any}>; 25 | 26 | const eve = "//Eve"; 27 | const owner = eve; 28 | const recipientUri = null; 29 | const royalty = null; 30 | const nftMetadata = "eve-NFT-metadata"; 31 | 32 | before(async () => { 33 | api = await getApiConnection(); 34 | 35 | collections = [ 36 | { 37 | id: 291, 38 | metadata: "Metadata#291", 39 | collectionMax: null, 40 | symbol: "Sym291", 41 | }, 42 | { 43 | id: 292, 44 | metadata: "Metadata#292", 45 | collectionMax: null, 46 | symbol: "Sym292", 47 | }, 48 | { 49 | id: 293, 50 | metadata: "Metadata#293", 51 | collectionMax: null, 52 | symbol: "Sym293", 53 | } 54 | ]; 55 | nfts = [ 56 | {nftId: 0, collectionId: collections[0].id}, 57 | {nftId: 1, collectionId: collections[0].id}, 58 | {nftId: 0, collectionId: collections[1].id}, 59 | {nftId: 0, collectionId: collections[2].id} 60 | ]; 61 | 62 | for(const collection of collections) { 63 | await createCollection( 64 | api, 65 | collection.id, 66 | eve, 67 | collection.metadata, 68 | collection.collectionMax, 69 | collection.symbol 70 | ); 71 | } 72 | 73 | for(const nft of nfts) { 74 | await mintNft( 75 | api, 76 | nft.nftId, 77 | eve, 78 | owner, 79 | nft.collectionId, 80 | nftMetadata + `-${nft.nftId}`, 81 | recipientUri, 82 | royalty 83 | ); 84 | } 85 | }); 86 | 87 | it("fetch all NFTs owned by a user over multiple collections", async () => { 88 | const ownedNfts = await getOwnedNfts(api, eve, null, null); 89 | 90 | nfts.forEach(({nftId, collectionId}) => { 91 | const nft = ownedNfts.find((ownedNft) => { 92 | return ownedNft[0].toNumber() === collectionId && ownedNft[1].toNumber() === nftId; 93 | }); 94 | 95 | expect(nft !== undefined, `NFT (${collectionId}, ${nftId}) should be owned by ${owner}`).to.be 96 | .true; 97 | 98 | checkMetadata(nft, nftMetadata, nftId); 99 | }); 100 | }); 101 | 102 | it("fetch all NFTs owned by a user over multiple collections providing start", async () => { 103 | // We are skipping the first collection by setting the start index to "1". So the 104 | // collection we are skipping here is 291. 105 | const ownedNfts = await getOwnedNfts(api, eve, "1", null); 106 | expect(ownedNfts.length === 2, "Two NFTs should be returned since we skipped the first collection.").to.be 107 | .true; 108 | 109 | ownedNfts.forEach((nft) => { 110 | expect(nft[0].toNumber() === collections[1].id || nft[0].toNumber() === collections[2].id, 111 | "The NFTs we received should be from collection 292 and 293.").to.be.true; 112 | }) 113 | }); 114 | 115 | it("fetch all NFTs owned by a user over multiple collections providing count", async () => { 116 | // We should get the NFTs from collection 291 and 292. 117 | const ownedNfts = await getOwnedNfts(api, eve, null, "2"); 118 | expect(ownedNfts.length === 3, "Three NFTs should be returned.").to.be 119 | .true; 120 | 121 | ownedNfts.forEach((nft) => { 122 | expect(nft[0].toNumber() === collections[0].id || nft[0].toNumber() === collections[1].id, 123 | "The NFT we received should be from collection 291 and 292.").to.be.true; 124 | }) 125 | }); 126 | 127 | it("fetch all NFTs owned by a user over multiple collections providing start and count", async () => { 128 | // We are skipping the first collection by setting the start index to "1". But 129 | // because we are setting the count to "1" we are only going to receive NFTs 130 | // from one collection. 131 | const ownedNfts = await getOwnedNfts(api, eve, "1", "1"); 132 | expect(ownedNfts.length === 1, "Only one NFT should be returned.").to.be 133 | .true; 134 | 135 | ownedNfts.forEach((nft) => { 136 | expect(nft[0].toNumber() === collections[1].id, "The NFT we received should be from collection 292.").to.be 137 | .true; 138 | }) 139 | }); 140 | 141 | after(() => { 142 | api.disconnect(); 143 | }); 144 | }); 145 | -------------------------------------------------------------------------------- /tests/src/getOwnedNftsInCollection.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { getApiConnection } from "./substrate/substrate-api"; 3 | import { getOwnedNftsInCollection } from "./util/fetch"; 4 | import { mintNft, createCollection } from "./util/tx"; 5 | 6 | describe("integration test: get owned NFTs inside a collection", () => { 7 | let api: any; 8 | before(async () => { 9 | api = await getApiConnection(); 10 | }); 11 | 12 | const alice = "//Alice"; 13 | 14 | it("fetch all NFTs owned by a user in a specific collection", async () => { 15 | const owner = alice; 16 | const collectionMetadata = "aliceCollectionMetadata"; 17 | const collectionMax = null; 18 | const collectionSymbol = "AliceSym"; 19 | const recipientUri = null; 20 | const royalty = null; 21 | const nftMetadata = "alice-NFT-metadata"; 22 | 23 | let collectionId = await createCollection( 24 | api, 25 | 80, 26 | alice, 27 | collectionMetadata, 28 | collectionMax, 29 | collectionSymbol 30 | ); 31 | 32 | const nftIds = [ 33 | await mintNft( 34 | api, 35 | 0, 36 | alice, 37 | owner, 38 | collectionId, 39 | nftMetadata + "-0", 40 | recipientUri, 41 | royalty 42 | ), 43 | await mintNft( 44 | api, 45 | 1, 46 | alice, 47 | owner, 48 | collectionId, 49 | nftMetadata + "-1", 50 | recipientUri, 51 | royalty 52 | ), 53 | await mintNft( 54 | api, 55 | 2, 56 | alice, 57 | owner, 58 | collectionId, 59 | nftMetadata + "-2", 60 | recipientUri, 61 | royalty 62 | ), 63 | ]; 64 | 65 | const ownedNfts = await getOwnedNftsInCollection(api, alice, collectionId); 66 | 67 | const isFound = (nftId: number) => { 68 | return ( 69 | ownedNfts.find((ownedNftId) => { 70 | return ownedNftId === nftId; 71 | }) !== undefined 72 | ); 73 | }; 74 | 75 | nftIds.forEach((nftId) => { 76 | expect(isFound(nftId), `NFT ${nftId} should be owned by ${alice}`).to.be 77 | .true; 78 | }); 79 | }); 80 | 81 | after(() => { 82 | api.disconnect(); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /tests/src/getPropertiesOfOwnedNfts.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { getApiConnection } from "./substrate/substrate-api"; 3 | import { getPropertiesOfOwnedNfts } from "./util/fetch"; 4 | import { mintNft, createCollection, setNftProperty } from "./util/tx"; 5 | 6 | describe("integration test: get properties of owned NFTs", () => { 7 | let api: any; 8 | let collections: Array<{id: number, metadata: string, collectionMax: any, symbol: string}>; 9 | let nftProperties: Array<{nftId: number, collectionId: any, key: string, value: string}>; 10 | 11 | before(async () => { 12 | api = await getApiConnection(); 13 | 14 | collections = [ 15 | { 16 | id: 421, 17 | metadata: "Metadata#421", 18 | collectionMax: null, 19 | symbol: "Sym421", 20 | }, 21 | { 22 | id: 422, 23 | metadata: "Metadata#422", 24 | collectionMax: null, 25 | symbol: "Sym422", 26 | }, 27 | { 28 | id: 423, 29 | metadata: "Metadata#423", 30 | collectionMax: null, 31 | symbol: "Sym423", 32 | } 33 | ]; 34 | 35 | nftProperties = [ 36 | { 37 | nftId: 0, 38 | collectionId: collections[0].id, 39 | key: `nft-${collections[0].id}-0-test-key`, 40 | value: `nft-${collections[0].id}-0-test-key-value`, 41 | }, 42 | { 43 | nftId: 1, 44 | collectionId: collections[0].id, 45 | key: `nft-${collections[0].id}-1-test-key`, 46 | value: `nft-${collections[0].id}-1-test-key-value`, 47 | }, 48 | { 49 | nftId: 0, 50 | collectionId: collections[1].id, 51 | key: `nft-${collections[1].id}-0-test-key`, 52 | value: `nft-${collections[1].id}-0-test-key-value`, 53 | }, 54 | { 55 | nftId: 0, 56 | collectionId: collections[2].id, 57 | key: `nft-${collections[2].id}-0-test-key`, 58 | value: `nft-${collections[2].id}-0-test-key-value`, 59 | } 60 | ]; 61 | 62 | for(const collection of collections) { 63 | await createCollection( 64 | api, 65 | collection.id, 66 | dave, 67 | collection.metadata, 68 | collection.collectionMax, 69 | collection.symbol 70 | ); 71 | } 72 | 73 | for(const nftProps of nftProperties) { 74 | await mintNft( 75 | api, 76 | nftProps.nftId, 77 | dave, 78 | owner, 79 | nftProps.collectionId, 80 | nftMetadata + `-${nftProps.nftId}`, 81 | recipientUri, 82 | royalty 83 | ); 84 | 85 | await setNftProperty( 86 | api, 87 | dave, 88 | nftProps.collectionId, 89 | nftProps.nftId, 90 | nftProps.key, 91 | nftProps.value, 92 | ); 93 | } 94 | }); 95 | 96 | const dave = "//Dave"; 97 | const owner = dave; 98 | const recipientUri = null; 99 | const royalty = null; 100 | const nftMetadata = "dave-NFT-metadata"; 101 | 102 | it("fetch all the properites of the NFTs owned by a user over multiple collections", async () => { 103 | const ownedNfts = await getPropertiesOfOwnedNfts(api, dave, null, null); 104 | 105 | nftProperties.forEach(({nftId, collectionId, key, value}) => { 106 | const nft = ownedNfts.find((ownedNft) => { 107 | return ownedNft[0].toNumber() === collectionId && ownedNft[1].toNumber() === nftId; 108 | }); 109 | 110 | expect(nft !== undefined, `NFT (${collectionId}, ${nftId}) should be owned by ${owner}`).to.be 111 | .true; 112 | if(nft) { 113 | const actualProperties = nft[2]; 114 | actualProperties.forEach((property) => { 115 | expect(property.key.toUtf8() === key, `The key for (${collectionId}, ${nftId}) is incorrect.`).to.be.true; 116 | expect(property.value.toUtf8() === value, `The value for (${collectionId}, ${nftId}) is incorrect.`).to.be.true; 117 | }); 118 | } 119 | }); 120 | }); 121 | 122 | it("fetch all the properites of the NFTs owned by a user over multiple collections with specified start", async () => { 123 | // We are skipping the first collection by setting the start index to "1". 124 | // So we should only get the properties of the NFTs from the collection 192 125 | // and 193. 126 | const ownedNfts = await getPropertiesOfOwnedNfts(api, dave, "1", null); 127 | 128 | expect(ownedNfts.length === 2, "Two NFTs should be returned.").to.be 129 | .true; 130 | 131 | ownedNfts.forEach((nft) => { 132 | expect(nft[0].toNumber() === collections[1].id || nft[0].toNumber() === collections[2].id, 133 | "The returned NFTs should be from collection 192 and 193.").to.be.true; 134 | }); 135 | }); 136 | 137 | it("fetch all the properites of the NFTs owned by a user over multiple collections with specified count", async () => { 138 | // We should only get the properties from the NFTs in collection 191 and 192. 139 | const ownedNfts = await getPropertiesOfOwnedNfts(api, dave, null, "2"); 140 | 141 | expect(ownedNfts.length === 3, "Three NFTs should be returned.").to.be 142 | .true; 143 | 144 | ownedNfts.forEach((nft) => { 145 | expect(nft[0].toNumber() === collections[0].id || nft[0].toNumber() === collections[1].id, 146 | "The returned NFTs shouldn't be from collection 193.").to.be.true; 147 | }); 148 | }); 149 | 150 | it("fetch all the properites of the NFTs owned by a user over multiple collections with specified start and count", async () => { 151 | // We are skipping the first collection by setting the start index to "1". 152 | // But because we are setting the count to "1" we are only going to receive 153 | // the properties from NFTs inside one collection, i.e. the collection 154 | // following the first one, in this case collection number 422. 155 | const ownedNfts = await getPropertiesOfOwnedNfts(api, dave, "1", "1"); 156 | 157 | expect(ownedNfts.length === 1, "Only one NFT should be returned.").to.be 158 | .true; 159 | 160 | ownedNfts.forEach((nft) => { 161 | expect(nft[0].toNumber() === collections[1].id, "The returned NFTs should be from collection 192").to.be 162 | .true; 163 | }); 164 | }); 165 | 166 | after(() => { 167 | api.disconnect(); 168 | }); 169 | }); 170 | -------------------------------------------------------------------------------- /tests/src/interfaces/augment-api.ts: -------------------------------------------------------------------------------- 1 | // Auto-generated via `yarn polkadot-types-from-chain`, do not edit 2 | /* eslint-disable */ 3 | 4 | import './augment-api-consts'; 5 | import './augment-api-errors'; 6 | import './augment-api-events'; 7 | import './augment-api-query'; 8 | import './augment-api-tx'; 9 | import './augment-api-rpc'; 10 | -------------------------------------------------------------------------------- /tests/src/interfaces/definitions.ts: -------------------------------------------------------------------------------- 1 | export {default as rmrk} from './rmrk/definitions'; 2 | -------------------------------------------------------------------------------- /tests/src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | // Auto-generated via `yarn polkadot-types-from-defs`, do not edit 2 | /* eslint-disable */ 3 | 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /tests/src/interfaces/rmrk/definitions.ts: -------------------------------------------------------------------------------- 1 | import types from "../lookup"; 2 | 3 | type RpcParam = { 4 | name: string; 5 | type: string; 6 | isOptional?: true; 7 | }; 8 | 9 | const atParam = { name: "at", type: "Hash", isOptional: true }; 10 | const fn = (description: string, params: RpcParam[], type: string) => ({ 11 | description, 12 | params: [...params, atParam], 13 | type, 14 | }); 15 | 16 | export default { 17 | types, 18 | rpc: { 19 | // lastCollectionIdx: fn('Get the latest created collection id', [], 'u32'), 20 | collectionById: fn( 21 | "Get collection by id", 22 | [{ name: "id", type: "u32" }], 23 | "Option" 24 | ), 25 | nftById: fn( 26 | "Get NFT by collection id and NFT id", 27 | [ 28 | { name: "collectionId", type: "u32" }, 29 | { name: "nftId", type: "u32" }, 30 | ], 31 | "Option" 32 | ), 33 | nftsOwnedBy: fn( 34 | "Get all the nfts owned by a user", 35 | [ 36 | { name: "accountId", type: "AccountId32" }, 37 | { name: "startIndex", type: "Option" }, 38 | { name: "count", type: "Option" }, 39 | ], 40 | "Vec<(u32, u32, RmrkTraitsNftNftInfo)>" 41 | ), 42 | propertiesOfNftsOwnedBy: fn( 43 | "Get the properties of all the nfts owned by a user", 44 | [ 45 | { name: "accountId", type: "AccountId32" }, 46 | { name: "startIndex", type: "Option" }, 47 | { name: "count", type: "Option" }, 48 | ], 49 | "Vec<(u32, u32, Vec)>" 50 | ), 51 | accountTokens: fn( 52 | "Get tokens owned by an account in a collection", 53 | [ 54 | { name: "accountId", type: "AccountId32" }, 55 | { name: "collectionId", type: "u32" }, 56 | ], 57 | "Vec" 58 | ), 59 | nftChildren: fn( 60 | "Get NFT children", 61 | [ 62 | { name: "collectionId", type: "u32" }, 63 | { name: "nftId", type: "u32" }, 64 | ], 65 | "Vec" 66 | ), 67 | collectionProperties: fn( 68 | "Get collection properties", 69 | [{ name: "collectionId", type: "u32" }], 70 | "Vec" 71 | ), 72 | nftProperties: fn( 73 | "Get NFT properties", 74 | [ 75 | { name: "collectionId", type: "u32" }, 76 | { name: "nftId", type: "u32" }, 77 | ], 78 | "Vec" 79 | ), 80 | nftResources: fn( 81 | "Get NFT resources", 82 | [ 83 | { name: "collectionId", type: "u32" }, 84 | { name: "nftId", type: "u32" }, 85 | ], 86 | "Vec" 87 | ), 88 | nftResourcePriority: fn( 89 | "Get NFT resource priority", 90 | [ 91 | { name: "collectionId", type: "u32" }, 92 | { name: "nftId", type: "u32" }, 93 | { name: "resourceId", type: "u32" }, 94 | ], 95 | "Option" 96 | ), 97 | base: fn( 98 | "Get base info", 99 | [{ name: "baseId", type: "u32" }], 100 | "Option" 101 | ), 102 | baseParts: fn( 103 | "Get all Base's parts", 104 | [{ name: "baseId", type: "u32" }], 105 | "Vec" 106 | ), 107 | themeNames: fn( 108 | "Get Base's theme names", 109 | [{ name: "baseId", type: "u32" }], 110 | "Vec" 111 | ), 112 | themes: fn( 113 | "Get Theme info -- name, properties, and inherit flag", 114 | [ 115 | { name: "baseId", type: "u32" }, 116 | { name: "themeName", type: "String" }, 117 | { name: "keys", type: "Option>" }, 118 | ], 119 | "Option" 120 | ), 121 | }, 122 | }; 123 | -------------------------------------------------------------------------------- /tests/src/interfaces/rmrk/index.ts: -------------------------------------------------------------------------------- 1 | // Auto-generated via `yarn polkadot-types-from-defs`, do not edit 2 | /* eslint-disable */ 3 | 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /tests/src/interfaces/types.ts: -------------------------------------------------------------------------------- 1 | // Auto-generated via `yarn polkadot-types-from-defs`, do not edit 2 | /* eslint-disable */ 3 | 4 | export * from './rmrk/types'; 5 | -------------------------------------------------------------------------------- /tests/src/lockCollection.test.ts: -------------------------------------------------------------------------------- 1 | import { getApiConnection } from "./substrate/substrate-api"; 2 | import { expectTxFailure } from "./util/helpers"; 3 | import { createCollection, lockCollection, mintNft } from "./util/tx"; 4 | 5 | describe("integration test: lock collection", () => { 6 | const Alice = "//Alice"; 7 | const Bob = "//Bob"; 8 | const Max = 5; 9 | 10 | let api: any; 11 | before(async () => { 12 | api = await getApiConnection(); 13 | }); 14 | 15 | it("lock collection", async () => { 16 | await createCollection( 17 | api, 18 | 90, 19 | Alice, 20 | "test-metadata", 21 | null, 22 | "test-symbol" 23 | ).then(async (collectionId) => { 24 | await lockCollection(api, Alice, collectionId); 25 | }); 26 | }); 27 | 28 | it("[negative] lock non-existing NFT collection", async () => { 29 | const tx = lockCollection(api, Alice, 99999); 30 | await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); 31 | }); 32 | 33 | it("[negative] lock not an owner NFT collection issuer", async () => { 34 | await createCollection( 35 | api, 36 | 91, 37 | Alice, 38 | "test-metadata", 39 | null, 40 | "test-symbol" 41 | ).then(async (collectionId) => { 42 | const tx = lockCollection(api, Bob, collectionId); 43 | await expectTxFailure(/rmrkCore\.NoPermission/, tx); 44 | }); 45 | }); 46 | 47 | it("lock collection with minting", async () => { 48 | await createCollection( 49 | api, 50 | 92, 51 | Alice, 52 | "test-metadata", 53 | Max, 54 | "test-symbol" 55 | ).then(async (collectionId) => { 56 | for (let i = 0; i < 5; i++) { 57 | await mintNft( 58 | api, 59 | i, 60 | Alice, 61 | Alice, 62 | collectionId, 63 | "test-metadata", 64 | null, 65 | null 66 | ); 67 | } 68 | await lockCollection(api, Alice, collectionId, Max); 69 | }); 70 | }); 71 | 72 | it("[negative] unable to mint NFT inside a locked collection", async () => { 73 | await createCollection( 74 | api, 75 | 93, 76 | Alice, 77 | "test-metadata", 78 | Max, 79 | "test-symbol" 80 | ).then(async (collectionId) => { 81 | await lockCollection(api, Alice, collectionId); 82 | const tx = mintNft( 83 | api, 84 | 0, 85 | Alice, 86 | Alice, 87 | collectionId, 88 | "test-metadata", 89 | null, 90 | null 91 | ); 92 | await expectTxFailure(/rmrkCore\.CollectionFullOrLocked/, tx); 93 | }); 94 | }); 95 | 96 | it("[negative] unable to mint NFT inside a full collection", async () => { 97 | await createCollection( 98 | api, 99 | 94, 100 | Alice, 101 | "test-metadata", 102 | 1, 103 | "test-symbol" 104 | ).then(async (collectionId) => { 105 | await mintNft( 106 | api, 107 | 0, 108 | Alice, 109 | Alice, 110 | collectionId, 111 | "test-metadata", 112 | null, 113 | null 114 | ); 115 | const tx = mintNft( 116 | api, 117 | 1, 118 | Alice, 119 | Alice, 120 | collectionId, 121 | "test-metadata", 122 | null, 123 | null 124 | ); 125 | await expectTxFailure(/rmrkCore\.CollectionFullOrLocked/, tx); 126 | }); 127 | }); 128 | 129 | after(() => { 130 | api.disconnect(); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /tests/src/mintNft.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { getApiConnection } from "./substrate/substrate-api"; 3 | import { getNft } from "./util/fetch"; 4 | import { expectTxFailure } from "./util/helpers"; 5 | import { createCollection, mintNft } from "./util/tx"; 6 | 7 | describe("integration test: mint new NFT", () => { 8 | let api: any; 9 | before(async () => { 10 | api = await getApiConnection(); 11 | }); 12 | 13 | const alice = "//Alice"; 14 | const bob = "//Bob"; 15 | const maxCollectionId = 0xffffffff; 16 | const maxNftId = 0xffffffff; 17 | 18 | it("mint NFT", async () => { 19 | const owner = null; 20 | const collectionMetadata = "mintingCollectionMetadata"; 21 | const collectionMax = null; 22 | const collectionSymbol = "MCS"; 23 | const recipientUri = null; 24 | const royalty = null; 25 | const nftMetadata = "NFT-test-metadata"; 26 | 27 | let collectionId = await createCollection( 28 | api, 29 | 100, 30 | alice, 31 | collectionMetadata, 32 | collectionMax, 33 | collectionSymbol 34 | ); 35 | 36 | await mintNft( 37 | api, 38 | 200, 39 | alice, 40 | owner, 41 | collectionId, 42 | nftMetadata, 43 | recipientUri, 44 | royalty 45 | ); 46 | }); 47 | 48 | it("mint NFT and set another owner", async () => { 49 | const owner = bob; 50 | const collectionMetadata = "setOwnerCollectionMetadata"; 51 | const collectionMax = null; 52 | const collectionSymbol = "SOCS"; 53 | const recipientUri = null; 54 | const royalty = null; 55 | const nftMetadata = "setOwner-NFT-metadata"; 56 | 57 | let collectionId = await createCollection( 58 | api, 59 | 101, 60 | alice, 61 | collectionMetadata, 62 | collectionMax, 63 | collectionSymbol 64 | ); 65 | 66 | await mintNft( 67 | api, 68 | 201, 69 | alice, 70 | owner, 71 | collectionId, 72 | nftMetadata, 73 | recipientUri, 74 | royalty 75 | ); 76 | }); 77 | 78 | it("mint NFT with recipient and roalty", async () => { 79 | const owner = alice; 80 | const collectionMetadata = "mintingCollectionMetadata"; 81 | const collectionMax = null; 82 | const collectionSymbol = "MCS"; 83 | const recipientUri = bob; 84 | const royalty = 70000; 85 | const nftMetadata = "recipient-royalty-NFT-test-metadata"; 86 | 87 | let collectionId = await createCollection( 88 | api, 89 | 102, 90 | alice, 91 | collectionMetadata, 92 | collectionMax, 93 | collectionSymbol 94 | ); 95 | 96 | await mintNft( 97 | api, 98 | 210, 99 | alice, 100 | owner, 101 | collectionId, 102 | nftMetadata, 103 | recipientUri, 104 | royalty 105 | ); 106 | }); 107 | 108 | it("mint NFT with resources", async () => { 109 | const owner = alice; 110 | const collectionMetadata = "mintingCollectionMetadata"; 111 | const collectionMax = null; 112 | const collectionSymbol = "MCS"; 113 | const nftMetadata = "NFT-with-resources-test-metadata"; 114 | const resources = [ 115 | { 116 | id: 101, 117 | resource: { 118 | basic: { 119 | metadata: "basic-resource-nft-minting", 120 | }, 121 | }, 122 | }, 123 | { 124 | id: 102, 125 | resource: { 126 | slot: { 127 | metadata: "slot-resource-nft-minting", 128 | slot: 9, 129 | }, 130 | }, 131 | }, 132 | 133 | { 134 | id: 103, 135 | resource: { 136 | composable: { 137 | metadata: "composable-resource-nft-minting", 138 | parts: [0, 5, 2], 139 | }, 140 | }, 141 | }, 142 | { 143 | id: 104, 144 | resource: { 145 | slot: { 146 | metadata: "slot-resource-nft-minting-2", 147 | base: 5, 148 | }, 149 | }, 150 | }, 151 | ]; 152 | 153 | let collectionId = await createCollection( 154 | api, 155 | 103, 156 | alice, 157 | collectionMetadata, 158 | collectionMax, 159 | collectionSymbol 160 | ); 161 | 162 | await mintNft( 163 | api, 164 | 211, 165 | alice, 166 | owner, 167 | collectionId, 168 | nftMetadata, 169 | null, 170 | null, 171 | true, 172 | resources 173 | ); 174 | }); 175 | 176 | it("[negative] unable to mint NFT within non-existing collection", async () => { 177 | const owner = alice; 178 | const recipientUri = null; 179 | const royalty = null; 180 | const nftMetadata = "NFT-test-metadata"; 181 | 182 | const tx = mintNft( 183 | api, 184 | 220, 185 | alice, 186 | owner, 187 | maxCollectionId, 188 | nftMetadata, 189 | recipientUri, 190 | royalty 191 | ); 192 | 193 | await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); 194 | }); 195 | 196 | it("[negative] unable to mint NFT by a user that isn't the owner of the collection", async () => { 197 | const owner = alice; 198 | const collectionMetadata = "mintingCollectionMetadata"; 199 | const collectionMax = null; 200 | const collectionSymbol = "MCS"; 201 | const recipientUri = null; 202 | const royalty = null; 203 | const nftMetadata = "NFT-test-metadata"; 204 | 205 | let collectionId = await createCollection( 206 | api, 207 | 104, 208 | alice, 209 | collectionMetadata, 210 | collectionMax, 211 | collectionSymbol 212 | ); 213 | 214 | const tx = mintNft( 215 | api, 216 | 230, 217 | bob, 218 | owner, 219 | collectionId, 220 | nftMetadata, 221 | recipientUri, 222 | royalty 223 | ); 224 | 225 | await expectTxFailure(/rmrkCore\.NoPermission/, tx); 226 | }); 227 | 228 | it("[negative] unable to fetch non-existing NFT", async () => { 229 | const nft = await getNft(api, maxCollectionId, maxNftId); 230 | expect(nft.isSome).to.be.false; 231 | }); 232 | 233 | after(() => { 234 | api.disconnect(); 235 | }); 236 | }); 237 | -------------------------------------------------------------------------------- /tests/src/rejectNft.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { getApiConnection } from "./substrate/substrate-api"; 3 | import { createCollection, mintNft, sendNft, rejectNft } from "./util/tx"; 4 | import { getChildren, NftIdTuple } from "./util/fetch"; 5 | import { isNftChildOfAnother, expectTxFailure } from "./util/helpers"; 6 | 7 | describe("integration test: reject NFT", () => { 8 | let api: any; 9 | before(async () => { 10 | api = await getApiConnection(); 11 | }); 12 | 13 | const alice = "//Alice"; 14 | const bob = "//Bob"; 15 | 16 | const createTestCollection = async ( 17 | issuerUri: string, 18 | collectionId: number 19 | ) => { 20 | return await createCollection( 21 | api, 22 | collectionId, 23 | issuerUri, 24 | "reject-metadata", 25 | null, 26 | "rjct" 27 | ); 28 | }; 29 | 30 | it("reject NFT", async () => { 31 | const ownerAlice = alice; 32 | const ownerBob = bob; 33 | 34 | const aliceCollectionId = await createTestCollection(alice, 110); 35 | const bobCollectionId = await createTestCollection(bob, 111); 36 | 37 | const parentNftId = await mintNft( 38 | api, 39 | 0, 40 | alice, 41 | ownerAlice, 42 | aliceCollectionId, 43 | "parent-nft-metadata" 44 | ); 45 | const childNftId = await mintNft( 46 | api, 47 | 1, 48 | bob, 49 | ownerBob, 50 | bobCollectionId, 51 | "child-nft-metadata" 52 | ); 53 | 54 | const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; 55 | 56 | await sendNft( 57 | api, 58 | "pending", 59 | ownerBob, 60 | bobCollectionId, 61 | childNftId, 62 | newOwnerNFT 63 | ); 64 | await rejectNft(api, alice, bobCollectionId, childNftId); 65 | 66 | const isChild = await isNftChildOfAnother( 67 | api, 68 | bobCollectionId, 69 | childNftId, 70 | newOwnerNFT 71 | ); 72 | expect(isChild, "Error: rejected NFT is still a child of the target NFT").to 73 | .be.false; 74 | }); 75 | 76 | it("[negative] unable to reject NFT by a not-an-owner", async () => { 77 | const ownerAlice = alice; 78 | const ownerBob = bob; 79 | 80 | const aliceCollectionId = await createTestCollection(alice, 112); 81 | const bobCollectionId = await createTestCollection(bob, 113); 82 | 83 | const parentNftId = await mintNft( 84 | api, 85 | 0, 86 | alice, 87 | ownerAlice, 88 | aliceCollectionId, 89 | "parent-nft-metadata" 90 | ); 91 | const childNftId = await mintNft( 92 | api, 93 | 1, 94 | bob, 95 | ownerBob, 96 | bobCollectionId, 97 | "child-nft-metadata" 98 | ); 99 | 100 | const newOwnerNFT: NftIdTuple = [aliceCollectionId, parentNftId]; 101 | 102 | await sendNft( 103 | api, 104 | "pending", 105 | ownerBob, 106 | bobCollectionId, 107 | childNftId, 108 | newOwnerNFT 109 | ); 110 | const tx = rejectNft(api, bob, bobCollectionId, childNftId); 111 | 112 | await expectTxFailure(/rmrkCore\.CannotRejectNonOwnedNft/, tx); 113 | }); 114 | 115 | it("[negative] unable to reject non-existing NFT", async () => { 116 | const maxNftId = 0xffffffff; 117 | 118 | const collectionId = await createTestCollection(alice, 114); 119 | 120 | const tx = rejectNft(api, alice, collectionId, maxNftId); 121 | 122 | await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); 123 | }); 124 | 125 | it("[negative] unable to reject NFT which is not sent", async () => { 126 | const ownerAlice = alice; 127 | 128 | const collectionId = await createTestCollection(alice, 115); 129 | 130 | const nftId = await mintNft( 131 | api, 132 | 0, 133 | alice, 134 | ownerAlice, 135 | collectionId, 136 | "parent-nft-metadata" 137 | ); 138 | 139 | const tx = rejectNft(api, alice, collectionId, nftId); 140 | 141 | await expectTxFailure(/rmrkCore\.CannotRejectNonPendingNft/, tx); 142 | }); 143 | 144 | after(() => { 145 | api.disconnect(); 146 | }); 147 | }); 148 | -------------------------------------------------------------------------------- /tests/src/replaceResource.test.ts: -------------------------------------------------------------------------------- 1 | import { getApiConnection } from "./substrate/substrate-api"; 2 | import { 3 | addNftBasicResource, 4 | createCollection, 5 | mintNft, 6 | addNftSlotResource, 7 | replaceResource, 8 | } from "./util/tx"; 9 | 10 | describe("integration test: replace NFT resource", () => { 11 | const Alice = "//Alice"; 12 | const Bob = "//Bob"; 13 | const metadata = "test-res-metadata"; 14 | 15 | const baseId = 42; 16 | const slotId = 10; 17 | const parts = [0, 5, 2]; 18 | 19 | let api: any; 20 | before(async () => { 21 | api = await getApiConnection(); 22 | }); 23 | 24 | it("replace resource ( basic )", async () => { 25 | const collectionId = await createCollection( 26 | api, 27 | 483, 28 | Alice, 29 | "test-metadata", 30 | null, 31 | "test-symbol" 32 | ); 33 | 34 | const nft = await mintNft( 35 | api, 36 | 0, 37 | Alice, 38 | Alice, 39 | collectionId, 40 | "nft-metadata" 41 | ); 42 | 43 | const resourceId = await addNftBasicResource( 44 | api, 45 | 0, 46 | Alice, 47 | "added", 48 | collectionId, 49 | nft, 50 | metadata 51 | ); 52 | 53 | const resource = { 54 | Basic: { 55 | metadata: "basic-resource-nft-minting", 56 | }, 57 | }; 58 | 59 | await replaceResource(api, Bob, collectionId, nft, resourceId, resource); 60 | }); 61 | 62 | it("replace resource ( slot with basic )", async () => { 63 | const collectionId = await createCollection( 64 | api, 65 | 484, 66 | Alice, 67 | "test-metadata", 68 | null, 69 | "test-symbol" 70 | ); 71 | 72 | const nft = await mintNft( 73 | api, 74 | 0, 75 | Alice, 76 | Alice, 77 | collectionId, 78 | "nft-metadata" 79 | ); 80 | 81 | const resourceId = await addNftSlotResource( 82 | api, 83 | 3, 84 | Alice, 85 | "added", 86 | collectionId, 87 | nft, 88 | baseId, 89 | slotId, 90 | metadata 91 | ); 92 | 93 | const resource = { 94 | Basic: { 95 | metadata: "basic-resource-nft-minting", 96 | }, 97 | }; 98 | 99 | await replaceResource(api, Bob, collectionId, nft, resourceId, resource); 100 | }); 101 | 102 | after(() => { 103 | api.disconnect(); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /tests/src/setCollectionProperty.test.ts: -------------------------------------------------------------------------------- 1 | import { getApiConnection } from "./substrate/substrate-api"; 2 | import { expectTxFailure } from "./util/helpers"; 3 | import { createCollection, setPropertyCollection } from "./util/tx"; 4 | 5 | describe("integration test: set collection property", () => { 6 | const Alice = "//Alice"; 7 | const Bob = "//Bob"; 8 | 9 | let api: any; 10 | before(async () => { 11 | api = await getApiConnection(); 12 | }); 13 | 14 | it("set collection property", async () => { 15 | await createCollection( 16 | api, 17 | 160, 18 | Alice, 19 | "test-metadata", 20 | null, 21 | "test-symbol" 22 | ).then(async (collectionId) => { 23 | await setPropertyCollection(api, Alice, collectionId, "test_key", "42"); 24 | await setPropertyCollection(api, Alice, collectionId, "test_key", "10"); 25 | await setPropertyCollection( 26 | api, 27 | Alice, 28 | collectionId, 29 | "second_test_key", 30 | "111" 31 | ); 32 | }); 33 | }); 34 | 35 | it("[negative] set non-existing collection property", async () => { 36 | const tx = setPropertyCollection(api, Alice, 9999, "test_key", "42"); 37 | await expectTxFailure(/rmrkCore\.CollectionUnknown/, tx); 38 | }); 39 | 40 | it("[negative] set property not an owner NFT collection issuer", async () => { 41 | await createCollection( 42 | api, 43 | 161, 44 | Bob, 45 | "test-metadata", 46 | null, 47 | "test-symbol" 48 | ).then(async (collectionId) => { 49 | const tx = setPropertyCollection( 50 | api, 51 | Alice, 52 | collectionId, 53 | "test_key", 54 | "42" 55 | ); 56 | await expectTxFailure(/rmrkCore\.NoPermission/, tx); 57 | }); 58 | }); 59 | 60 | after(() => { 61 | api.disconnect(); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /tests/src/setEquippableList.test.ts: -------------------------------------------------------------------------------- 1 | import { getApiConnection } from "./substrate/substrate-api"; 2 | import { expectTxFailure } from "./util/helpers"; 3 | import { 4 | createCollection, 5 | createBase, 6 | setEquippableList, 7 | addToEquippableList, 8 | removeFromEquippableList, 9 | } from "./util/tx"; 10 | 11 | describe("integration test: set slot's Equippable List", () => { 12 | let api: any; 13 | before(async () => { 14 | api = await getApiConnection(); 15 | }); 16 | 17 | const alice = "//Alice"; 18 | const bob = "//Bob"; 19 | 20 | it("set Base's slot Equippable List", async () => { 21 | const collectionIds = [ 22 | await createCollection( 23 | api, 24 | 170, 25 | alice, 26 | "equiplist-collection-metadata", 27 | null, 28 | "equiplist-0" 29 | ), 30 | await createCollection( 31 | api, 32 | 171, 33 | alice, 34 | "equiplist-collection-metadata", 35 | null, 36 | "equiplist-1" 37 | ), 38 | ]; 39 | 40 | const slotId = 202; 41 | 42 | const baseId = await createBase( 43 | api, 44 | alice, 45 | "slotpartany-base-type", 46 | "slotpartany", 47 | [ 48 | { 49 | SlotPart: { 50 | id: slotId, 51 | equippable: "All", 52 | z: 1, 53 | src: "some-fallback-slot-url", 54 | }, 55 | }, 56 | ] 57 | ); 58 | 59 | await setEquippableList(api, alice, baseId, slotId, "All"); 60 | await setEquippableList(api, alice, baseId, slotId, "Empty"); 61 | await setEquippableList(api, alice, baseId, slotId, { 62 | Custom: collectionIds, 63 | }); 64 | await removeFromEquippableList( 65 | api, 66 | alice, 67 | baseId, 68 | slotId, 69 | collectionIds[0] 70 | ); 71 | await addToEquippableList(api, alice, baseId, slotId, collectionIds[0]); 72 | }); 73 | 74 | it("[negative] unable to set equippable list of a slot of non-existing base", async () => { 75 | const maxBaseId = 0xffffffff; 76 | const slotId = 0; 77 | 78 | const tx = setEquippableList(api, alice, maxBaseId, slotId, "All"); 79 | await expectTxFailure(/rmrkEquip\.BaseDoesntExist/, tx); 80 | }); 81 | 82 | it("[negative] unable to set equippable list by a not-an-owner", async () => { 83 | const slotId = 42; 84 | 85 | const baseId = await createBase( 86 | api, 87 | alice, 88 | "slotpartany-base-type", 89 | "slotpartany", 90 | [ 91 | { 92 | SlotPart: { 93 | id: slotId, 94 | equippable: "All", 95 | z: 1, 96 | src: "some-fallback-slot-url", 97 | }, 98 | }, 99 | ] 100 | ); 101 | 102 | const tx = setEquippableList(api, bob, baseId, slotId, "All"); 103 | await expectTxFailure(/rmrkEquip\.PermissionError/, tx); 104 | }); 105 | 106 | it("[negative] unable to set equippable list to a fixed part", async () => { 107 | const fixedPartId = 42; 108 | 109 | const baseId = await createBase( 110 | api, 111 | alice, 112 | "fixedpart-base-type", 113 | "fixedpart", 114 | [ 115 | { 116 | FixedPart: { 117 | id: fixedPartId, 118 | z: 0, 119 | src: "fixed-part-url", 120 | }, 121 | }, 122 | ] 123 | ); 124 | 125 | const tx = setEquippableList(api, alice, baseId, fixedPartId, "All"); 126 | await expectTxFailure(/rmrkEquip\.NoEquippableOnFixedPart/, tx); 127 | }); 128 | 129 | it("[negative] unable to set equippable list to non-existing slot", async () => { 130 | const slotId = 777; 131 | const maxSlotId = 0xffffffff; 132 | 133 | const baseId = await createBase( 134 | api, 135 | alice, 136 | "slotpartany-base-type", 137 | "slotpartany", 138 | [ 139 | { 140 | SlotPart: { 141 | id: slotId, 142 | equippable: "All", 143 | z: 1, 144 | src: "some-fallback-slot-url", 145 | }, 146 | }, 147 | ] 148 | ); 149 | 150 | const tx = setEquippableList(api, alice, baseId, maxSlotId, "All"); 151 | await expectTxFailure(/rmrkEquip\.PartDoesntExist/, tx); 152 | }); 153 | 154 | after(() => { 155 | api.disconnect(); 156 | }); 157 | }); 158 | -------------------------------------------------------------------------------- /tests/src/setNftProperty.test.ts: -------------------------------------------------------------------------------- 1 | import { getApiConnection } from "./substrate/substrate-api"; 2 | import { NftIdTuple } from "./util/fetch"; 3 | import { expectTxFailure } from "./util/helpers"; 4 | import { createCollection, mintNft, sendNft, setNftProperty } from "./util/tx"; 5 | 6 | describe("integration test: set NFT property", () => { 7 | let api: any; 8 | before(async () => { 9 | api = await getApiConnection(); 10 | }); 11 | 12 | const alice = "//Alice"; 13 | const bob = "//Bob"; 14 | 15 | const createTestCollection = async ( 16 | issuerUri: string, 17 | collectionId: number 18 | ) => { 19 | return await createCollection( 20 | api, 21 | collectionId, 22 | issuerUri, 23 | "setprop-nft-collection-metadata", 24 | null, 25 | "setprop" 26 | ); 27 | }; 28 | 29 | it("set NFT property", async () => { 30 | const ownerAlice = alice; 31 | 32 | const collectionId = await createTestCollection(alice, 180); 33 | const nftId = await mintNft( 34 | api, 35 | 300, 36 | alice, 37 | ownerAlice, 38 | collectionId, 39 | "prop-nft" 40 | ); 41 | 42 | await setNftProperty( 43 | api, 44 | alice, 45 | collectionId, 46 | nftId, 47 | "test-key", 48 | "test-key-value" 49 | ); 50 | await setNftProperty( 51 | api, 52 | alice, 53 | collectionId, 54 | nftId, 55 | "test-key", 56 | "updated-key-value" 57 | ); 58 | await setNftProperty( 59 | api, 60 | alice, 61 | collectionId, 62 | nftId, 63 | "second-test-key", 64 | "second-test-key-value" 65 | ); 66 | }); 67 | 68 | it("[negative] unable to set a property of non-existing NFT", async () => { 69 | const collectionId = 0; 70 | const maxNftId = 0xffffffff; 71 | 72 | const tx = setNftProperty( 73 | api, 74 | alice, 75 | collectionId, 76 | maxNftId, 77 | "test-key", 78 | "test-value" 79 | ); 80 | 81 | await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); 82 | }); 83 | 84 | it("[negative] unable to set a property by not-an-owner", async () => { 85 | const ownerAlice = alice; 86 | 87 | const collectionId = await createTestCollection(alice, 181); 88 | const nftId = await mintNft( 89 | api, 90 | 301, 91 | alice, 92 | ownerAlice, 93 | collectionId, 94 | "prop-nft" 95 | ); 96 | 97 | const tx = setNftProperty( 98 | api, 99 | bob, 100 | collectionId, 101 | nftId, 102 | "test-key", 103 | "test-key-value" 104 | ); 105 | 106 | await expectTxFailure(/rmrkCore\.NoPermission/, tx); 107 | }); 108 | 109 | it("set a property to nested NFT", async () => { 110 | const ownerAlice = alice; 111 | 112 | const collectionId = await createTestCollection(alice, 182); 113 | const parentNftId = await mintNft( 114 | api, 115 | 302, 116 | alice, 117 | ownerAlice, 118 | collectionId, 119 | "prop-parent-nft" 120 | ); 121 | const childNftId = await mintNft( 122 | api, 123 | 303, 124 | alice, 125 | ownerAlice, 126 | collectionId, 127 | "prop-child-nft" 128 | ); 129 | 130 | const ownerNft: NftIdTuple = [collectionId, parentNftId]; 131 | 132 | await sendNft(api, "sent", ownerAlice, collectionId, childNftId, ownerNft); 133 | 134 | await setNftProperty( 135 | api, 136 | alice, 137 | collectionId, 138 | childNftId, 139 | "test-key", 140 | "test-key-value" 141 | ); 142 | }); 143 | 144 | it("[negative] set a property to nested NFT (by not-root-owner)", async () => { 145 | const ownerAlice = alice; 146 | 147 | const collectionId = await createTestCollection(alice, 183); 148 | const parentNftId = await mintNft( 149 | api, 150 | 0, 151 | alice, 152 | ownerAlice, 153 | collectionId, 154 | "prop-parent-nft" 155 | ); 156 | const childNftId = await mintNft( 157 | api, 158 | 304, 159 | alice, 160 | ownerAlice, 161 | collectionId, 162 | "prop-child-nft" 163 | ); 164 | 165 | const ownerNft: NftIdTuple = [collectionId, parentNftId]; 166 | 167 | await sendNft(api, "sent", ownerAlice, collectionId, childNftId, ownerNft); 168 | 169 | const tx = setNftProperty( 170 | api, 171 | bob, 172 | collectionId, 173 | childNftId, 174 | "test-key", 175 | "test-key-value" 176 | ); 177 | 178 | await expectTxFailure(/rmrkCore\.NoPermission/, tx); 179 | }); 180 | 181 | after(() => { 182 | api.disconnect(); 183 | }); 184 | }); 185 | -------------------------------------------------------------------------------- /tests/src/setResourcePriorities.test.ts: -------------------------------------------------------------------------------- 1 | import { getApiConnection } from "./substrate/substrate-api"; 2 | import { expectTxFailure } from "./util/helpers"; 3 | import { mintNft, createCollection, setResourcePriorities } from "./util/tx"; 4 | 5 | describe("integration test: set NFT resource priorities", () => { 6 | let api: any; 7 | before(async () => { 8 | api = await getApiConnection(); 9 | }); 10 | 11 | const alice = "//Alice"; 12 | const bob = "//Bob"; 13 | 14 | const createTestCollection = (issuerUri: string, collectionId: number) => { 15 | return createCollection( 16 | api, 17 | collectionId, 18 | issuerUri, 19 | "resprio-collection-metadata", 20 | null, 21 | "resprio" 22 | ); 23 | }; 24 | 25 | it("set NFT resource priorities", async () => { 26 | const owner = alice; 27 | 28 | const collectionId = await createTestCollection(alice, 190); 29 | const nftId = await mintNft( 30 | api, 31 | 0, 32 | alice, 33 | owner, 34 | collectionId, 35 | "resprio-nft-metadata" 36 | ); 37 | 38 | await setResourcePriorities(api, alice, collectionId, nftId, [10, 42]); 39 | }); 40 | 41 | it("[negative] set NFT resource priorities by a not-an-owner", async () => { 42 | const owner = alice; 43 | const attacker = bob; 44 | 45 | const collectionId = await createTestCollection(alice, 191); 46 | const nftId = await mintNft( 47 | api, 48 | 0, 49 | alice, 50 | owner, 51 | collectionId, 52 | "resprio-nft-metadata" 53 | ); 54 | 55 | const tx = setResourcePriorities( 56 | api, 57 | attacker, 58 | collectionId, 59 | nftId, 60 | [10, 42] 61 | ); 62 | 63 | await expectTxFailure(/rmrkCore\.NoPermission/, tx); 64 | }); 65 | 66 | it("[negative] set NFT resource priorities to non-existing NFT", async () => { 67 | const owner = alice; 68 | 69 | const collectionId = 0; 70 | const maxNftId = 0xffffffff; 71 | 72 | const tx = setResourcePriorities( 73 | api, 74 | alice, 75 | collectionId, 76 | maxNftId, 77 | [10, 42] 78 | ); 79 | 80 | await expectTxFailure(/rmrkCore\.NoAvailableNftId/, tx); 81 | }); 82 | 83 | after(() => { 84 | api.disconnect(); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /tests/src/substrate/privateKey.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2022 Unique Network (Gibraltar) Ltd. 2 | // This file is part of Unique Network. 3 | 4 | // Unique Network is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Unique Network is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Unique Network. If not, see . 16 | 17 | import {Keyring} from '@polkadot/api'; 18 | import {IKeyringPair} from '@polkadot/types/types'; 19 | 20 | export default function privateKey( 21 | account: string, 22 | ss58Format: number 23 | ): IKeyringPair { 24 | const keyring = new Keyring({ ss58Format, type: "sr25519" }); 25 | 26 | return keyring.addFromUri(account); 27 | } 28 | -------------------------------------------------------------------------------- /tests/src/substrate/promisify-substrate.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2019-2022 Unique Network (Gibraltar) Ltd. 2 | // This file is part of Unique Network. 3 | 4 | // Unique Network is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // Unique Network is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with Unique Network. If not, see . 16 | 17 | import {ApiPromise} from '@polkadot/api'; 18 | 19 | type PromiseType = T extends PromiseLike ? TInner : T; 20 | 21 | export default function promisifySubstrate any>(api: ApiPromise, action: T): (...args: Parameters) => Promise>> { 22 | return (...args: Parameters) => { 23 | const promise = new Promise>>((resolve: ((result: PromiseType>) => void) | undefined, reject: ((error: any) => void) | undefined) => { 24 | const cleanup = () => { 25 | api.off('disconnected', fail); 26 | api.off('error', fail); 27 | resolve = undefined; 28 | reject = undefined; 29 | }; 30 | 31 | const success = (r: any) => { 32 | resolve && resolve(r); 33 | cleanup(); 34 | }; 35 | const fail = (error: any) => { 36 | reject && reject(error); 37 | cleanup(); 38 | }; 39 | 40 | api.on('disconnected', fail); 41 | api.on('error', fail); 42 | 43 | const result = action(...args); 44 | Promise.resolve(result) 45 | .then(success, fail); 46 | 47 | }); 48 | return promise as any; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /tests/src/util/fetch.ts: -------------------------------------------------------------------------------- 1 | import { ApiPromise } from "@polkadot/api"; 2 | import { Option, Vec, u32 } from "@polkadot/types-codec"; 3 | import { ITuple } from "@polkadot/types-codec/types"; 4 | import type { 5 | RmrkTraitsCollectionCollectionInfo as Collection, 6 | RmrkTraitsNftNftInfo as Nft, 7 | RmrkTraitsResourceResourceInfo as Resource, 8 | RmrkTraitsBaseBaseInfo as Base, 9 | RmrkTraitsPartPartType as PartType, 10 | RmrkTraitsNftNftChild as NftChild, 11 | RmrkTraitsTheme as Theme, 12 | RmrkTraitsPropertyPropertyInfo as Property, 13 | } from "../interfaces/rmrk/types"; // '@polkadot/types/lookup'; 14 | import "../interfaces/augment-api"; 15 | import "../interfaces/augment-api-query"; 16 | import privateKey from "../substrate/privateKey"; 17 | 18 | export type NftIdTuple = [number, number]; 19 | 20 | export async function getCollection( 21 | api: ApiPromise, 22 | id: number 23 | ): Promise> { 24 | return api.rpc.rmrk.collectionById(id); 25 | } 26 | 27 | export async function getOwnedNftsInCollection( 28 | api: ApiPromise, 29 | ownerUri: string, 30 | collectionId: number 31 | ): Promise { 32 | const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; 33 | const owner = privateKey(ownerUri, Number(ss58Format)); 34 | 35 | return (await api.rpc.rmrk.accountTokens(owner.address, collectionId)).map( 36 | (value) => value.toNumber() 37 | ); 38 | } 39 | 40 | export async function getNft( 41 | api: ApiPromise, 42 | collectionId: number, 43 | nftId: number 44 | ): Promise> { 45 | return api.rpc.rmrk.nftById(collectionId, nftId); 46 | } 47 | 48 | export async function getOwnedNfts( 49 | api: ApiPromise, 50 | ownerUri: string, 51 | startIndex: string | null, 52 | count: string | null, 53 | ): Promise>> { 54 | const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; 55 | const owner = privateKey(ownerUri, Number(ss58Format)); 56 | 57 | return api.rpc.rmrk.nftsOwnedBy(owner.address, startIndex, count); 58 | } 59 | 60 | export async function getPropertiesOfOwnedNfts( 61 | api: ApiPromise, 62 | ownerUri: string, 63 | startIndex: string | null = null, 64 | count: string | null = null, 65 | ): Promise]>>> { 66 | const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; 67 | const owner = privateKey(ownerUri, Number(ss58Format)); 68 | 69 | return api.rpc.rmrk.propertiesOfNftsOwnedBy(owner.address, startIndex, count); 70 | } 71 | 72 | export async function getCollectionProperties( 73 | api: ApiPromise, 74 | collectionId: number 75 | ): Promise { 76 | return (await api.rpc.rmrk.collectionProperties(collectionId)).toArray(); 77 | } 78 | 79 | export async function getNftProperties( 80 | api: ApiPromise, 81 | collectionId: number, 82 | nftId: number 83 | ): Promise { 84 | return (await api.rpc.rmrk.nftProperties(collectionId, nftId)).toArray(); 85 | } 86 | 87 | export async function getChildren( 88 | api: ApiPromise, 89 | collectionId: number, 90 | nftId: number 91 | ): Promise { 92 | return (await api.rpc.rmrk.nftChildren(collectionId, nftId)).toArray(); 93 | } 94 | 95 | export async function getBase( 96 | api: ApiPromise, 97 | baseId: number 98 | ): Promise> { 99 | return api.rpc.rmrk.base(baseId); 100 | } 101 | 102 | export async function getParts( 103 | api: ApiPromise, 104 | baseId: number 105 | ): Promise { 106 | return (await api.rpc.rmrk.baseParts(baseId)).toArray(); 107 | } 108 | 109 | export async function getEquippableList( 110 | api: ApiPromise, 111 | baseId: number, 112 | slotId: number 113 | ): Promise<"All" | "Empty" | { Custom: number[] } | null> { 114 | const parts = await getParts(api, baseId); 115 | 116 | const part = parts.find((part) => { 117 | if (part.isSlotPart) { 118 | return part.asSlotPart.id.toNumber() === slotId; 119 | } else { 120 | return false; 121 | } 122 | }); 123 | 124 | if (part) { 125 | const slot = part.asSlotPart; 126 | if (slot.equippable.isCustom) { 127 | return { 128 | Custom: slot.equippable.asCustom 129 | .toArray() 130 | .map((collectionId) => collectionId.toNumber()), 131 | }; 132 | } else if (slot.equippable.isAll) { 133 | return "All"; 134 | } else { 135 | return "Empty"; 136 | } 137 | } else { 138 | return null; 139 | } 140 | } 141 | 142 | export async function getResourcePriority( 143 | api: ApiPromise, 144 | collectionId: number, 145 | nftId: number, 146 | resourceId: number 147 | ): Promise { 148 | return ( 149 | await api.rpc.rmrk.nftResourcePriority(collectionId, nftId, resourceId) 150 | ) 151 | .unwrap() 152 | .toNumber(); 153 | } 154 | 155 | export async function getThemeNames( 156 | api: ApiPromise, 157 | baseId: number 158 | ): Promise { 159 | return (await api.rpc.rmrk.themeNames(baseId)).map((name) => name.toUtf8()); 160 | } 161 | 162 | export async function getTheme( 163 | api: ApiPromise, 164 | baseId: number, 165 | themeName: string, 166 | keys: string[] | null = null 167 | ): Promise> { 168 | return api.rpc.rmrk.themes(baseId, themeName, keys); 169 | } 170 | 171 | export async function getResources( 172 | api: ApiPromise, 173 | collectionId: number, 174 | nftId: number 175 | ): Promise { 176 | return (await api.rpc.rmrk.nftResources(collectionId, nftId)).toArray(); 177 | } 178 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "moduleResolution": "node", 5 | "esModuleInterop": true, 6 | "resolveJsonModule": true, 7 | "module": "commonjs", 8 | "sourceMap": true, 9 | "outDir": "dist", 10 | "strict": true, 11 | "paths": { 12 | "@polkadot/types/lookup": [ 13 | "./src/interfaces/types-lookup.ts" 14 | ], 15 | } 16 | }, 17 | "include": [ 18 | "./src/**/*", 19 | "./src/interfaces/*.ts" 20 | ], 21 | "lib": [ 22 | "es2017" 23 | ], 24 | } 25 | -------------------------------------------------------------------------------- /traits/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rmrk-traits" 3 | description = "Shared traits" 4 | repository = "https://github.com/rmrk-team/rmrk-substrate" 5 | license = "Apache-2.0" 6 | version = "0.0.1" 7 | authors = ["RMRK Team"] 8 | edition = "2021" 9 | 10 | [package.metadata.docs.rs] 11 | targets = ['x86_64-unknown-linux-gnu'] 12 | 13 | [dependencies] 14 | serde = { version = "1.0.111", default-features = false, features = ["derive"] } 15 | sp-runtime = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 16 | sp-std = { default-features = false, version = "5.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 17 | codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ 18 | "derive", 19 | ] } 20 | scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } 21 | frame-support = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 22 | frame-system = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 23 | frame-benchmarking = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", optional = true, branch = "polkadot-v0.9.36" } 24 | 25 | pallet-balances = { default-features = false, version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 26 | 27 | [dev-dependencies] 28 | sp-runtime = { default-features = false, version = "7.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 29 | sp-std = { default-features = false, version = "5.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.36" } 30 | 31 | [features] 32 | default = ["std"] 33 | std = [ 34 | "serde/std", 35 | "codec/std", 36 | "scale-info/std", 37 | "frame-support/std", 38 | "frame-system/std", 39 | "frame-benchmarking/std", 40 | "pallet-balances/std", 41 | ] 42 | 43 | 44 | runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] 45 | try-runtime = ["frame-support/try-runtime"] 46 | -------------------------------------------------------------------------------- /traits/src/base.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021-2022 RMRK 2 | // This file is part of rmrk-substrate. 3 | // License: Apache 2.0 modified by RMRK, see LICENSE.md 4 | 5 | use super::{part::EquippableList, theme::Theme}; 6 | use crate::{ 7 | primitives::{BaseId, ResourceId, SlotId}, 8 | serialize, 9 | }; 10 | use codec::{Decode, Encode}; 11 | use frame_support::pallet_prelude::MaxEncodedLen; 12 | use scale_info::TypeInfo; 13 | use sp_runtime::DispatchError; 14 | 15 | #[cfg(feature = "std")] 16 | use serde::Serialize; 17 | 18 | #[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))] 19 | #[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] 20 | #[cfg_attr( 21 | feature = "std", 22 | serde(bound = r#" 23 | AccountId: Serialize, 24 | BoundedString: AsRef<[u8]> 25 | "#) 26 | )] 27 | pub struct BaseInfo { 28 | /// Original creator of the Base 29 | pub issuer: AccountId, 30 | 31 | /// Specifies how an NFT should be rendered, ie "svg" 32 | #[cfg_attr(feature = "std", serde(with = "serialize::vec"))] 33 | pub base_type: BoundedString, 34 | 35 | /// User provided symbol during Base creation 36 | #[cfg_attr(feature = "std", serde(with = "serialize::vec"))] 37 | pub symbol: BoundedString, 38 | } 39 | 40 | pub enum EquippableOperation { 41 | /// Adds a new collection that is allowed to be equipped. 42 | Add(CollectionId), 43 | /// Removes a collection from the list of equippables. 44 | Remove(CollectionId), 45 | /// Overrides all of the equippables. 46 | Override(EquippableList), 47 | } 48 | 49 | // Abstraction over a Base system. 50 | pub trait Base< 51 | AccountId, 52 | CollectionId, 53 | NftId, 54 | BoundedString, 55 | BoundedParts, 56 | BoundedCollectionList, 57 | BoundedThemeProperties, 58 | > 59 | { 60 | fn base_create( 61 | issuer: AccountId, 62 | base_type: BoundedString, 63 | symbol: BoundedString, 64 | parts: BoundedParts, 65 | ) -> Result; 66 | fn base_change_issuer( 67 | base_id: BaseId, 68 | new_issuer: AccountId, 69 | ) -> Result<(AccountId, BaseId), DispatchError>; 70 | fn do_equip( 71 | issuer: AccountId, // Maybe don't need? 72 | item: (CollectionId, NftId), 73 | equipper: (CollectionId, NftId), 74 | resource_id: ResourceId, 75 | base_id: BaseId, // Maybe BaseId ? 76 | slot: SlotId, // Maybe SlotId ? 77 | ) -> Result<(CollectionId, NftId, BaseId, SlotId), DispatchError>; 78 | fn do_unequip( 79 | issuer: AccountId, // Maybe don't need? 80 | item: (CollectionId, NftId), 81 | equipper: (CollectionId, NftId), 82 | base_id: BaseId, // Maybe BaseId ? 83 | slot: SlotId, // Maybe SlotId ? 84 | ) -> Result<(CollectionId, NftId, BaseId, SlotId), DispatchError>; 85 | fn do_equippable( 86 | issuer: AccountId, 87 | base_id: BaseId, 88 | slot: SlotId, 89 | operation: EquippableOperation, 90 | ) -> Result<(BaseId, SlotId), DispatchError>; 91 | fn add_theme( 92 | issuer: AccountId, 93 | base_id: BaseId, 94 | theme: Theme, 95 | ) -> Result<(), DispatchError>; 96 | } 97 | -------------------------------------------------------------------------------- /traits/src/budget.rs: -------------------------------------------------------------------------------- 1 | use core::cell::Cell; 2 | 3 | pub trait Budget { 4 | /// Returns true while not exceeded 5 | fn consume(&self) -> bool { 6 | self.consume_custom(1) 7 | } 8 | /// Returns true while not exceeded 9 | /// Implementations should use interior mutabilitiy 10 | fn consume_custom(&self, calls: u32) -> bool; 11 | 12 | fn budget_left_value(&self) -> u32 { 13 | self.get_budget_left_value() 14 | } 15 | fn budget_consumed_value(&self) -> u32 { 16 | self.get_budget_consumed_value() 17 | } 18 | 19 | fn get_budget_left_value(&self) -> u32; 20 | fn get_budget_consumed_value(&self) -> u32; 21 | } 22 | 23 | pub struct Value { 24 | budget_left: Cell, 25 | budget_consumed: Cell, 26 | } 27 | 28 | impl Value { 29 | pub fn new(v: u32) -> Self { 30 | Self { budget_left: Cell::new(v), budget_consumed: Cell::new(0) } 31 | } 32 | pub fn refund(self) -> u32 { 33 | self.budget_left.get() 34 | } 35 | } 36 | 37 | impl Budget for Value { 38 | fn consume_custom(&self, calls: u32) -> bool { 39 | let (budget_left_result, sub_overflown) = self.budget_left.get().overflowing_sub(calls); 40 | let (budget_consumed_result, add_overflown) = 41 | self.budget_consumed.get().overflowing_add(calls); 42 | if sub_overflown || add_overflown { 43 | return false 44 | } 45 | self.budget_left.set(budget_left_result); 46 | self.budget_consumed.set(budget_consumed_result); 47 | true 48 | } 49 | fn get_budget_left_value(&self) -> u32 { 50 | self.budget_left.get() 51 | } 52 | fn get_budget_consumed_value(&self) -> u32 { 53 | self.budget_consumed.get() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /traits/src/collection.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021-2022 RMRK 2 | // This file is part of rmrk-substrate. 3 | // License: Apache 2.0 modified by RMRK, see LICENSE.md 4 | 5 | use codec::{Decode, Encode}; 6 | use frame_support::pallet_prelude::MaxEncodedLen; 7 | use scale_info::TypeInfo; 8 | use sp_runtime::{DispatchError, DispatchResult}; 9 | 10 | #[cfg(feature = "std")] 11 | use serde::Serialize; 12 | 13 | use crate::serialize; 14 | use sp_std::result::Result; 15 | 16 | /// Collection info. 17 | #[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))] 18 | #[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] 19 | #[cfg_attr( 20 | feature = "std", 21 | serde(bound = r#" 22 | AccountId: Serialize, 23 | BoundedString: AsRef<[u8]>, 24 | BoundedSymbol: AsRef<[u8]> 25 | "#) 26 | )] 27 | pub struct CollectionInfo { 28 | /// Current bidder and bid price. 29 | pub issuer: AccountId, 30 | 31 | #[cfg_attr(feature = "std", serde(with = "serialize::vec"))] 32 | pub metadata: BoundedString, 33 | pub max: Option, 34 | 35 | #[cfg_attr(feature = "std", serde(with = "serialize::vec"))] 36 | pub symbol: BoundedSymbol, 37 | pub nfts_count: u32, 38 | } 39 | 40 | /// Abstraction over a Collection system. 41 | #[allow(clippy::upper_case_acronyms)] 42 | pub trait Collection { 43 | fn issuer(collection_id: CollectionId) -> Option; 44 | fn collection_create( 45 | issuer: AccountId, 46 | collection_id: CollectionId, 47 | metadata: BoundedString, 48 | max: Option, 49 | symbol: BoundedSymbol, 50 | ) -> Result<(), DispatchError>; 51 | fn collection_burn(issuer: AccountId, collection_id: CollectionId) -> DispatchResult; 52 | fn collection_change_issuer( 53 | collection_id: CollectionId, 54 | new_issuer: AccountId, 55 | ) -> Result<(AccountId, CollectionId), DispatchError>; 56 | fn collection_lock( 57 | sender: AccountId, 58 | collection_id: CollectionId, 59 | ) -> Result; 60 | } 61 | -------------------------------------------------------------------------------- /traits/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021-2022 RMRK 2 | // This file is part of rmrk-substrate. 3 | // License: Apache 2.0 modified by RMRK, see LICENSE.md 4 | 5 | #![cfg_attr(not(feature = "std"), no_std)] 6 | 7 | pub mod base; 8 | pub mod budget; 9 | pub mod collection; 10 | pub mod misc; 11 | pub mod nft; 12 | pub mod part; 13 | pub mod phantom_type; 14 | pub mod priority; 15 | pub mod property; 16 | pub mod resource; 17 | mod serialize; 18 | pub mod theme; 19 | 20 | pub use base::{Base, BaseInfo}; 21 | pub use collection::{Collection, CollectionInfo}; 22 | pub use misc::TransferHooks; 23 | pub use nft::{AccountIdOrCollectionNftTuple, Nft, NftChild, NftInfo, RoyaltyInfo}; 24 | pub use part::{EquippableList, FixedPart, PartType, SlotPart}; 25 | pub use priority::Priority; 26 | pub use property::{Property, PropertyInfo}; 27 | pub use resource::{ 28 | BasicResource, ComposableResource, Resource, ResourceInfo, ResourceInfoMin, ResourceTypes, 29 | SlotResource, 30 | }; 31 | pub use theme::{Theme, ThemeProperty}; 32 | pub mod primitives { 33 | pub type CollectionId = u32; 34 | pub type ResourceId = u32; 35 | pub type NftId = u32; 36 | pub type BaseId = u32; 37 | pub type SlotId = u32; 38 | pub type PartId = u32; 39 | pub type ZIndex = u32; 40 | } 41 | pub use phantom_type::PhantomType; 42 | -------------------------------------------------------------------------------- /traits/src/misc.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021-2022 RMRK 2 | // This file is part of rmrk-substrate. 3 | // License: Apache 2.0 modified by RMRK, see LICENSE.md 4 | 5 | /// Trait for pre-checks and post-checks for transfers that can be implemented downstream to extend 6 | /// the logic of RMRK's current funcitonality. 7 | pub trait TransferHooks { 8 | /// Check if the NFT's pre-checks and post-checks for the transfer function based on the sender, 9 | /// `collection_id` and `nft_id` parameters. 10 | fn pre_check(sender: &AccountId, collection_id: &CollectionId, nft_id: &NftId) -> bool; 11 | fn post_transfer( 12 | sender: &AccountId, 13 | recipient: &AccountId, 14 | collection_id: &CollectionId, 15 | nft_id: &NftId, 16 | ) -> bool; 17 | } 18 | 19 | impl TransferHooks for () { 20 | fn pre_check(_sender: &AccountId, _collection_id: &CollectionId, _nft_id: &NftId) -> bool { 21 | true 22 | } 23 | 24 | fn post_transfer( 25 | _sender: &AccountId, 26 | _recipient: &AccountId, 27 | _collection_id: &CollectionId, 28 | _nft_id: &NftId, 29 | ) -> bool { 30 | true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /traits/src/nft.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021-2022 RMRK 2 | // This file is part of rmrk-substrate. 3 | // License: Apache 2.0 modified by RMRK, see LICENSE.md 4 | #![allow(clippy::too_many_arguments)] 5 | 6 | use codec::{Decode, Encode}; 7 | use scale_info::TypeInfo; 8 | use sp_runtime::DispatchError; 9 | use sp_std::cmp::Eq; 10 | 11 | use frame_support::pallet_prelude::*; 12 | use sp_runtime::Permill; 13 | 14 | use crate::{ 15 | budget::Budget, 16 | primitives::{ResourceId, SlotId}, 17 | serialize, 18 | }; 19 | use sp_std::result::Result; 20 | 21 | #[cfg(feature = "std")] 22 | use serde::Serialize; 23 | 24 | #[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, Debug, TypeInfo, MaxEncodedLen)] 25 | #[cfg_attr(feature = "std", derive(Serialize))] 26 | pub enum AccountIdOrCollectionNftTuple { 27 | AccountId(AccountId), 28 | CollectionAndNftTuple(CollectionId, NftId), 29 | } 30 | 31 | /// Royalty information (recipient and amount) 32 | #[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))] 33 | #[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] 34 | pub struct RoyaltyInfo { 35 | /// Recipient (AccountId) of the royalty 36 | pub recipient: AccountId, 37 | /// Amount (Permill) of the royalty 38 | pub amount: RoyaltyAmount, 39 | } 40 | 41 | /// Nft info. 42 | #[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))] 43 | #[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] 44 | #[cfg_attr( 45 | feature = "std", 46 | serde(bound = r#" 47 | AccountId: Serialize, 48 | RoyaltyAmount: Serialize, 49 | BoundedString: AsRef<[u8]>, 50 | NftId: Serialize, 51 | CollectionId: Serialize, 52 | "#) 53 | )] 54 | pub struct NftInfo { 55 | /// The owner of the NFT, can be either an Account or a tuple (CollectionId, NftId) 56 | pub owner: AccountIdOrCollectionNftTuple, 57 | /// Royalty (optional) 58 | pub royalty: Option>, 59 | 60 | /// Arbitrary data about an instance, e.g. IPFS hash 61 | #[cfg_attr(feature = "std", serde(with = "serialize::vec"))] 62 | pub metadata: BoundedString, 63 | 64 | /// Contains an optional `ResourceId` and the `SlotId` for the equipped nft. 65 | pub equipped: Option<(ResourceId, SlotId)>, 66 | /// Pending state (if sent to NFT) 67 | pub pending: bool, 68 | /// transferability ( non-transferable is "souldbound" ) 69 | pub transferable: bool, 70 | } 71 | 72 | #[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))] 73 | #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] 74 | pub struct NftChild { 75 | pub collection_id: CollectionId, 76 | pub nft_id: NftId, 77 | } 78 | 79 | /// Abstraction over a Nft system. 80 | #[allow(clippy::upper_case_acronyms)] 81 | pub trait Nft { 82 | fn nft_mint( 83 | sender: AccountId, 84 | owner: AccountId, 85 | nft_id: NftId, 86 | collection_id: CollectionId, 87 | royalty_recipient: Option, 88 | royalty_amount: Option, 89 | metadata: BoundedString, 90 | transferable: bool, 91 | resources: Option, 92 | ) -> Result<(CollectionId, NftId), DispatchError>; 93 | fn nft_mint_directly_to_nft( 94 | sender: AccountId, 95 | owner: (CollectionId, NftId), 96 | nft_id: NftId, 97 | collection_id: CollectionId, 98 | royalty_recipient: Option, 99 | royalty_amount: Option, 100 | metadata: BoundedString, 101 | transferable: bool, 102 | resources: Option, 103 | ) -> Result<(CollectionId, NftId), DispatchError>; 104 | fn nft_burn( 105 | owner: AccountId, 106 | collection_id: CollectionId, 107 | nft_id: NftId, 108 | budget: &dyn Budget, 109 | ) -> DispatchResultWithPostInfo; 110 | fn nft_send( 111 | sender: AccountId, 112 | collection_id: CollectionId, 113 | nft_id: NftId, 114 | new_owner: AccountIdOrCollectionNftTuple, 115 | ) -> Result<(AccountId, bool), DispatchError>; 116 | fn nft_accept( 117 | sender: AccountId, 118 | collection_id: CollectionId, 119 | nft_id: NftId, 120 | new_owner: AccountIdOrCollectionNftTuple, 121 | ) -> Result<(AccountId, CollectionId, NftId), DispatchError>; 122 | fn nft_reject( 123 | sender: AccountId, 124 | collection_id: CollectionId, 125 | nft_id: NftId, 126 | ) -> DispatchResultWithPostInfo; 127 | } 128 | -------------------------------------------------------------------------------- /traits/src/part.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021-2022 RMRK 2 | // This file is part of rmrk-substrate. 3 | // License: Apache 2.0 modified by RMRK, see LICENSE.md 4 | 5 | use crate::{ 6 | primitives::{CollectionId, PartId, ZIndex}, 7 | serialize, 8 | }; 9 | use codec::{Decode, Encode}; 10 | use frame_support::pallet_prelude::MaxEncodedLen; 11 | use scale_info::TypeInfo; 12 | 13 | #[cfg(feature = "std")] 14 | use serde::Serialize; 15 | 16 | // #[cfg_attr(feature = "std", derive(PartialEq, Eq))] 17 | #[cfg_attr(feature = "std", derive(Serialize))] 18 | #[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)] 19 | #[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))] 20 | pub struct FixedPart { 21 | pub id: PartId, 22 | pub z: ZIndex, 23 | 24 | #[cfg_attr(feature = "std", serde(with = "serialize::vec"))] 25 | pub src: BoundedString, 26 | } 27 | 28 | #[cfg_attr(feature = "std", derive(Serialize))] 29 | #[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)] 30 | #[cfg_attr(feature = "std", serde(bound = "BoundedCollectionList: AsRef<[CollectionId]>"))] 31 | pub enum EquippableList { 32 | All, 33 | Empty, 34 | Custom(#[cfg_attr(feature = "std", serde(with = "serialize::vec"))] BoundedCollectionList), 35 | } 36 | 37 | // #[cfg_attr(feature = "std", derive(PartialEq, Eq))] 38 | #[cfg_attr(feature = "std", derive(Serialize))] 39 | #[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)] 40 | #[cfg_attr( 41 | feature = "std", 42 | serde(bound = r#" 43 | BoundedString: AsRef<[u8]>, 44 | BoundedCollectionList: AsRef<[CollectionId]> 45 | "#) 46 | )] 47 | pub struct SlotPart { 48 | pub id: PartId, 49 | pub equippable: EquippableList, 50 | #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))] 51 | pub src: Option, 52 | pub z: ZIndex, 53 | } 54 | 55 | // #[cfg_attr(feature = "std", derive(PartialEq, Eq))] 56 | #[cfg_attr(feature = "std", derive(Serialize))] 57 | #[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)] 58 | #[cfg_attr( 59 | feature = "std", 60 | serde(bound = r#" 61 | BoundedString: AsRef<[u8]>, 62 | BoundedCollectionList: AsRef<[CollectionId]> 63 | "#) 64 | )] 65 | pub enum PartType { 66 | FixedPart(FixedPart), 67 | SlotPart(SlotPart), 68 | } 69 | -------------------------------------------------------------------------------- /traits/src/phantom_type.rs: -------------------------------------------------------------------------------- 1 | use codec::{Decode, Encode, MaxEncodedLen}; 2 | use scale_info::TypeInfo; 3 | 4 | #[derive(Encode, Decode, PartialEq, Clone, Debug)] 5 | pub struct PhantomType(core::marker::PhantomData); 6 | 7 | impl TypeInfo for PhantomType { 8 | type Identity = PhantomType; 9 | 10 | fn type_info() -> scale_info::Type { 11 | use scale_info::{build::Fields, type_params, Path, Type}; 12 | Type::builder() 13 | .path(Path::new("phantom_type", "PhantomType")) 14 | .type_params(type_params!(T)) 15 | .composite(Fields::unnamed().field(|b| b.ty::<[T; 0]>())) 16 | } 17 | } 18 | impl MaxEncodedLen for PhantomType { 19 | fn max_encoded_len() -> usize { 20 | 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /traits/src/priority.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021-2022 RMRK 2 | // This file is part of rmrk-substrate. 3 | // License: Apache 2.0 modified by RMRK, see LICENSE.md 4 | 5 | use frame_support::pallet_prelude::DispatchResultWithPostInfo; 6 | // use sp_runtime::DispatchResult; 7 | 8 | /// Abstraction over a Priority system. 9 | #[allow(clippy::upper_case_acronyms)] 10 | pub trait Priority { 11 | fn priority_set( 12 | sender: AccountId, 13 | collection_id: CollectionId, 14 | nft_id: NftId, 15 | priorities: BoundedPriorities, 16 | ) -> DispatchResultWithPostInfo; 17 | } 18 | -------------------------------------------------------------------------------- /traits/src/property.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021-2022 RMRK 2 | // This file is part of rmrk-substrate. 3 | // License: Apache 2.0 modified by RMRK, see LICENSE.md 4 | 5 | use codec::{Decode, Encode}; 6 | use scale_info::TypeInfo; 7 | use sp_runtime::DispatchResult; 8 | 9 | #[cfg(feature = "std")] 10 | use serde::Serialize; 11 | 12 | use crate::serialize; 13 | 14 | #[cfg_attr(feature = "std", derive(Serialize))] 15 | #[derive(Encode, Decode, PartialEq, TypeInfo)] 16 | #[cfg_attr( 17 | feature = "std", 18 | serde(bound = r#" 19 | BoundedKey: AsRef<[u8]>, 20 | BoundedValue: AsRef<[u8]> 21 | "#) 22 | )] 23 | pub struct PropertyInfo { 24 | /// Key of the property 25 | #[cfg_attr(feature = "std", serde(with = "serialize::vec"))] 26 | pub key: BoundedKey, 27 | 28 | /// Value of the property 29 | #[cfg_attr(feature = "std", serde(with = "serialize::vec"))] 30 | pub value: BoundedValue, 31 | } 32 | 33 | /// Abstraction over a Property system. 34 | #[allow(clippy::upper_case_acronyms)] 35 | pub trait Property { 36 | fn property_set( 37 | sender: AccountId, 38 | collection_id: CollectionId, 39 | maybe_nft_id: Option, 40 | key: KeyLimit, 41 | value: ValueLimit, 42 | ) -> DispatchResult; 43 | 44 | /// Internal function to set a property that can be called from `Origin::root()` downstream. 45 | fn do_set_property( 46 | collection_id: CollectionId, 47 | maybe_nft_id: Option, 48 | key: KeyLimit, 49 | value: ValueLimit, 50 | ) -> DispatchResult; 51 | 52 | /// Internal function to remove a property that can be called from `Origin::root()` downstream. 53 | fn do_remove_property( 54 | collection_id: CollectionId, 55 | maybe_nft_id: Option, 56 | key: KeyLimit, 57 | ) -> DispatchResult; 58 | 59 | // Internal function to remove all of the properties that can be called from `Origin::root()` 60 | // downstream. 61 | fn do_remove_properties( 62 | collection_id: CollectionId, 63 | maybe_nft_id: Option, 64 | limit: u32, 65 | ) -> DispatchResult; 66 | } 67 | -------------------------------------------------------------------------------- /traits/src/resource.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021-2022 RMRK 2 | // This file is part of rmrk-substrate. 3 | // License: Apache 2.0 modified by RMRK, see LICENSE.md 4 | 5 | #![allow(clippy::too_many_arguments)] 6 | 7 | use codec::{Decode, Encode}; 8 | use frame_support::pallet_prelude::MaxEncodedLen; 9 | use scale_info::TypeInfo; 10 | use serde::Serialize; 11 | use sp_runtime::{DispatchError, DispatchResult, RuntimeDebug}; 12 | use sp_std::{cmp::Eq, result::Result}; 13 | 14 | use crate::{ 15 | primitives::{BaseId, PartId, ResourceId, SlotId}, 16 | serialize, 17 | }; 18 | #[derive(Encode, Decode, Eq, PartialEq, Clone, RuntimeDebug, TypeInfo, MaxEncodedLen)] 19 | #[cfg_attr(feature = "std", derive(Serialize))] 20 | #[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))] 21 | pub struct BasicResource { 22 | /// Reference to IPFS location of metadata 23 | #[cfg_attr(feature = "std", serde(with = "serialize::vec"))] 24 | pub metadata: BoundedString, 25 | } 26 | 27 | #[derive(Encode, Decode, Eq, PartialEq, Clone, RuntimeDebug, TypeInfo, MaxEncodedLen)] 28 | #[cfg_attr(feature = "std", derive(Serialize))] 29 | #[cfg_attr( 30 | feature = "std", 31 | serde(bound = r#" 32 | BoundedString: AsRef<[u8]>, 33 | BoundedParts: AsRef<[PartId]> 34 | "#) 35 | )] 36 | pub struct ComposableResource { 37 | /// If a resource is composed, it will have an array of parts that compose it 38 | #[cfg_attr(feature = "std", serde(with = "serialize::vec"))] 39 | pub parts: BoundedParts, 40 | 41 | /// A Base is uniquely identified by the combination of the word `base`, its minting block 42 | /// number, and user provided symbol during Base creation, glued by dashes `-`, e.g. 43 | /// base-4477293-kanaria_superbird. 44 | pub base: BaseId, 45 | 46 | /// Reference to IPFS location of metadata 47 | #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))] 48 | pub metadata: Option, 49 | 50 | /// If the resource has the slot property, it was designed to fit into a specific Base's slot. 51 | pub slot: Option<(BaseId, SlotId)>, 52 | } 53 | 54 | #[derive(Encode, Decode, Eq, PartialEq, Clone, RuntimeDebug, TypeInfo, MaxEncodedLen)] 55 | #[cfg_attr(feature = "std", derive(Serialize))] 56 | #[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))] 57 | pub struct SlotResource { 58 | /// A Base is uniquely identified by the combination of the word `base`, its minting block 59 | /// number, and user provided symbol during Base creation, glued by dashes `-`, e.g. 60 | /// base-4477293-kanaria_superbird. 61 | pub base: BaseId, 62 | 63 | /// Reference to IPFS location of metadata 64 | #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))] 65 | pub metadata: Option, 66 | 67 | /// If the resource has the slot property, it was designed to fit into a specific Base's slot. 68 | /// The baseslot will be composed of two dot-delimited values, like so: 69 | /// "base-4477293-kanaria_superbird.machine_gun_scope". This means: "This resource is 70 | /// compatible with the machine_gun_scope slot of base base-4477293-kanaria_superbird 71 | pub slot: SlotId, 72 | } 73 | 74 | #[derive(Encode, Decode, Eq, PartialEq, Clone, RuntimeDebug, TypeInfo, MaxEncodedLen)] 75 | #[cfg_attr(feature = "std", derive(Serialize))] 76 | #[cfg_attr( 77 | feature = "std", 78 | serde(bound = r#" 79 | BoundedString: AsRef<[u8]>, 80 | BoundedParts: AsRef<[PartId]> 81 | "#) 82 | )] 83 | pub enum ResourceTypes { 84 | Basic(BasicResource), 85 | Composable(ComposableResource), 86 | Slot(SlotResource), 87 | } 88 | 89 | #[derive(Encode, Decode, Eq, PartialEq, Clone, RuntimeDebug, TypeInfo, MaxEncodedLen)] 90 | #[cfg_attr(feature = "std", derive(Serialize))] 91 | #[cfg_attr( 92 | feature = "std", 93 | serde(bound = r#" 94 | BoundedString: AsRef<[u8]>, 95 | BoundedParts: AsRef<[PartId]> 96 | "#) 97 | )] 98 | pub struct ResourceInfo { 99 | pub id: ResourceId, 100 | 101 | /// Resource 102 | pub resource: ResourceTypes, 103 | 104 | /// If resource is sent to non-rootowned NFT, pending will be false and need to be accepted 105 | pub pending: bool, 106 | 107 | /// If resource removal request is sent by non-rootowned NFT, pending will be true and need to 108 | /// be accepted 109 | pub pending_removal: bool, 110 | } 111 | 112 | #[derive(Encode, Decode, Eq, PartialEq, Clone, RuntimeDebug, TypeInfo, MaxEncodedLen)] 113 | #[cfg_attr(feature = "std", derive(Serialize))] 114 | #[cfg_attr( 115 | feature = "std", 116 | serde(bound = r#" 117 | BoundedString: AsRef<[u8]>, 118 | BoundedParts: AsRef<[PartId]> 119 | "#) 120 | )] 121 | pub struct ResourceInfoMin { 122 | pub id: ResourceId, 123 | pub resource: ResourceTypes, 124 | } 125 | 126 | /// Abstraction over a Resource system. 127 | pub trait Resource { 128 | fn resource_add( 129 | sender: AccountId, 130 | collection_id: CollectionId, 131 | nft_id: NftId, 132 | resource: ResourceTypes, 133 | pending: bool, 134 | resource_id: ResourceId, 135 | ) -> Result; 136 | fn accept( 137 | sender: AccountId, 138 | collection_id: CollectionId, 139 | nft_id: NftId, 140 | resource_id: ResourceId, 141 | ) -> DispatchResult; 142 | fn resource_remove( 143 | sender: AccountId, 144 | collection_id: CollectionId, 145 | nft_id: NftId, 146 | resource_id: ResourceId, 147 | pending_resource: bool, 148 | ) -> DispatchResult; 149 | fn resource_replace( 150 | sender: AccountId, 151 | collection_id: CollectionId, 152 | nft_id: NftId, 153 | resource: ResourceTypes, 154 | resource_id: ResourceId, 155 | ) -> DispatchResult; 156 | fn accept_removal( 157 | sender: AccountId, 158 | collection_id: CollectionId, 159 | nft_id: NftId, 160 | resource_id: ResourceId, 161 | ) -> DispatchResult; 162 | } 163 | -------------------------------------------------------------------------------- /traits/src/serialize.rs: -------------------------------------------------------------------------------- 1 | use core::convert::AsRef; 2 | use serde::ser::{self, Serialize}; 3 | 4 | pub mod vec { 5 | use super::*; 6 | 7 | pub fn serialize(value: &C, serializer: D) -> Result 8 | where 9 | D: ser::Serializer, 10 | V: Serialize, 11 | C: AsRef<[V]>, 12 | { 13 | value.as_ref().serialize(serializer) 14 | } 15 | } 16 | 17 | pub mod opt_vec { 18 | use super::*; 19 | 20 | pub fn serialize(value: &Option, serializer: D) -> Result 21 | where 22 | D: ser::Serializer, 23 | V: Serialize, 24 | C: AsRef<[V]>, 25 | { 26 | match value { 27 | Some(value) => super::vec::serialize(value, serializer), 28 | None => serializer.serialize_none(), 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /traits/src/theme.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021-2022 RMRK 2 | // This file is part of rmrk-substrate. 3 | // License: Apache 2.0 modified by RMRK, see LICENSE.md 4 | 5 | use codec::{Decode, Encode}; 6 | use scale_info::TypeInfo; 7 | 8 | #[cfg(feature = "std")] 9 | use serde::Serialize; 10 | 11 | use crate::serialize; 12 | 13 | #[cfg_attr(feature = "std", derive(Eq, Serialize))] 14 | #[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq)] 15 | #[cfg_attr( 16 | feature = "std", 17 | serde(bound = r#" 18 | BoundedString: AsRef<[u8]>, 19 | BoundedThemeProperties: AsRef<[ThemeProperty]>, 20 | "#) 21 | )] 22 | pub struct Theme { 23 | /// Name of the theme 24 | #[cfg_attr(feature = "std", serde(with = "serialize::vec"))] 25 | pub name: BoundedString, 26 | 27 | /// Theme properties 28 | #[cfg_attr(feature = "std", serde(with = "serialize::vec"))] 29 | pub properties: BoundedThemeProperties, 30 | 31 | /// Inheritability 32 | pub inherit: bool, 33 | } 34 | 35 | #[cfg_attr(feature = "std", derive(Eq, Serialize))] 36 | #[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq)] 37 | #[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))] 38 | pub struct ThemeProperty { 39 | /// Key of the property 40 | #[cfg_attr(feature = "std", serde(with = "serialize::vec"))] 41 | pub key: BoundedString, 42 | 43 | /// Value of the property 44 | #[cfg_attr(feature = "std", serde(with = "serialize::vec"))] 45 | pub value: BoundedString, 46 | } 47 | --------------------------------------------------------------------------------