diff --git a/README.md b/README.md index cff7ee7..df3d2a1 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ near view $NFT_CONTRACT_ID nft_metadata ### Minting Token ```bash= -near call $NFT_CONTRACT_ID nft_mint '{"token_id": "token-1", "metadata": {"title": "My Non Fungible Team Token", "description": "The Team Most Certainly Goes :)", "media": "https://bafybeiftczwrtyr3k7a2k4vutd3amkwsmaqyhrdzlhvpt33dyjivufqusq.ipfs.dweb.link/goteam-gif.gif"}, "receiver_id": "'$MAIN_ACCOUNT'"}' --accountId $MAIN_ACCOUNT --amount 0.1 +near call $NFT_CONTRACT_ID nft_mint '{"token_id": "token-1", "token_metadata": {"title": "My Non Fungible Team Token", "description": "The Team Most Certainly Goes :)", "media": "https://bafybeiftczwrtyr3k7a2k4vutd3amkwsmaqyhrdzlhvpt33dyjivufqusq.ipfs.dweb.link/goteam-gif.gif"}, "token_owner_id": "'$MAIN_ACCOUNT'"}' --accountId $MAIN_ACCOUNT --amount 0.1 ``` After you've minted the token go to wallet.testnet.near.org to `your-account.testnet` and look in the collections tab and check out your new sample NFT! diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 62f9ee5..283baac 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -5,8 +5,8 @@ publish = false edition = "2021" [dev-dependencies] -near-sdk = { version = "5.4.0", features = ["unit-testing"] } -near-workspaces = { version = "0.14.1", features = ["unstable"] } +near-sdk = { version = "5.11.0", features = ["unit-testing"] } +near-workspaces = { version = "0.18.0", features = ["unstable"] } tokio = { version = "1.12.0", features = ["full"] } serde_json = "1" diff --git a/integration-tests/src/helpers.rs b/integration-tests/src/helpers.rs index 5495e51..1ffcf7c 100644 --- a/integration-tests/src/helpers.rs +++ b/integration-tests/src/helpers.rs @@ -1,5 +1,9 @@ +use near_sdk::Gas; +use near_workspaces::{ + types::{AccountDetails, NearToken}, + Account, Contract, +}; use serde_json::json; -use near_workspaces::{types::{NearToken, AccountDetails}, Account, Contract}; pub const DEFAULT_DEPOSIT: u128 = 10000000000000000000000; pub const ONE_YOCTO_NEAR: NearToken = NearToken::from_yoctonear(1); @@ -8,23 +12,24 @@ pub async fn mint_nft( user: &Account, nft_contract: &Contract, token_id: &str, -) -> Result<(), Box> { +) -> Result<(), Box> { let request_payload = json!({ "token_id": token_id, - "receiver_id": user.id(), - "metadata": { + "token_owner_id": user.id(), + "token_metadata": { "title": "Grumpy Cat", "description": "Not amused.", "media": "https://www.adamsdrafting.com/wp-content/uploads/2018/06/More-Grumpy-Cat.jpg" }, }); - let _ = user.call(nft_contract.id(), "nft_mint") + let _ = user + .call(nft_contract.id(), "nft_mint") .args_json(request_payload) .deposit(NearToken::from_yoctonear(DEFAULT_DEPOSIT)) .transact() .await; - + Ok(()) } @@ -34,13 +39,14 @@ pub async fn approve_nft( nft_contract: &Contract, token_id: &str, ) -> Result<(), Box> { - let request_payload = json!({ + let request_payload = json!({ "token_id": token_id, "account_id": market_contract.id(), "msg": serde_json::Value::Null, }); - let _ = user.call(nft_contract.id(), "nft_approve") + let _ = user + .call(nft_contract.id(), "nft_approve") .args_json(request_payload) .deposit(NearToken::from_yoctonear(DEFAULT_DEPOSIT)) .transact() @@ -55,8 +61,9 @@ pub async fn pay_for_storage( amount: NearToken, ) -> Result<(), Box> { let request_payload = json!({}); - - let _ = user.call(market_contract.id(), "storage_deposit") + + let _ = user + .call(market_contract.id(), "storage_deposit") .args_json(request_payload) .deposit(amount) .transact() @@ -70,7 +77,7 @@ pub async fn place_nft_for_sale( market_contract: &Contract, nft_contract: &Contract, token_id: &str, - approval_id: u32, + approval_id: u64, price: &NearToken, ) -> Result<(), Box> { let request_payload = json!({ @@ -79,20 +86,21 @@ pub async fn place_nft_for_sale( "approval_id": approval_id, "sale_conditions": NearToken::as_yoctonear(price).to_string(), }); - let _ = user.call(market_contract.id(), "list_nft_for_sale") + let _ = user + .call(market_contract.id(), "list_nft_for_sale") .args_json(request_payload) - .max_gas() - .deposit(NearToken::from_yoctonear(DEFAULT_DEPOSIT)) + .gas(Gas::from_tgas(100)) .transact() .await; Ok(()) } -pub async fn get_user_balance( - user: &Account, -) -> NearToken { - let details: AccountDetails = user.view_account().await.expect("Account has to have some balance"); +pub async fn get_user_balance(user: &Account) -> NearToken { + let details: AccountDetails = user + .view_account() + .await + .expect("Account has to have some balance"); details.balance } @@ -101,14 +109,15 @@ pub async fn purchase_listed_nft( market_contract: &Contract, nft_contract: &Contract, token_id: &str, - offer_price: NearToken + offer_price: NearToken, ) -> Result<(), Box> { - let request_payload = json!({ + let request_payload = json!({ "token_id": token_id, "nft_contract_id": nft_contract.id(), }); - let _ = bidder.call(market_contract.id(), "offer") + let _ = bidder + .call(market_contract.id(), "offer") .args_json(request_payload) .max_gas() .deposit(offer_price) @@ -124,19 +133,20 @@ pub async fn transfer_nft( nft_contract: &Contract, token_id: &str, ) -> Result<(), Box> { - let request_payload = json!({ + let request_payload = json!({ "token_id": token_id, "receiver_id": receiver.id(), "approval_id": 1 as u64, }); - let _ = sender.call(nft_contract.id(), "nft_transfer") + let _ = sender + .call(nft_contract.id(), "nft_transfer") .args_json(request_payload) .max_gas() .deposit(ONE_YOCTO_NEAR) .transact() .await; - + Ok(()) } @@ -155,10 +165,7 @@ pub async fn get_nft_token_info( Ok(token_info) } -pub fn round_to_near_dp( - amount: u128, - sf: u128, -) -> String { - let near_amount = amount as f64 / 1_000_000_000_000_000_000_000_000.0; // yocto in 1 NEAR +pub fn round_to_near_dp(amount: u128, sf: u128) -> String { + let near_amount = amount as f64 / 1_000_000_000_000_000_000_000_000.0; // yocto in 1 NEAR return format!("{:.1$}", near_amount, sf as usize); -} \ No newline at end of file +} diff --git a/integration-tests/src/tests.rs b/integration-tests/src/tests.rs index c10d6e7..4b387cb 100644 --- a/integration-tests/src/tests.rs +++ b/integration-tests/src/tests.rs @@ -99,8 +99,8 @@ async fn test_nft_mint_call( ) -> Result<(), Box> { let request_payload = json!({ "token_id": "1", - "receiver_id": user.id(), - "metadata": { + "token_owner_id": user.id(), + "token_metadata": { "title": "LEEROYYYMMMJENKINSSS", "description": "Alright time's up, let's do this.", "media": "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse3.mm.bing.net%2Fth%3Fid%3DOIP.Fhp4lHufCdTzTeGCAblOdgHaF7%26pid%3DApi&f=1" @@ -182,15 +182,10 @@ async fn test_sell_nft_listed_on_marketplace( ) -> Result<(), Box> { let token_id = "4"; let approval_id = 0; - let sale_price: NearToken = NearToken::from_yoctonear(10000000000000000000000000); + let sale_price: NearToken = NearToken::from_near(1); helpers::mint_nft(seller, nft_contract, token_id).await?; - helpers::pay_for_storage( - seller, - market_contract, - NearToken::from_yoctonear(1000000000000000000000000), - ) - .await?; + helpers::pay_for_storage(seller, market_contract, NearToken::from_millinear(10)).await?; helpers::approve_nft(market_contract, seller, nft_contract, token_id).await?; helpers::place_nft_for_sale( seller, @@ -392,8 +387,8 @@ async fn test_reselling_and_royalties( // mint with royalties let request_payload = json!({ "token_id": token_id, - "receiver_id": user.id(), - "metadata": { + "token_owner_id": user.id(), + "token_metadata": { "title": "Grumpy Cat", "description": "Not amused.", "media": "https://www.adamsdrafting.com/wp-content/uploads/2018/06/More-Grumpy-Cat.jpg" @@ -555,8 +550,8 @@ async fn test_royalties_exceeding_100_percents( // mint with royalties let request_payload = json!({ "token_id": token_id, - "receiver_id": user.id(), - "metadata": { + "token_owner_id": user.id(), + "token_metadata": { "title": "Grumpy Cat", "description": "Not amused.", "media": "https://www.adamsdrafting.com/wp-content/uploads/2018/06/More-Grumpy-Cat.jpg" diff --git a/market-contract/Cargo.toml b/market-contract/Cargo.toml index 6091233..fc56dbb 100644 --- a/market-contract/Cargo.toml +++ b/market-contract/Cargo.toml @@ -8,11 +8,11 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] -near-sdk = { version = "5.4.0", features = ["legacy"] } +near-sdk = { version = "5.11.0", features = ["legacy"] } [dev-dependencies] -near-sdk = { version = "5.4.0", features = ["unit-testing"] } -near-workspaces = { version = "0.14.1", features = ["unstable"] } +near-sdk = { version = "5.11.0", features = ["unit-testing"] } +near-workspaces = { version = "0.18.0", features = ["unstable"] } tokio = { version = "1.12.0", features = ["full"] } serde_json = "1" diff --git a/market-contract/src/external.rs b/market-contract/src/external.rs index c169659..2b808f0 100644 --- a/market-contract/src/external.rs +++ b/market-contract/src/external.rs @@ -9,22 +9,17 @@ trait ExtContract { fn nft_transfer_payout( &mut self, receiver_id: AccountId, //purchaser (person to transfer the NFT to) - token_id: TokenId, //token ID to transfer - approval_id: u32, //market contract's approval ID in order to transfer the token on behalf of the owner - memo: String, //memo (to include some context) + token_id: TokenId, //token ID to transfer + approval_id: u64, //market contract's approval ID in order to transfer the token on behalf of the owner + memo: String, //memo (to include some context) /* the price that the token was purchased for. This will be used in conjunction with the royalty percentages - for the token in order to determine how much money should go to which account. + for the token in order to determine how much money should go to which account. */ balance: NearToken, //the maximum amount of accounts the market can payout at once (this is limited by GAS) - max_len_payout: u32, + max_len_payout: u32, ); fn nft_token(&self, token_id: TokenId); - fn nft_is_approved( - &self, - token_id: TokenId, - approved_account_id: AccountId, - approval_id: u32, - ); -} \ No newline at end of file + fn nft_is_approved(&self, token_id: TokenId, approved_account_id: AccountId, approval_id: u64); +} diff --git a/market-contract/src/nft_callbacks.rs b/market-contract/src/nft_callbacks.rs index 1d4bd68..f07dafc 100644 --- a/market-contract/src/nft_callbacks.rs +++ b/market-contract/src/nft_callbacks.rs @@ -5,14 +5,14 @@ use crate::*; /* trait that will be used as the callback from the NFT contract. When nft_approve is called, it will fire a cross contract call to this marketplace and this is the function - that is invoked. + that is invoked. */ trait NonFungibleTokenApprovalsReceiver { fn nft_on_approve( &mut self, token_id: TokenId, owner_id: AccountId, - approval_id: u32, + approval_id: u64, msg: String, ); } @@ -24,7 +24,7 @@ impl NonFungibleTokenApprovalsReceiver for Contract { &mut self, token_id: TokenId, owner_id: AccountId, - approval_id: u32, + approval_id: u64, msg: String, ) { /* diff --git a/market-contract/src/sale.rs b/market-contract/src/sale.rs index a63c4a3..74adf9f 100644 --- a/market-contract/src/sale.rs +++ b/market-contract/src/sale.rs @@ -1,6 +1,6 @@ use crate::*; -use near_sdk::{log, promise_result_as_success, NearSchema, PromiseError}; use near_sdk::serde_json::json; +use near_sdk::{log, promise_result_as_success, NearSchema, PromiseError}; //struct that holds important information about each sale on the market #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, NearSchema)] @@ -10,7 +10,7 @@ pub struct Sale { //owner of the sale pub owner_id: AccountId, //market contract's approval ID to transfer the token on behalf of the owner - pub approval_id: u32, + pub approval_id: u64, //nft contract where the token was minted pub nft_contract_id: String, //actual token ID for sale @@ -19,7 +19,7 @@ pub struct Sale { pub sale_conditions: SalePriceInYoctoNear, } -//The Json token is what will be returned from view calls. +//The Json token is what will be returned from view calls. #[derive(Serialize, Deserialize, NearSchema)] #[serde(crate = "near_sdk::serde")] pub struct JsonToken { @@ -32,11 +32,11 @@ impl Contract { // lists a nft for sale on the market #[payable] pub fn list_nft_for_sale( - &mut self, - nft_contract_id: AccountId, - token_id: TokenId, - approval_id: u32, - sale_conditions: SalePriceInYoctoNear, + &mut self, + nft_contract_id: AccountId, + token_id: TokenId, + approval_id: u64, + sale_conditions: SalePriceInYoctoNear, ) { let owner_id = env::predecessor_account_id(); @@ -46,21 +46,24 @@ impl Contract { let storage_amount = self.storage_minimum_balance(); //get the total storage paid by the owner let owner_paid_storage = self.storage_deposits.get(&owner_id).unwrap_or(ZERO_NEAR); - //get the storage required which is simply the storage for the number of sales they have + 1 - let signer_storage_required = storage_amount.saturating_mul((self.get_supply_by_owner_id(owner_id.clone()).0 + 1).into()); - + //get the storage required which is simply the storage for the number of sales they have + 1 + let signer_storage_required = storage_amount + .saturating_mul((self.get_supply_by_owner_id(owner_id.clone()).0 + 1).into()); + //make sure that the total paid is >= the required storage assert!( owner_paid_storage.ge(&signer_storage_required), "Insufficient storage paid: {}, for {} sales at {} rate of per sale", - owner_paid_storage, signer_storage_required.saturating_div(storage_per_sale().as_yoctonear()), storage_per_sale() + owner_paid_storage, + signer_storage_required.saturating_div(storage_per_sale().as_yoctonear()), + storage_per_sale() ); let nft_token_promise = Promise::new(nft_contract_id.clone()).function_call( - "nft_token".to_owned(), - json!({ "token_id": token_id }).to_string().into_bytes(), - ZERO_NEAR, - Gas::from_gas(10u64.pow(13)) + "nft_token".to_owned(), + json!({ "token_id": token_id }).to_string().into_bytes(), + ZERO_NEAR, + Gas::from_gas(10u64.pow(13)), ); let nft_is_approved_promise = Promise::new(nft_contract_id.clone()).function_call( "nft_is_approved".to_owned(), @@ -68,12 +71,18 @@ impl Contract { ZERO_NEAR, Gas::from_gas(10u64.pow(13)) ); - nft_token_promise - .and(nft_is_approved_promise) - .then(Self::ext(env::current_account_id()).process_listing(owner_id.clone(), nft_contract_id, token_id, approval_id, sale_conditions)); + nft_token_promise.and(nft_is_approved_promise).then( + Self::ext(env::current_account_id()).process_listing( + owner_id.clone(), + nft_contract_id, + token_id, + approval_id, + sale_conditions, + ), + ); } - //removes a sale from the market. + //removes a sale from the market. #[payable] pub fn remove_sale(&mut self, nft_contract_id: AccountId, token_id: String) { //assert that the user has attached exactly 1 yoctoNEAR (for security reasons) @@ -88,20 +97,15 @@ impl Contract { //updates the price for a sale on the market #[payable] - pub fn update_price( - &mut self, - nft_contract_id: AccountId, - token_id: String, - price: NearToken, - ) { + pub fn update_price(&mut self, nft_contract_id: AccountId, token_id: String, price: NearToken) { //assert that the user has attached exactly 1 yoctoNEAR (for security reasons) assert_one_yocto(); - + //create the unique sale ID from the nft contract and token let contract_id: AccountId = nft_contract_id.into(); let contract_and_token_id = format!("{}{}{}", contract_id, DELIMETER, token_id); - - //get the sale object from the unique sale ID. If there is no token, panic. + + //get the sale object from the unique sale ID. If there is no token, panic. let mut sale = self.sales.get(&contract_and_token_id).expect("No sale"); //assert that the caller of the function is the sale owner @@ -110,7 +114,7 @@ impl Contract { sale.owner_id, "Must be sale owner" ); - + //set the sale conditions equal to the passed in price sale.sale_conditions = price; //insert the sale back into the map for the unique sale ID @@ -122,16 +126,19 @@ impl Contract { pub fn offer(&mut self, nft_contract_id: AccountId, token_id: String) { //get the attached deposit and make sure it's greater than 0 let deposit = env::attached_deposit(); - assert!(!deposit.is_zero(), "Attached deposit must be greater than 0"); + assert!( + !deposit.is_zero(), + "Attached deposit must be greater than 0" + ); //convert the nft_contract_id from a AccountId to an AccountId let contract_id: AccountId = nft_contract_id.into(); //get the unique sale ID (contract + DELIMITER + token ID) let contract_and_token_id = format!("{}{}{}", contract_id, DELIMETER, token_id); - + //get the sale object from the unique sale ID. If the sale doesn't exist, panic. let sale = self.sales.get(&contract_and_token_id).expect("No sale"); - + //get the buyer ID which is the person who called the function and make sure they're not the owner of the sale let buyer_id = env::predecessor_account_id(); assert_ne!(sale.owner_id, buyer_id, "Cannot bid on your own sale."); @@ -141,16 +148,11 @@ impl Contract { //make sure the deposit is greater than the price assert!(deposit.ge(&price), "Attached deposit must be greater than or equal to the current price: {:?}. Your deposit: {:?}", price, deposit); - //process the purchase (which will remove the sale, transfer and get the payout from the nft contract, and then distribute royalties) - self.process_purchase( - contract_id, - token_id, - deposit, - buyer_id, - ); + //process the purchase (which will remove the sale, transfer and get the payout from the nft contract, and then distribute royalties) + self.process_purchase(contract_id, token_id, deposit, buyer_id); } - //private function used when a sale is purchased. + //private function used when a sale is purchased. //this will remove the sale, transfer and get the payout from the nft contract, and then distribute royalties #[private] pub fn process_purchase( @@ -170,41 +172,37 @@ impl Contract { .with_attached_deposit(ONE_YOCTONEAR) .with_static_gas(GAS_FOR_NFT_TRANSFER) .nft_transfer_payout( - buyer_id.clone(), //purchaser (person to transfer the NFT to) - token_id, //token ID to transfer + buyer_id.clone(), //purchaser (person to transfer the NFT to) + token_id, //token ID to transfer sale.approval_id, //market contract's approval ID in order to transfer the token on behalf of the owner - "payout from market".to_string(), //memo (to include some context) - /* - the price that the token was purchased for. This will be used in conjunction with the royalty percentages - for the token in order to determine how much money should go to which account. - */ - price, - 10, //the maximum amount of accounts the market can payout at once (this is limited by GAS) - ) - //after the transfer payout has been initiated, we resolve the promise by calling our own resolve_purchase function. - //resolve purchase will take the payout object returned from the nft_transfer_payout and actually pay the accounts - .then( - // No attached deposit with static GAS equal to the GAS for resolving the purchase. Also attach an unused GAS weight of 1 by default. - Self::ext(env::current_account_id()) - .with_static_gas(GAS_FOR_RESOLVE_PURCHASE) - .resolve_purchase( - buyer_id, //the buyer and price are passed in incase something goes wrong and we need to refund the buyer + "payout from market".to_string(), //memo (to include some context) + /* + the price that the token was purchased for. This will be used in conjunction with the royalty percentages + for the token in order to determine how much money should go to which account. + */ price, + 10, //the maximum amount of accounts the market can payout at once (this is limited by GAS) + ) + //after the transfer payout has been initiated, we resolve the promise by calling our own resolve_purchase function. + //resolve purchase will take the payout object returned from the nft_transfer_payout and actually pay the accounts + .then( + // No attached deposit with static GAS equal to the GAS for resolving the purchase. Also attach an unused GAS weight of 1 by default. + Self::ext(env::current_account_id()) + .with_static_gas(GAS_FOR_RESOLVE_PURCHASE) + .resolve_purchase( + buyer_id, //the buyer and price are passed in incase something goes wrong and we need to refund the buyer + price, + ), ) - ) } /* - private method used to resolve the promise when calling nft_transfer_payout. This will take the payout object and + private method used to resolve the promise when calling nft_transfer_payout. This will take the payout object and check to see if it's authentic and there's no problems. If everything is fine, it will pay the accounts. If there's a problem, - it will refund the buyer for the price. + it will refund the buyer for the price. */ #[private] - pub fn resolve_purchase( - &mut self, - buyer_id: AccountId, - price: NearToken, - ) -> NearToken { + pub fn resolve_purchase(&mut self, buyer_id: AccountId, price: NearToken) -> NearToken { // checking for payout information returned from the nft_transfer_payout method let payout_option = promise_result_as_success().and_then(|value| { //if we set the payout_option to None, that means something went wrong and we should refund the buyer @@ -217,20 +215,20 @@ impl Contract { if payout_object.payout.len() > 10 || payout_object.payout.is_empty() { env::log_str("Cannot have more than 10 royalties"); None - + //if the payout object is the correct length, we move forward } else { //we'll keep track of how much the nft contract wants us to payout. Starting at the full price payed by the buyer let mut remainder = price; - - //loop through the payout and subtract the values from the remainder. + + //loop through the payout and subtract the values from the remainder. for &value in payout_object.payout.values() { //checked sub checks for overflow or any errors and returns None if there are problems remainder = remainder.checked_sub(value)?; } - //Check to see if the NFT contract sent back a faulty payout that requires us to pay more or too little. + //Check to see if the NFT contract sent back a faulty payout that requires us to pay more or too little. //The remainder will be 0 if the payout summed to the total price. The remainder will be 1 if the royalties - //we something like 3333 + 3333 + 3333. + //we something like 3333 + 3333 + 3333. if remainder.eq(&ZERO_NEAR) || remainder.eq(&NearToken::from_yoctonear(1)) { //set the payout_option to be the payout because nothing went wrong Some(payout_object.payout) @@ -267,57 +265,47 @@ impl Contract { owner_id: AccountId, nft_contract_id: AccountId, token_id: TokenId, - approval_id: u32, + approval_id: u64, sale_conditions: SalePriceInYoctoNear, #[callback_result] nft_token_result: Result, #[callback_result] nft_is_approved_result: Result, ) { if let Ok(result) = nft_token_result { - assert_eq!( - result.owner_id, - owner_id, - "Signer is not NFT owner", - ) + assert_eq!(result.owner_id, owner_id, "Signer is not NFT owner",) } else { log!("nft_is_approved call failed"); } if let Ok(result) = nft_is_approved_result { - assert_eq!( - result, - true, - "Marketplace contract is not approved", - ) + assert_eq!(result, true, "Marketplace contract is not approved",) } else { log!("nft_is_approved call failed"); - } - + } + //create the unique sale ID which is the contract + DELIMITER + token ID let contract_and_token_id = format!("{}{}{}", nft_contract_id, DELIMETER, token_id); - + //insert the key value pair into the sales map. Key is the unique ID. value is the sale object self.sales.insert( &contract_and_token_id, &Sale { - owner_id: owner_id.clone(), //owner of the sale / token + owner_id: owner_id.clone(), //owner of the sale / token approval_id, //approval ID for that token that was given to the market nft_contract_id: nft_contract_id.to_string(), //NFT contract the token was minted on token_id: token_id.clone(), //the actual token ID - sale_conditions, //the sale conditions - }, + sale_conditions, //the sale conditions + }, ); - //Extra functionality that populates collections necessary for the view calls + //Extra functionality that populates collections necessary for the view calls //get the sales by owner ID for the given owner. If there are none, we create a new empty set let mut by_owner_id = self.by_owner_id.get(&owner_id).unwrap_or_else(|| { - UnorderedSet::new( - StorageKey::ByOwnerIdInner { - //we get a new unique prefix for the collection by hashing the owner - account_id_hash: hash_account_id(&owner_id), - } - ) + UnorderedSet::new(StorageKey::ByOwnerIdInner { + //we get a new unique prefix for the collection by hashing the owner + account_id_hash: hash_account_id(&owner_id), + }) }); - + //insert the unique sale ID into the set by_owner_id.insert(&contract_and_token_id); //insert that set back into the collection for the owner @@ -328,14 +316,12 @@ impl Contract { .by_nft_contract_id .get(&nft_contract_id) .unwrap_or_else(|| { - UnorderedSet::new( - StorageKey::ByNFTContractIdInner { - //we get a new unique prefix for the collection by hashing the owner - account_id_hash: hash_account_id(&nft_contract_id), - } - ) + UnorderedSet::new(StorageKey::ByNFTContractIdInner { + //we get a new unique prefix for the collection by hashing the owner + account_id_hash: hash_account_id(&nft_contract_id), + }) }); - + //insert the token ID into the set by_nft_contract_id.insert(&token_id); //insert the set back into the collection for the given nft contract ID @@ -344,17 +330,13 @@ impl Contract { } } -//this is the cross contract call that we call on our own contract. +//this is the cross contract call that we call on our own contract. /* - private method used to resolve the promise when calling nft_transfer_payout. This will take the payout object and + private method used to resolve the promise when calling nft_transfer_payout. This will take the payout object and check to see if it's authentic and there's no problems. If everything is fine, it will pay the accounts. If there's a problem, - it will refund the buyer for the price. + it will refund the buyer for the price. */ #[ext_contract(ext_self)] trait ExtSelf { - fn resolve_purchase( - &mut self, - buyer_id: AccountId, - price: NearToken, - ) -> Promise; -} \ No newline at end of file + fn resolve_purchase(&mut self, buyer_id: AccountId, price: NearToken) -> Promise; +} diff --git a/nft-contract-approval/Cargo.toml b/nft-contract-approval/Cargo.toml index 130ae71..97488df 100644 --- a/nft-contract-approval/Cargo.toml +++ b/nft-contract-approval/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] -near-sdk = { version = "5.4.0", features = ["legacy"] } +near-sdk = { version = "5.11.0", features = ["legacy"] } serde_json = "1.0.113" [profile.release] diff --git a/nft-contract-approval/src/approval.rs b/nft-contract-approval/src/approval.rs index 6ea2317..b909e71 100644 --- a/nft-contract-approval/src/approval.rs +++ b/nft-contract-approval/src/approval.rs @@ -1,5 +1,5 @@ use crate::*; -use near_sdk::{ext_contract}; +use near_sdk::ext_contract; pub trait NonFungibleTokenCore { //approve an account ID to transfer a token on your behalf @@ -10,7 +10,7 @@ pub trait NonFungibleTokenCore { &self, token_id: TokenId, approved_account_id: AccountId, - approval_id: Option, + approval_id: Option, ) -> bool; //revoke a specific account from transferring the token on your behalf @@ -27,7 +27,7 @@ trait NonFungibleTokenApprovalsReceiver { &mut self, token_id: TokenId, owner_id: AccountId, - approval_id: u32, + approval_id: u64, msg: String, ); } @@ -54,12 +54,12 @@ impl NonFungibleTokenCore for Contract { ); //get the next approval ID if we need a new approval - let approval_id: u32 = token.next_approval_id; + let approval_id: u64 = token.next_approval_id; //check if the account has been approved already for this token let is_new_approval = token .approved_account_ids - //insert returns none if the key was not present. + //insert returns none if the key was not present. .insert(account_id.clone(), approval_id) //if the key was not present, .is_none() will return true so it is a new approval. .is_none(); @@ -77,36 +77,32 @@ impl NonFungibleTokenCore for Contract { //insert the token back into the tokens_by_id collection self.tokens_by_id.insert(&token_id, &token); - //refund any excess storage attached by the user. If the user didn't attach enough, panic. + //refund any excess storage attached by the user. If the user didn't attach enough, panic. refund_deposit(storage_used); //if some message was passed into the function, we initiate a cross contract call on the - //account we're giving access to. + //account we're giving access to. if let Some(msg) = msg { // Defaulting GAS weight to 1, no attached deposit, and no static GAS to attach. ext_non_fungible_approval_receiver::ext(account_id) - .nft_on_approve( - token_id, - token.owner_id, - approval_id, - msg - ).as_return(); + .nft_on_approve(token_id, token.owner_id, approval_id, msg) + .as_return(); } } //check if the passed in account has access to approve the token ID - fn nft_is_approved( + fn nft_is_approved( &self, token_id: TokenId, approved_account_id: AccountId, - approval_id: Option, + approval_id: Option, ) -> bool { //get the token object from the token_id let token = self.tokens_by_id.get(&token_id).expect("No token"); - + //get the approval number for the passed in account ID let approval = token.approved_account_ids.get(&approved_account_id); - + //if there was some approval ID found for the account ID if let Some(approval) = approval { //if a specific approval_id was passed into the function @@ -123,7 +119,7 @@ impl NonFungibleTokenCore for Contract { } } - //revoke a specific account from transferring the token on your behalf + //revoke a specific account from transferring the token on your behalf #[payable] fn nft_revoke(&mut self, token_id: TokenId, account_id: AccountId) { //assert that the user attached exactly 1 yoctoNEAR for security reasons @@ -136,11 +132,7 @@ impl NonFungibleTokenCore for Contract { assert_eq!(&predecessor_account_id, &token.owner_id); //if the account ID was in the token's approval, we remove it and the if statement logic executes - if token - .approved_account_ids - .remove(&account_id) - .is_some() - { + if token.approved_account_ids.remove(&account_id).is_some() { //refund the funds released by removing the approved_account_id to the caller of the function refund_approved_account_ids_iter(predecessor_account_id, [account_id].iter()); @@ -171,4 +163,4 @@ impl NonFungibleTokenCore for Contract { self.tokens_by_id.insert(&token_id, &token); } } -} \ No newline at end of file +} diff --git a/nft-contract-approval/src/enumeration.rs b/nft-contract-approval/src/enumeration.rs index 49ebc6f..9a3bc61 100644 --- a/nft-contract-approval/src/enumeration.rs +++ b/nft-contract-approval/src/enumeration.rs @@ -3,22 +3,23 @@ use crate::*; #[near_bindgen] impl Contract { //Query for the total supply of NFTs on the contract - pub fn nft_total_supply(&self) -> U64 { + pub fn nft_total_supply(&self) -> U128 { //return the length of the token metadata by ID - U64(self.token_metadata_by_id.len()) + U128(self.token_metadata_by_id.len().into()) } //Query for nft tokens on the contract regardless of the owner using pagination - pub fn nft_tokens(&self, from_index: Option, limit: Option) -> Vec { + pub fn nft_tokens(&self, from_index: Option, limit: Option) -> Vec { //where to start pagination - if we have a from_index, we'll use that - otherwise start from 0 index let start = u128::from(from_index.unwrap_or(U128(0))); //iterate through each token using an iterator - self.token_metadata_by_id.keys() + self.token_metadata_by_id + .keys() //skip to the index we specified in the start variable - .skip(start as usize) + .skip(start as usize) //take the first "limit" elements in the vector. If we didn't specify a limit, use 50 - .take(limit.unwrap_or(50) as usize) + .take(limit.unwrap_or(50) as usize) //we'll map the token IDs which are strings into Json Tokens .map(|token_id| self.nft_token(token_id.clone()).unwrap()) //since we turned the keys into an iterator, we need to turn it back into a vector to return @@ -26,19 +27,16 @@ impl Contract { } //get the total supply of NFTs for a given owner - pub fn nft_supply_for_owner( - &self, - account_id: AccountId, - ) -> U64 { + pub fn nft_supply_for_owner(&self, account_id: AccountId) -> U128 { //get the set of tokens for the passed in owner let tokens_for_owner_set = self.tokens_per_owner.get(&account_id); //if there is some set of tokens, we'll return the length if let Some(tokens_for_owner_set) = tokens_for_owner_set { - U64(tokens_for_owner_set.len()) + U128(tokens_for_owner_set.len().into()) } else { //if there isn't a set of tokens for the passed in account ID, we'll return 0 - U64(0) + U128(0) } } @@ -47,7 +45,7 @@ impl Contract { &self, account_id: AccountId, from_index: Option, - limit: Option, + limit: Option, ) -> Vec { //get the set of tokens for the passed in owner let tokens_for_owner_set = self.tokens_per_owner.get(&account_id); @@ -55,7 +53,7 @@ impl Contract { let tokens = if let Some(tokens_for_owner_set) = tokens_for_owner_set { tokens_for_owner_set } else { - //if there is no set of tokens, we'll simply return an empty vector. + //if there is no set of tokens, we'll simply return an empty vector. return vec![]; }; @@ -63,14 +61,15 @@ impl Contract { let start = u128::from(from_index.unwrap_or(U128(0))); //iterate through the keys vector - tokens.iter() + tokens + .iter() //skip to the index we specified in the start variable - .skip(start as usize) + .skip(start as usize) //take the first "limit" elements in the vector. If we didn't specify a limit, use 50 - .take(limit.unwrap_or(50) as usize) + .take(limit.unwrap_or(50) as usize) //we'll map the token IDs which are strings into Json Tokens .map(|token_id| self.nft_token(token_id.clone()).unwrap()) //since we turned the keys into an iterator, we need to turn it back into a vector to return .collect() } -} \ No newline at end of file +} diff --git a/nft-contract-approval/src/internal.rs b/nft-contract-approval/src/internal.rs index 54ce98c..a6a8b75 100644 --- a/nft-contract-approval/src/internal.rs +++ b/nft-contract-approval/src/internal.rs @@ -1,5 +1,5 @@ use crate::*; -use near_sdk::{CryptoHash}; +use near_sdk::CryptoHash; use std::mem::size_of; //calculate how many bytes the account ID is taking up @@ -8,13 +8,18 @@ pub(crate) fn bytes_for_approved_account_id(account_id: &AccountId) -> u128 { account_id.as_str().len() as u128 + 4 + size_of::() as u128 } -//refund the storage taken up by passed in approved account IDs and send the funds to the passed in account ID. +//refund the storage taken up by passed in approved account IDs and send the funds to the passed in account ID. pub(crate) fn refund_approved_account_ids_iter<'a, I>( account_id: AccountId, approved_account_ids: I, //the approved account IDs must be passed in as an iterator -) -> Promise where I: Iterator { +) -> Promise +where + I: Iterator, +{ //get the storage total by going through and summing all the bytes for each approved account IDs - let storage_released = approved_account_ids.map(bytes_for_approved_account_id).sum(); + let storage_released = approved_account_ids + .map(bytes_for_approved_account_id) + .sum(); //transfer the account the storage that is released Promise::new(account_id).transfer(env::storage_byte_cost().saturating_mul(storage_released)) } @@ -22,7 +27,7 @@ pub(crate) fn refund_approved_account_ids_iter<'a, I>( //refund a map of approved account IDs and send the funds to the passed in account ID pub(crate) fn refund_approved_account_ids( account_id: AccountId, - approved_account_ids: &HashMap, + approved_account_ids: &HashMap, ) -> Promise { //call the refund_approved_account_ids_iter with the approved account IDs as keys refund_approved_account_ids_iter(account_id, approved_account_ids.keys()) @@ -87,18 +92,16 @@ impl Contract { //get the set of tokens for the given account let mut tokens_set = self.tokens_per_owner.get(account_id).unwrap_or_else(|| { //if the account doesn't have any tokens, we create a new unordered set - UnorderedSet::new( - StorageKey::TokenPerOwnerInner { - //we get a new unique prefix for the collection - account_id_hash: hash_account_id(&account_id), - }, - ) + UnorderedSet::new(StorageKey::TokenPerOwnerInner { + //we get a new unique prefix for the collection + account_id_hash: hash_account_id(&account_id), + }) }); //we insert the token ID into the set tokens_set.insert(token_id); - //we insert that set for the given account ID. + //we insert that set for the given account ID. self.tokens_per_owner.insert(account_id, &tokens_set); } @@ -122,7 +125,7 @@ impl Contract { if tokens_set.is_empty() { self.tokens_per_owner.remove(account_id); } else { - //if the token set is not empty, we simply insert it back for the account ID. + //if the token set is not empty, we simply insert it back for the account ID. self.tokens_per_owner.insert(account_id, &tokens_set); } } @@ -134,7 +137,7 @@ impl Contract { receiver_id: &AccountId, token_id: &TokenId, //we introduce an approval ID so that people with that approval ID can transfer the token - approval_id: Option, + approval_id: Option, memo: Option, ) -> Token { //get the token object by passing in the token_id @@ -151,16 +154,16 @@ impl Contract { if let Some(enforced_approval_id) = approval_id { //get the actual approval ID let actual_approval_id = token - .approved_account_ids - .get(sender_id) - //if the sender isn't in the map, we panic - .expect("Sender is not approved account"); + .approved_account_ids + .get(sender_id) + //if the sender isn't in the map, we panic + .expect("Sender is not approved account"); //make sure that the actual approval ID is the same as the one provided assert_eq!( - actual_approval_id, &enforced_approval_id, - "The actual approval_id {} is different from the given approval_id {}", - actual_approval_id, enforced_approval_id, + actual_approval_id, &enforced_approval_id, + "The actual approval_id {} is different from the given approval_id {}", + actual_approval_id, enforced_approval_id, ); } } @@ -176,17 +179,17 @@ impl Contract { //we then add the token to the receiver_id's set self.internal_add_token_to_owner(receiver_id, token_id); - //we create a new token struct + //we create a new token struct let new_token = Token { owner_id: receiver_id.clone(), //reset the approval account IDs approved_account_ids: Default::default(), next_approval_id: token.next_approval_id, }; - //insert that new token into the tokens_by_id, replacing the old entry + //insert that new token into the tokens_by_id, replacing the old entry self.tokens_by_id.insert(token_id, &new_token); - //if there was some memo attached, we log it. + //if there was some memo attached, we log it. if let Some(memo) = memo.as_ref() { env::log_str(&format!("Memo: {}", memo).to_string()); } @@ -221,8 +224,8 @@ impl Contract { // Log the serialized json. env::log_str(&nft_transfer_log.to_string()); - + //return the previous token object that was transferred. token } -} \ No newline at end of file +} diff --git a/nft-contract-approval/src/metadata.rs b/nft-contract-approval/src/metadata.rs index 92f7120..b8bcf73 100644 --- a/nft-contract-approval/src/metadata.rs +++ b/nft-contract-approval/src/metadata.rs @@ -5,7 +5,7 @@ pub type TokenId = String; #[serde(crate = "near_sdk::serde")] pub struct Payout { pub payout: HashMap, -} +} #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, NearSchema)] #[borsh(crate = "near_sdk::borsh")] @@ -44,12 +44,12 @@ pub struct Token { //owner of the token pub owner_id: AccountId, //list of approved account IDs that have access to transfer the token. This maps an account ID to an approval ID - pub approved_account_ids: HashMap, - //the next approval ID to give out. - pub next_approval_id: u32, + pub approved_account_ids: HashMap, + //the next approval ID to give out. + pub next_approval_id: u64, } -//The Json token is what will be returned from view calls. +//The Json token is what will be returned from view calls. #[derive(Serialize, Deserialize, NearSchema)] #[serde(crate = "near_sdk::serde")] pub struct JsonToken { @@ -60,7 +60,7 @@ pub struct JsonToken { //token metadata pub metadata: TokenMetadata, //list of approved account IDs that have access to transfer the token. This maps an account ID to an approval ID - pub approved_account_ids: HashMap, + pub approved_account_ids: HashMap, } pub trait NonFungibleTokenMetadata { @@ -73,4 +73,4 @@ impl NonFungibleTokenMetadata for Contract { fn nft_metadata(&self) -> NFTContractMetadata { self.metadata.get().unwrap() } -} \ No newline at end of file +} diff --git a/nft-contract-approval/src/mint.rs b/nft-contract-approval/src/mint.rs index cb053e7..c9307a9 100644 --- a/nft-contract-approval/src/mint.rs +++ b/nft-contract-approval/src/mint.rs @@ -6,49 +6,52 @@ impl Contract { pub fn nft_mint( &mut self, token_id: TokenId, - metadata: TokenMetadata, - receiver_id: AccountId, + token_owner_id: AccountId, + token_metadata: TokenMetadata, //we add an optional parameter for perpetual royalties perpetual_royalties: Option>, ) { //measure the initial storage being used on the contract let initial_storage_usage = env::storage_usage(); - + // create a royalty map to store in the token let mut royalty = HashMap::new(); - - // if perpetual royalties were passed into the function: + + // if perpetual royalties were passed into the function: if let Some(perpetual_royalties) = perpetual_royalties { //make sure that the length of the perpetual royalties is below 7 since we won't have enough GAS to pay out that many people - assert!(perpetual_royalties.len() < 7, "Cannot add more than 6 perpetual royalty amounts"); - + assert!( + perpetual_royalties.len() < 7, + "Cannot add more than 6 perpetual royalty amounts" + ); + //iterate through the perpetual royalties and insert the account and amount in the royalty map for (account, amount) in perpetual_royalties { royalty.insert(account, amount); } } - - //specify the token struct that contains the owner ID + + //specify the token struct that contains the owner ID let token = Token { - owner_id: receiver_id, + owner_id: token_owner_id, //we set the approved account IDs to the default value (an empty map) approved_account_ids: Default::default(), //the next approval ID is set to 0 next_approval_id: 0, }; - + //insert the token ID and token struct and make sure that the token doesn't exist assert!( self.tokens_by_id.insert(&token_id, &token).is_none(), "Token already exists" ); - + //insert the token ID and metadata - self.token_metadata_by_id.insert(&token_id, &metadata); - + self.token_metadata_by_id.insert(&token_id, &token_metadata); + //call the internal method for adding the token to the owner self.internal_add_token_to_owner(&token.owner_id, &token_id); - + // Construct the mint log as per the events standard. let nft_mint_log: EventLog = EventLog { // Standard name ("nep171"). @@ -65,14 +68,14 @@ impl Contract { memo: None, }]), }; - + // Log the serialized json. env::log_str(&nft_mint_log.to_string()); - + //calculate the required storage which was the used - initial let required_storage_in_bytes = env::storage_usage() - initial_storage_usage; - + //refund any excess storage if the user attached too much. Panic if they didn't attach enough to cover the required. refund_deposit(required_storage_in_bytes.into()); } -} \ No newline at end of file +} diff --git a/nft-contract-approval/src/nft_core.rs b/nft-contract-approval/src/nft_core.rs index 6f04739..facf2d6 100644 --- a/nft-contract-approval/src/nft_core.rs +++ b/nft-contract-approval/src/nft_core.rs @@ -1,5 +1,5 @@ use crate::*; -use near_sdk::{ext_contract, Gas, log, PromiseResult}; +use near_sdk::{ext_contract, log, Gas, PromiseResult}; const GAS_FOR_RESOLVE_TRANSFER: Gas = Gas::from_tgas(10); const GAS_FOR_NFT_ON_TRANSFER: Gas = Gas::from_tgas(25); @@ -11,7 +11,7 @@ pub trait NonFungibleTokenCore { receiver_id: AccountId, token_id: TokenId, //we introduce an approval ID so that people with that approval ID can transfer the token - approval_id: Option, + approval_id: Option, memo: Option, ); @@ -22,7 +22,7 @@ pub trait NonFungibleTokenCore { receiver_id: AccountId, token_id: TokenId, //we introduce an approval ID so that people with that approval ID can transfer the token - approval_id: Option, + approval_id: Option, memo: Option, msg: String, ) -> PromiseOrValue; @@ -55,11 +55,11 @@ trait NonFungibleTokenResolver { &mut self, //we introduce an authorized ID for logging the transfer event authorized_id: Option, - owner_id: AccountId, + previous_owner_id: AccountId, receiver_id: AccountId, token_id: TokenId, //we introduce the approval map so we can keep track of what the approvals were before the transfer - approved_account_ids: HashMap, + approved_account_ids: HashMap, //we introduce a memo for logging the transfer event memo: Option, ) -> bool; @@ -67,29 +67,24 @@ trait NonFungibleTokenResolver { #[near_bindgen] impl NonFungibleTokenCore for Contract { - //implementation of the nft_transfer method. This transfers the NFT from the current owner to the receiver. + //implementation of the nft_transfer method. This transfers the NFT from the current owner to the receiver. #[payable] fn nft_transfer( &mut self, receiver_id: AccountId, token_id: TokenId, //we introduce an approval ID so that people with that approval ID can transfer the token - approval_id: Option, + approval_id: Option, memo: Option, ) { - //assert that the user attached exactly 1 yoctoNEAR. This is for security and so that the user will be redirected to the NEAR wallet. + //assert that the user attached exactly 1 yoctoNEAR. This is for security and so that the user will be redirected to the NEAR wallet. assert_one_yocto(); //get the sender to transfer the token from the sender to the receiver let sender_id = env::predecessor_account_id(); //call the internal transfer method and get back the previous token so we can refund the approved account IDs - let previous_token = self.internal_transfer( - &sender_id, - &receiver_id, - &token_id, - approval_id, - memo, - ); + let previous_token = + self.internal_transfer(&sender_id, &receiver_id, &token_id, approval_id, memo); //we refund the owner for releasing the storage used up by the approved account IDs refund_approved_account_ids( @@ -105,14 +100,14 @@ impl NonFungibleTokenCore for Contract { receiver_id: AccountId, token_id: TokenId, //we introduce an approval ID so that people with that approval ID can transfer the token - approval_id: Option, + approval_id: Option, memo: Option, msg: String, ) -> PromiseOrValue { - //assert that the user attached exactly 1 yocto for security reasons. + //assert that the user attached exactly 1 yocto for security reasons. assert_one_yocto(); - //get the sender ID + //get the sender ID let sender_id = env::predecessor_account_id(); //transfer the token and get the previous token object @@ -125,7 +120,7 @@ impl NonFungibleTokenCore for Contract { ); //default the authorized_id to none - let mut authorized_id = None; + let mut authorized_id = None; //if the sender isn't the owner of the token, we set the authorized ID equal to the sender. if sender_id != previous_token.owner_id { authorized_id = Some(sender_id.to_string()); @@ -136,25 +131,26 @@ impl NonFungibleTokenCore for Contract { ext_non_fungible_token_receiver::ext(receiver_id.clone()) .with_static_gas(GAS_FOR_NFT_ON_TRANSFER) .nft_on_transfer( - sender_id, - previous_token.owner_id.clone(), - token_id.clone(), - msg + sender_id, + previous_token.owner_id.clone(), + token_id.clone(), + msg, + ) + // We then resolve the promise and call nft_resolve_transfer on our own contract + .then( + // Defaulting GAS weight to 1, no attached deposit, and static GAS equal to the GAS for resolve transfer + Self::ext(env::current_account_id()) + .with_static_gas(GAS_FOR_RESOLVE_TRANSFER) + .nft_resolve_transfer( + authorized_id, // we introduce an authorized ID so that we can log the transfer + previous_token.owner_id, + receiver_id, + token_id, + previous_token.approved_account_ids, + memo, // we introduce a memo for logging in the events standard + ), ) - // We then resolve the promise and call nft_resolve_transfer on our own contract - .then( - // Defaulting GAS weight to 1, no attached deposit, and static GAS equal to the GAS for resolve transfer - Self::ext(env::current_account_id()) - .with_static_gas(GAS_FOR_RESOLVE_TRANSFER) - .nft_resolve_transfer( - authorized_id, // we introduce an authorized ID so that we can log the transfer - previous_token.owner_id, - receiver_id, - token_id, - previous_token.approved_account_ids, - memo, // we introduce a memo for logging in the events standard - ) - ).into() + .into() } //get the information for a specific token ID @@ -170,7 +166,8 @@ impl NonFungibleTokenCore for Contract { metadata, approved_account_ids: token.approved_account_ids, }) - } else { //if there wasn't a token ID in the tokens_by_id collection, we return None + } else { + //if there wasn't a token ID in the tokens_by_id collection, we return None None } } @@ -185,11 +182,11 @@ impl NonFungibleTokenResolver for Contract { &mut self, //we introduce an authorized ID for logging the transfer event authorized_id: Option, - owner_id: AccountId, + previous_owner_id: AccountId, receiver_id: AccountId, token_id: TokenId, //we introduce the approval map so we can keep track of what the approvals were before the transfer - approved_account_ids: HashMap, + approved_account_ids: HashMap, //we introduce a memo for logging the transfer event memo: Option, ) -> bool { @@ -200,12 +197,12 @@ impl NonFungibleTokenResolver for Contract { if let Ok(return_token) = near_sdk::serde_json::from_slice::(&value) { //if we need don't need to return the token, we simply return true meaning everything went fine if !return_token { - /* - since we've already transferred the token and nft_on_transfer returned false, we don't have to + /* + since we've already transferred the token and nft_on_transfer returned false, we don't have to revert the original transfer and thus we can just return true since nothing went wrong. */ //we refund the owner for releasing the storage used up by the approved account IDs - refund_approved_account_ids(owner_id, &approved_account_ids); + refund_approved_account_ids(previous_owner_id, &approved_account_ids); return true; } } @@ -215,7 +212,7 @@ impl NonFungibleTokenResolver for Contract { let mut token = if let Some(token) = self.tokens_by_id.get(&token_id) { if token.owner_id != receiver_id { //we refund the owner for releasing the storage used up by the approved account IDs - refund_approved_account_ids(owner_id, &approved_account_ids); + refund_approved_account_ids(previous_owner_id, &approved_account_ids); // The token is not owner by the receiver anymore. Can't return it. return true; } @@ -223,20 +220,25 @@ impl NonFungibleTokenResolver for Contract { //if there isn't a token object, it was burned and so we return true } else { //we refund the owner for releasing the storage used up by the approved account IDs - refund_approved_account_ids(owner_id, &approved_account_ids); + refund_approved_account_ids(previous_owner_id, &approved_account_ids); return true; }; //if at the end, we haven't returned true, that means that we should return the token to it's original owner - log!("Return {} from @{} to @{}", token_id, receiver_id, owner_id); + log!( + "Return {} from @{} to @{}", + token_id, + receiver_id, + previous_owner_id + ); //we remove the token from the receiver self.internal_remove_token_from_owner(&receiver_id.clone(), &token_id); //we add the token to the original owner - self.internal_add_token_to_owner(&owner_id, &token_id); + self.internal_add_token_to_owner(&previous_owner_id, &token_id); - //we change the token struct's owner to be the original owner - token.owner_id = owner_id.clone(); + //we change the token struct's owner to be the original owner + token.owner_id = previous_owner_id.clone(); //we refund the receiver any approved account IDs that they may have set on the token refund_approved_account_ids(receiver_id.clone(), &token.approved_account_ids); @@ -263,7 +265,7 @@ impl NonFungibleTokenResolver for Contract { // The old owner's account ID. old_owner_id: receiver_id.to_string(), // The account ID of the new owner of the token. - new_owner_id: owner_id.to_string(), + new_owner_id: previous_owner_id.to_string(), // A vector containing the token IDs as strings. token_ids: vec![token_id.to_string()], // An optional memo to include. diff --git a/nft-contract-approval/src/royalty.rs b/nft-contract-approval/src/royalty.rs index b324dc1..68277aa 100644 --- a/nft-contract-approval/src/royalty.rs +++ b/nft-contract-approval/src/royalty.rs @@ -2,14 +2,14 @@ use crate::*; pub trait NonFungibleTokenCore { //calculates the payout for a token given the passed in balance. This is a view method - fn nft_payout(&self, token_id: TokenId, balance: NearToken, max_len_payout: u32) -> Payout; - - //transfers the token to the receiver ID and returns the payout object that should be payed given the passed in balance. + fn nft_payout(&self, token_id: TokenId, balance: NearToken, max_len_payout: u32) -> Payout; + + //transfers the token to the receiver ID and returns the payout object that should be payed given the passed in balance. fn nft_transfer_payout( &mut self, receiver_id: AccountId, token_id: TokenId, - approval_id: u32, + approval_id: u64, memo: Option, balance: NearToken, max_len_payout: u32, @@ -18,22 +18,21 @@ pub trait NonFungibleTokenCore { #[near_bindgen] impl NonFungibleTokenCore for Contract { - //calculates the payout for a token given the passed in balance. This is a view method fn nft_payout(&self, token_id: TokenId, balance: NearToken, max_len_payout: u32) -> Payout { - /* + /* FILL THIS IN */ todo!(); //remove once code is filled in. - } + } - //transfers the token to the receiver ID and returns the payout object that should be payed given the passed in balance. + //transfers the token to the receiver ID and returns the payout object that should be payed given the passed in balance. #[payable] fn nft_transfer_payout( &mut self, receiver_id: AccountId, token_id: TokenId, - approval_id: u32, + approval_id: u64, memo: Option, balance: NearToken, max_len_payout: u32, diff --git a/nft-contract-basic/Cargo.toml b/nft-contract-basic/Cargo.toml index 3e734c0..977334b 100644 --- a/nft-contract-basic/Cargo.toml +++ b/nft-contract-basic/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] -near-sdk = { version = "5.4.0", features = ["legacy"] } +near-sdk = { version = "5.11.0", features = ["legacy"] } [profile.release] codegen-units = 1 diff --git a/nft-contract-basic/src/approval.rs b/nft-contract-basic/src/approval.rs index 8e55739..1d3ccce 100644 --- a/nft-contract-basic/src/approval.rs +++ b/nft-contract-basic/src/approval.rs @@ -1,5 +1,5 @@ use crate::*; -use near_sdk::{ext_contract}; +use near_sdk::ext_contract; pub trait NonFungibleTokenCore { //approve an account ID to transfer a token on your behalf @@ -10,7 +10,7 @@ pub trait NonFungibleTokenCore { &self, token_id: TokenId, approved_account_id: AccountId, - approval_id: Option, + approval_id: Option, ) -> bool; //revoke a specific account from transferring the token on your behalf @@ -27,7 +27,7 @@ trait NonFungibleTokenApprovalsReceiver { &mut self, token_id: TokenId, owner_id: AccountId, - approval_id: u32, + approval_id: u64, msg: String, ); } @@ -43,11 +43,11 @@ impl NonFungibleTokenCore for Contract { } //check if the passed in account has access to approve the token ID - fn nft_is_approved( + fn nft_is_approved( &self, token_id: TokenId, approved_account_id: AccountId, - approval_id: Option, + approval_id: Option, ) -> bool { /* FILL THIS IN @@ -55,7 +55,7 @@ impl NonFungibleTokenCore for Contract { todo!(); //remove once code is filled in. } - //revoke a specific account from transferring the token on your behalf + //revoke a specific account from transferring the token on your behalf #[payable] fn nft_revoke(&mut self, token_id: TokenId, account_id: AccountId) { /* @@ -70,4 +70,4 @@ impl NonFungibleTokenCore for Contract { FILL THIS IN */ } -} \ No newline at end of file +} diff --git a/nft-contract-basic/src/enumeration.rs b/nft-contract-basic/src/enumeration.rs index 49ebc6f..9a3bc61 100644 --- a/nft-contract-basic/src/enumeration.rs +++ b/nft-contract-basic/src/enumeration.rs @@ -3,22 +3,23 @@ use crate::*; #[near_bindgen] impl Contract { //Query for the total supply of NFTs on the contract - pub fn nft_total_supply(&self) -> U64 { + pub fn nft_total_supply(&self) -> U128 { //return the length of the token metadata by ID - U64(self.token_metadata_by_id.len()) + U128(self.token_metadata_by_id.len().into()) } //Query for nft tokens on the contract regardless of the owner using pagination - pub fn nft_tokens(&self, from_index: Option, limit: Option) -> Vec { + pub fn nft_tokens(&self, from_index: Option, limit: Option) -> Vec { //where to start pagination - if we have a from_index, we'll use that - otherwise start from 0 index let start = u128::from(from_index.unwrap_or(U128(0))); //iterate through each token using an iterator - self.token_metadata_by_id.keys() + self.token_metadata_by_id + .keys() //skip to the index we specified in the start variable - .skip(start as usize) + .skip(start as usize) //take the first "limit" elements in the vector. If we didn't specify a limit, use 50 - .take(limit.unwrap_or(50) as usize) + .take(limit.unwrap_or(50) as usize) //we'll map the token IDs which are strings into Json Tokens .map(|token_id| self.nft_token(token_id.clone()).unwrap()) //since we turned the keys into an iterator, we need to turn it back into a vector to return @@ -26,19 +27,16 @@ impl Contract { } //get the total supply of NFTs for a given owner - pub fn nft_supply_for_owner( - &self, - account_id: AccountId, - ) -> U64 { + pub fn nft_supply_for_owner(&self, account_id: AccountId) -> U128 { //get the set of tokens for the passed in owner let tokens_for_owner_set = self.tokens_per_owner.get(&account_id); //if there is some set of tokens, we'll return the length if let Some(tokens_for_owner_set) = tokens_for_owner_set { - U64(tokens_for_owner_set.len()) + U128(tokens_for_owner_set.len().into()) } else { //if there isn't a set of tokens for the passed in account ID, we'll return 0 - U64(0) + U128(0) } } @@ -47,7 +45,7 @@ impl Contract { &self, account_id: AccountId, from_index: Option, - limit: Option, + limit: Option, ) -> Vec { //get the set of tokens for the passed in owner let tokens_for_owner_set = self.tokens_per_owner.get(&account_id); @@ -55,7 +53,7 @@ impl Contract { let tokens = if let Some(tokens_for_owner_set) = tokens_for_owner_set { tokens_for_owner_set } else { - //if there is no set of tokens, we'll simply return an empty vector. + //if there is no set of tokens, we'll simply return an empty vector. return vec![]; }; @@ -63,14 +61,15 @@ impl Contract { let start = u128::from(from_index.unwrap_or(U128(0))); //iterate through the keys vector - tokens.iter() + tokens + .iter() //skip to the index we specified in the start variable - .skip(start as usize) + .skip(start as usize) //take the first "limit" elements in the vector. If we didn't specify a limit, use 50 - .take(limit.unwrap_or(50) as usize) + .take(limit.unwrap_or(50) as usize) //we'll map the token IDs which are strings into Json Tokens .map(|token_id| self.nft_token(token_id.clone()).unwrap()) //since we turned the keys into an iterator, we need to turn it back into a vector to return .collect() } -} \ No newline at end of file +} diff --git a/nft-contract-basic/src/mint.rs b/nft-contract-basic/src/mint.rs index ae0b238..4f54e9f 100644 --- a/nft-contract-basic/src/mint.rs +++ b/nft-contract-basic/src/mint.rs @@ -6,34 +6,34 @@ impl Contract { pub fn nft_mint( &mut self, token_id: TokenId, - metadata: TokenMetadata, - receiver_id: AccountId, + token_owner_id: AccountId, + token_metadata: TokenMetadata, ) { //measure the initial storage being used on the contract let initial_storage_usage = env::storage_usage(); - - //specify the token struct that contains the owner ID + + //specify the token struct that contains the owner ID let token = Token { //set the owner ID equal to the receiver ID passed into the function - owner_id: receiver_id, + owner_id: token_owner_id, }; - + //insert the token ID and token struct and make sure that the token doesn't exist assert!( self.tokens_by_id.insert(&token_id, &token).is_none(), "Token already exists" ); - + //insert the token ID and metadata - self.token_metadata_by_id.insert(&token_id, &metadata); - + self.token_metadata_by_id.insert(&token_id, &token_metadata); + //call the internal method for adding the token to the owner self.internal_add_token_to_owner(&token.owner_id, &token_id); - + //calculate the required storage which was the used - initial let required_storage_in_bytes = env::storage_usage() - initial_storage_usage; - + //refund any excess storage if the user attached too much. Panic if they didn't attach enough to cover the required. refund_deposit(required_storage_in_bytes.into()); } -} \ No newline at end of file +} diff --git a/nft-contract-basic/src/nft_core.rs b/nft-contract-basic/src/nft_core.rs index 1c91a14..fcf808d 100644 --- a/nft-contract-basic/src/nft_core.rs +++ b/nft-contract-basic/src/nft_core.rs @@ -1,17 +1,12 @@ use crate::*; -use near_sdk::{ext_contract, Gas, log, PromiseResult}; +use near_sdk::{ext_contract, log, Gas, PromiseResult}; const GAS_FOR_RESOLVE_TRANSFER: Gas = Gas::from_tgas(10); const GAS_FOR_NFT_ON_TRANSFER: Gas = Gas::from_tgas(25); pub trait NonFungibleTokenCore { //transfers an NFT to a receiver ID - fn nft_transfer( - &mut self, - receiver_id: AccountId, - token_id: TokenId, - memo: Option, - ); + fn nft_transfer(&mut self, receiver_id: AccountId, token_id: TokenId, memo: Option); //transfers an NFT to a receiver and calls a function on the receiver ID's contract /// Returns `true` if the token was transferred from the sender's account. @@ -48,35 +43,25 @@ trait NonFungibleTokenResolver { as part of the nft_transfer_call method */ fn nft_resolve_transfer( - &mut self, - owner_id: AccountId, - receiver_id: AccountId, - token_id: TokenId, - ) -> bool; + &mut self, + previous_owner_id: AccountId, + receiver_id: AccountId, + token_id: TokenId, + ) -> bool; } #[near_bindgen] impl NonFungibleTokenCore for Contract { - //implementation of the nft_transfer method. This transfers the NFT from the current owner to the receiver. + //implementation of the nft_transfer method. This transfers the NFT from the current owner to the receiver. #[payable] - fn nft_transfer( - &mut self, - receiver_id: AccountId, - token_id: TokenId, - memo: Option, - ) { - //assert that the user attached exactly 1 yoctoNEAR. This is for security and so that the user will be redirected to the NEAR wallet. + fn nft_transfer(&mut self, receiver_id: AccountId, token_id: TokenId, memo: Option) { + //assert that the user attached exactly 1 yoctoNEAR. This is for security and so that the user will be redirected to the NEAR wallet. assert_one_yocto(); //get the sender to transfer the token from the sender to the receiver let sender_id = env::predecessor_account_id(); //call the internal transfer method - self.internal_transfer( - &sender_id, - &receiver_id, - &token_id, - memo - ); + self.internal_transfer(&sender_id, &receiver_id, &token_id, memo); } //implementation of the transfer call method. This will transfer the NFT and call a method on the receiver_id contract @@ -88,41 +73,34 @@ impl NonFungibleTokenCore for Contract { memo: Option, msg: String, ) -> PromiseOrValue { - //assert that the user attached exactly 1 yocto for security reasons. + //assert that the user attached exactly 1 yocto for security reasons. assert_one_yocto(); - //get the sender ID + //get the sender ID let sender_id = env::predecessor_account_id(); //transfer the token and get the previous token object - let previous_token = self.internal_transfer( - &sender_id, - &receiver_id, - &token_id, - memo.clone(), - ); + let previous_token = + self.internal_transfer(&sender_id, &receiver_id, &token_id, memo.clone()); // Initiating receiver's call and the callback // Defaulting GAS weight to 1, no attached deposit, and static GAS equal to the GAS for nft on transfer. ext_non_fungible_token_receiver::ext(receiver_id.clone()) .with_static_gas(GAS_FOR_NFT_ON_TRANSFER) .nft_on_transfer( - sender_id, - previous_token.owner_id.clone(), - token_id.clone(), - msg + sender_id, + previous_token.owner_id.clone(), + token_id.clone(), + msg, ) - // We then resolve the promise and call nft_resolve_transfer on our own contract - .then( - // Defaulting GAS weight to 1, no attached deposit, and static GAS equal to the GAS for resolve transfer - Self::ext(env::current_account_id()) - .with_static_gas(GAS_FOR_RESOLVE_TRANSFER) - .nft_resolve_transfer( - previous_token.owner_id, - receiver_id, - token_id, - ) - ).into() + // We then resolve the promise and call nft_resolve_transfer on our own contract + .then( + // Defaulting GAS weight to 1, no attached deposit, and static GAS equal to the GAS for resolve transfer + Self::ext(env::current_account_id()) + .with_static_gas(GAS_FOR_RESOLVE_TRANSFER) + .nft_resolve_transfer(previous_token.owner_id, receiver_id, token_id), + ) + .into() } //get the information for a specific token ID @@ -137,7 +115,8 @@ impl NonFungibleTokenCore for Contract { owner_id: token.owner_id, metadata, }) - } else { //if there wasn't a token ID in the tokens_by_id collection, we return None + } else { + //if there wasn't a token ID in the tokens_by_id collection, we return None None } } @@ -150,7 +129,7 @@ impl NonFungibleTokenResolver for Contract { #[private] fn nft_resolve_transfer( &mut self, - owner_id: AccountId, + previous_owner_id: AccountId, receiver_id: AccountId, token_id: TokenId, ) -> bool { @@ -161,8 +140,8 @@ impl NonFungibleTokenResolver for Contract { if let Ok(return_token) = near_sdk::serde_json::from_slice::(&value) { //if we need don't need to return the token, we simply return true meaning everything went fine if !return_token { - /* - since we've already transferred the token and nft_on_transfer returned false, we don't have to + /* + since we've already transferred the token and nft_on_transfer returned false, we don't have to revert the original transfer and thus we can just return true since nothing went wrong. */ return true; @@ -183,15 +162,20 @@ impl NonFungibleTokenResolver for Contract { }; //if at the end, we haven't returned true, that means that we should return the token to it's original owner - log!("Return {} from @{} to @{}", token_id, receiver_id, owner_id); + log!( + "Return {} from @{} to @{}", + token_id, + receiver_id, + previous_owner_id + ); //we remove the token from the receiver self.internal_remove_token_from_owner(&receiver_id, &token_id); //we add the token to the original owner - self.internal_add_token_to_owner(&owner_id, &token_id); + self.internal_add_token_to_owner(&previous_owner_id, &token_id); - //we change the token struct's owner to be the original owner - token.owner_id = owner_id.clone(); + //we change the token struct's owner to be the original owner + token.owner_id = previous_owner_id.clone(); //we inset the token back into the tokens_by_id collection self.tokens_by_id.insert(&token_id, &token); diff --git a/nft-contract-basic/src/royalty.rs b/nft-contract-basic/src/royalty.rs index b324dc1..68277aa 100644 --- a/nft-contract-basic/src/royalty.rs +++ b/nft-contract-basic/src/royalty.rs @@ -2,14 +2,14 @@ use crate::*; pub trait NonFungibleTokenCore { //calculates the payout for a token given the passed in balance. This is a view method - fn nft_payout(&self, token_id: TokenId, balance: NearToken, max_len_payout: u32) -> Payout; - - //transfers the token to the receiver ID and returns the payout object that should be payed given the passed in balance. + fn nft_payout(&self, token_id: TokenId, balance: NearToken, max_len_payout: u32) -> Payout; + + //transfers the token to the receiver ID and returns the payout object that should be payed given the passed in balance. fn nft_transfer_payout( &mut self, receiver_id: AccountId, token_id: TokenId, - approval_id: u32, + approval_id: u64, memo: Option, balance: NearToken, max_len_payout: u32, @@ -18,22 +18,21 @@ pub trait NonFungibleTokenCore { #[near_bindgen] impl NonFungibleTokenCore for Contract { - //calculates the payout for a token given the passed in balance. This is a view method fn nft_payout(&self, token_id: TokenId, balance: NearToken, max_len_payout: u32) -> Payout { - /* + /* FILL THIS IN */ todo!(); //remove once code is filled in. - } + } - //transfers the token to the receiver ID and returns the payout object that should be payed given the passed in balance. + //transfers the token to the receiver ID and returns the payout object that should be payed given the passed in balance. #[payable] fn nft_transfer_payout( &mut self, receiver_id: AccountId, token_id: TokenId, - approval_id: u32, + approval_id: u64, memo: Option, balance: NearToken, max_len_payout: u32, diff --git a/nft-contract-events/Cargo.toml b/nft-contract-events/Cargo.toml index 130ae71..97488df 100644 --- a/nft-contract-events/Cargo.toml +++ b/nft-contract-events/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] -near-sdk = { version = "5.4.0", features = ["legacy"] } +near-sdk = { version = "5.11.0", features = ["legacy"] } serde_json = "1.0.113" [profile.release] diff --git a/nft-contract-events/src/approval.rs b/nft-contract-events/src/approval.rs index 8e55739..1d3ccce 100644 --- a/nft-contract-events/src/approval.rs +++ b/nft-contract-events/src/approval.rs @@ -1,5 +1,5 @@ use crate::*; -use near_sdk::{ext_contract}; +use near_sdk::ext_contract; pub trait NonFungibleTokenCore { //approve an account ID to transfer a token on your behalf @@ -10,7 +10,7 @@ pub trait NonFungibleTokenCore { &self, token_id: TokenId, approved_account_id: AccountId, - approval_id: Option, + approval_id: Option, ) -> bool; //revoke a specific account from transferring the token on your behalf @@ -27,7 +27,7 @@ trait NonFungibleTokenApprovalsReceiver { &mut self, token_id: TokenId, owner_id: AccountId, - approval_id: u32, + approval_id: u64, msg: String, ); } @@ -43,11 +43,11 @@ impl NonFungibleTokenCore for Contract { } //check if the passed in account has access to approve the token ID - fn nft_is_approved( + fn nft_is_approved( &self, token_id: TokenId, approved_account_id: AccountId, - approval_id: Option, + approval_id: Option, ) -> bool { /* FILL THIS IN @@ -55,7 +55,7 @@ impl NonFungibleTokenCore for Contract { todo!(); //remove once code is filled in. } - //revoke a specific account from transferring the token on your behalf + //revoke a specific account from transferring the token on your behalf #[payable] fn nft_revoke(&mut self, token_id: TokenId, account_id: AccountId) { /* @@ -70,4 +70,4 @@ impl NonFungibleTokenCore for Contract { FILL THIS IN */ } -} \ No newline at end of file +} diff --git a/nft-contract-events/src/enumeration.rs b/nft-contract-events/src/enumeration.rs index 49ebc6f..9a3bc61 100644 --- a/nft-contract-events/src/enumeration.rs +++ b/nft-contract-events/src/enumeration.rs @@ -3,22 +3,23 @@ use crate::*; #[near_bindgen] impl Contract { //Query for the total supply of NFTs on the contract - pub fn nft_total_supply(&self) -> U64 { + pub fn nft_total_supply(&self) -> U128 { //return the length of the token metadata by ID - U64(self.token_metadata_by_id.len()) + U128(self.token_metadata_by_id.len().into()) } //Query for nft tokens on the contract regardless of the owner using pagination - pub fn nft_tokens(&self, from_index: Option, limit: Option) -> Vec { + pub fn nft_tokens(&self, from_index: Option, limit: Option) -> Vec { //where to start pagination - if we have a from_index, we'll use that - otherwise start from 0 index let start = u128::from(from_index.unwrap_or(U128(0))); //iterate through each token using an iterator - self.token_metadata_by_id.keys() + self.token_metadata_by_id + .keys() //skip to the index we specified in the start variable - .skip(start as usize) + .skip(start as usize) //take the first "limit" elements in the vector. If we didn't specify a limit, use 50 - .take(limit.unwrap_or(50) as usize) + .take(limit.unwrap_or(50) as usize) //we'll map the token IDs which are strings into Json Tokens .map(|token_id| self.nft_token(token_id.clone()).unwrap()) //since we turned the keys into an iterator, we need to turn it back into a vector to return @@ -26,19 +27,16 @@ impl Contract { } //get the total supply of NFTs for a given owner - pub fn nft_supply_for_owner( - &self, - account_id: AccountId, - ) -> U64 { + pub fn nft_supply_for_owner(&self, account_id: AccountId) -> U128 { //get the set of tokens for the passed in owner let tokens_for_owner_set = self.tokens_per_owner.get(&account_id); //if there is some set of tokens, we'll return the length if let Some(tokens_for_owner_set) = tokens_for_owner_set { - U64(tokens_for_owner_set.len()) + U128(tokens_for_owner_set.len().into()) } else { //if there isn't a set of tokens for the passed in account ID, we'll return 0 - U64(0) + U128(0) } } @@ -47,7 +45,7 @@ impl Contract { &self, account_id: AccountId, from_index: Option, - limit: Option, + limit: Option, ) -> Vec { //get the set of tokens for the passed in owner let tokens_for_owner_set = self.tokens_per_owner.get(&account_id); @@ -55,7 +53,7 @@ impl Contract { let tokens = if let Some(tokens_for_owner_set) = tokens_for_owner_set { tokens_for_owner_set } else { - //if there is no set of tokens, we'll simply return an empty vector. + //if there is no set of tokens, we'll simply return an empty vector. return vec![]; }; @@ -63,14 +61,15 @@ impl Contract { let start = u128::from(from_index.unwrap_or(U128(0))); //iterate through the keys vector - tokens.iter() + tokens + .iter() //skip to the index we specified in the start variable - .skip(start as usize) + .skip(start as usize) //take the first "limit" elements in the vector. If we didn't specify a limit, use 50 - .take(limit.unwrap_or(50) as usize) + .take(limit.unwrap_or(50) as usize) //we'll map the token IDs which are strings into Json Tokens .map(|token_id| self.nft_token(token_id.clone()).unwrap()) //since we turned the keys into an iterator, we need to turn it back into a vector to return .collect() } -} \ No newline at end of file +} diff --git a/nft-contract-events/src/mint.rs b/nft-contract-events/src/mint.rs index 51fd42d..6983baf 100644 --- a/nft-contract-events/src/mint.rs +++ b/nft-contract-events/src/mint.rs @@ -6,30 +6,30 @@ impl Contract { pub fn nft_mint( &mut self, token_id: TokenId, - metadata: TokenMetadata, - receiver_id: AccountId, + token_owner_id: AccountId, + token_metadata: TokenMetadata, ) { //measure the initial storage being used on the contract let initial_storage_usage = env::storage_usage(); - - //specify the token struct that contains the owner ID + + //specify the token struct that contains the owner ID let token = Token { //set the owner ID equal to the receiver ID passed into the function - owner_id: receiver_id, + owner_id: token_owner_id, }; - + //insert the token ID and token struct and make sure that the token doesn't exist assert!( self.tokens_by_id.insert(&token_id, &token).is_none(), "Token already exists" ); - + //insert the token ID and metadata - self.token_metadata_by_id.insert(&token_id, &metadata); - + self.token_metadata_by_id.insert(&token_id, &token_metadata); + //call the internal method for adding the token to the owner self.internal_add_token_to_owner(&token.owner_id, &token_id); - + // Construct the mint log as per the events standard. let nft_mint_log: EventLog = EventLog { // Standard name ("nep171"). @@ -46,14 +46,14 @@ impl Contract { memo: None, }]), }; - + // Log the serialized json. env::log_str(&nft_mint_log.to_string()); - + //calculate the required storage which was the used - initial let required_storage_in_bytes = env::storage_usage() - initial_storage_usage; - + //refund any excess storage if the user attached too much. Panic if they didn't attach enough to cover the required. refund_deposit(required_storage_in_bytes.into()); } -} \ No newline at end of file +} diff --git a/nft-contract-events/src/nft_core.rs b/nft-contract-events/src/nft_core.rs index a74afb1..2e5a2ac 100644 --- a/nft-contract-events/src/nft_core.rs +++ b/nft-contract-events/src/nft_core.rs @@ -1,17 +1,12 @@ use crate::*; -use near_sdk::{ext_contract, Gas, log, PromiseResult}; +use near_sdk::{ext_contract, log, Gas, PromiseResult}; const GAS_FOR_RESOLVE_TRANSFER: Gas = Gas::from_tgas(10); const GAS_FOR_NFT_ON_TRANSFER: Gas = Gas::from_tgas(25); pub trait NonFungibleTokenCore { //transfers an NFT to a receiver ID - fn nft_transfer( - &mut self, - receiver_id: AccountId, - token_id: TokenId, - memo: Option, - ); + fn nft_transfer(&mut self, receiver_id: AccountId, token_id: TokenId, memo: Option); //transfers an NFT to a receiver and calls a function on the receiver ID's contract /// Returns `true` if the token was transferred from the sender's account. @@ -48,39 +43,29 @@ trait NonFungibleTokenResolver { as part of the nft_transfer_call method */ fn nft_resolve_transfer( - &mut self, - //we introduce an authorized ID for logging the transfer event - authorized_id: Option, - owner_id: AccountId, - receiver_id: AccountId, - token_id: TokenId, - //we introduce a memo for logging the transfer event - memo: Option, - ) -> bool; + &mut self, + //we introduce an authorized ID for logging the transfer event + authorized_id: Option, + previous_owner_id: AccountId, + receiver_id: AccountId, + token_id: TokenId, + //we introduce a memo for logging the transfer event + memo: Option, + ) -> bool; } #[near_bindgen] impl NonFungibleTokenCore for Contract { - //implementation of the nft_transfer method. This transfers the NFT from the current owner to the receiver. + //implementation of the nft_transfer method. This transfers the NFT from the current owner to the receiver. #[payable] - fn nft_transfer( - &mut self, - receiver_id: AccountId, - token_id: TokenId, - memo: Option, - ) { - //assert that the user attached exactly 1 yoctoNEAR. This is for security and so that the user will be redirected to the NEAR wallet. + fn nft_transfer(&mut self, receiver_id: AccountId, token_id: TokenId, memo: Option) { + //assert that the user attached exactly 1 yoctoNEAR. This is for security and so that the user will be redirected to the NEAR wallet. assert_one_yocto(); //get the sender to transfer the token from the sender to the receiver let sender_id = env::predecessor_account_id(); //call the internal transfer method - self.internal_transfer( - &sender_id, - &receiver_id, - &token_id, - memo, - ); + self.internal_transfer(&sender_id, &receiver_id, &token_id, memo); } //implementation of the transfer call method. This will transfer the NFT and call a method on the receiver_id contract @@ -92,46 +77,43 @@ impl NonFungibleTokenCore for Contract { memo: Option, msg: String, ) -> PromiseOrValue { - //assert that the user attached exactly 1 yocto for security reasons. + //assert that the user attached exactly 1 yocto for security reasons. assert_one_yocto(); - //get the sender ID + //get the sender ID let sender_id = env::predecessor_account_id(); //transfer the token and get the previous token object - let previous_token = self.internal_transfer( - &sender_id, - &receiver_id, - &token_id, - memo.clone(), - ); + let previous_token = + self.internal_transfer(&sender_id, &receiver_id, &token_id, memo.clone()); //default the authorized_id to none - let mut authorized_id = None; + let mut authorized_id = None; // Initiating receiver's call and the callback // Defaulting GAS weight to 1, no attached deposit, and static GAS equal to the GAS for nft on transfer. ext_non_fungible_token_receiver::ext(receiver_id.clone()) .with_static_gas(GAS_FOR_NFT_ON_TRANSFER) .nft_on_transfer( - sender_id, - previous_token.owner_id.clone(), - token_id.clone(), - msg + sender_id, + previous_token.owner_id.clone(), + token_id.clone(), + msg, + ) + // We then resolve the promise and call nft_resolve_transfer on our own contract + .then( + // Defaulting GAS weight to 1, no attached deposit, and static GAS equal to the GAS for resolve transfer + Self::ext(env::current_account_id()) + .with_static_gas(GAS_FOR_RESOLVE_TRANSFER) + .nft_resolve_transfer( + authorized_id, // we introduce an authorized ID so that we can log the transfer + previous_token.owner_id, + receiver_id, + token_id, + memo, // we introduce a memo for logging in the events standard + ), ) - // We then resolve the promise and call nft_resolve_transfer on our own contract - .then( - // Defaulting GAS weight to 1, no attached deposit, and static GAS equal to the GAS for resolve transfer - Self::ext(env::current_account_id()) - .with_static_gas(GAS_FOR_RESOLVE_TRANSFER) - .nft_resolve_transfer( - authorized_id, // we introduce an authorized ID so that we can log the transfer - previous_token.owner_id, - receiver_id, - token_id, - memo, // we introduce a memo for logging in the events standard - ) - ).into() + .into() } //get the information for a specific token ID @@ -146,7 +128,8 @@ impl NonFungibleTokenCore for Contract { owner_id: token.owner_id, metadata, }) - } else { //if there wasn't a token ID in the tokens_by_id collection, we return None + } else { + //if there wasn't a token ID in the tokens_by_id collection, we return None None } } @@ -161,7 +144,7 @@ impl NonFungibleTokenResolver for Contract { &mut self, //we introduce an authorized ID for logging the transfer event authorized_id: Option, - owner_id: AccountId, + previous_owner_id: AccountId, receiver_id: AccountId, token_id: TokenId, //we introduce a memo for logging the transfer event @@ -174,8 +157,8 @@ impl NonFungibleTokenResolver for Contract { if let Ok(return_token) = near_sdk::serde_json::from_slice::(&value) { //if we need don't need to return the token, we simply return true meaning everything went fine if !return_token { - /* - since we've already transferred the token and nft_on_transfer returned false, we don't have to + /* + since we've already transferred the token and nft_on_transfer returned false, we don't have to revert the original transfer and thus we can just return true since nothing went wrong. */ return true; @@ -196,15 +179,20 @@ impl NonFungibleTokenResolver for Contract { }; //if at the end, we haven't returned true, that means that we should return the token to it's original owner - log!("Return {} from @{} to @{}", token_id, receiver_id, owner_id); + log!( + "Return {} from @{} to @{}", + token_id, + receiver_id, + previous_owner_id + ); //we remove the token from the receiver self.internal_remove_token_from_owner(&receiver_id, &token_id); //we add the token to the original owner - self.internal_add_token_to_owner(&owner_id, &token_id); + self.internal_add_token_to_owner(&previous_owner_id, &token_id); - //we change the token struct's owner to be the original owner - token.owner_id = owner_id.clone(); + //we change the token struct's owner to be the original owner + token.owner_id = previous_owner_id.clone(); //we inset the token back into the tokens_by_id collection self.tokens_by_id.insert(&token_id, &token); @@ -225,7 +213,7 @@ impl NonFungibleTokenResolver for Contract { // The old owner's account ID. old_owner_id: receiver_id.to_string(), // The account ID of the new owner of the token. - new_owner_id: owner_id.to_string(), + new_owner_id: previous_owner_id.to_string(), // A vector containing the token IDs as strings. token_ids: vec![token_id.to_string()], // An optional memo to include. diff --git a/nft-contract-events/src/royalty.rs b/nft-contract-events/src/royalty.rs index b324dc1..68277aa 100644 --- a/nft-contract-events/src/royalty.rs +++ b/nft-contract-events/src/royalty.rs @@ -2,14 +2,14 @@ use crate::*; pub trait NonFungibleTokenCore { //calculates the payout for a token given the passed in balance. This is a view method - fn nft_payout(&self, token_id: TokenId, balance: NearToken, max_len_payout: u32) -> Payout; - - //transfers the token to the receiver ID and returns the payout object that should be payed given the passed in balance. + fn nft_payout(&self, token_id: TokenId, balance: NearToken, max_len_payout: u32) -> Payout; + + //transfers the token to the receiver ID and returns the payout object that should be payed given the passed in balance. fn nft_transfer_payout( &mut self, receiver_id: AccountId, token_id: TokenId, - approval_id: u32, + approval_id: u64, memo: Option, balance: NearToken, max_len_payout: u32, @@ -18,22 +18,21 @@ pub trait NonFungibleTokenCore { #[near_bindgen] impl NonFungibleTokenCore for Contract { - //calculates the payout for a token given the passed in balance. This is a view method fn nft_payout(&self, token_id: TokenId, balance: NearToken, max_len_payout: u32) -> Payout { - /* + /* FILL THIS IN */ todo!(); //remove once code is filled in. - } + } - //transfers the token to the receiver ID and returns the payout object that should be payed given the passed in balance. + //transfers the token to the receiver ID and returns the payout object that should be payed given the passed in balance. #[payable] fn nft_transfer_payout( &mut self, receiver_id: AccountId, token_id: TokenId, - approval_id: u32, + approval_id: u64, memo: Option, balance: NearToken, max_len_payout: u32, diff --git a/nft-contract-royalty/Cargo.toml b/nft-contract-royalty/Cargo.toml index 130ae71..97488df 100644 --- a/nft-contract-royalty/Cargo.toml +++ b/nft-contract-royalty/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] -near-sdk = { version = "5.4.0", features = ["legacy"] } +near-sdk = { version = "5.11.0", features = ["legacy"] } serde_json = "1.0.113" [profile.release] diff --git a/nft-contract-royalty/src/approval.rs b/nft-contract-royalty/src/approval.rs index 6ea2317..b909e71 100644 --- a/nft-contract-royalty/src/approval.rs +++ b/nft-contract-royalty/src/approval.rs @@ -1,5 +1,5 @@ use crate::*; -use near_sdk::{ext_contract}; +use near_sdk::ext_contract; pub trait NonFungibleTokenCore { //approve an account ID to transfer a token on your behalf @@ -10,7 +10,7 @@ pub trait NonFungibleTokenCore { &self, token_id: TokenId, approved_account_id: AccountId, - approval_id: Option, + approval_id: Option, ) -> bool; //revoke a specific account from transferring the token on your behalf @@ -27,7 +27,7 @@ trait NonFungibleTokenApprovalsReceiver { &mut self, token_id: TokenId, owner_id: AccountId, - approval_id: u32, + approval_id: u64, msg: String, ); } @@ -54,12 +54,12 @@ impl NonFungibleTokenCore for Contract { ); //get the next approval ID if we need a new approval - let approval_id: u32 = token.next_approval_id; + let approval_id: u64 = token.next_approval_id; //check if the account has been approved already for this token let is_new_approval = token .approved_account_ids - //insert returns none if the key was not present. + //insert returns none if the key was not present. .insert(account_id.clone(), approval_id) //if the key was not present, .is_none() will return true so it is a new approval. .is_none(); @@ -77,36 +77,32 @@ impl NonFungibleTokenCore for Contract { //insert the token back into the tokens_by_id collection self.tokens_by_id.insert(&token_id, &token); - //refund any excess storage attached by the user. If the user didn't attach enough, panic. + //refund any excess storage attached by the user. If the user didn't attach enough, panic. refund_deposit(storage_used); //if some message was passed into the function, we initiate a cross contract call on the - //account we're giving access to. + //account we're giving access to. if let Some(msg) = msg { // Defaulting GAS weight to 1, no attached deposit, and no static GAS to attach. ext_non_fungible_approval_receiver::ext(account_id) - .nft_on_approve( - token_id, - token.owner_id, - approval_id, - msg - ).as_return(); + .nft_on_approve(token_id, token.owner_id, approval_id, msg) + .as_return(); } } //check if the passed in account has access to approve the token ID - fn nft_is_approved( + fn nft_is_approved( &self, token_id: TokenId, approved_account_id: AccountId, - approval_id: Option, + approval_id: Option, ) -> bool { //get the token object from the token_id let token = self.tokens_by_id.get(&token_id).expect("No token"); - + //get the approval number for the passed in account ID let approval = token.approved_account_ids.get(&approved_account_id); - + //if there was some approval ID found for the account ID if let Some(approval) = approval { //if a specific approval_id was passed into the function @@ -123,7 +119,7 @@ impl NonFungibleTokenCore for Contract { } } - //revoke a specific account from transferring the token on your behalf + //revoke a specific account from transferring the token on your behalf #[payable] fn nft_revoke(&mut self, token_id: TokenId, account_id: AccountId) { //assert that the user attached exactly 1 yoctoNEAR for security reasons @@ -136,11 +132,7 @@ impl NonFungibleTokenCore for Contract { assert_eq!(&predecessor_account_id, &token.owner_id); //if the account ID was in the token's approval, we remove it and the if statement logic executes - if token - .approved_account_ids - .remove(&account_id) - .is_some() - { + if token.approved_account_ids.remove(&account_id).is_some() { //refund the funds released by removing the approved_account_id to the caller of the function refund_approved_account_ids_iter(predecessor_account_id, [account_id].iter()); @@ -171,4 +163,4 @@ impl NonFungibleTokenCore for Contract { self.tokens_by_id.insert(&token_id, &token); } } -} \ No newline at end of file +} diff --git a/nft-contract-royalty/src/enumeration.rs b/nft-contract-royalty/src/enumeration.rs index 49ebc6f..9a3bc61 100644 --- a/nft-contract-royalty/src/enumeration.rs +++ b/nft-contract-royalty/src/enumeration.rs @@ -3,22 +3,23 @@ use crate::*; #[near_bindgen] impl Contract { //Query for the total supply of NFTs on the contract - pub fn nft_total_supply(&self) -> U64 { + pub fn nft_total_supply(&self) -> U128 { //return the length of the token metadata by ID - U64(self.token_metadata_by_id.len()) + U128(self.token_metadata_by_id.len().into()) } //Query for nft tokens on the contract regardless of the owner using pagination - pub fn nft_tokens(&self, from_index: Option, limit: Option) -> Vec { + pub fn nft_tokens(&self, from_index: Option, limit: Option) -> Vec { //where to start pagination - if we have a from_index, we'll use that - otherwise start from 0 index let start = u128::from(from_index.unwrap_or(U128(0))); //iterate through each token using an iterator - self.token_metadata_by_id.keys() + self.token_metadata_by_id + .keys() //skip to the index we specified in the start variable - .skip(start as usize) + .skip(start as usize) //take the first "limit" elements in the vector. If we didn't specify a limit, use 50 - .take(limit.unwrap_or(50) as usize) + .take(limit.unwrap_or(50) as usize) //we'll map the token IDs which are strings into Json Tokens .map(|token_id| self.nft_token(token_id.clone()).unwrap()) //since we turned the keys into an iterator, we need to turn it back into a vector to return @@ -26,19 +27,16 @@ impl Contract { } //get the total supply of NFTs for a given owner - pub fn nft_supply_for_owner( - &self, - account_id: AccountId, - ) -> U64 { + pub fn nft_supply_for_owner(&self, account_id: AccountId) -> U128 { //get the set of tokens for the passed in owner let tokens_for_owner_set = self.tokens_per_owner.get(&account_id); //if there is some set of tokens, we'll return the length if let Some(tokens_for_owner_set) = tokens_for_owner_set { - U64(tokens_for_owner_set.len()) + U128(tokens_for_owner_set.len().into()) } else { //if there isn't a set of tokens for the passed in account ID, we'll return 0 - U64(0) + U128(0) } } @@ -47,7 +45,7 @@ impl Contract { &self, account_id: AccountId, from_index: Option, - limit: Option, + limit: Option, ) -> Vec { //get the set of tokens for the passed in owner let tokens_for_owner_set = self.tokens_per_owner.get(&account_id); @@ -55,7 +53,7 @@ impl Contract { let tokens = if let Some(tokens_for_owner_set) = tokens_for_owner_set { tokens_for_owner_set } else { - //if there is no set of tokens, we'll simply return an empty vector. + //if there is no set of tokens, we'll simply return an empty vector. return vec![]; }; @@ -63,14 +61,15 @@ impl Contract { let start = u128::from(from_index.unwrap_or(U128(0))); //iterate through the keys vector - tokens.iter() + tokens + .iter() //skip to the index we specified in the start variable - .skip(start as usize) + .skip(start as usize) //take the first "limit" elements in the vector. If we didn't specify a limit, use 50 - .take(limit.unwrap_or(50) as usize) + .take(limit.unwrap_or(50) as usize) //we'll map the token IDs which are strings into Json Tokens .map(|token_id| self.nft_token(token_id.clone()).unwrap()) //since we turned the keys into an iterator, we need to turn it back into a vector to return .collect() } -} \ No newline at end of file +} diff --git a/nft-contract-royalty/src/internal.rs b/nft-contract-royalty/src/internal.rs index d6905f6..e786b17 100644 --- a/nft-contract-royalty/src/internal.rs +++ b/nft-contract-royalty/src/internal.rs @@ -1,10 +1,12 @@ use crate::*; -use near_sdk::{CryptoHash}; +use near_sdk::CryptoHash; use std::mem::size_of; //convert the royalty percentage and amount to pay into a payout pub(crate) fn royalty_to_payout(royalty_percentage: u128, amount_to_pay: NearToken) -> NearToken { - amount_to_pay.saturating_mul(royalty_percentage).saturating_div(10000) + amount_to_pay + .saturating_mul(royalty_percentage) + .saturating_div(10000) } //calculate how many bytes the account ID is taking up @@ -13,21 +15,27 @@ pub(crate) fn bytes_for_approved_account_id(account_id: &AccountId) -> u128 { account_id.as_str().len() as u128 + 4 + size_of::() as u128 } -//refund the storage taken up by passed in approved account IDs and send the funds to the passed in account ID. +//refund the storage taken up by passed in approved account IDs and send the funds to the passed in account ID. pub(crate) fn refund_approved_account_ids_iter<'a, I>( account_id: AccountId, approved_account_ids: I, //the approved account IDs must be passed in as an iterator -) -> Promise where I: Iterator { +) -> Promise +where + I: Iterator, +{ //get the storage total by going through and summing all the bytes for each approved account IDs - let storage_released = approved_account_ids.map(bytes_for_approved_account_id).sum(); + let storage_released = approved_account_ids + .map(bytes_for_approved_account_id) + .sum(); //transfer the account the storage that is released - Promise::new(account_id).transfer(env::storage_byte_cost().saturating_mul(storage_released)) + let amount_to_be_released = env::storage_byte_cost().saturating_mul(storage_released); + Promise::new(account_id).transfer(amount_to_be_released) } //refund a map of approved account IDs and send the funds to the passed in account ID pub(crate) fn refund_approved_account_ids( account_id: AccountId, - approved_account_ids: &HashMap, + approved_account_ids: &HashMap, ) -> Promise { //call the refund_approved_account_ids_iter with the approved account IDs as keys refund_approved_account_ids_iter(account_id, approved_account_ids.keys()) @@ -92,18 +100,16 @@ impl Contract { //get the set of tokens for the given account let mut tokens_set = self.tokens_per_owner.get(account_id).unwrap_or_else(|| { //if the account doesn't have any tokens, we create a new unordered set - UnorderedSet::new( - StorageKey::TokenPerOwnerInner { - //we get a new unique prefix for the collection - account_id_hash: hash_account_id(&account_id), - }, - ) + UnorderedSet::new(StorageKey::TokenPerOwnerInner { + //we get a new unique prefix for the collection + account_id_hash: hash_account_id(&account_id), + }) }); //we insert the token ID into the set tokens_set.insert(token_id); - //we insert that set for the given account ID. + //we insert that set for the given account ID. self.tokens_per_owner.insert(account_id, &tokens_set); } @@ -127,7 +133,7 @@ impl Contract { if tokens_set.is_empty() { self.tokens_per_owner.remove(account_id); } else { - //if the token set is not empty, we simply insert it back for the account ID. + //if the token set is not empty, we simply insert it back for the account ID. self.tokens_per_owner.insert(account_id, &tokens_set); } } @@ -139,7 +145,7 @@ impl Contract { receiver_id: &AccountId, token_id: &TokenId, //we introduce an approval ID so that people with that approval ID can transfer the token - approval_id: Option, + approval_id: Option, memo: Option, ) -> Token { //get the token object by passing in the token_id @@ -156,16 +162,16 @@ impl Contract { if let Some(enforced_approval_id) = approval_id { //get the actual approval ID let actual_approval_id = token - .approved_account_ids - .get(sender_id) - //if the sender isn't in the map, we panic - .expect("Sender is not approved account"); + .approved_account_ids + .get(sender_id) + //if the sender isn't in the map, we panic + .expect("Sender is not approved account"); //make sure that the actual approval ID is the same as the one provided assert_eq!( - actual_approval_id, &enforced_approval_id, - "The actual approval_id {} is different from the given approval_id {}", - actual_approval_id, enforced_approval_id, + actual_approval_id, &enforced_approval_id, + "The actual approval_id {} is different from the given approval_id {}", + actual_approval_id, enforced_approval_id, ); } } @@ -181,7 +187,7 @@ impl Contract { //we then add the token to the receiver_id's set self.internal_add_token_to_owner(receiver_id, token_id); - //we create a new token struct + //we create a new token struct let new_token = Token { owner_id: receiver_id.clone(), //reset the approval account IDs @@ -190,10 +196,10 @@ impl Contract { //we copy over the royalties from the previous token royalty: token.royalty.clone(), }; - //insert that new token into the tokens_by_id, replacing the old entry + //insert that new token into the tokens_by_id, replacing the old entry self.tokens_by_id.insert(token_id, &new_token); - //if there was some memo attached, we log it. + //if there was some memo attached, we log it. if let Some(memo) = memo.as_ref() { env::log_str(&format!("Memo: {}", memo).to_string()); } @@ -228,8 +234,8 @@ impl Contract { // Log the serialized json. env::log_str(&nft_transfer_log.to_string()); - + //return the previous token object that was transferred. token } -} \ No newline at end of file +} diff --git a/nft-contract-royalty/src/metadata.rs b/nft-contract-royalty/src/metadata.rs index 597961e..8c02011 100644 --- a/nft-contract-royalty/src/metadata.rs +++ b/nft-contract-royalty/src/metadata.rs @@ -5,7 +5,7 @@ pub type TokenId = String; #[serde(crate = "near_sdk::serde")] pub struct Payout { pub payout: HashMap, -} +} #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, NearSchema)] #[borsh(crate = "near_sdk::borsh")] @@ -44,14 +44,14 @@ pub struct Token { //owner of the token pub owner_id: AccountId, //list of approved account IDs that have access to transfer the token. This maps an account ID to an approval ID - pub approved_account_ids: HashMap, - //the next approval ID to give out. - pub next_approval_id: u32, + pub approved_account_ids: HashMap, + //the next approval ID to give out. + pub next_approval_id: u64, //keep track of the royalty percentages for the token in a hash map pub royalty: HashMap, } -//The Json token is what will be returned from view calls. +//The Json token is what will be returned from view calls. #[derive(Serialize, Deserialize, NearSchema)] #[serde(crate = "near_sdk::serde")] pub struct JsonToken { @@ -62,7 +62,7 @@ pub struct JsonToken { //token metadata pub metadata: TokenMetadata, //list of approved account IDs that have access to transfer the token. This maps an account ID to an approval ID - pub approved_account_ids: HashMap, + pub approved_account_ids: HashMap, //keep track of the royalty percentages for the token in a hash map pub royalty: HashMap, } @@ -77,4 +77,4 @@ impl NonFungibleTokenMetadata for Contract { fn nft_metadata(&self) -> NFTContractMetadata { self.metadata.get().unwrap() } -} \ No newline at end of file +} diff --git a/nft-contract-royalty/src/mint.rs b/nft-contract-royalty/src/mint.rs index 93f0b98..547e19c 100644 --- a/nft-contract-royalty/src/mint.rs +++ b/nft-contract-royalty/src/mint.rs @@ -6,8 +6,8 @@ impl Contract { pub fn nft_mint( &mut self, token_id: TokenId, - metadata: TokenMetadata, - receiver_id: AccountId, + token_owner_id: AccountId, + token_metadata: TokenMetadata, //we add an optional parameter for perpetual royalties perpetual_royalties: Option>, ) { @@ -17,10 +17,13 @@ impl Contract { // create a royalty map to store in the token let mut royalty = HashMap::new(); - // if perpetual royalties were passed into the function: + // if perpetual royalties were passed into the function: if let Some(perpetual_royalties) = perpetual_royalties { //make sure that the length of the perpetual royalties is below 7 since we won't have enough GAS to pay out that many people - assert!(perpetual_royalties.len() < 7, "Cannot add more than 6 perpetual royalty amounts"); + assert!( + perpetual_royalties.len() < 7, + "Cannot add more than 6 perpetual royalty amounts" + ); //iterate through the perpetual royalties and insert the account and amount in the royalty map for (account, amount) in perpetual_royalties { @@ -28,10 +31,10 @@ impl Contract { } } - //specify the token struct that contains the owner ID + //specify the token struct that contains the owner ID let token = Token { //set the owner ID equal to the receiver ID passed into the function - owner_id: receiver_id, + owner_id: token_owner_id, //we set the approved account IDs to the default value (an empty map) approved_account_ids: Default::default(), //the next approval ID is set to 0 @@ -47,7 +50,7 @@ impl Contract { ); //insert the token ID and metadata - self.token_metadata_by_id.insert(&token_id, &metadata); + self.token_metadata_by_id.insert(&token_id, &token_metadata); //call the internal method for adding the token to the owner self.internal_add_token_to_owner(&token.owner_id, &token_id); @@ -68,14 +71,14 @@ impl Contract { memo: None, }]), }; - + // Log the serialized json. env::log_str(&nft_mint_log.to_string()); - + //calculate the required storage which was the used - initial let required_storage_in_bytes = env::storage_usage() - initial_storage_usage; - + //refund any excess storage if the user attached too much. Panic if they didn't attach enough to cover the required. refund_deposit(required_storage_in_bytes.into()); } -} \ No newline at end of file +} diff --git a/nft-contract-royalty/src/nft_core.rs b/nft-contract-royalty/src/nft_core.rs index 5286a97..7fa1aa0 100644 --- a/nft-contract-royalty/src/nft_core.rs +++ b/nft-contract-royalty/src/nft_core.rs @@ -1,5 +1,5 @@ use crate::*; -use near_sdk::{ext_contract, Gas, log, PromiseResult}; +use near_sdk::{ext_contract, log, Gas, PromiseResult}; const GAS_FOR_RESOLVE_TRANSFER: Gas = Gas::from_tgas(10); const GAS_FOR_NFT_ON_TRANSFER: Gas = Gas::from_tgas(25); @@ -11,7 +11,7 @@ pub trait NonFungibleTokenCore { receiver_id: AccountId, token_id: TokenId, //we introduce an approval ID so that people with that approval ID can transfer the token - approval_id: Option, + approval_id: Option, memo: Option, ); @@ -22,7 +22,7 @@ pub trait NonFungibleTokenCore { receiver_id: AccountId, token_id: TokenId, //we introduce an approval ID so that people with that approval ID can transfer the token - approval_id: Option, + approval_id: Option, memo: Option, msg: String, ) -> PromiseOrValue; @@ -55,11 +55,11 @@ trait NonFungibleTokenResolver { &mut self, //we introduce an authorized ID for logging the transfer event authorized_id: Option, - owner_id: AccountId, + previous_owner_id: AccountId, receiver_id: AccountId, token_id: TokenId, //we introduce the approval map so we can keep track of what the approvals were before the transfer - approved_account_ids: HashMap, + approved_account_ids: HashMap, //we introduce a memo for logging the transfer event memo: Option, ) -> bool; @@ -67,29 +67,24 @@ trait NonFungibleTokenResolver { #[near_bindgen] impl NonFungibleTokenCore for Contract { - //implementation of the nft_transfer method. This transfers the NFT from the current owner to the receiver. + //implementation of the nft_transfer method. This transfers the NFT from the current owner to the receiver. #[payable] fn nft_transfer( &mut self, receiver_id: AccountId, token_id: TokenId, //we introduce an approval ID so that people with that approval ID can transfer the token - approval_id: Option, + approval_id: Option, memo: Option, ) { - //assert that the user attached exactly 1 yoctoNEAR. This is for security and so that the user will be redirected to the NEAR wallet. + //assert that the user attached exactly 1 yoctoNEAR. This is for security and so that the user will be redirected to the NEAR wallet. assert_one_yocto(); //get the sender to transfer the token from the sender to the receiver let sender_id = env::predecessor_account_id(); //call the internal transfer method and get back the previous token so we can refund the approved account IDs - let previous_token = self.internal_transfer( - &sender_id, - &receiver_id, - &token_id, - approval_id, - memo, - ); + let previous_token = + self.internal_transfer(&sender_id, &receiver_id, &token_id, approval_id, memo); //we refund the owner for releasing the storage used up by the approved account IDs refund_approved_account_ids( @@ -105,14 +100,14 @@ impl NonFungibleTokenCore for Contract { receiver_id: AccountId, token_id: TokenId, //we introduce an approval ID so that people with that approval ID can transfer the token - approval_id: Option, + approval_id: Option, memo: Option, msg: String, ) -> PromiseOrValue { - //assert that the user attached exactly 1 yocto for security reasons. + //assert that the user attached exactly 1 yocto for security reasons. assert_one_yocto(); - //get the sender ID + //get the sender ID let sender_id = env::predecessor_account_id(); //transfer the token and get the previous token object @@ -125,7 +120,7 @@ impl NonFungibleTokenCore for Contract { ); //default the authorized_id to none - let mut authorized_id = None; + let mut authorized_id = None; //if the sender isn't the owner of the token, we set the authorized ID equal to the sender. if sender_id != previous_token.owner_id { authorized_id = Some(sender_id.to_string()); @@ -136,25 +131,26 @@ impl NonFungibleTokenCore for Contract { ext_non_fungible_token_receiver::ext(receiver_id.clone()) .with_static_gas(GAS_FOR_NFT_ON_TRANSFER) .nft_on_transfer( - sender_id, - previous_token.owner_id.clone(), - token_id.clone(), - msg + sender_id, + previous_token.owner_id.clone(), + token_id.clone(), + msg, + ) + // We then resolve the promise and call nft_resolve_transfer on our own contract + .then( + // Defaulting GAS weight to 1, no attached deposit, and static GAS equal to the GAS for resolve transfer + Self::ext(env::current_account_id()) + .with_static_gas(GAS_FOR_RESOLVE_TRANSFER) + .nft_resolve_transfer( + authorized_id, // we introduce an authorized ID so that we can log the transfer + previous_token.owner_id, + receiver_id, + token_id, + previous_token.approved_account_ids, + memo, // we introduce a memo for logging in the events standard + ), ) - // We then resolve the promise and call nft_resolve_transfer on our own contract - .then( - // Defaulting GAS weight to 1, no attached deposit, and static GAS equal to the GAS for resolve transfer - Self::ext(env::current_account_id()) - .with_static_gas(GAS_FOR_RESOLVE_TRANSFER) - .nft_resolve_transfer( - authorized_id, // we introduce an authorized ID so that we can log the transfer - previous_token.owner_id, - receiver_id, - token_id, - previous_token.approved_account_ids, - memo, // we introduce a memo for logging in the events standard - ) - ).into() + .into() } //get the information for a specific token ID @@ -171,7 +167,8 @@ impl NonFungibleTokenCore for Contract { approved_account_ids: token.approved_account_ids, royalty: token.royalty, }) - } else { //if there wasn't a token ID in the tokens_by_id collection, we return None + } else { + //if there wasn't a token ID in the tokens_by_id collection, we return None None } } @@ -186,11 +183,11 @@ impl NonFungibleTokenResolver for Contract { &mut self, //we introduce an authorized ID for logging the transfer event authorized_id: Option, - owner_id: AccountId, + previous_owner_id: AccountId, receiver_id: AccountId, token_id: TokenId, //we introduce the approval map so we can keep track of what the approvals were before the transfer - approved_account_ids: HashMap, + approved_account_ids: HashMap, //we introduce a memo for logging the transfer event memo: Option, ) -> bool { @@ -201,12 +198,12 @@ impl NonFungibleTokenResolver for Contract { if let Ok(return_token) = near_sdk::serde_json::from_slice::(&value) { //if we need don't need to return the token, we simply return true meaning everything went fine if !return_token { - /* - since we've already transferred the token and nft_on_transfer returned false, we don't have to + /* + since we've already transferred the token and nft_on_transfer returned false, we don't have to revert the original transfer and thus we can just return true since nothing went wrong. */ //we refund the owner for releasing the storage used up by the approved account IDs - refund_approved_account_ids(owner_id, &approved_account_ids); + refund_approved_account_ids(previous_owner_id, &approved_account_ids); return true; } } @@ -216,7 +213,7 @@ impl NonFungibleTokenResolver for Contract { let mut token = if let Some(token) = self.tokens_by_id.get(&token_id) { if token.owner_id != receiver_id { //we refund the owner for releasing the storage used up by the approved account IDs - refund_approved_account_ids(owner_id, &approved_account_ids); + refund_approved_account_ids(previous_owner_id, &approved_account_ids); // The token is not owner by the receiver anymore. Can't return it. return true; } @@ -224,20 +221,25 @@ impl NonFungibleTokenResolver for Contract { //if there isn't a token object, it was burned and so we return true } else { //we refund the owner for releasing the storage used up by the approved account IDs - refund_approved_account_ids(owner_id, &approved_account_ids); + refund_approved_account_ids(previous_owner_id, &approved_account_ids); return true; }; //if at the end, we haven't returned true, that means that we should return the token to it's original owner - log!("Return {} from @{} to @{}", token_id, receiver_id, owner_id); + log!( + "Return {} from @{} to @{}", + token_id, + receiver_id, + previous_owner_id + ); //we remove the token from the receiver self.internal_remove_token_from_owner(&receiver_id.clone(), &token_id); //we add the token to the original owner - self.internal_add_token_to_owner(&owner_id, &token_id); + self.internal_add_token_to_owner(&previous_owner_id, &token_id); - //we change the token struct's owner to be the original owner - token.owner_id = owner_id.clone(); + //we change the token struct's owner to be the original owner + token.owner_id = previous_owner_id.clone(); //we refund the receiver any approved account IDs that they may have set on the token refund_approved_account_ids(receiver_id.clone(), &token.approved_account_ids); @@ -264,7 +266,7 @@ impl NonFungibleTokenResolver for Contract { // The old owner's account ID. old_owner_id: receiver_id.to_string(), // The account ID of the new owner of the token. - new_owner_id: owner_id.to_string(), + new_owner_id: previous_owner_id.to_string(), // A vector containing the token IDs as strings. token_ids: vec![token_id.to_string()], // An optional memo to include. diff --git a/nft-contract-royalty/src/royalty.rs b/nft-contract-royalty/src/royalty.rs index b90d62e..2894319 100644 --- a/nft-contract-royalty/src/royalty.rs +++ b/nft-contract-royalty/src/royalty.rs @@ -9,7 +9,7 @@ pub trait NonFungibleTokenCore { &mut self, receiver_id: AccountId, token_id: TokenId, - approval_id: u32, + approval_id: u64, memo: Option, balance: NearToken, max_len_payout: u32, @@ -70,7 +70,7 @@ impl NonFungibleTokenCore for Contract { &mut self, receiver_id: AccountId, token_id: TokenId, - approval_id: u32, + approval_id: u64, memo: Option, balance: NearToken, max_len_payout: u32, diff --git a/nft-contract-skeleton/Cargo.toml b/nft-contract-skeleton/Cargo.toml index 3e734c0..977334b 100644 --- a/nft-contract-skeleton/Cargo.toml +++ b/nft-contract-skeleton/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] -near-sdk = { version = "5.4.0", features = ["legacy"] } +near-sdk = { version = "5.11.0", features = ["legacy"] } [profile.release] codegen-units = 1 diff --git a/nft-contract-skeleton/src/approval.rs b/nft-contract-skeleton/src/approval.rs index 8e55739..1d3ccce 100644 --- a/nft-contract-skeleton/src/approval.rs +++ b/nft-contract-skeleton/src/approval.rs @@ -1,5 +1,5 @@ use crate::*; -use near_sdk::{ext_contract}; +use near_sdk::ext_contract; pub trait NonFungibleTokenCore { //approve an account ID to transfer a token on your behalf @@ -10,7 +10,7 @@ pub trait NonFungibleTokenCore { &self, token_id: TokenId, approved_account_id: AccountId, - approval_id: Option, + approval_id: Option, ) -> bool; //revoke a specific account from transferring the token on your behalf @@ -27,7 +27,7 @@ trait NonFungibleTokenApprovalsReceiver { &mut self, token_id: TokenId, owner_id: AccountId, - approval_id: u32, + approval_id: u64, msg: String, ); } @@ -43,11 +43,11 @@ impl NonFungibleTokenCore for Contract { } //check if the passed in account has access to approve the token ID - fn nft_is_approved( + fn nft_is_approved( &self, token_id: TokenId, approved_account_id: AccountId, - approval_id: Option, + approval_id: Option, ) -> bool { /* FILL THIS IN @@ -55,7 +55,7 @@ impl NonFungibleTokenCore for Contract { todo!(); //remove once code is filled in. } - //revoke a specific account from transferring the token on your behalf + //revoke a specific account from transferring the token on your behalf #[payable] fn nft_revoke(&mut self, token_id: TokenId, account_id: AccountId) { /* @@ -70,4 +70,4 @@ impl NonFungibleTokenCore for Contract { FILL THIS IN */ } -} \ No newline at end of file +} diff --git a/nft-contract-skeleton/src/enumeration.rs b/nft-contract-skeleton/src/enumeration.rs index c41d21b..57c80a7 100644 --- a/nft-contract-skeleton/src/enumeration.rs +++ b/nft-contract-skeleton/src/enumeration.rs @@ -3,7 +3,7 @@ use crate::*; #[near_bindgen] impl Contract { //Query for the total supply of NFTs on the contract - pub fn nft_total_supply(&self) -> U64 { + pub fn nft_total_supply(&self) -> U128 { /* FILL THIS IN */ @@ -11,7 +11,7 @@ impl Contract { } //Query for nft tokens on the contract regardless of the owner using pagination - pub fn nft_tokens(&self, from_index: Option, limit: Option) -> Vec { + pub fn nft_tokens(&self, from_index: Option, limit: Option) -> Vec { /* FILL THIS IN */ @@ -19,10 +19,7 @@ impl Contract { } //get the total supply of NFTs for a given owner - pub fn nft_supply_for_owner( - &self, - account_id: AccountId, - ) -> U64 { + pub fn nft_supply_for_owner(&self, account_id: AccountId) -> U128 { /* FILL THIS IN */ @@ -34,11 +31,11 @@ impl Contract { &self, account_id: AccountId, from_index: Option, - limit: Option, + limit: Option, ) -> Vec { /* FILL THIS IN */ todo!(); //remove once code is filled in. } -} \ No newline at end of file +} diff --git a/nft-contract-skeleton/src/mint.rs b/nft-contract-skeleton/src/mint.rs index ece73ad..9a22cfe 100644 --- a/nft-contract-skeleton/src/mint.rs +++ b/nft-contract-skeleton/src/mint.rs @@ -6,11 +6,11 @@ impl Contract { pub fn nft_mint( &mut self, token_id: Option, - metadata: TokenMetadata, - receiver_id: Option, + token_owner_id: AccountId, + token_metadata: TokenMetadata, ) { /* FILL THIS IN */ } -} \ No newline at end of file +} diff --git a/nft-contract-skeleton/src/nft_core.rs b/nft-contract-skeleton/src/nft_core.rs index 3c35d2c..9c77c75 100644 --- a/nft-contract-skeleton/src/nft_core.rs +++ b/nft-contract-skeleton/src/nft_core.rs @@ -1,17 +1,12 @@ use crate::*; -use near_sdk::{ext_contract, Gas, log, PromiseResult}; +use near_sdk::{ext_contract, log, Gas, PromiseResult}; const GAS_FOR_RESOLVE_TRANSFER: Gas = Gas::from_tgas(10); const GAS_FOR_NFT_ON_TRANSFER: Gas = Gas::from_tgas(25); pub trait NonFungibleTokenCore { //transfers an NFT to a receiver ID - fn nft_transfer( - &mut self, - receiver_id: AccountId, - token_id: TokenId, - memo: Option, - ); + fn nft_transfer(&mut self, receiver_id: AccountId, token_id: TokenId, memo: Option); //transfers an NFT to a receiver and calls a function on the receiver ID's contract /// Returns `true` if the token was transferred from the sender's account. @@ -49,7 +44,7 @@ trait NonFungibleTokenResolver { */ fn nft_resolve_transfer( &mut self, - owner_id: AccountId, + previous_owner_id: AccountId, receiver_id: AccountId, token_id: TokenId, ) -> bool; @@ -57,14 +52,9 @@ trait NonFungibleTokenResolver { #[near_bindgen] impl NonFungibleTokenCore for Contract { - //implementation of the nft_transfer method. This transfers the NFT from the current owner to the receiver. + //implementation of the nft_transfer method. This transfers the NFT from the current owner to the receiver. #[payable] - fn nft_transfer( - &mut self, - receiver_id: AccountId, - token_id: TokenId, - memo: Option, - ) { + fn nft_transfer(&mut self, receiver_id: AccountId, token_id: TokenId, memo: Option) { /* FILL THIS IN */ @@ -101,7 +91,7 @@ impl NonFungibleTokenResolver for Contract { #[private] fn nft_resolve_transfer( &mut self, - owner_id: AccountId, + previous_owner_id: AccountId, receiver_id: AccountId, token_id: TokenId, ) -> bool { diff --git a/nft-contract-skeleton/src/royalty.rs b/nft-contract-skeleton/src/royalty.rs index df6babd..68277aa 100644 --- a/nft-contract-skeleton/src/royalty.rs +++ b/nft-contract-skeleton/src/royalty.rs @@ -2,14 +2,14 @@ use crate::*; pub trait NonFungibleTokenCore { //calculates the payout for a token given the passed in balance. This is a view method - fn nft_payout(&self, token_id: TokenId, balance: NearToken, max_len_payout: u32) -> Payout; - - //transfers the token to the receiver ID and returns the payout object that should be payed given the passed in balance. + fn nft_payout(&self, token_id: TokenId, balance: NearToken, max_len_payout: u32) -> Payout; + + //transfers the token to the receiver ID and returns the payout object that should be payed given the passed in balance. fn nft_transfer_payout( &mut self, receiver_id: AccountId, token_id: TokenId, - approval_id: u32, + approval_id: u64, memo: Option, balance: NearToken, max_len_payout: u32, @@ -20,19 +20,19 @@ pub trait NonFungibleTokenCore { impl NonFungibleTokenCore for Contract { //calculates the payout for a token given the passed in balance. This is a view method fn nft_payout(&self, token_id: TokenId, balance: NearToken, max_len_payout: u32) -> Payout { - /* + /* FILL THIS IN */ todo!(); //remove once code is filled in. - } + } - //transfers the token to the receiver ID and returns the payout object that should be payed given the passed in balance. + //transfers the token to the receiver ID and returns the payout object that should be payed given the passed in balance. #[payable] fn nft_transfer_payout( &mut self, receiver_id: AccountId, token_id: TokenId, - approval_id: u32, + approval_id: u64, memo: Option, balance: NearToken, max_len_payout: u32, diff --git a/nft-series/Cargo.toml b/nft-series/Cargo.toml index d6cc333..d3dd8a8 100644 --- a/nft-series/Cargo.toml +++ b/nft-series/Cargo.toml @@ -8,12 +8,12 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] -near-sdk = { version = "5.4.0", features = ["legacy"] } +near-sdk = { version = "5.11.0", features = ["legacy"] } serde_json = "1.0.113" [dev-dependencies] -near-sdk = { version = "5.4.0", features = ["unit-testing"] } -near-workspaces = { version = "0.14.1", features = ["unstable"] } +near-sdk = { version = "5.11.0", features = ["unit-testing"] } +near-workspaces = { version = "0.18.0", features = ["unstable"] } tokio = { version = "1.12.0", features = ["full"] } serde_json = "1" diff --git a/nft-series/src/approval.rs b/nft-series/src/approval.rs index 4791558..6755a53 100644 --- a/nft-series/src/approval.rs +++ b/nft-series/src/approval.rs @@ -10,7 +10,7 @@ pub trait NonFungibleTokenCore { &self, token_id: TokenId, approved_account_id: AccountId, - approval_id: Option, + approval_id: Option, ) -> bool; //revoke a specific account from transferring the token on your behalf @@ -27,7 +27,7 @@ trait NonFungibleTokenApprovalsReceiver { &mut self, token_id: TokenId, owner_id: AccountId, - approval_id: u32, + approval_id: u64, msg: String, ); } @@ -54,7 +54,7 @@ impl NonFungibleTokenCore for Contract { ); //get the next approval ID if we need a new approval - let approval_id: u32 = token.next_approval_id; + let approval_id: u64 = token.next_approval_id; //check if the account has been approved already for this token let is_new_approval = token @@ -95,7 +95,7 @@ impl NonFungibleTokenCore for Contract { &self, token_id: TokenId, approved_account_id: AccountId, - approval_id: Option, + approval_id: Option, ) -> bool { //get the token object from the token_id let token = self.tokens_by_id.get(&token_id).expect("No token"); diff --git a/nft-series/src/enumeration.rs b/nft-series/src/enumeration.rs index f04c00e..1a1c5b7 100644 --- a/nft-series/src/enumeration.rs +++ b/nft-series/src/enumeration.rs @@ -1,6 +1,5 @@ -use crate::*; use crate::nft_core::NonFungibleTokenCore; - +use crate::*; /// Struct to return in views to query for specific data related to a series #[derive(BorshDeserialize, BorshSerialize, Serialize, NearSchema)] @@ -19,13 +18,13 @@ pub struct JsonSeries { #[near_bindgen] impl Contract { //Query for the total supply of NFTs on the contract - pub fn nft_total_supply(&self) -> U64 { + pub fn nft_total_supply(&self) -> U128 { //return the length of the tokens by id - U64(self.tokens_by_id.len()) + U128(self.tokens_by_id.len().into()) } //Query for nft tokens on the contract regardless of the owner using pagination - pub fn nft_tokens(&self, from_index: Option, limit: Option) -> Vec { + pub fn nft_tokens(&self, from_index: Option, limit: Option) -> Vec { //where to start pagination - if we have a from_index, we'll use that - otherwise start from 0 index let start = u128::from(from_index.unwrap_or(U128(0))); @@ -43,16 +42,16 @@ impl Contract { } //get the total supply of NFTs for a given owner - pub fn nft_supply_for_owner(&self, account_id: AccountId) -> U64 { + pub fn nft_supply_for_owner(&self, account_id: AccountId) -> U128 { //get the set of tokens for the passed in owner let tokens_for_owner_set = self.tokens_per_owner.get(&account_id); //if there is some set of tokens, we'll return the length if let Some(tokens_for_owner_set) = tokens_for_owner_set { - U64(tokens_for_owner_set.len()) + U128(tokens_for_owner_set.len().into()) } else { //if there isn't a set of tokens for the passed in account ID, we'll return 0 - U64(0) + U128(0) } } @@ -61,7 +60,7 @@ impl Contract { &self, account_id: AccountId, from_index: Option, - limit: Option, + limit: Option, ) -> Vec { //get the set of tokens for the passed in owner let tokens_for_owner_set = self.tokens_per_owner.get(&account_id); @@ -143,7 +142,7 @@ impl Contract { } } - /// Paginate through NFTs within a given series + /// Paginate through NFTs within a given series pub fn nft_tokens_for_series( &self, id: U64, diff --git a/nft-series/src/internal.rs b/nft-series/src/internal.rs index a7ba2ab..c44cfc5 100644 --- a/nft-series/src/internal.rs +++ b/nft-series/src/internal.rs @@ -17,21 +17,26 @@ pub(crate) fn bytes_for_approved_account_id(account_id: &AccountId) -> u128 { account_id.as_str().len() as u128 + 4 + size_of::() as u128 } -//refund the storage taken up by passed in approved account IDs and send the funds to the passed in account ID. +//refund the storage taken up by passed in approved account IDs and send the funds to the passed in account ID. pub(crate) fn refund_approved_account_ids_iter<'a, I>( - account_id: AccountId, - approved_account_ids: I, //the approved account IDs must be passed in as an iterator -) -> Promise where I: Iterator { - //get the storage total by going through and summing all the bytes for each approved account IDs - let storage_released = approved_account_ids.map(bytes_for_approved_account_id).sum(); - //transfer the account the storage that is released - Promise::new(account_id).transfer(env::storage_byte_cost().saturating_mul(storage_released)) + account_id: AccountId, + approved_account_ids: I, //the approved account IDs must be passed in as an iterator +) -> Promise +where + I: Iterator, +{ + //get the storage total by going through and summing all the bytes for each approved account IDs + let storage_released = approved_account_ids + .map(bytes_for_approved_account_id) + .sum(); + //transfer the account the storage that is released + Promise::new(account_id).transfer(env::storage_byte_cost().saturating_mul(storage_released)) } //refund a map of approved account IDs and send the funds to the passed in account ID pub(crate) fn refund_approved_account_ids( account_id: AccountId, - approved_account_ids: &HashMap, + approved_account_ids: &HashMap, ) -> Promise { //call the refund_approved_account_ids_iter with the approved account IDs as keys refund_approved_account_ids_iter(account_id, approved_account_ids.keys()) @@ -48,23 +53,27 @@ pub(crate) fn hash_account_id(account_id: &String) -> CryptoHash { //used to make sure the user attached exactly 1 yoctoNEAR pub(crate) fn assert_one_yocto() { - assert_eq!( - env::attached_deposit(), - ONE_YOCTONEAR, - "Requires attached deposit of exactly 1 yoctoNEAR", - ) + assert_eq!( + env::attached_deposit(), + ONE_YOCTONEAR, + "Requires attached deposit of exactly 1 yoctoNEAR", + ) } //Assert that the user has attached at least 1 yoctoNEAR (for security reasons and to pay for storage) pub(crate) fn assert_at_least_one_yocto() { - assert!( - env::attached_deposit() >= ONE_YOCTONEAR, - "Requires attached deposit of at least 1 yoctoNEAR", - ) + assert!( + env::attached_deposit() >= ONE_YOCTONEAR, + "Requires attached deposit of at least 1 yoctoNEAR", + ) } // Send all the non storage funds to the series owner -pub(crate) fn payout_series_owner(storage_used: u128, price_per_token: NearToken, owner_id: AccountId) { +pub(crate) fn payout_series_owner( + storage_used: u128, + price_per_token: NearToken, + owner_id: AccountId, +) { //get how much it would cost to store the information let required_cost = env::storage_byte_cost().saturating_mul(storage_used); //get the attached deposit @@ -86,25 +95,25 @@ pub(crate) fn payout_series_owner(storage_used: u128, price_per_token: NearToken //refund the initial deposit based on the amount of storage that was used up pub(crate) fn refund_deposit(storage_used: u128) { - //get how much it would cost to store the information - let required_cost = env::storage_byte_cost().saturating_mul(storage_used); - //get the attached deposit - let attached_deposit = env::attached_deposit(); - - //make sure that the attached deposit is greater than or equal to the required cost - assert!( - required_cost <= attached_deposit, - "Must attach {} yoctoNEAR to cover storage", - required_cost, - ); - - //get the refund amount from the attached deposit - required cost - let refund = attached_deposit.saturating_sub(required_cost); - - //if the refund is greater than 1 yocto NEAR, we refund the predecessor that amount - if refund.gt(&ONE_YOCTONEAR) { - Promise::new(env::predecessor_account_id()).transfer(refund); - } + //get how much it would cost to store the information + let required_cost = env::storage_byte_cost().saturating_mul(storage_used); + //get the attached deposit + let attached_deposit = env::attached_deposit(); + + //make sure that the attached deposit is greater than or equal to the required cost + assert!( + required_cost <= attached_deposit, + "Must attach {} yoctoNEAR to cover storage", + required_cost, + ); + + //get the refund amount from the attached deposit - required cost + let refund = attached_deposit.saturating_sub(required_cost); + + //if the refund is greater than 1 yocto NEAR, we refund the predecessor that amount + if refund.gt(&ONE_YOCTONEAR) { + Promise::new(env::predecessor_account_id()).transfer(refund); + } } impl Contract { @@ -125,12 +134,10 @@ impl Contract { //get the set of tokens for the given account let mut tokens_set = self.tokens_per_owner.get(account_id).unwrap_or_else(|| { //if the account doesn't have any tokens, we create a new unordered set - UnorderedSet::new( - StorageKey::TokenPerOwnerInner { - //we get a new unique prefix for the collection - account_id_hash: hash_account_id(&account_id.to_string()), - } - ) + UnorderedSet::new(StorageKey::TokenPerOwnerInner { + //we get a new unique prefix for the collection + account_id_hash: hash_account_id(&account_id.to_string()), + }) }); //we insert the token ID into the set @@ -172,7 +179,7 @@ impl Contract { receiver_id: &AccountId, token_id: &TokenId, //we introduce an approval ID so that people with that approval ID can transfer the token - approval_id: Option, + approval_id: Option, memo: Option, ) -> Token { //get the token object by passing in the token_id diff --git a/nft-series/src/metadata.rs b/nft-series/src/metadata.rs index 366de98..0934281 100644 --- a/nft-series/src/metadata.rs +++ b/nft-series/src/metadata.rs @@ -46,9 +46,9 @@ pub struct Token { //owner of the token pub owner_id: AccountId, //list of approved account IDs that have access to transfer the token. This maps an account ID to an approval ID - pub approved_account_ids: HashMap, + pub approved_account_ids: HashMap, //the next approval ID to give out. - pub next_approval_id: u32, + pub next_approval_id: u64, } //The Json token is what will be returned from view calls. @@ -64,7 +64,7 @@ pub struct JsonToken { //token metadata pub metadata: TokenMetadata, //list of approved account IDs that have access to transfer the token. This maps an account ID to an approval ID - pub approved_account_ids: HashMap, + pub approved_account_ids: HashMap, //keep track of the royalty percentages for the token in a hash map pub royalty: Option>, } diff --git a/nft-series/src/nft_core.rs b/nft-series/src/nft_core.rs index 9f3d3a1..550ff2e 100644 --- a/nft-series/src/nft_core.rs +++ b/nft-series/src/nft_core.rs @@ -11,7 +11,7 @@ pub trait NonFungibleTokenCore { receiver_id: AccountId, token_id: TokenId, //we introduce an approval ID so that people with that approval ID can transfer the token - approval_id: Option, + approval_id: Option, memo: Option, ); @@ -22,7 +22,7 @@ pub trait NonFungibleTokenCore { receiver_id: AccountId, token_id: TokenId, //we introduce an approval ID so that people with that approval ID can transfer the token - approval_id: Option, + approval_id: Option, memo: Option, msg: String, ) -> PromiseOrValue; @@ -55,11 +55,11 @@ trait NonFungibleTokenResolver { &mut self, //we introduce an authorized ID for logging the transfer event authorized_id: Option, - owner_id: AccountId, + previous_owner_id: AccountId, receiver_id: AccountId, token_id: TokenId, //we introduce the approval map so we can keep track of what the approvals were before the transfer - approved_account_ids: HashMap, + approved_account_ids: HashMap, //we introduce a memo for logging the transfer event memo: Option, ) -> bool; @@ -74,7 +74,7 @@ impl NonFungibleTokenCore for Contract { receiver_id: AccountId, token_id: TokenId, //we introduce an approval ID so that people with that approval ID can transfer the token - approval_id: Option, + approval_id: Option, memo: Option, ) { //assert that the user attached exactly 1 yoctoNEAR. This is for security and so that the user will be redirected to the NEAR wallet. @@ -100,7 +100,7 @@ impl NonFungibleTokenCore for Contract { receiver_id: AccountId, token_id: TokenId, //we introduce an approval ID so that people with that approval ID can transfer the token - approval_id: Option, + approval_id: Option, memo: Option, msg: String, ) -> PromiseOrValue { @@ -201,11 +201,11 @@ impl NonFungibleTokenResolver for Contract { &mut self, //we introduce an authorized ID for logging the transfer event authorized_id: Option, - owner_id: AccountId, + previous_owner_id: AccountId, receiver_id: AccountId, token_id: TokenId, //we introduce the approval map so we can keep track of what the approvals were before the transfer - approved_account_ids: HashMap, + approved_account_ids: HashMap, //we introduce a memo for logging the transfer event memo: Option, ) -> bool { @@ -221,7 +221,7 @@ impl NonFungibleTokenResolver for Contract { revert the original transfer and thus we can just return true since nothing went wrong. */ //we refund the owner for releasing the storage used up by the approved account IDs - refund_approved_account_ids(owner_id, &approved_account_ids); + refund_approved_account_ids(previous_owner_id, &approved_account_ids); return true; } } @@ -231,7 +231,7 @@ impl NonFungibleTokenResolver for Contract { let mut token = if let Some(token) = self.tokens_by_id.get(&token_id) { if token.owner_id != receiver_id { //we refund the owner for releasing the storage used up by the approved account IDs - refund_approved_account_ids(owner_id, &approved_account_ids); + refund_approved_account_ids(previous_owner_id, &approved_account_ids); // The token is not owner by the receiver anymore. Can't return it. return true; } @@ -239,17 +239,17 @@ impl NonFungibleTokenResolver for Contract { //if there isn't a token object, it was burned and so we return true } else { //we refund the owner for releasing the storage used up by the approved account IDs - refund_approved_account_ids(owner_id, &approved_account_ids); + refund_approved_account_ids(previous_owner_id, &approved_account_ids); return true; }; //we remove the token from the receiver self.internal_remove_token_from_owner(&receiver_id.clone(), &token_id); //we add the token to the original owner - self.internal_add_token_to_owner(&owner_id, &token_id); + self.internal_add_token_to_owner(&previous_owner_id, &token_id); //we change the token struct's owner to be the original owner - token.owner_id = owner_id.clone(); + token.owner_id = previous_owner_id.clone(); //we refund the receiver any approved account IDs that they may have set on the token refund_approved_account_ids(receiver_id.clone(), &token.approved_account_ids); @@ -276,7 +276,7 @@ impl NonFungibleTokenResolver for Contract { // The old owner's account ID. old_owner_id: receiver_id.to_string(), // The account ID of the new owner of the token. - new_owner_id: owner_id.to_string(), + new_owner_id: previous_owner_id.to_string(), // A vector containing the token IDs as strings. token_ids: vec![token_id.to_string()], // An optional memo to include. diff --git a/nft-series/src/royalty.rs b/nft-series/src/royalty.rs index 24c0521..55aabfe 100644 --- a/nft-series/src/royalty.rs +++ b/nft-series/src/royalty.rs @@ -9,7 +9,7 @@ pub trait NonFungibleTokenCore { &mut self, receiver_id: AccountId, token_id: TokenId, - approval_id: u32, + approval_id: u64, memo: Option, balance: NearToken, max_len_payout: u32, @@ -42,9 +42,7 @@ impl NonFungibleTokenCore for Contract { if royalty_option.is_none() { let mut payout = HashMap::new(); payout.insert(owner_id, balance); - return Payout { - payout: payout - }; + return Payout { payout: payout }; } // Otherwise, we will get the royalty object from the series let royalty = royalty_option.unwrap(); @@ -72,7 +70,7 @@ impl NonFungibleTokenCore for Contract { // payout to previous owner who gets 100% - total perpetual royalties payout_object.payout.insert( owner_id, - royalty_to_payout((10000 - total_perpetual).into(), balance) + royalty_to_payout((10000 - total_perpetual).into(), balance), ); //return the payout object @@ -85,7 +83,7 @@ impl NonFungibleTokenCore for Contract { &mut self, receiver_id: AccountId, token_id: TokenId, - approval_id: u32, + approval_id: u64, memo: Option, balance: NearToken, max_len_payout: u32, @@ -124,9 +122,7 @@ impl NonFungibleTokenCore for Contract { if royalty_option.is_none() { let mut payout = HashMap::new(); payout.insert(owner_id, balance); - return Payout { - payout: payout - }; + return Payout { payout: payout }; } // Otherwise, we will get the royalty object from the series let royalty = royalty_option.unwrap(); @@ -154,7 +150,7 @@ impl NonFungibleTokenCore for Contract { // payout to previous owner who gets 100% - total perpetual royalties payout_object.payout.insert( owner_id, - royalty_to_payout((10000 - total_perpetual).into(), balance) + royalty_to_payout((10000 - total_perpetual).into(), balance), ); //return the payout object diff --git a/nft-series/src/series.rs b/nft-series/src/series.rs index 0b119c2..c194a0c 100644 --- a/nft-series/src/series.rs +++ b/nft-series/src/series.rs @@ -65,7 +65,7 @@ impl Contract { /// Mint a new NFT that is part of a series. The caller must be an approved minter. /// The series ID must exist and if the metadata specifies a copy limit, you cannot exceed it. #[payable] - pub fn nft_mint(&mut self, id: U64, receiver_id: AccountId) { + pub fn nft_mint(&mut self, id: U64, token_owner_id: AccountId) { // Measure the initial storage being used on the contract let initial_storage_usage = env::storage_usage(); @@ -109,7 +109,7 @@ impl Contract { // Series ID that the token belongs to series_id: id.0, //set the owner ID equal to the receiver ID passed into the function - owner_id: receiver_id, + owner_id: token_owner_id, //we set the approved account IDs to the default value (an empty map) approved_account_ids: Default::default(), //the next approval ID is set to 0