def Main(operation, ctr): if operation == 'get_contract': return GetContract(ctr) elif operation == 'get_script': return GetContract(ctr).Script elif operation == 'get_storage_context': return GetContract(ctr).StorageContext elif operation == 'destroy': Destroy() return True return 'unknown operation'
def CheckWitnessOrCaller(scripthash, caller): """ Method to check if the transaction is signed by a private key or is the scripthash of a contract that is authorized to perform the requested function for its own address only :param scripthash: the scripthash to be checked :type scripthash: bytearray :param caller: the scripthash of the calling script :type caller: bytearray :return: whether the scripthash is authorized :rtype: bool """ if GetContract(scripthash): if scripthash == caller: return True # a contract can spend its own funds else: # deny third-party contracts from transferring # tokens of a user even with the user signature # (this will break ability of some DEX to list the token) return False return CheckWitness(scripthash)
def do_mint_token(ctx, args): """Mints a new NFT token; stores it's properties, URI info, and owner on the blockchain; updates the totalSupply :param StorageContext ctx: current store context :param list args: 0: byte[] t_owner: token owner 1: byte[] t_properties: token's read only data 2: bytes t_uri: token's uri 3: extra_arg (optional): extra arg to be passed to a smart contract :return: mint success :rtype: bool """ t_id = Get(ctx, TOKEN_CIRC_KEY) # the int 0 is represented as b'' in neo-boa, this caused bugs # throughout my code # This is the reason why token id's start at 1 instead t_id += 1 # this should never already exist if len(Get(ctx, t_id)) == 20: Notify('token already exists') return False t_owner = args[0] if len(t_owner) != 20: Notify(INVALID_ADDRESS_ERROR) return False t_properties = args[1] if len(t_properties) == b'\x00': Notify('missing properties data string') return False t_uri = args[2] if GetContract(t_owner): contract_args = [t_owner, t_id] if len(args) == 4: # append optional extra arg contract_args.append(args[3]) success = transfer_to_smart_contract(ctx, GetExecutingScriptHash(), contract_args, True) if success is False: return False Put(ctx, t_id, t_owner) # update token's owner Put(ctx, concat('properties/', t_id), t_properties) Put(ctx, concat('uri/', t_id), t_uri) add_token_to_owners_list(ctx, t_owner, t_id) Put(ctx, TOKEN_CIRC_KEY, t_id) # update total supply # Log this minting event OnMint(t_owner, 1) OnNFTMint(t_owner, t_id) return True
def do_approve(ctx, caller, t_receiver, t_id, revoke): """Approve a token to eventually be transferred to the t_receiver :param StorageContext ctx: current store context :param byte[] caller: calling script hash :param byte[] t_receiver: address of the future token owner :param bytes t_id: int: token id :param bytes revoke: set to 1 to revoke previous approval :return: approval success :rtype: bool """ if len(t_receiver) != 20: Notify(INVALID_ADDRESS_ERROR) return False if len(revoke) == b'\x00': revoke = b'\x00' t_owner = Get(ctx, t_id) if len(t_owner) != 20: Notify(TOKEN_DNE_ERROR) return False if t_owner == t_receiver: Notify('approved spend to self') return True is_token_owner = CheckWitness(t_owner) if is_token_owner and GetEntryScriptHash() != caller: Notify('third party script is bouncing the signature to us') return False # if token owner is a smart contract and is the calling # script hash, continue elif GetContract(t_owner) and t_owner == caller: is_token_owner = True if is_token_owner: approval_key = concat('approved/', t_id) # revoke previous approval if revoke != 0 if revoke != b'\x00': Delete(ctx, approval_key) # log the revoking of previous approvals OnApprove(t_owner, t_receiver, b'\x00') OnNFTApprove(t_owner, '', t_id) return True # approve this transfer Put(ctx, approval_key, concat(t_owner, t_receiver)) # Log this approval event OnApprove(t_owner, t_receiver, 1) OnNFTApprove(t_owner, t_receiver, t_id) return True Notify(PERMISSION_ERROR) return False
def do_transfer_from(ctx, args): """Transfers the approved token at the specified id from the t_from address to the t_to address :param StorageContext ctx: current store context :param list args: 0: byte[] t_from: transfer from address (token owner) 1: byte[] t_to: transfer to address (token receiver) 2: bytes t_id: token id 3: extra_arg: optional argument that can be passed (for use only with smart contracts) :return: transferFrom success :rtype: bool """ t_from = args[0] t_to = args[1] t_id = args[2] if len(t_from) != 20 or len(t_to) != 20: Notify(INVALID_ADDRESS_ERROR) return False if t_from == t_to: Notify('transfer to self') return True t_owner = Get(ctx, t_id) if len(t_owner) != 20: Notify(TOKEN_DNE_ERROR) return False if t_from != t_owner: Notify('from address is not the owner of this token') return False approval_key = concat('approved/', t_id) # authorized spend should be concat(t_owner, t_receiver) authorized_spend = Get(ctx, approval_key) # len(t_owner) == 20 and len(t_receiver) == 20, thus the length of # authorized_spender should be 40 if len(authorized_spend) != 40: Notify('no approval exists for this token') return False # if the input transfer from and transfer to addresses match the # authorized spend if authorized_spend == concat(t_from, t_to): # 1. Is t_to a smart contract? # If True, invoke the transfer_to_smart_contract method. # if transfer_to_smart_contract() returns False, then # reject the transfer if GetContract(t_to): args.remove(0) success = transfer_to_smart_contract(ctx, t_from, args, False) if success is False: return False else: # if t_to is not a contract, there shouldn't be any # extra args to transfer(), this could be a phishing # attempt so reject the transfer if len(args) > 3: Notify(ARG_ERROR) return False res = remove_token_from_owners_list(ctx, t_from, t_id) if res is False: Notify('unable to transfer token') return False Put(ctx, t_id, t_to) # record token's new owner Delete(ctx, approval_key) # remove previous approval add_token_to_owners_list(ctx, t_to, t_id) # log this transfer event OnTransfer(t_from, t_to, 1) OnNFTTransfer(t_from, t_to, t_id) return True Notify(PERMISSION_ERROR) return False
def do_transfer(ctx, caller, args): """Transfers a token at the specified id from the t_owner address to the t_to address :param StorageContext ctx: current store context :param bytes caller: calling script hash :param list args: 0: byte[] t_to: transfer to address 1: bytes t_id: token id 2: extra_arg: optional argument that can be passed (for use only with smart contracts) :return: transfer success :rtype: bool """ t_to = args[0] t_id = args[1] if len(t_to) != 20: Notify(INVALID_ADDRESS_ERROR) return False t_owner = Get(ctx, t_id) if len(t_owner) != 20: Notify(TOKEN_DNE_ERROR) return False if t_owner == t_to: Notify('transfer to self') return True # Verifies that the calling contract has verified the required # script hashes of the transaction/block is_token_owner = CheckWitness(t_owner) if is_token_owner and GetEntryScriptHash() != caller: Notify('third party script is bouncing the signature to us') return False # if token owner is a smart contract and is the calling # script hash, continue elif GetContract(t_owner) and t_owner == caller: is_token_owner = True if is_token_owner: # 1. Is t_to a smart contract? # If True, invoke the transfer_to_smart_contract # method, if transfer_to_smart_contract() returns False, # then reject the transfer if GetContract(t_to): success = transfer_to_smart_contract(ctx, t_owner, args, False) if success is False: return False else: if len(args) > 2: Notify(ARG_ERROR) return False res = remove_token_from_owners_list(ctx, t_owner, t_id) if res is False: Notify('unable to transfer token') return False Put(ctx, t_id, t_to) # update token's owner # remove any existing approvals for this token Delete(ctx, concat('approved/', t_id)) add_token_to_owners_list(ctx, t_to, t_id) # log this transfer event OnTransfer(t_owner, t_to, 1) OnNFTTransfer(t_owner, t_to, t_id) return True Notify(PERMISSION_ERROR) return False
def authenticate(scripthash, Caller): if CheckWitness(scripthash): return True if GetContract(scripthash) and scripthash == Caller: return True return False
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 - ownerOf(token_id): returns the owner of the specified token. - postMintContract(): returns the contract that a freshly minted token gets sent to by default - properties(token_id): returns a token's read-only data - supportedStandards(): returns a list of supported standards {"NEP-10"} - symbol(): returns token symbol - tokensOfOwner(owner, starting_index): returns a list 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): transfers a token - transferFrom(from, to, token_id): allows a third party to execute an 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(properties, URI, owner, extra_arg): create a new NFT token with the specified properties and URI - modifyURI(token_id, token_data): modify specified token's URI data setters: - setName(name): sets the name of the token - setPostMintContract(contract_address): sets the contract freshly minted tokens get sent to by default - setSymbol(symbol): sets the token's symbol - setSupportedStandards(supported_standards): sets the supported standards, 'NEP-10' must be 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(): 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 == 'postMintContract': return Get(ctx, 'postMintContract') elif operation == 'totalSupply': return Get(ctx, TOKEN_CIRC_KEY) if operation == 'allowance': if len(args) == 1: return Get(ctx, concat('approved/', args[0])) Notify(ARG_ERROR) return False elif operation == 'approve': if len(args) == 3: # GetCallingScriptHash() can't be done within the # function because the calling script hash changes # depending on where the function is called return do_approve(ctx, GetCallingScriptHash(), args[0], args[1], args[2]) Notify(ARG_ERROR) return False elif operation == 'balanceOf': if len(args) == 1: if len(args[0]) == 20: return Get(ctx, args[0]) Notify(INVALID_ADDRESS_ERROR) return False Notify(ARG_ERROR) return False elif operation == 'ownerOf': if len(args) == 1: t_owner = Get(ctx, args[0]) if len(t_owner) == 20: return t_owner Notify(TOKEN_DNE_ERROR) return False Notify(ARG_ERROR) return False elif operation == 'properties': if len(args) == 1: token_properties = Get(ctx, concat('properties/', args[0])) if token_properties: return token_properties Notify(TOKEN_DNE_ERROR) return False Notify(ARG_ERROR) return False elif operation == 'transfer': if len(args) >= 2: # GetCallingScriptHash() can't be done within the # function because the calling script hash changes # depending on where the function is called return do_transfer(ctx, GetCallingScriptHash(), args) Notify(ARG_ERROR) return False elif operation == 'transferFrom': if len(args) >= 3: return do_transfer_from(ctx, args) Notify(ARG_ERROR) return False elif operation == 'tokensOfOwner': if len(args) == 2: return do_tokens_of_owner(ctx, args[0], args[1]) Notify(ARG_ERROR) return False elif operation == 'uri': if len(args) == 1: token_uri = Get(ctx, concat('uri/', args[0])) if token_uri: return token_uri Notify(TOKEN_DNE_ERROR) return False Notify(ARG_ERROR) return False # Administrative operations if CheckWitness(TOKEN_CONTRACT_OWNER): if operation == 'mintToken': if len(args) >= 2: return do_mint_token(ctx, args) Notify(ARG_ERROR) return False elif operation == 'modifyURI': if len(args) == 2: return do_modify_uri(ctx, args[0], args[1]) Notify(ARG_ERROR) return False elif operation == 'setName': if len(args) == 1: return do_set_config(ctx, 'name', args[0]) Notify(ARG_ERROR) return False elif operation == 'setSymbol': if len(args) == 1: return do_set_config(ctx, 'symbol', args[0]) Notify(ARG_ERROR) return False elif operation == 'setPostMintContract': if len(args) == 1: if len(args[0]) == 20: if GetContract(args[0]): return do_set_config(ctx, 'postMintContract', args[0]) Notify('address is not a contract') return False Notify(INVALID_ADDRESS_ERROR) return False Notify(ARG_ERROR) return False elif operation == 'setSupportedStandards': if len(args) >= 1: if args[0] != 'NEP-10': Notify('NEP-10 must be the first arg') return False return do_set_config(ctx, 'supportedStandards', Serialize(args)) Notify(ARG_ERROR) return False else: Notify(PERMISSION_ERROR) return False Notify('unknown operation') return False
def do_mint_token(ctx, args): """Mints a new NFT token; stores it's properties, URI info, and owner on the blockchain; updates the totalSupply :param StorageContext ctx: current store context :param list args: 0: byte[] t_properties: token's read only data 1: bytes t_uri: token's uri 2: byte[] t_owner (optional): default is postMintContract, can be a user address, or another smart contract 3: extra_arg (optional): extra arg to be passed to smart contract :return: new total supply of tokens :rtype: boolean or integer """ t_id = Get(ctx, TOKEN_CIRC_KEY) # the int 0 is represented as b'' in neo-boa, this caused bugs # throughout my code # This is the reason why token id's start at 1 instead t_id += 1 exists = Get(ctx, t_id) # this should never already exist if len(exists) == 20: Notify('token already exists') return False t_properties = args[0] if len(t_properties) == b'\x00': Notify('missing properties data string') return False t_uri = args[1] # if nft contract owner passed a third argument, # check if it is a user/contract address, if so set t_owner # to the specified address t_owner = b'' if len(args) > 2: if len(args[2]) == 20: t_owner = args[2] # if nft contract owner didn't pass an address, transfer the # newly minted token to the default contract. # If nft contract owner did pass an address and it is a # smart contract, transfer the newly minted token to the # passed contract this_contract = GetExecutingScriptHash() if len(t_owner) != 20: t_owner = Get(ctx, 'postMintContract') contract_args = [t_owner, t_id] if len(args) == 3: # append optional extra_arg contract_args.append(args[2]) success = transfer_to_smart_contract(ctx, this_contract, contract_args, True) if success is False: return False elif len(t_owner) == 20: if GetContract(t_owner): contract_args = [t_owner, t_id] if len(args) == 4: # append optional extra arg contract_args.append(args[3]) success = transfer_to_smart_contract(ctx, this_contract, contract_args, True) if success is False: return False Put(ctx, t_id, t_owner) # update token's owner Put(ctx, concat('properties/', t_id), t_properties) Put(ctx, concat('uri/', t_id), t_uri) add_token_to_owners_list(ctx, t_owner, t_id) Put(ctx, TOKEN_CIRC_KEY, t_id) # update total supply # Log this minting event OnMint(t_owner, 1) OnNFTMint(t_owner, t_id) return t_id
def mint_nft_token(ctx, args): """Mints a new NFT token; stores it's properties, URI info, and owner on the blockchain; updates the totalSupply :param StorageContext ctx: current store context :param list args: 0: byte[] t_owner: token owner 1: byte[] t_properties: manufacture_data_json 2: extra_arg (optional): extra arg to be passed to a smart contract :return: mint success :rtype: bool build partchain.py test 0710 05 True False False mint_nft_token ["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y", '{"Manufacture_name": "ROLLSROYCE", "Part_name": "Radar", "Serial_number":"1234567890"}'] """ details = json.loads(args[1]) t_id = Get(ctx, TOKEN_CIRC_KEY) # the int 0 is represented as b'' in neo-boa, this caused bugs # throughout my code # This is the reason why token id's start at 1 instead t_id += 1 # this should never already exist if len(Get(ctx, t_id)) == 20: Notify('token already exists') return False t_owner = args[0] if len(t_owner) != 20: Notify(INVALID_ADDRESS_ERROR) return False t_properties = args[1] if len(t_properties) == b'\x00': Notify('missing properties data string') return False t_uri = details['Manufacture_name'] + "_" + details[ 'Part_name'] + "_" + details['Serial_number'] print(t_uri) if GetContract(t_owner): contract_args = [t_owner, t_id] if len(args) == 3: # append optional extra arg contract_args.append(args[2]) success = transfer_to_smart_contract(ctx, GetExecutingScriptHash(), contract_args, True) if success is False: return False Put(ctx, t_id, t_owner) # update token's owner Put(ctx, concat('properties/', t_id), t_properties) Put(ctx, concat('uri/', t_id), t_uri) add_token_to_owners_list(ctx, t_owner, t_id) Put(ctx, TOKEN_CIRC_KEY, t_id) # update total supply # Log this minting event OnMint(t_owner, 1) OnNFTMint(t_owner, t_id) return True