def do_tokens_of_owner(ctx, t_owner, start_index): """This method returns ten of the owner's tokens starting at the given index. The index is used for paginating through the results. Pagination is needed for the situation where the owner's dict of tokens could be quite large. For example, the specified owner could have 100,000 tokens out of 1,000,000 minted tokens. In such a scenario, returning the full list of token id's would be quite expensive and could possibly be too large to return anyway. Hence, @hal0x2328 recognized the need to paginate the data in such a scenario. So, if we know that this user has a balanceOf() 100,000 tokens and we want to get their 10 most recent tokens, then our call would be like so: `testinvoke {my_hash} tokensOfOwner [{owner address string}, 999990]` The results would look something like: [{'type': 'ByteArray', 'value': '82060007746f6b656e2f010001010007746f6b656e2f020001020007746f6b656e2f030001030007746f6b656e2f040001040007746f6b656e2f050001050007746f6b656e2f06000106''}] :param StorageContext ctx: current store context :param byte[] t_owner: token owner :param bytes start_index: the index to start searching through the owner's tokens :return: dict of tokens :rtype: bool or dict """ if len(t_owner) != 20: Notify(INVALID_ADDRESS_ERROR) return False if len(start_index) == b'\x00': start_index = b'\x01' # token id's cannot go below 1 start_key = concat(t_owner, start_index) count = 0 token_dict = {} token_iter = Find(ctx, t_owner) # while loop explained: keep looping through the owner's list # of tokens until 10 have been found beginning at the starting # index. # if statement explained: once a key has been found matching # my search key (or of greater value), # update the dictionary, increment the counter, # and disregard trying to find a matching key thereafter. # (once a key has been found matching my search key # (or greater), just get everything afterward while count < 10) while token_iter.next() and (count < 10): if (token_iter.Key >= start_key) or (count > 0): token_dict[concat('token/', token_iter.Value)] = token_iter.Value count += 1 if len(token_dict) >= 1: return token_dict Notify(TOKEN_DNE_ERROR) return False
def do_tokens_data_of_owner(ctx, t_owner, start_index): """This method returns five of the owner's token's id and data starting at the given index. See `do_tokens_of_owner` for more detailed information behind rationale. :param StorageContext ctx: current store context :param byte[] t_owner: token owner :param bytes start_index: the index to start searching through the owner's tokens :return: dictionary of id, properties, and uri keys mapped to their corresponding token's data :rtype: bool or dict """ if len(t_owner) != 20: Notify(INVALID_ADDRESS_ERROR) return False if len(start_index) == b'\x00': start_index = b'\x01' # token id's cannot go below 1 start_key = concat(t_owner, start_index) count = 0 token_dict = {} token_iter = Find(ctx, t_owner) # while loop explained: keep looping through the owner's list # of tokens until 5 have been found beginning at the starting # index. # if statement explained: once a key has been found matching # my search key (or of greater value), # update the dictionary, increment the counter, # and disregard trying to find a matching key thereafter. # (once a key has been found matching my search key # (or greater), just get everything afterward while count < 5) while token_iter.next() and (count < 5): if (token_iter.Key >= start_key) or (count > 0): token_data = do_token_data(ctx, token_iter.Value) # simplify this if/when neo-boa implements something # like token_dict.update(token_data) # keys token_key = concat('token/', token_iter.Value) prop_key = concat('properties/', token_iter.Value) uri_key = concat('uri/', token_iter.Value) # update dictionary token_dict[token_key] = token_data[token_key] token_dict[prop_key] = token_data[prop_key] token_dict[uri_key] = token_data[uri_key] count += 1 if len(token_dict) >= 1: return token_dict Notify(TOKEN_DNE_ERROR) return False
def do_tokens_of_owner(ctx, t_owner, start_id): """This method returns ten of the owner's tokens starting at the given index. The index is used for paginating through the results. Pagination is needed for the situation where the owner's dict of tokens could be quite large. :param StorageContext ctx: current store context :param bytearray t_owner: token owner :param int start_id: the id to start searching through the owner's tokens :return: dictionary of id, properties, and uri keys mapped to their corresponding token's data :rtype: bool or dict """ assert len(t_owner) == 20, INVALID_ADDRESS_ERROR if start_id == 0: start_id = 1 # token id's cannot go below 1 start_key = concat(t_owner, start_id) count = 0 token_dict = {} token_iter = Find(ctx, t_owner) # while loop explained: keep looping through the owner's list # of tokens until 10 have been found beginning at the starting # index. # if statement explained: once a key has been found matching # my search key (or of greater value), # update the dictionary, increment the counter, # and disregard trying to find a matching key thereafter. # (once a key has been found matching my search key # (or greater), just get everything afterward while count < 10) while token_iter.next() and (count < 10): if (token_iter.Key >= start_key) or (count > 0): token_key = concat('token/', token_iter.Value) token = safe_deserialize(Get(ctx, token_key)) if token: token_dict[token_key] = token count += 1 return token_dict
def Main(operation, args): """Entry point to the program :param str operation: The name of the operation to perform :param list args: A list of arguments along with the operation :return: The result of the operation :rtype: bytearray Token operations: - allowance(token_id): returns approved third-party spender of a token - approve(token_receiver, token_id, revoke): approve third party to spend a token - balanceOf(owner): returns owner's current total tokens owned - name(): returns name of token - decimals(): returns token decimal precision - ownerOf(token_id): returns the owner of the specified token. - properties(token_id): returns a token's read-only data - rwProperties(token_id): returns a token's read/write data - supportedStandards(): returns a list of supported standards {"NEP-10"} - symbol(): returns token symbol - token(token_id): returns a dictionary where token, property, and uri keys map to the corresponding data for the given `token_id` - tokensOfOwner(owner, starting_index): returns a dictionary that contains less than or equal to ten of the tokens owned by the specified address starting at the `starting_index`. - totalSupply(): Returns the total token supply deployed in the system. - transfer(to, token_id, extra_arg): transfers a token - transferFrom(spender, from, to, token_id): allows a third party to execute a pre-approved transfer - uri(token_id): Returns a distinct Uniform Resource Identifier (URI) for a given asset. The URI data of a token supplies a reference to get more information about a specific token or its data. TOKEN_CONTRACT_OWNER operations: - mintToken(owner, properties, URI, extra_arg): create a new NFT token with the specified properties and URI and send it to the specified owner - modifyURI(token_id, token_data): modify specified token's URI data setters: - setName(name): sets the name of the token - setSymbol(symbol): sets the token's symbol - setSupportedStandards(supported_standards): sets the supported standards, 'NEP-10' is always the first element in the array """ # The trigger determines whether this smart contract is being run # in 'verification' mode or 'application' trigger = GetTrigger() # 'Verification' mode is used when trying to spend assets # (eg NEO, Gas) on behalf of this contract's address if trigger == Verification(): # if the script that sent this is the owner, we allow the spend if CheckWitness(TOKEN_CONTRACT_OWNER): return True elif trigger == Application(): # Need to get this at the top level caller = GetCallingScriptHash() ctx = GetContext() if operation == 'name': name = Get(ctx, 'name') if name: return name else: return TOKEN_NAME elif operation == 'symbol': symbol = Get(ctx, 'symbol') if symbol: return symbol else: return TOKEN_SYMBOL elif operation == 'supportedStandards': supported_standards = Get(ctx, 'supportedStandards') if supported_standards: return supported_standards else: return Serialize(['NEP-10']) elif operation == 'totalSupply': return Get(ctx, TOKEN_CIRC_KEY) elif operation == 'allowance': assert len(args) == 1, ARG_ERROR ownership = safe_deserialize( Get(ctx, concat('ownership/', args[0]))) assert ownership, TOKEN_DNE_ERROR # don't fault here in case a calling contract is just checking allowance value if not has_key(ownership, 'approved'): return False if len(ownership['approved']) != 40: return False return ownership['approved'] elif operation == 'balanceOf': assert len(args) == 1, ARG_ERROR assert len(args[0]) == 20, INVALID_ADDRESS_ERROR token_iter = Find(ctx, args[0]) count = 0 while token_iter.next(): count += 1 return count elif operation == 'ownerOf': assert len(args) == 1, ARG_ERROR ownership = safe_deserialize( Get(ctx, concat('ownership/', args[0]))) assert ownership, TOKEN_DNE_ERROR assert has_key(ownership, 'owner'), TOKEN_DNE_ERROR assert len(ownership['owner']) == 20, TOKEN_DNE_ERROR return ownership['owner'] elif operation == 'properties': assert len(args) == 1, ARG_ERROR return get_properties(ctx, args[0]) elif operation == 'rwProperties': assert len(args) == 1, ARG_ERROR return get_rw_properties(ctx, args[0]) elif operation == 'token': assert len(args) == 1, ARG_ERROR token = Get(ctx, concat('token/', args[0])) assert token, TOKEN_DNE_ERROR return token elif operation == 'tokensOfOwner': assert len(args) == 2, ARG_ERROR tokens_of_owner = do_tokens_of_owner(ctx, args[0], args[1]) assert tokens_of_owner, 'address has no tokens' return Serialize(tokens_of_owner) elif operation == 'uri': assert len(args) == 1, ARG_ERROR token = safe_deserialize(Get(ctx, concat('token/', args[0]))) assert token, TOKEN_DNE_ERROR assert has_key(token, 'uri'), TOKEN_DNE_ERROR return token['uri'] elif operation == 'decimals': return TOKEN_DECIMALS # # User RW operations # if operation == 'approve': # args: from, spender, id, revoke # (NFT needs a fourth argument to revoke approval) assert len(args) > 2, ARG_ERROR assert args[2], TOKEN_DNE_ERROR return do_approve(ctx, caller, args) elif operation == 'transfer': assert len(args) > 1, ARG_ERROR return do_transfer(ctx, caller, args) elif operation == 'transferFrom': assert len(args) > 2, ARG_ERROR if len(args) == 3: # Nash-style (from, to, amount/id) transferFrom that can # be invoked only by whitelisted DEX to initiate a # pre-approved transfer return nash_do_transfer_from(ctx, caller, args) else: # Moonlight-style (spender, from, to, amount/id) # transfer where an authenticated spender/originator is # the only one who can initiate a transfer but can send # to an arbitrary third party (or themselves) return do_transfer_from(ctx, caller, args) # # dApp operations # if operation == 'setRWProperties': # args: token id, rwdata assert CheckWitness(DAPP_ADMIN), PERMISSION_ERROR assert len(args) == 2, ARG_ERROR return set_rw_properties(ctx, args[0], args[1]) # Administrative operations if CheckWitness(TOKEN_CONTRACT_OWNER): if operation == 'mintToken': assert len(args) > 3, ARG_ERROR return do_mint_token(ctx, args) elif operation == 'modifyURI': assert len(args) == 2, ARG_ERROR return do_modify_uri(ctx, args) elif operation == 'setName': assert len(args) == 1, ARG_ERROR return do_set_config(ctx, 'name', args[0]) elif operation == 'setSymbol': assert len(args) == 1, ARG_ERROR return do_set_config(ctx, 'symbol', args[0]) elif operation == 'setSupportedStandards': assert len(args) >= 1, ARG_ERROR supported_standards = ['NEP-10'] for arg in args: supported_standards.append(arg) return do_set_config(ctx, 'supportedStandards', Serialize(supported_standards)) AssertionError('unknown operation') return False