177 | );
178 | }
179 |
180 | export default Upload;
181 |
--------------------------------------------------------------------------------
/contracts/src/approval.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 | use near_sdk::{ext_contract, Gas};
3 |
4 | const GAS_FOR_NFT_APPROVE: Gas = Gas(10_000_000_000_000);
5 | const NO_DEPOSIT: Balance = 0;
6 |
7 | pub trait NonFungibleTokenCore {
8 | //approve an account ID to transfer a token on your behalf
9 | fn nft_approve(&mut self, token_id: TokenId, account_id: AccountId, msg: Option);
10 |
11 | //check if the passed in account has access to approve the token ID
12 | fn nft_is_approved(
13 | &self,
14 | token_id: TokenId,
15 | approved_account_id: AccountId,
16 | approval_id: Option,
17 | ) -> bool;
18 |
19 | //revoke a specific account from transferring the token on your behalf
20 | fn nft_revoke(&mut self, token_id: TokenId, account_id: AccountId);
21 |
22 | //revoke all accounts from transferring the token on your behalf
23 | fn nft_revoke_all(&mut self, token_id: TokenId);
24 | }
25 |
26 | #[ext_contract(ext_non_fungible_approval_receiver)]
27 | trait NonFungibleTokenApprovalsReceiver {
28 | //cross contract call to an external contract that is initiated during nft_approve
29 | fn nft_on_approve(
30 | &mut self,
31 | token_id: TokenId,
32 | owner_id: AccountId,
33 | approval_id: u64,
34 | msg: String,
35 | );
36 | }
37 |
38 | #[near_bindgen]
39 | impl NonFungibleTokenCore for Contract {
40 | //allow a specific account ID to approve a token on your behalf
41 | #[payable]
42 | fn nft_approve(&mut self, token_id: TokenId, account_id: AccountId, msg: Option) {
43 | assert_at_least_one_yocto();
44 | let mut token = self.tokens_by_id.get(&token_id).expect("No token");
45 | assert_eq!(
46 | &env::predecessor_account_id(),
47 | &token.owner_id,
48 | "Predecessor must be the token owner."
49 | );
50 | let approval_id: u64 = token.next_approval_id;
51 | let is_new_approval = token
52 | .approved_account_ids
53 | .insert(account_id.clone(), approval_id)
54 | .is_none();
55 | let storage_used = if is_new_approval {
56 | bytes_for_approved_account_id(&account_id)
57 | //if it was not a new approval, we used no storage.
58 | } else {
59 | 0
60 | };
61 |
62 | token.next_approval_id += 1;
63 | self.tokens_by_id.insert(&token_id, &token);
64 | refund_deposit(storage_used);
65 | if let Some(msg) = msg {
66 | ext_non_fungible_approval_receiver::nft_on_approve(
67 | token_id,
68 | token.owner_id,
69 | approval_id,
70 | msg,
71 | account_id, //contract account we're calling
72 | NO_DEPOSIT, //NEAR deposit we attach to the call
73 | env::prepaid_gas() - GAS_FOR_NFT_APPROVE, //GAS we're attaching
74 | )
75 | .as_return(); // Returning this promise
76 | }
77 | }
78 |
79 | //check if the passed in account has access to approve the token ID
80 | fn nft_is_approved(
81 | &self,
82 | token_id: TokenId,
83 | approved_account_id: AccountId,
84 | approval_id: Option,
85 | ) -> bool {
86 | //get the token object from the token_id
87 | let token = self.tokens_by_id.get(&token_id).expect("No token");
88 |
89 | //get the approval number for the passed in account ID
90 | let approval = token.approved_account_ids.get(&approved_account_id);
91 |
92 | //if there was some approval ID found for the account ID
93 | if let Some(approval) = approval {
94 | //if a specific approval_id was passed into the function
95 | if let Some(approval_id) = approval_id {
96 | //return if the approval ID passed in matches the actual approval ID for the account
97 | approval_id == *approval
98 | //if there was no approval_id passed into the function, we simply return true
99 | } else {
100 | true
101 | }
102 | //if there was no approval ID found for the account ID, we simply return false
103 | } else {
104 | false
105 | }
106 | }
107 |
108 | //revoke a specific account from transferring the token on your behalf
109 | #[payable]
110 | fn nft_revoke(&mut self, token_id: TokenId, account_id: AccountId) {
111 | assert_one_yocto();
112 | //get the token object using the passed in token_id
113 | let mut token = self.tokens_by_id.get(&token_id).expect("No token");
114 |
115 | //get the caller of the function and assert that they are the owner of the token
116 | let predecessor_account_id = env::predecessor_account_id();
117 | assert_eq!(&predecessor_account_id, &token.owner_id);
118 |
119 | //if the account ID was in the token's approval, we remove it and the if statement logic executes
120 | if token.approved_account_ids.remove(&account_id).is_some() {
121 | //refund the funds released by removing the approved_account_id to the caller of the function
122 | refund_approved_account_ids_iter(predecessor_account_id, [account_id].iter());
123 |
124 | //insert the token back into the tokens_by_id collection with the account_id removed from the approval list
125 | self.tokens_by_id.insert(&token_id, &token);
126 | }
127 | }
128 |
129 | //revoke all accounts from transferring the token on your behalf
130 | #[payable]
131 | fn nft_revoke_all(&mut self, token_id: TokenId) {
132 | //assert that the caller attached exactly 1 yoctoNEAR for security
133 | assert_one_yocto();
134 |
135 | //get the token object from the passed in token ID
136 | let mut token = self.tokens_by_id.get(&token_id).expect("No token");
137 | //get the caller and make sure they are the owner of the tokens
138 | let predecessor_account_id = env::predecessor_account_id();
139 | assert_eq!(&predecessor_account_id, &token.owner_id);
140 |
141 | //only revoke if the approved account IDs for the token is not empty
142 | if !token.approved_account_ids.is_empty() {
143 | //refund the approved account IDs to the caller of the function
144 | refund_approved_account_ids(predecessor_account_id, &token.approved_account_ids);
145 | //clear the approved account IDs
146 | token.approved_account_ids.clear();
147 | //insert the token back into the tokens_by_id collection with the approved account IDs cleared
148 | self.tokens_by_id.insert(&token_id, &token);
149 | }
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/contracts/src/internal.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 | use near_sdk::CryptoHash;
3 | use std::mem::size_of;
4 |
5 | //calculate how many bytes the account ID is taking up
6 | pub(crate) fn bytes_for_approved_account_id(account_id: &AccountId) -> u64 {
7 | // The extra 4 bytes are coming from Borsh serialization to store the length of the string.
8 | account_id.as_str().len() as u64 + 4 + size_of::() as u64
9 | }
10 |
11 | //refund the storage taken up by passed in approved account IDs and send the funds to the passed in account ID.
12 | pub(crate) fn refund_approved_account_ids_iter<'a, I>(
13 | account_id: AccountId,
14 | approved_account_ids: I, //the approved account IDs must be passed in as an iterator
15 | ) -> Promise
16 | where
17 | I: Iterator,
18 | {
19 | //get the storage total by going through and summing all the bytes for each approved account IDs
20 | let storage_released: u64 = approved_account_ids
21 | .map(bytes_for_approved_account_id)
22 | .sum();
23 | //transfer the account the storage that is released
24 | Promise::new(account_id).transfer(Balance::from(storage_released) * env::storage_byte_cost())
25 | }
26 |
27 | //refund a map of approved account IDs and send the funds to the passed in account ID
28 | pub(crate) fn refund_approved_account_ids(
29 | account_id: AccountId,
30 | approved_account_ids: &HashMap,
31 | ) -> Promise {
32 | //call the refund_approved_account_ids_iter with the approved account IDs as keys
33 | refund_approved_account_ids_iter(account_id, approved_account_ids.keys())
34 | }
35 |
36 | //used to generate a unique prefix in our storage collections (this is to avoid data collisions)
37 | pub(crate) fn hash_account_id(account_id: &AccountId) -> CryptoHash {
38 | //get the default hash
39 | let mut hash = CryptoHash::default();
40 | //we hash the account ID and return it
41 | hash.copy_from_slice(&env::sha256(account_id.as_bytes()));
42 | hash
43 | }
44 | //used to make sure the user attached exactly 1 yoctoNEAR
45 | pub(crate) fn assert_one_yocto() {
46 | assert_eq!(
47 | env::attached_deposit(),
48 | 1,
49 | "Requires attached deposit of exactly 1 yoctoNEAR",
50 | )
51 | }
52 |
53 | pub(crate) fn assert_at_least_one_yocto() {
54 | assert!(
55 | env::attached_deposit() >= 1,
56 | "Requires attached deposit of at least 1 yoctoNEAR",
57 | )
58 | }
59 | //refund the initial deposit based on the amount of storage that was used up
60 | pub(crate) fn refund_deposit(storage_used: u64) {
61 | //get how much it would cost to store the information
62 | let required_cost = env::storage_byte_cost() * Balance::from(storage_used);
63 | //get the attached deposit
64 | let attached_deposit = env::attached_deposit();
65 |
66 | //make sure that the attached deposit is greater than or equal to the required cost
67 | assert!(
68 | required_cost <= attached_deposit,
69 | "Must attach {} yoctoNEAR to cover storage",
70 | required_cost,
71 | );
72 |
73 | //get the refund amount from the attached deposit - required cost
74 | let refund = attached_deposit - required_cost;
75 |
76 | //if the refund is greater than 1 yocto NEAR, we refund the predecessor that amount
77 | if refund > 1 {
78 | Promise::new(env::predecessor_account_id()).transfer(refund);
79 | }
80 | }
81 |
82 | impl Contract {
83 | //add a token to the set of tokens an owner has
84 | pub(crate) fn internal_add_token_to_owner(
85 | &mut self,
86 | account_id: &AccountId,
87 | token_id: &TokenId,
88 | ) {
89 | //get the set of tokens for the given account
90 | let mut tokens_set = self.tokens_per_owner.get(account_id).unwrap_or_else(|| {
91 | //if the account doesn't have any tokens, we create a new unordered set
92 | UnorderedSet::new(
93 | StorageKey::TokenPerOwnerInner {
94 | //we get a new unique prefix for the collection
95 | account_id_hash: hash_account_id(&account_id),
96 | }
97 | .try_to_vec()
98 | .unwrap(),
99 | )
100 | });
101 |
102 | //we insert the token ID into the set
103 | tokens_set.insert(token_id);
104 |
105 | //we insert that set for the given account ID.
106 | self.tokens_per_owner.insert(account_id, &tokens_set);
107 | }
108 |
109 | pub(crate) fn internal_remove_token_from_owner(
110 | &mut self,
111 | account_id: &AccountId,
112 | token_id: &TokenId,
113 | ) {
114 | //we get the set of tokens that the owner has
115 | let mut tokens_set = self
116 | .tokens_per_owner
117 | .get(account_id)
118 | //if there is no set of tokens for the owner, we panic with the following message:
119 | .expect("Token should be owned by the sender");
120 |
121 | //we remove the the token_id from the set of tokens
122 | tokens_set.remove(token_id);
123 |
124 | //if the token set is now empty, we remove the owner from the tokens_per_owner collection
125 | if tokens_set.is_empty() {
126 | self.tokens_per_owner.remove(account_id);
127 | } else {
128 | //if the token set is not empty, we simply insert it back for the account ID.
129 | self.tokens_per_owner.insert(account_id, &tokens_set);
130 | }
131 | }
132 |
133 | pub(crate) fn internal_transfer(
134 | &mut self,
135 | sender_id: &AccountId,
136 | receiver_id: &AccountId,
137 | token_id: &TokenId,
138 | approval_id: Option,
139 | memo: Option,
140 | ) -> Token {
141 | //get the token object by passing in the token_id
142 | let token = self.tokens_by_id.get(token_id).expect("No token");
143 |
144 | //if the sender doesn't equal the owner, we check if the sender is in the approval list
145 | if sender_id != &token.owner_id {
146 | //if the token's approved account IDs doesn't contain the sender, we panic
147 | if !token.approved_account_ids.contains_key(sender_id) {
148 | env::panic_str("Unauthorized");
149 | }
150 |
151 | // If they included an approval_id, check if the sender's actual approval_id is the same as the one included
152 | if let Some(enforced_approval_id) = approval_id {
153 | //get the actual approval ID
154 | let actual_approval_id = token
155 | .approved_account_ids
156 | .get(sender_id)
157 | //if the sender isn't in the map, we panic
158 | .expect("Sender is not approved account");
159 |
160 | //make sure that the actual approval ID is the same as the one provided
161 | assert_eq!(
162 | actual_approval_id, &enforced_approval_id,
163 | "The actual approval_id {} is different from the given approval_id {}",
164 | actual_approval_id, enforced_approval_id,
165 | );
166 | }
167 | }
168 |
169 | //we make sure that the sender isn't sending the token to themselves
170 | assert_ne!(
171 | &token.owner_id, receiver_id,
172 | "The token owner and the receiver should be different"
173 | );
174 |
175 | //we remove the token from it's current owner's set
176 | self.internal_remove_token_from_owner(&token.owner_id, token_id);
177 | //we then add the token to the receiver_id's set
178 | self.internal_add_token_to_owner(receiver_id, token_id);
179 |
180 | //we create a new token struct
181 | let new_token = Token {
182 | owner_id: receiver_id.clone(),
183 | //reset the approval account IDs
184 | approved_account_ids: Default::default(),
185 | next_approval_id: token.next_approval_id,
186 | };
187 | //insert that new token into the tokens_by_id, replacing the old entry
188 | self.tokens_by_id.insert(token_id, &new_token);
189 |
190 | //if there was some memo attached, we log it.
191 | if let Some(memo) = memo.as_ref() {
192 | env::log_str(&format!("Memo: {}", memo).to_string());
193 | }
194 |
195 | // Default the authorized ID to be None for the logs.
196 | let mut authorized_id = None;
197 | //if the approval ID was provided, set the authorized ID equal to the sender
198 | if approval_id.is_some() {
199 | authorized_id = Some(sender_id.to_string());
200 | }
201 |
202 | // Construct the transfer log as per the events standard.
203 | let nft_transfer_log: EventLog = EventLog {
204 | // Standard name ("nep171").
205 | standard: NFT_STANDARD_NAME.to_string(),
206 | // Version of the standard ("nft-1.0.0").
207 | version: NFT_METADATA_SPEC.to_string(),
208 | // The data related with the event stored in a vector.
209 | event: EventLogVariant::NftTransfer(vec![NftTransferLog {
210 | // The optional authorized account ID to transfer the token on behalf of the old owner.
211 | authorized_id,
212 | // The old owner's account ID.
213 | old_owner_id: token.owner_id.to_string(),
214 | // The account ID of the new owner of the token.
215 | new_owner_id: receiver_id.to_string(),
216 | // A vector containing the token IDs as strings.
217 | token_ids: vec![token_id.to_string()],
218 | // An optional memo to include.
219 | memo,
220 | }]),
221 | };
222 |
223 | // Log the serialized json.
224 | env::log_str(&nft_transfer_log.to_string());
225 |
226 | //return the preivous token object that was transferred.
227 | token
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/dspyt/src/components/Navigation.jsx:
--------------------------------------------------------------------------------
1 | import { NavLink } from "react-router-dom";
2 | import { Fragment } from "react";
3 | import { Disclosure, Menu, Transition } from "@headlessui/react";
4 | import { SignOut } from "phosphor-react";
5 | import { MenuIcon, XIcon } from '@heroicons/react/outline'
6 |
7 | import { useStore } from "../store";
8 |
9 | const navigation = [
10 | { name: 'Home', href: '/', current: false },
11 | { name: 'Upload', href: '/upload', current: false },
12 | { name: 'Saved', href: '/saved', current: false },
13 | ]
14 |
15 | //const userNavigation = [
16 | //{name : 'Upload', href: '/upload'}
17 | // { name: 'Your Profile', href: '/' },
18 | //{ name: 'Settings', href: '/saved' },
19 | //{ name: 'Sign out', href: '/' },
20 | //]
21 |
22 | function classNames(...classes) {
23 | return classes.filter(Boolean).join(' ')
24 | }
25 |
26 | export default function Navigation() {
27 |
28 | const wallet = useStore((state) => state.wallet);
29 | const contract = useStore((state) => state.contract);
30 | const nearConfig = useStore((state) => state.nearConfig);
31 | const currentUser = useStore((state) => state.currentUser);
32 | const ConnectWallet = () => {
33 | wallet.requestSignIn(
34 | {
35 | contractId: nearConfig.contractName,
36 | methodNames: [contract.nft_mint.name],
37 | }, //contract requesting access
38 | "PinSave",
39 | null,
40 | null
41 | );
42 | };
43 |
44 |
45 | return (
46 |
47 | {({ open }) => (
48 | <>
49 |
50 |
51 |
52 |
53 | {/* Mobile menu */}
54 |
55 | Open main menu
56 | {open ? (
57 |
58 | ) : (
59 |
60 | )}
61 |
62 |
224 | ) : ""}
225 |
226 | >
227 | )}
228 |
229 |
230 | );
231 | }
--------------------------------------------------------------------------------
/contracts/src/nft_core.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 | use near_sdk::{ext_contract, log, Gas, PromiseResult};
3 |
4 | const GAS_FOR_RESOLVE_TRANSFER: Gas = Gas(10_000_000_000_000);
5 | const GAS_FOR_NFT_TRANSFER_CALL: Gas = Gas(25_000_000_000_000 + GAS_FOR_RESOLVE_TRANSFER.0);
6 | const MIN_GAS_FOR_NFT_TRANSFER_CALL: Gas = Gas(100_000_000_000_000);
7 | const NO_DEPOSIT: Balance = 0;
8 |
9 | pub trait NonFungibleTokenCore {
10 | //transfers an NFT to a receiver ID
11 | fn nft_transfer(
12 | &mut self,
13 | receiver_id: AccountId,
14 | token_id: TokenId,
15 | approval_id: u64,
16 | memo: Option,
17 | );
18 |
19 | //transfers an NFT to a receiver and calls a function on the receiver ID's contract
20 | /// Returns `true` if the token was transferred from the sender's account.
21 | fn nft_transfer_call(
22 | &mut self,
23 | receiver_id: AccountId,
24 | token_id: TokenId,
25 | approval_id: u64,
26 | memo: Option,
27 | msg: String,
28 | ) -> PromiseOrValue;
29 |
30 | //get information about the NFT token passed in
31 | fn nft_token(&self, token_id: TokenId) -> Option;
32 | }
33 |
34 | #[ext_contract(ext_non_fungible_token_receiver)]
35 | trait NonFungibleTokenReceiver {
36 | //Method stored on the receiver contract that is called via cross contract call when nft_transfer_call is called
37 | /// Returns `true` if the token should be returned back to the sender.
38 | fn nft_on_transfer(
39 | &mut self,
40 | sender_id: AccountId,
41 | previous_owner_id: AccountId,
42 | token_id: TokenId,
43 | msg: String,
44 | ) -> Promise;
45 | }
46 |
47 | #[ext_contract(ext_self)]
48 | trait NonFungibleTokenResolver {
49 | /*
50 | resolves the promise of the cross contract call to the receiver contract
51 | this is stored on THIS contract and is meant to analyze what happened in the cross contract call when nft_on_transfer was called
52 | as part of the nft_transfer_call method
53 | */
54 | fn nft_resolve_transfer(
55 | &mut self,
56 | //we introduce an authorized ID for logging the transfer event
57 | authorized_id: Option,
58 | owner_id: AccountId,
59 | receiver_id: AccountId,
60 | token_id: TokenId,
61 | //we introduce the approval map so we can keep track of what the approvals were before the transfer
62 | approved_account_ids: HashMap,
63 | //we introduce a memo for logging the transfer event
64 | memo: Option,
65 | ) -> bool;
66 | }
67 |
68 | /*
69 | resolves the promise of the cross contract call to the receiver contract
70 | this is stored on THIS contract and is meant to analyze what happened in the cross contract call when nft_on_transfer was called
71 | as part of the nft_transfer_call method
72 | */
73 | trait NonFungibleTokenResolver {
74 | fn nft_resolve_transfer(
75 | &mut self,
76 | //we introduce an authorized ID for logging the transfer event
77 | authorized_id: Option,
78 | owner_id: AccountId,
79 | receiver_id: AccountId,
80 | token_id: TokenId,
81 | //we introduce the approval map so we can keep track of what the approvals were before the transfer
82 | approved_account_ids: HashMap,
83 | //we introduce a memo for logging the transfer event
84 | memo: Option,
85 | ) -> bool;
86 | }
87 |
88 | #[near_bindgen]
89 | impl NonFungibleTokenCore for Contract {
90 | //implementation of the nft_transfer method. This transfers the NFT from the current owner to the receiver.
91 | #[payable]
92 | fn nft_transfer(
93 | &mut self,
94 | receiver_id: AccountId,
95 | token_id: TokenId,
96 | //we introduce an approval ID so that people with that approval ID can transfer the token
97 | approval_id: u64,
98 | memo: Option,
99 | ) {
100 | //assert that the user attached exactly 1 yoctoNEAR. This is for security and so that the user will be redirected to the NEAR wallet.
101 | assert_one_yocto();
102 | //get the sender to transfer the token from the sender to the receiver
103 | let sender_id = env::predecessor_account_id();
104 |
105 | //call the internal transfer method and get back the previous token so we can refund the approved account IDs
106 | let previous_token =
107 | self.internal_transfer(&sender_id, &receiver_id, &token_id, Some(approval_id), memo);
108 |
109 | //we refund the owner for releasing the storage used up by the approved account IDs
110 | refund_approved_account_ids(
111 | previous_token.owner_id.clone(),
112 | &previous_token.approved_account_ids,
113 | );
114 | }
115 |
116 | //implementation of the transfer call method. This will transfer the NFT and call a method on the reciver_id contract
117 | #[payable]
118 | fn nft_transfer_call(
119 | &mut self,
120 | receiver_id: AccountId,
121 | token_id: TokenId,
122 | //we introduce an approval ID so that people with that approval ID can transfer the token
123 | approval_id: u64,
124 | memo: Option,
125 | msg: String,
126 | ) -> PromiseOrValue {
127 | //assert that the user attached exactly 1 yocto for security reasons.
128 | assert_one_yocto();
129 |
130 | //get the GAS attached to the call
131 | let attached_gas = env::prepaid_gas();
132 |
133 | /*
134 | make sure that the attached gas is greater than the minimum GAS for NFT transfer call.
135 | This is to ensure that the cross contract call to nft_on_transfer won't cause a prepaid GAS error.
136 | If this happens, the event will be logged in internal_transfer but the actual transfer logic will be
137 | reverted due to the panic. This will result in the databases thinking the NFT belongs to the wrong person.
138 | */
139 | assert!(
140 | attached_gas >= MIN_GAS_FOR_NFT_TRANSFER_CALL,
141 | "You cannot attach less than {:?} Gas to nft_transfer_call",
142 | MIN_GAS_FOR_NFT_TRANSFER_CALL
143 | );
144 |
145 | //get the sender ID
146 | let sender_id = env::predecessor_account_id();
147 |
148 | //transfer the token and get the previous token object
149 | let previous_token = self.internal_transfer(
150 | &sender_id,
151 | &receiver_id,
152 | &token_id,
153 | Some(approval_id),
154 | memo.clone(),
155 | );
156 |
157 | //default the authorized_id to none
158 | let mut authorized_id = None;
159 | //if the sender isn't the owner of the token, we set the authorized ID equal to the sender.
160 | if sender_id != previous_token.owner_id {
161 | authorized_id = Some(sender_id.to_string());
162 | }
163 |
164 | // Initiating receiver's call and the callback
165 | ext_non_fungible_token_receiver::nft_on_transfer(
166 | sender_id,
167 | previous_token.owner_id.clone(),
168 | token_id.clone(),
169 | msg,
170 | receiver_id.clone(), //contract account to make the call to
171 | NO_DEPOSIT, //attached deposit
172 | env::prepaid_gas() - GAS_FOR_NFT_TRANSFER_CALL, //attached GAS
173 | )
174 | //we then resolve the promise and call nft_resolve_transfer on our own contract
175 | .then(ext_self::nft_resolve_transfer(
176 | authorized_id, // we introduce an authorized ID so that we can log the transfer
177 | previous_token.owner_id,
178 | receiver_id,
179 | token_id,
180 | previous_token.approved_account_ids,
181 | memo, // we introduce a memo for logging in the events standard
182 | env::current_account_id(), //contract account to make the call to
183 | NO_DEPOSIT, //attached deposit
184 | GAS_FOR_RESOLVE_TRANSFER, //GAS attached to the call
185 | ))
186 | .into()
187 | }
188 |
189 | //get the information for a specific token ID
190 | fn nft_token(&self, token_id: TokenId) -> Option {
191 | //if there is some token ID in the tokens_by_id collection
192 | if let Some(token) = self.tokens_by_id.get(&token_id) {
193 | //we'll get the metadata for that token
194 | let metadata = self.token_metadata_by_id.get(&token_id).unwrap();
195 | //we return the JsonToken (wrapped by Some since we return an option)
196 | Some(JsonToken {
197 | token_id,
198 | owner_id: token.owner_id,
199 | metadata,
200 | approved_account_ids: token.approved_account_ids,
201 | })
202 | } else {
203 | //if there wasn't a token ID in the tokens_by_id collection, we return None
204 | None
205 | }
206 | }
207 | }
208 |
209 | #[near_bindgen]
210 | impl NonFungibleTokenResolver for Contract {
211 | //resolves the cross contract call when calling nft_on_transfer in the nft_transfer_call method
212 | //returns true if the token was successfully transferred to the receiver_id
213 | #[private]
214 | fn nft_resolve_transfer(
215 | &mut self,
216 | //we introduce an authorized ID for logging the transfer event
217 | authorized_id: Option,
218 | owner_id: AccountId,
219 | receiver_id: AccountId,
220 | token_id: TokenId,
221 | //we introduce the approval map so we can keep track of what the approvals were before the transfer
222 | approved_account_ids: HashMap,
223 | //we introduce a memo for logging the transfer event
224 | memo: Option,
225 | ) -> bool {
226 | // Whether receiver wants to return token back to the sender, based on `nft_on_transfer`
227 | // call result.
228 | if let PromiseResult::Successful(value) = env::promise_result(0) {
229 | //As per the standard, the nft_on_transfer should return whether we should return the token to it's owner or not
230 | if let Ok(return_token) = near_sdk::serde_json::from_slice::(&value) {
231 | //if we need don't need to return the token, we simply return true meaning everything went fine
232 | if !return_token {
233 | /*
234 | since we've already transferred the token and nft_on_transfer returned false, we don't have to
235 | revert the original transfer and thus we can just return true since nothing went wrong.
236 | */
237 | //we refund the owner for releasing the storage used up by the approved account IDs
238 | refund_approved_account_ids(owner_id, &approved_account_ids);
239 | return true;
240 | }
241 | }
242 | }
243 |
244 | //get the token object if there is some token object
245 | let mut token = if let Some(token) = self.tokens_by_id.get(&token_id) {
246 | if token.owner_id != receiver_id {
247 | //we refund the owner for releasing the storage used up by the approved account IDs
248 | refund_approved_account_ids(owner_id, &approved_account_ids);
249 | // The token is not owner by the receiver anymore. Can't return it.
250 | return true;
251 | }
252 | token
253 | //if there isn't a token object, it was burned and so we return true
254 | } else {
255 | //we refund the owner for releasing the storage used up by the approved account IDs
256 | refund_approved_account_ids(owner_id, &approved_account_ids);
257 | return true;
258 | };
259 |
260 | //we remove the token from the receiver
261 | self.internal_remove_token_from_owner(&receiver_id.clone(), &token_id);
262 | //we add the token to the original owner
263 | self.internal_add_token_to_owner(&owner_id, &token_id);
264 |
265 | //we change the token struct's owner to be the original owner
266 | token.owner_id = owner_id.clone();
267 |
268 | //we refund the receiver any approved account IDs that they may have set on the token
269 | refund_approved_account_ids(receiver_id.clone(), &token.approved_account_ids);
270 | //reset the approved account IDs to what they were before the transfer
271 | token.approved_account_ids = approved_account_ids;
272 |
273 | //we inset the token back into the tokens_by_id collection
274 | self.tokens_by_id.insert(&token_id, &token);
275 |
276 | /*
277 | We need to log that the NFT was reverted back to the original owner.
278 | The old_owner_id will be the receiver and the new_owner_id will be the
279 | original owner of the token since we're reverting the transfer.
280 | */
281 | let nft_transfer_log: EventLog = EventLog {
282 | // Standard name ("nep171").
283 | standard: NFT_STANDARD_NAME.to_string(),
284 | // Version of the standard ("nft-1.0.0").
285 | version: NFT_METADATA_SPEC.to_string(),
286 | // The data related with the event stored in a vector.
287 | event: EventLogVariant::NftTransfer(vec![NftTransferLog {
288 | // The optional authorized account ID to transfer the token on behalf of the old owner.
289 | authorized_id,
290 | // The old owner's account ID.
291 | old_owner_id: receiver_id.to_string(),
292 | // The account ID of the new owner of the token.
293 | new_owner_id: owner_id.to_string(),
294 | // A vector containing the token IDs as strings.
295 | token_ids: vec![token_id.to_string()],
296 | // An optional memo to include.
297 | memo,
298 | }]),
299 | };
300 |
301 | //we perform the actual logging
302 | env::log_str(&nft_transfer_log.to_string());
303 |
304 | //return false
305 | false
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/contracts/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "Inflector"
7 | version = "0.11.4"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
10 |
11 | [[package]]
12 | name = "ahash"
13 | version = "0.4.7"
14 | source = "registry+https://github.com/rust-lang/crates.io-index"
15 | checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
16 |
17 | [[package]]
18 | name = "aho-corasick"
19 | version = "0.7.18"
20 | source = "registry+https://github.com/rust-lang/crates.io-index"
21 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
22 | dependencies = [
23 | "memchr",
24 | ]
25 |
26 | [[package]]
27 | name = "autocfg"
28 | version = "1.1.0"
29 | source = "registry+https://github.com/rust-lang/crates.io-index"
30 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
31 |
32 | [[package]]
33 | name = "base64"
34 | version = "0.13.0"
35 | source = "registry+https://github.com/rust-lang/crates.io-index"
36 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
37 |
38 | [[package]]
39 | name = "block-buffer"
40 | version = "0.9.0"
41 | source = "registry+https://github.com/rust-lang/crates.io-index"
42 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
43 | dependencies = [
44 | "block-padding",
45 | "generic-array",
46 | ]
47 |
48 | [[package]]
49 | name = "block-padding"
50 | version = "0.2.1"
51 | source = "registry+https://github.com/rust-lang/crates.io-index"
52 | checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
53 |
54 | [[package]]
55 | name = "borsh"
56 | version = "0.8.2"
57 | source = "registry+https://github.com/rust-lang/crates.io-index"
58 | checksum = "09a7111f797cc721407885a323fb071636aee57f750b1a4ddc27397eba168a74"
59 | dependencies = [
60 | "borsh-derive",
61 | "hashbrown 0.9.1",
62 | ]
63 |
64 | [[package]]
65 | name = "borsh-derive"
66 | version = "0.8.2"
67 | source = "registry+https://github.com/rust-lang/crates.io-index"
68 | checksum = "307f3740906bac2c118a8122fe22681232b244f1369273e45f1156b45c43d2dd"
69 | dependencies = [
70 | "borsh-derive-internal",
71 | "borsh-schema-derive-internal",
72 | "proc-macro-crate",
73 | "proc-macro2",
74 | "syn",
75 | ]
76 |
77 | [[package]]
78 | name = "borsh-derive-internal"
79 | version = "0.8.2"
80 | source = "registry+https://github.com/rust-lang/crates.io-index"
81 | checksum = "d2104c73179359431cc98e016998f2f23bc7a05bc53e79741bcba705f30047bc"
82 | dependencies = [
83 | "proc-macro2",
84 | "quote",
85 | "syn",
86 | ]
87 |
88 | [[package]]
89 | name = "borsh-schema-derive-internal"
90 | version = "0.8.2"
91 | source = "registry+https://github.com/rust-lang/crates.io-index"
92 | checksum = "ae29eb8418fcd46f723f8691a2ac06857d31179d33d2f2d91eb13967de97c728"
93 | dependencies = [
94 | "proc-macro2",
95 | "quote",
96 | "syn",
97 | ]
98 |
99 | [[package]]
100 | name = "bs58"
101 | version = "0.4.0"
102 | source = "registry+https://github.com/rust-lang/crates.io-index"
103 | checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
104 |
105 | [[package]]
106 | name = "byteorder"
107 | version = "1.4.3"
108 | source = "registry+https://github.com/rust-lang/crates.io-index"
109 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
110 |
111 | [[package]]
112 | name = "cfg-if"
113 | version = "0.1.10"
114 | source = "registry+https://github.com/rust-lang/crates.io-index"
115 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
116 |
117 | [[package]]
118 | name = "cfg-if"
119 | version = "1.0.0"
120 | source = "registry+https://github.com/rust-lang/crates.io-index"
121 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
122 |
123 | [[package]]
124 | name = "contracts"
125 | version = "0.1.0"
126 | dependencies = [
127 | "near-sdk",
128 | "serde_json",
129 | ]
130 |
131 | [[package]]
132 | name = "convert_case"
133 | version = "0.4.0"
134 | source = "registry+https://github.com/rust-lang/crates.io-index"
135 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
136 |
137 | [[package]]
138 | name = "cpufeatures"
139 | version = "0.2.1"
140 | source = "registry+https://github.com/rust-lang/crates.io-index"
141 | checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
142 | dependencies = [
143 | "libc",
144 | ]
145 |
146 | [[package]]
147 | name = "derive_more"
148 | version = "0.99.17"
149 | source = "registry+https://github.com/rust-lang/crates.io-index"
150 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
151 | dependencies = [
152 | "convert_case",
153 | "proc-macro2",
154 | "quote",
155 | "rustc_version",
156 | "syn",
157 | ]
158 |
159 | [[package]]
160 | name = "digest"
161 | version = "0.9.0"
162 | source = "registry+https://github.com/rust-lang/crates.io-index"
163 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
164 | dependencies = [
165 | "generic-array",
166 | ]
167 |
168 | [[package]]
169 | name = "generic-array"
170 | version = "0.14.5"
171 | source = "registry+https://github.com/rust-lang/crates.io-index"
172 | checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
173 | dependencies = [
174 | "typenum",
175 | "version_check",
176 | ]
177 |
178 | [[package]]
179 | name = "hashbrown"
180 | version = "0.9.1"
181 | source = "registry+https://github.com/rust-lang/crates.io-index"
182 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
183 | dependencies = [
184 | "ahash",
185 | ]
186 |
187 | [[package]]
188 | name = "hashbrown"
189 | version = "0.11.2"
190 | source = "registry+https://github.com/rust-lang/crates.io-index"
191 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
192 |
193 | [[package]]
194 | name = "hex"
195 | version = "0.4.3"
196 | source = "registry+https://github.com/rust-lang/crates.io-index"
197 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
198 |
199 | [[package]]
200 | name = "indexmap"
201 | version = "1.8.0"
202 | source = "registry+https://github.com/rust-lang/crates.io-index"
203 | checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
204 | dependencies = [
205 | "autocfg",
206 | "hashbrown 0.11.2",
207 | ]
208 |
209 | [[package]]
210 | name = "itoa"
211 | version = "1.0.1"
212 | source = "registry+https://github.com/rust-lang/crates.io-index"
213 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
214 |
215 | [[package]]
216 | name = "keccak"
217 | version = "0.1.0"
218 | source = "registry+https://github.com/rust-lang/crates.io-index"
219 | checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
220 |
221 | [[package]]
222 | name = "lazy_static"
223 | version = "1.4.0"
224 | source = "registry+https://github.com/rust-lang/crates.io-index"
225 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
226 |
227 | [[package]]
228 | name = "libc"
229 | version = "0.2.119"
230 | source = "registry+https://github.com/rust-lang/crates.io-index"
231 | checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
232 |
233 | [[package]]
234 | name = "memchr"
235 | version = "2.4.1"
236 | source = "registry+https://github.com/rust-lang/crates.io-index"
237 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
238 |
239 | [[package]]
240 | name = "memory_units"
241 | version = "0.4.0"
242 | source = "registry+https://github.com/rust-lang/crates.io-index"
243 | checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
244 |
245 | [[package]]
246 | name = "near-primitives-core"
247 | version = "0.4.0"
248 | source = "registry+https://github.com/rust-lang/crates.io-index"
249 | checksum = "c2b3fb5acf3a494aed4e848446ef2d6ebb47dbe91c681105d4d1786c2ee63e52"
250 | dependencies = [
251 | "base64",
252 | "borsh",
253 | "bs58",
254 | "derive_more",
255 | "hex",
256 | "lazy_static",
257 | "num-rational",
258 | "serde",
259 | "serde_json",
260 | "sha2",
261 | ]
262 |
263 | [[package]]
264 | name = "near-rpc-error-core"
265 | version = "0.1.0"
266 | source = "registry+https://github.com/rust-lang/crates.io-index"
267 | checksum = "ffa8dbf8437a28ac40fcb85859ab0d0b8385013935b000c7a51ae79631dd74d9"
268 | dependencies = [
269 | "proc-macro2",
270 | "quote",
271 | "serde",
272 | "serde_json",
273 | "syn",
274 | ]
275 |
276 | [[package]]
277 | name = "near-rpc-error-macro"
278 | version = "0.1.0"
279 | source = "registry+https://github.com/rust-lang/crates.io-index"
280 | checksum = "0c6111d713e90c7c551dee937f4a06cb9ea2672243455a4454cc7566387ba2d9"
281 | dependencies = [
282 | "near-rpc-error-core",
283 | "proc-macro2",
284 | "quote",
285 | "serde",
286 | "serde_json",
287 | "syn",
288 | ]
289 |
290 | [[package]]
291 | name = "near-runtime-utils"
292 | version = "4.0.0-pre.1"
293 | source = "registry+https://github.com/rust-lang/crates.io-index"
294 | checksum = "a48d80c4ca1d4cf99bc16490e1e3d49826c150dfc4410ac498918e45c7d98e07"
295 | dependencies = [
296 | "lazy_static",
297 | "regex",
298 | ]
299 |
300 | [[package]]
301 | name = "near-sdk"
302 | version = "4.0.0-pre.4"
303 | source = "registry+https://github.com/rust-lang/crates.io-index"
304 | checksum = "10f570e260eae9169dd5ffe0764728b872c2df79b8786dfb5e57183bd383908e"
305 | dependencies = [
306 | "base64",
307 | "borsh",
308 | "bs58",
309 | "near-primitives-core",
310 | "near-sdk-macros",
311 | "near-sys",
312 | "near-vm-logic",
313 | "serde",
314 | "serde_json",
315 | "wee_alloc",
316 | ]
317 |
318 | [[package]]
319 | name = "near-sdk-macros"
320 | version = "4.0.0-pre.4"
321 | source = "registry+https://github.com/rust-lang/crates.io-index"
322 | checksum = "65ddca18086f1ab14ce8541e0c23f503a322d45914f2fe08f571844045d32bde"
323 | dependencies = [
324 | "Inflector",
325 | "proc-macro2",
326 | "quote",
327 | "syn",
328 | ]
329 |
330 | [[package]]
331 | name = "near-sys"
332 | version = "0.1.0"
333 | source = "registry+https://github.com/rust-lang/crates.io-index"
334 | checksum = "f6a7aa3f46fac44416d8a93d14f30a562c4d730a1c6bf14bffafab5f475c244a"
335 |
336 | [[package]]
337 | name = "near-vm-errors"
338 | version = "4.0.0-pre.1"
339 | source = "registry+https://github.com/rust-lang/crates.io-index"
340 | checksum = "e281d8730ed8cb0e3e69fb689acee6b93cdb43824cd69a8ffd7e1bfcbd1177d7"
341 | dependencies = [
342 | "borsh",
343 | "hex",
344 | "near-rpc-error-macro",
345 | "serde",
346 | ]
347 |
348 | [[package]]
349 | name = "near-vm-logic"
350 | version = "4.0.0-pre.1"
351 | source = "registry+https://github.com/rust-lang/crates.io-index"
352 | checksum = "e11cb28a2d07f37680efdaf860f4c9802828c44fc50c08009e7884de75d982c5"
353 | dependencies = [
354 | "base64",
355 | "borsh",
356 | "bs58",
357 | "byteorder",
358 | "near-primitives-core",
359 | "near-runtime-utils",
360 | "near-vm-errors",
361 | "serde",
362 | "sha2",
363 | "sha3",
364 | ]
365 |
366 | [[package]]
367 | name = "num-bigint"
368 | version = "0.3.3"
369 | source = "registry+https://github.com/rust-lang/crates.io-index"
370 | checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3"
371 | dependencies = [
372 | "autocfg",
373 | "num-integer",
374 | "num-traits",
375 | ]
376 |
377 | [[package]]
378 | name = "num-integer"
379 | version = "0.1.44"
380 | source = "registry+https://github.com/rust-lang/crates.io-index"
381 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
382 | dependencies = [
383 | "autocfg",
384 | "num-traits",
385 | ]
386 |
387 | [[package]]
388 | name = "num-rational"
389 | version = "0.3.2"
390 | source = "registry+https://github.com/rust-lang/crates.io-index"
391 | checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
392 | dependencies = [
393 | "autocfg",
394 | "num-bigint",
395 | "num-integer",
396 | "num-traits",
397 | "serde",
398 | ]
399 |
400 | [[package]]
401 | name = "num-traits"
402 | version = "0.2.14"
403 | source = "registry+https://github.com/rust-lang/crates.io-index"
404 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
405 | dependencies = [
406 | "autocfg",
407 | ]
408 |
409 | [[package]]
410 | name = "opaque-debug"
411 | version = "0.3.0"
412 | source = "registry+https://github.com/rust-lang/crates.io-index"
413 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
414 |
415 | [[package]]
416 | name = "proc-macro-crate"
417 | version = "0.1.5"
418 | source = "registry+https://github.com/rust-lang/crates.io-index"
419 | checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
420 | dependencies = [
421 | "toml",
422 | ]
423 |
424 | [[package]]
425 | name = "proc-macro2"
426 | version = "1.0.36"
427 | source = "registry+https://github.com/rust-lang/crates.io-index"
428 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
429 | dependencies = [
430 | "unicode-xid",
431 | ]
432 |
433 | [[package]]
434 | name = "quote"
435 | version = "1.0.15"
436 | source = "registry+https://github.com/rust-lang/crates.io-index"
437 | checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
438 | dependencies = [
439 | "proc-macro2",
440 | ]
441 |
442 | [[package]]
443 | name = "regex"
444 | version = "1.5.6"
445 | source = "registry+https://github.com/rust-lang/crates.io-index"
446 | checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
447 | dependencies = [
448 | "aho-corasick",
449 | "memchr",
450 | "regex-syntax",
451 | ]
452 |
453 | [[package]]
454 | name = "regex-syntax"
455 | version = "0.6.26"
456 | source = "registry+https://github.com/rust-lang/crates.io-index"
457 | checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
458 |
459 | [[package]]
460 | name = "rustc_version"
461 | version = "0.4.0"
462 | source = "registry+https://github.com/rust-lang/crates.io-index"
463 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
464 | dependencies = [
465 | "semver",
466 | ]
467 |
468 | [[package]]
469 | name = "ryu"
470 | version = "1.0.9"
471 | source = "registry+https://github.com/rust-lang/crates.io-index"
472 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
473 |
474 | [[package]]
475 | name = "semver"
476 | version = "1.0.6"
477 | source = "registry+https://github.com/rust-lang/crates.io-index"
478 | checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d"
479 |
480 | [[package]]
481 | name = "serde"
482 | version = "1.0.118"
483 | source = "registry+https://github.com/rust-lang/crates.io-index"
484 | checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
485 | dependencies = [
486 | "serde_derive",
487 | ]
488 |
489 | [[package]]
490 | name = "serde_derive"
491 | version = "1.0.118"
492 | source = "registry+https://github.com/rust-lang/crates.io-index"
493 | checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
494 | dependencies = [
495 | "proc-macro2",
496 | "quote",
497 | "syn",
498 | ]
499 |
500 | [[package]]
501 | name = "serde_json"
502 | version = "1.0.79"
503 | source = "registry+https://github.com/rust-lang/crates.io-index"
504 | checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
505 | dependencies = [
506 | "indexmap",
507 | "itoa",
508 | "ryu",
509 | "serde",
510 | ]
511 |
512 | [[package]]
513 | name = "sha2"
514 | version = "0.9.9"
515 | source = "registry+https://github.com/rust-lang/crates.io-index"
516 | checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
517 | dependencies = [
518 | "block-buffer",
519 | "cfg-if 1.0.0",
520 | "cpufeatures",
521 | "digest",
522 | "opaque-debug",
523 | ]
524 |
525 | [[package]]
526 | name = "sha3"
527 | version = "0.9.1"
528 | source = "registry+https://github.com/rust-lang/crates.io-index"
529 | checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
530 | dependencies = [
531 | "block-buffer",
532 | "digest",
533 | "keccak",
534 | "opaque-debug",
535 | ]
536 |
537 | [[package]]
538 | name = "syn"
539 | version = "1.0.57"
540 | source = "registry+https://github.com/rust-lang/crates.io-index"
541 | checksum = "4211ce9909eb971f111059df92c45640aad50a619cf55cd76476be803c4c68e6"
542 | dependencies = [
543 | "proc-macro2",
544 | "quote",
545 | "unicode-xid",
546 | ]
547 |
548 | [[package]]
549 | name = "toml"
550 | version = "0.5.8"
551 | source = "registry+https://github.com/rust-lang/crates.io-index"
552 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
553 | dependencies = [
554 | "serde",
555 | ]
556 |
557 | [[package]]
558 | name = "typenum"
559 | version = "1.15.0"
560 | source = "registry+https://github.com/rust-lang/crates.io-index"
561 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
562 |
563 | [[package]]
564 | name = "unicode-xid"
565 | version = "0.2.2"
566 | source = "registry+https://github.com/rust-lang/crates.io-index"
567 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
568 |
569 | [[package]]
570 | name = "version_check"
571 | version = "0.9.4"
572 | source = "registry+https://github.com/rust-lang/crates.io-index"
573 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
574 |
575 | [[package]]
576 | name = "wee_alloc"
577 | version = "0.4.5"
578 | source = "registry+https://github.com/rust-lang/crates.io-index"
579 | checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e"
580 | dependencies = [
581 | "cfg-if 0.1.10",
582 | "libc",
583 | "memory_units",
584 | "winapi",
585 | ]
586 |
587 | [[package]]
588 | name = "winapi"
589 | version = "0.3.9"
590 | source = "registry+https://github.com/rust-lang/crates.io-index"
591 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
592 | dependencies = [
593 | "winapi-i686-pc-windows-gnu",
594 | "winapi-x86_64-pc-windows-gnu",
595 | ]
596 |
597 | [[package]]
598 | name = "winapi-i686-pc-windows-gnu"
599 | version = "0.4.0"
600 | source = "registry+https://github.com/rust-lang/crates.io-index"
601 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
602 |
603 | [[package]]
604 | name = "winapi-x86_64-pc-windows-gnu"
605 | version = "0.4.0"
606 | source = "registry+https://github.com/rust-lang/crates.io-index"
607 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
608 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Creative Commons Legal Code
2 |
3 | Attribution-ShareAlike 3.0 Unported
4 |
5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6 | LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9 | REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
10 | DAMAGES RESULTING FROM ITS USE.
11 |
12 | License
13 |
14 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
15 | COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
16 | COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
17 | AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
18 |
19 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
20 | TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
21 | BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
22 | CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
23 | CONDITIONS.
24 |
25 | 1. Definitions
26 |
27 | a. "Adaptation" means a work based upon the Work, or upon the Work and
28 | other pre-existing works, such as a translation, adaptation,
29 | derivative work, arrangement of music or other alterations of a
30 | literary or artistic work, or phonogram or performance and includes
31 | cinematographic adaptations or any other form in which the Work may be
32 | recast, transformed, or adapted including in any form recognizably
33 | derived from the original, except that a work that constitutes a
34 | Collection will not be considered an Adaptation for the purpose of
35 | this License. For the avoidance of doubt, where the Work is a musical
36 | work, performance or phonogram, the synchronization of the Work in
37 | timed-relation with a moving image ("synching") will be considered an
38 | Adaptation for the purpose of this License.
39 | b. "Collection" means a collection of literary or artistic works, such as
40 | encyclopedias and anthologies, or performances, phonograms or
41 | broadcasts, or other works or subject matter other than works listed
42 | in Section 1(f) below, which, by reason of the selection and
43 | arrangement of their contents, constitute intellectual creations, in
44 | which the Work is included in its entirety in unmodified form along
45 | with one or more other contributions, each constituting separate and
46 | independent works in themselves, which together are assembled into a
47 | collective whole. A work that constitutes a Collection will not be
48 | considered an Adaptation (as defined below) for the purposes of this
49 | License.
50 | c. "Creative Commons Compatible License" means a license that is listed
51 | at https://creativecommons.org/compatiblelicenses that has been
52 | approved by Creative Commons as being essentially equivalent to this
53 | License, including, at a minimum, because that license: (i) contains
54 | terms that have the same purpose, meaning and effect as the License
55 | Elements of this License; and, (ii) explicitly permits the relicensing
56 | of adaptations of works made available under that license under this
57 | License or a Creative Commons jurisdiction license with the same
58 | License Elements as this License.
59 | d. "Distribute" means to make available to the public the original and
60 | copies of the Work or Adaptation, as appropriate, through sale or
61 | other transfer of ownership.
62 | e. "License Elements" means the following high-level license attributes
63 | as selected by Licensor and indicated in the title of this License:
64 | Attribution, ShareAlike.
65 | f. "Licensor" means the individual, individuals, entity or entities that
66 | offer(s) the Work under the terms of this License.
67 | g. "Original Author" means, in the case of a literary or artistic work,
68 | the individual, individuals, entity or entities who created the Work
69 | or if no individual or entity can be identified, the publisher; and in
70 | addition (i) in the case of a performance the actors, singers,
71 | musicians, dancers, and other persons who act, sing, deliver, declaim,
72 | play in, interpret or otherwise perform literary or artistic works or
73 | expressions of folklore; (ii) in the case of a phonogram the producer
74 | being the person or legal entity who first fixes the sounds of a
75 | performance or other sounds; and, (iii) in the case of broadcasts, the
76 | organization that transmits the broadcast.
77 | h. "Work" means the literary and/or artistic work offered under the terms
78 | of this License including without limitation any production in the
79 | literary, scientific and artistic domain, whatever may be the mode or
80 | form of its expression including digital form, such as a book,
81 | pamphlet and other writing; a lecture, address, sermon or other work
82 | of the same nature; a dramatic or dramatico-musical work; a
83 | choreographic work or entertainment in dumb show; a musical
84 | composition with or without words; a cinematographic work to which are
85 | assimilated works expressed by a process analogous to cinematography;
86 | a work of drawing, painting, architecture, sculpture, engraving or
87 | lithography; a photographic work to which are assimilated works
88 | expressed by a process analogous to photography; a work of applied
89 | art; an illustration, map, plan, sketch or three-dimensional work
90 | relative to geography, topography, architecture or science; a
91 | performance; a broadcast; a phonogram; a compilation of data to the
92 | extent it is protected as a copyrightable work; or a work performed by
93 | a variety or circus performer to the extent it is not otherwise
94 | considered a literary or artistic work.
95 | i. "You" means an individual or entity exercising rights under this
96 | License who has not previously violated the terms of this License with
97 | respect to the Work, or who has received express permission from the
98 | Licensor to exercise rights under this License despite a previous
99 | violation.
100 | j. "Publicly Perform" means to perform public recitations of the Work and
101 | to communicate to the public those public recitations, by any means or
102 | process, including by wire or wireless means or public digital
103 | performances; to make available to the public Works in such a way that
104 | members of the public may access these Works from a place and at a
105 | place individually chosen by them; to perform the Work to the public
106 | by any means or process and the communication to the public of the
107 | performances of the Work, including by public digital performance; to
108 | broadcast and rebroadcast the Work by any means including signs,
109 | sounds or images.
110 | k. "Reproduce" means to make copies of the Work by any means including
111 | without limitation by sound or visual recordings and the right of
112 | fixation and reproducing fixations of the Work, including storage of a
113 | protected performance or phonogram in digital form or other electronic
114 | medium.
115 |
116 | 2. Fair Dealing Rights. Nothing in this License is intended to reduce,
117 | limit, or restrict any uses free from copyright or rights arising from
118 | limitations or exceptions that are provided for in connection with the
119 | copyright protection under copyright law or other applicable laws.
120 |
121 | 3. License Grant. Subject to the terms and conditions of this License,
122 | Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
123 | perpetual (for the duration of the applicable copyright) license to
124 | exercise the rights in the Work as stated below:
125 |
126 | a. to Reproduce the Work, to incorporate the Work into one or more
127 | Collections, and to Reproduce the Work as incorporated in the
128 | Collections;
129 | b. to create and Reproduce Adaptations provided that any such Adaptation,
130 | including any translation in any medium, takes reasonable steps to
131 | clearly label, demarcate or otherwise identify that changes were made
132 | to the original Work. For example, a translation could be marked "The
133 | original work was translated from English to Spanish," or a
134 | modification could indicate "The original work has been modified.";
135 | c. to Distribute and Publicly Perform the Work including as incorporated
136 | in Collections; and,
137 | d. to Distribute and Publicly Perform Adaptations.
138 | e. For the avoidance of doubt:
139 |
140 | i. Non-waivable Compulsory License Schemes. In those jurisdictions in
141 | which the right to collect royalties through any statutory or
142 | compulsory licensing scheme cannot be waived, the Licensor
143 | reserves the exclusive right to collect such royalties for any
144 | exercise by You of the rights granted under this License;
145 | ii. Waivable Compulsory License Schemes. In those jurisdictions in
146 | which the right to collect royalties through any statutory or
147 | compulsory licensing scheme can be waived, the Licensor waives the
148 | exclusive right to collect such royalties for any exercise by You
149 | of the rights granted under this License; and,
150 | iii. Voluntary License Schemes. The Licensor waives the right to
151 | collect royalties, whether individually or, in the event that the
152 | Licensor is a member of a collecting society that administers
153 | voluntary licensing schemes, via that society, from any exercise
154 | by You of the rights granted under this License.
155 |
156 | The above rights may be exercised in all media and formats whether now
157 | known or hereafter devised. The above rights include the right to make
158 | such modifications as are technically necessary to exercise the rights in
159 | other media and formats. Subject to Section 8(f), all rights not expressly
160 | granted by Licensor are hereby reserved.
161 |
162 | 4. Restrictions. The license granted in Section 3 above is expressly made
163 | subject to and limited by the following restrictions:
164 |
165 | a. You may Distribute or Publicly Perform the Work only under the terms
166 | of this License. You must include a copy of, or the Uniform Resource
167 | Identifier (URI) for, this License with every copy of the Work You
168 | Distribute or Publicly Perform. You may not offer or impose any terms
169 | on the Work that restrict the terms of this License or the ability of
170 | the recipient of the Work to exercise the rights granted to that
171 | recipient under the terms of the License. You may not sublicense the
172 | Work. You must keep intact all notices that refer to this License and
173 | to the disclaimer of warranties with every copy of the Work You
174 | Distribute or Publicly Perform. When You Distribute or Publicly
175 | Perform the Work, You may not impose any effective technological
176 | measures on the Work that restrict the ability of a recipient of the
177 | Work from You to exercise the rights granted to that recipient under
178 | the terms of the License. This Section 4(a) applies to the Work as
179 | incorporated in a Collection, but this does not require the Collection
180 | apart from the Work itself to be made subject to the terms of this
181 | License. If You create a Collection, upon notice from any Licensor You
182 | must, to the extent practicable, remove from the Collection any credit
183 | as required by Section 4(c), as requested. If You create an
184 | Adaptation, upon notice from any Licensor You must, to the extent
185 | practicable, remove from the Adaptation any credit as required by
186 | Section 4(c), as requested.
187 | b. You may Distribute or Publicly Perform an Adaptation only under the
188 | terms of: (i) this License; (ii) a later version of this License with
189 | the same License Elements as this License; (iii) a Creative Commons
190 | jurisdiction license (either this or a later license version) that
191 | contains the same License Elements as this License (e.g.,
192 | Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible
193 | License. If you license the Adaptation under one of the licenses
194 | mentioned in (iv), you must comply with the terms of that license. If
195 | you license the Adaptation under the terms of any of the licenses
196 | mentioned in (i), (ii) or (iii) (the "Applicable License"), you must
197 | comply with the terms of the Applicable License generally and the
198 | following provisions: (I) You must include a copy of, or the URI for,
199 | the Applicable License with every copy of each Adaptation You
200 | Distribute or Publicly Perform; (II) You may not offer or impose any
201 | terms on the Adaptation that restrict the terms of the Applicable
202 | License or the ability of the recipient of the Adaptation to exercise
203 | the rights granted to that recipient under the terms of the Applicable
204 | License; (III) You must keep intact all notices that refer to the
205 | Applicable License and to the disclaimer of warranties with every copy
206 | of the Work as included in the Adaptation You Distribute or Publicly
207 | Perform; (IV) when You Distribute or Publicly Perform the Adaptation,
208 | You may not impose any effective technological measures on the
209 | Adaptation that restrict the ability of a recipient of the Adaptation
210 | from You to exercise the rights granted to that recipient under the
211 | terms of the Applicable License. This Section 4(b) applies to the
212 | Adaptation as incorporated in a Collection, but this does not require
213 | the Collection apart from the Adaptation itself to be made subject to
214 | the terms of the Applicable License.
215 | c. If You Distribute, or Publicly Perform the Work or any Adaptations or
216 | Collections, You must, unless a request has been made pursuant to
217 | Section 4(a), keep intact all copyright notices for the Work and
218 | provide, reasonable to the medium or means You are utilizing: (i) the
219 | name of the Original Author (or pseudonym, if applicable) if supplied,
220 | and/or if the Original Author and/or Licensor designate another party
221 | or parties (e.g., a sponsor institute, publishing entity, journal) for
222 | attribution ("Attribution Parties") in Licensor's copyright notice,
223 | terms of service or by other reasonable means, the name of such party
224 | or parties; (ii) the title of the Work if supplied; (iii) to the
225 | extent reasonably practicable, the URI, if any, that Licensor
226 | specifies to be associated with the Work, unless such URI does not
227 | refer to the copyright notice or licensing information for the Work;
228 | and (iv) , consistent with Ssection 3(b), in the case of an
229 | Adaptation, a credit identifying the use of the Work in the Adaptation
230 | (e.g., "French translation of the Work by Original Author," or
231 | "Screenplay based on original Work by Original Author"). The credit
232 | required by this Section 4(c) may be implemented in any reasonable
233 | manner; provided, however, that in the case of a Adaptation or
234 | Collection, at a minimum such credit will appear, if a credit for all
235 | contributing authors of the Adaptation or Collection appears, then as
236 | part of these credits and in a manner at least as prominent as the
237 | credits for the other contributing authors. For the avoidance of
238 | doubt, You may only use the credit required by this Section for the
239 | purpose of attribution in the manner set out above and, by exercising
240 | Your rights under this License, You may not implicitly or explicitly
241 | assert or imply any connection with, sponsorship or endorsement by the
242 | Original Author, Licensor and/or Attribution Parties, as appropriate,
243 | of You or Your use of the Work, without the separate, express prior
244 | written permission of the Original Author, Licensor and/or Attribution
245 | Parties.
246 | d. Except as otherwise agreed in writing by the Licensor or as may be
247 | otherwise permitted by applicable law, if You Reproduce, Distribute or
248 | Publicly Perform the Work either by itself or as part of any
249 | Adaptations or Collections, You must not distort, mutilate, modify or
250 | take other derogatory action in relation to the Work which would be
251 | prejudicial to the Original Author's honor or reputation. Licensor
252 | agrees that in those jurisdictions (e.g. Japan), in which any exercise
253 | of the right granted in Section 3(b) of this License (the right to
254 | make Adaptations) would be deemed to be a distortion, mutilation,
255 | modification or other derogatory action prejudicial to the Original
256 | Author's honor and reputation, the Licensor will waive or not assert,
257 | as appropriate, this Section, to the fullest extent permitted by the
258 | applicable national law, to enable You to reasonably exercise Your
259 | right under Section 3(b) of this License (right to make Adaptations)
260 | but not otherwise.
261 |
262 | 5. Representations, Warranties and Disclaimer
263 |
264 | UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
265 | OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
266 | KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
267 | INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
268 | FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
269 | LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
270 | WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
271 | OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
272 |
273 | 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
274 | LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
275 | ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
276 | ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
277 | BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
278 |
279 | 7. Termination
280 |
281 | a. This License and the rights granted hereunder will terminate
282 | automatically upon any breach by You of the terms of this License.
283 | Individuals or entities who have received Adaptations or Collections
284 | from You under this License, however, will not have their licenses
285 | terminated provided such individuals or entities remain in full
286 | compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
287 | survive any termination of this License.
288 | b. Subject to the above terms and conditions, the license granted here is
289 | perpetual (for the duration of the applicable copyright in the Work).
290 | Notwithstanding the above, Licensor reserves the right to release the
291 | Work under different license terms or to stop distributing the Work at
292 | any time; provided, however that any such election will not serve to
293 | withdraw this License (or any other license that has been, or is
294 | required to be, granted under the terms of this License), and this
295 | License will continue in full force and effect unless terminated as
296 | stated above.
297 |
298 | 8. Miscellaneous
299 |
300 | a. Each time You Distribute or Publicly Perform the Work or a Collection,
301 | the Licensor offers to the recipient a license to the Work on the same
302 | terms and conditions as the license granted to You under this License.
303 | b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
304 | offers to the recipient a license to the original Work on the same
305 | terms and conditions as the license granted to You under this License.
306 | c. If any provision of this License is invalid or unenforceable under
307 | applicable law, it shall not affect the validity or enforceability of
308 | the remainder of the terms of this License, and without further action
309 | by the parties to this agreement, such provision shall be reformed to
310 | the minimum extent necessary to make such provision valid and
311 | enforceable.
312 | d. No term or provision of this License shall be deemed waived and no
313 | breach consented to unless such waiver or consent shall be in writing
314 | and signed by the party to be charged with such waiver or consent.
315 | e. This License constitutes the entire agreement between the parties with
316 | respect to the Work licensed here. There are no understandings,
317 | agreements or representations with respect to the Work not specified
318 | here. Licensor shall not be bound by any additional provisions that
319 | may appear in any communication from You. This License may not be
320 | modified without the mutual written agreement of the Licensor and You.
321 | f. The rights granted under, and the subject matter referenced, in this
322 | License were drafted utilizing the terminology of the Berne Convention
323 | for the Protection of Literary and Artistic Works (as amended on
324 | September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
325 | Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
326 | and the Universal Copyright Convention (as revised on July 24, 1971).
327 | These rights and subject matter take effect in the relevant
328 | jurisdiction in which the License terms are sought to be enforced
329 | according to the corresponding provisions of the implementation of
330 | those treaty provisions in the applicable national law. If the
331 | standard suite of rights granted under applicable copyright law
332 | includes additional rights not granted under this License, such
333 | additional rights are deemed to be included in the License; this
334 | License is not intended to restrict the license of any rights under
335 | applicable law.
336 |
337 |
338 | Creative Commons Notice
339 |
340 | Creative Commons is not a party to this License, and makes no warranty
341 | whatsoever in connection with the Work. Creative Commons will not be
342 | liable to You or any party on any legal theory for any damages
343 | whatsoever, including without limitation any general, special,
344 | incidental or consequential damages arising in connection to this
345 | license. Notwithstanding the foregoing two (2) sentences, if Creative
346 | Commons has expressly identified itself as the Licensor hereunder, it
347 | shall have all rights and obligations of Licensor.
348 |
349 | Except for the limited purpose of indicating to the public that the
350 | Work is licensed under the CCPL, Creative Commons does not authorize
351 | the use by either party of the trademark "Creative Commons" or any
352 | related trademark or logo of Creative Commons without the prior
353 | written consent of Creative Commons. Any permitted use will be in
354 | compliance with Creative Commons' then-current trademark usage
355 | guidelines, as may be published on its website or otherwise made
356 | available upon request from time to time. For the avoidance of doubt,
357 | this trademark restriction does not form part of the License.
358 |
359 | Creative Commons may be contacted at https://creativecommons.org/.
--------------------------------------------------------------------------------