def get_deploy_transaction( cls, ledger_api: LedgerApi, deployer_address: str, **kwargs, ) -> Optional[JSONLike]: """ Get the transaction to create a batch of tokens. :param ledger_api: the ledger API :param deployer_address: the address of the deployer :param args: the price :param gas: the gas to be used :return: the transaction object """ gas = kwargs.get("gas") if isinstance(kwargs.get("gas"), int) else 60000000 args = kwargs.get("args") if isinstance(kwargs.get("args"), list) else [] contract_interface = cls.contract_interface.get(ledger_api.identifier, {}) nonce = ledger_api.api.eth.getTransactionCount(deployer_address) instance = ledger_api.get_contract_instance(contract_interface) constructed = instance.constructor(*args) data = constructed.buildTransaction()["data"] tx: JSONLike = { "from": deployer_address, # only 'from' address, don't insert 'to' address! "value": 0, # transfer as part of deployment "gas": gas, "gasPrice": gas, # TODO: refine "nonce": nonce, "data": data, } tx = ledger_api.update_with_gas_estimate(tx) return tx
def get_transaction_receipt( self, api: LedgerApi, message: LedgerApiMessage, dialogue: LedgerApiDialogue, ) -> LedgerApiMessage: """ Send the request 'get_transaction_receipt'. :param api: the API object. :param message: the Ledger API message :return: None """ is_settled = False attempts = 0 while (not is_settled and attempts < self.MAX_ATTEMPTS and self.connection_state.get() == ConnectionStates.connected): time.sleep(self.TIMEOUT) transaction_receipt = api.get_transaction_receipt( message.transaction_digest.body) if transaction_receipt is not None: is_settled = api.is_transaction_settled(transaction_receipt) attempts += 1 attempts = 0 transaction = api.get_transaction(message.transaction_digest.body) while (transaction is None and attempts < self.MAX_ATTEMPTS and self.connection_state.get() == ConnectionStates.connected): time.sleep(self.TIMEOUT) transaction = api.get_transaction(message.transaction_digest.body) attempts += 1 if not is_settled: # pragma: nocover response = self.get_error_message( ValueError("Transaction not settled within timeout"), api, message, dialogue, ) elif transaction_receipt is None: # pragma: nocover response = self.get_error_message( ValueError("No transaction_receipt returned"), api, message, dialogue) elif transaction is None: # pragma: nocover response = self.get_error_message( ValueError("No transaction returned"), api, message, dialogue) else: response = cast( LedgerApiMessage, dialogue.reply( performative=LedgerApiMessage.Performative. TRANSACTION_RECEIPT, target_message=message, transaction_receipt=TransactionReceipt( message.transaction_digest.ledger_id, transaction_receipt, transaction, ), ), ) return response
def get_deploy_transaction( cls, ledger_api: LedgerApi, deployer_address: str, args: list, gas: int = 60000000, ) -> Dict[str, Any]: """ Get the transaction to create a batch of tokens. :param ledger_api: the ledger API :param deployer_address: the address of the deployer :param args: the price :param gas: the gas to be used :return: the transaction object """ contract_interface = cls.contract_interface.get( ledger_api.identifier, {}) nonce = ledger_api.api.eth.getTransactionCount(deployer_address) instance = ledger_api.get_contract_instance(contract_interface) constructed = instance.constructor(*args) data = constructed.buildTransaction()["data"] tx = { "from": deployer_address, # only 'from' address, don't insert 'to' address! "value": 0, # transfer as part of deployment "gas": gas, "gasPrice": gas, # TODO: refine "nonce": nonce, "data": data, } tx = cls._try_estimate_gas(ledger_api, tx) return tx
def get_mint_single_transaction( cls, ledger_api: LedgerApi, contract_address: Address, deployer_address: Address, recipient_address: Address, token_id: int, mint_quantity: int, data: Optional[bytes] = b"", gas: int = 300000, ) -> JSONLike: """ Get the transaction to mint a single token. :param ledger_api: the ledger API :param contract_address: the address of the contract :param deployer_address: the address of the deployer :param recipient_address: the address of the recipient :param token_id: the token id :param mint_quantity: the quantity to mint :param data: the data to include in the transaction :param gas: the gas to be used :return: the transaction object """ if ledger_api.identifier == EthereumApi.identifier: nonce = ledger_api.api.eth.getTransactionCount(deployer_address) instance = cls.get_instance(ledger_api, contract_address) tx = instance.functions.mint(recipient_address, token_id, mint_quantity, data).buildTransaction({ "gas": gas, "gasPrice": ledger_api.api.toWei( "50", "gwei"), "nonce": nonce, }) tx = ledger_api.update_with_gas_estimate(tx) return tx if ledger_api.identifier in [ CosmosApi.identifier, FetchAIApi.identifier ]: msg = { "mint_single": { "to_address": recipient_address, "id": str(token_id), "supply": str(mint_quantity), "data": str(data), } } cosmos_api = cast(CosmosApi, ledger_api) tx = cosmos_api.get_handle_transaction(deployer_address, contract_address, msg, amount=0, tx_fee=0, gas=gas) return tx raise NotImplementedError
def get_grant_role_transaction( cls, ledger_api: LedgerApi, contract_address: Address, oracle_address: Address, gas: int = 0, ) -> JSONLike: """ Get transaction to grant oracle role to recipient_address :param ledger_api: the ledger API :param contract_address: the contract address :param oracle_address: the address of the oracle :param gas: the gas limit :return: the transaction object """ if ledger_api.identifier == EthereumApi.identifier: nonce = ledger_api.api.eth.getTransactionCount(oracle_address) instance = cls.get_instance(ledger_api, contract_address) tx = instance.functions.grantRole( CONTRACT_ROLE, oracle_address).buildTransaction({ "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, }) tx = ledger_api.update_with_gas_estimate(tx) return tx raise NotImplementedError
def get_balance( self, api: LedgerApi, message: LedgerApiMessage, dialogue: LedgerApiDialogue, ) -> LedgerApiMessage: """ Send the request 'get_balance'. :param api: the API object. :param message: the Ledger API message :return: None """ balance = api.get_balance(message.address) if balance is None: response = self.get_error_message( ValueError("No balance returned"), api, message, dialogue ) else: response = cast( LedgerApiMessage, dialogue.reply( performative=LedgerApiMessage.Performative.BALANCE, target_message=message, balance=balance, ledger_id=message.ledger_id, ), ) return response
def create_option( cls, ledger_api: LedgerApi, contract_address: str, deployer_address: str, amount: int, period: int, strike: int, type: int, data: Optional[bytes] = b"", gas: int = 300000, ) -> Dict[str, Any]: """ Get the transaction to create a single token. :param ledger_api: the ledger API :param contract_address: the address of the contract :param deployer_address: the address of the deployer :param token_id: the token id for creation :param data: the data to include in the transaction :param gas: the gas to be used :return: the transaction object """ # create the transaction dict nonce = ledger_api.api.eth.getTransactionCount(deployer_address) instance = cls.get_instance(ledger_api, contract_address) fee_estimate = instance.functions.fees(period, amount, strike, type).call() tx = instance.functions.create(period, amount, strike, type).buildTransaction( {"from": deployer_address, "value": fee_estimate[0], "nonce": nonce} ) tx = ledger_api.update_with_gas_estimate(tx) return tx
def send_signed_transaction( self, api: LedgerApi, message: LedgerApiMessage, dialogue: LedgerApiDialogue, ) -> LedgerApiMessage: """ Send the request 'send_signed_tx'. :param api: the API object. :param message: the Ledger API message :return: None """ transaction_digest = api.send_signed_transaction( message.signed_transaction.body ) if transaction_digest is None: # pragma: nocover response = self.get_error_message( ValueError("No transaction_digest returned"), api, message, dialogue ) else: response = cast( LedgerApiMessage, dialogue.reply( performative=LedgerApiMessage.Performative.TRANSACTION_DIGEST, target_message=message, transaction_digest=TransactionDigest( message.signed_transaction.ledger_id, transaction_digest ), ), ) return response
def exercise( cls, ledger_api: LedgerApi, contract_address: str, deployer_address: str, option_id: int, data: Optional[bytes] = b"", gas: int = 300000, ) -> Dict[str, Any]: """ Get the transaction to create a single token. :param ledger_api: the ledger API :param contract_address: the address of the contract :param deployer_address: the address of the deployer :param token_id: the token id for creation :param data: the data to include in the transaction :param gas: the gas to be used :return: the transaction object """ # create the transaction dict nonce = ledger_api.api.eth.getTransactionCount(deployer_address) instance = cls.get_instance(ledger_api, contract_address) tx = instance.functions.exercise(option_id).buildTransaction( { "from": deployer_address, "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, "value": 0, } ) tx = ledger_api.update_with_gas_estimate(tx) return tx
def get_balance( self, api: LedgerApi, message: LedgerApiMessage, dialogue: LedgerApiDialogue, ) -> LedgerApiMessage: """ Send the request 'get_balance'. :param api: the API object. :param message: the Ledger API message :return: None """ balance = api.get_balance(message.address) if balance is None: response = self.get_error_message( ValueError("No balance returned"), api, message, dialogue ) else: response = LedgerApiMessage( performative=LedgerApiMessage.Performative.BALANCE, message_id=message.message_id + 1, target=message.message_id, dialogue_reference=dialogue.dialogue_label.dialogue_reference, balance=balance, ledger_id=message.ledger_id, ) response.counterparty = message.counterparty dialogue.update(response) return response
def get_raw_transaction( self, api: LedgerApi, message: LedgerApiMessage, dialogue: LedgerApiDialogue, ) -> LedgerApiMessage: """ Send the request 'get_raw_transaction'. :param api: the API object. :param message: the Ledger API message :return: None """ raw_transaction = api.get_transfer_transaction( sender_address=message.terms.sender_address, destination_address=message.terms.counterparty_address, amount=message.terms.sender_payable_amount, tx_fee=message.terms.fee, tx_nonce=message.terms.nonce, **message.terms.kwargs, ) if raw_transaction is None: response = self.get_error_message( ValueError("No raw transaction returned"), api, message, dialogue ) else: response = cast( LedgerApiMessage, dialogue.reply( performative=LedgerApiMessage.Performative.RAW_TRANSACTION, target_message=message, raw_transaction=RawTransaction( message.terms.ledger_id, raw_transaction ), ), ) return response
def get_state( self, api: LedgerApi, message: LedgerApiMessage, dialogue: LedgerApiDialogue, ) -> LedgerApiMessage: """ Send the request 'get_state'. :param api: the API object. :param message: the Ledger API message :return: None """ result = api.get_state(message.callable, *message.args, **message.kwargs.body) if result is None: # pragma: nocover response = self.get_error_message( ValueError("Failed to get state"), api, message, dialogue) else: response = cast( LedgerApiMessage, dialogue.reply( performative=LedgerApiMessage.Performative.STATE, target_message=message, state=State(message.ledger_id, result), ledger_id=message.ledger_id, ), ) return response
def get_query_transaction( cls, ledger_api: LedgerApi, contract_address: Address, from_address: Address, query_function: str, gas: int = 0, ) -> None: """ Get transaction to query oracle value in contract :param ledger_api: the ledger apis. :param contract_address: the contract address. :param from_address: the address of the transaction sender. :param query_function: the query oracle value function. :param gas: the gas limit for the transaction. :return: None """ if ledger_api.identifier == EthereumApi.identifier: nonce = ledger_api.api.eth.getTransactionCount(from_address) instance = cls.get_instance(ledger_api, contract_address) function = getattr(instance.functions, query_function) query_args = () intermediate = function(*query_args) tx = intermediate.buildTransaction({ "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, }) tx = ledger_api.update_with_gas_estimate(tx) return tx raise NotImplementedError
def provide_liquidity( cls, ledger_api: LedgerApi, contract_address: str, deployer_address: str, args: list, data: Optional[bytes] = b"", gas: int = 300000, ) -> Dict[str, Any]: """ Get the transaction to create a single token. :param ledger_api: the ledger API :param contract_address: the address of the contract :param deployer_address: the address of the deployer :param token_id: the token id for creation :param data: the data to include in the transaction :param gas: the gas to be used :return: the transaction object """ # create the transaction dict nonce = ledger_api.api.eth.getTransactionCount(deployer_address) instance = cls.get_instance(ledger_api, contract_address) tx = instance.functions.provide(*args).buildTransaction( {"from": deployer_address, "value": 0, "nonce": nonce} ) tx = ledger_api.update_with_gas_estimate(tx) return tx
def get_update_transaction( cls, ledger_api: LedgerApi, contract_address: Address, oracle_address: Address, update_function: str, update_args: Dict[str, Any], gas: int = 0, ) -> JSONLike: """ Update oracle value in contract :param ledger_api: the ledger apis. :param contract_address: the contract address. :param oracle_address: the oracle address. :param update_function: the oracle value update function. :param update_args: the arguments to the contract's update function. :return: None """ if ledger_api.identifier == EthereumApi.identifier: nonce = ledger_api.api.eth.getTransactionCount(oracle_address) instance = cls.get_instance(ledger_api, contract_address) function = getattr(instance.functions, update_function) intermediate = function(*update_args) tx = intermediate.buildTransaction({ "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, }) tx = ledger_api.update_with_gas_estimate(tx) return tx raise NotImplementedError
def send_signed_transaction( self, api: LedgerApi, message: LedgerApiMessage, dialogue: LedgerApiDialogue, ) -> LedgerApiMessage: """ Send the request 'send_signed_tx'. :param api: the API object. :param message: the Ledger API message :return: None """ transaction_digest = api.send_signed_transaction( message.signed_transaction.body ) if transaction_digest is None: # pragma: nocover response = self.get_error_message( ValueError("No transaction_digest returned"), api, message, dialogue ) else: response = LedgerApiMessage( performative=LedgerApiMessage.Performative.TRANSACTION_DIGEST, message_id=message.message_id + 1, target=message.message_id, dialogue_reference=dialogue.dialogue_label.dialogue_reference, transaction_digest=TransactionDigest( message.signed_transaction.ledger_id, transaction_digest ), ) response.counterparty = message.counterparty dialogue.update(response) return response
def get_atomic_swap_batch_transaction( cls, ledger_api: LedgerApi, contract_address: Address, from_address: Address, to_address: Address, token_ids: List[int], from_supplies: List[int], to_supplies: List[int], value: int, trade_nonce: int, signature: str, data: Optional[bytes] = b"", gas: int = 2818111, ) -> JSONLike: """ Get the transaction for a trustless trade between two agents for a batch of tokens. :param ledger_api: the ledger API :param contract_address: the address of the contract :param from_address: the address of the agent sending tokens, receiving ether :param to_address: the address of the agent receiving tokens, sending ether :param token_id: the token id :param from_supply: the supply of tokens by the sender :param to_supply: the supply of tokens by the receiver :param value: the amount of ether sent from the to_address to the from_address :param trade_nonce: the nonce of the trade, this is separate from the nonce of the transaction :param signature: the signature of the trade :param data: the data to include in the transaction :param gas: the gas to be used :return: a ledger transaction object """ if ledger_api.identifier == EthereumApi.identifier: nonce = ledger_api.api.eth.getTransactionCount(from_address) instance = cls.get_instance(ledger_api, contract_address) value_eth_wei = ledger_api.api.toWei(value, "ether") tx = instance.functions.tradeBatch( from_address, to_address, token_ids, from_supplies, to_supplies, value_eth_wei, trade_nonce, signature, data, ).buildTransaction( { "gas": gas, "from": from_address, "value": value_eth_wei, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, } ) tx = ledger_api.update_with_gas_estimate(tx) return tx raise NotImplementedError
def get_create_batch_transaction( # pylint: disable=unused-argument cls, ledger_api: LedgerApi, contract_address: Address, deployer_address: Address, token_ids: List[int], data: Optional[bytes] = b"", gas: int = 300000, ) -> JSONLike: """ Get the transaction to create a batch of tokens. :param ledger_api: the ledger API :param contract_address: the address of the contract :param deployer_address: the address of the deployer :param token_ids: the list of token ids for creation :param data: the data to include in the transaction :param gas: the gas to be used :return: the transaction object """ if ledger_api.identifier == EthereumApi.identifier: nonce = ledger_api.api.eth.getTransactionCount(deployer_address) instance = cls.get_instance(ledger_api, contract_address) tx = instance.functions.createBatch(deployer_address, token_ids).buildTransaction({ "gas": gas, "gasPrice": ledger_api.api.toWei( "50", "gwei"), "nonce": nonce, }) tx = ledger_api.update_with_gas_estimate(tx) return tx if ledger_api.identifier in [ CosmosApi.identifier, FetchAIApi.identifier ]: tokens = [] for token_id in token_ids: tokens.append({"id": str(token_id), "path": str(token_id)}) msg = { "create_batch": { "item_owner": str(deployer_address), "tokens": tokens } } cosmos_api = cast(CosmosApi, ledger_api) tx = cosmos_api.get_handle_transaction(deployer_address, contract_address, msg, amount=0, tx_fee=0, gas=gas) return tx raise NotImplementedError
def get_mint_batch_transaction( cls, ledger_api: LedgerApi, contract_address: Address, deployer_address: Address, recipient_address: Address, token_ids: List[int], mint_quantities: List[int], data: Optional[bytes] = b"", gas: int = 500000, ) -> JSONLike: """ Get the transaction to mint a batch of tokens. :param ledger_api: the ledger API :param contract_address: the address of the contract :param deployer_address: the address of the deployer :param recipient_address: the address of the recipient :param token_ids: the token ids :param mint_quantities: the quantity to mint for each token :param data: the data to include in the transaction :param gas: the gas to be used :return: the transaction object """ cls.validate_mint_quantities(token_ids, mint_quantities) if ledger_api.identifier == EthereumApi.identifier: nonce = ledger_api.api.eth.getTransactionCount(deployer_address) instance = cls.get_instance(ledger_api, contract_address) tx = instance.functions.mintBatch( recipient_address, token_ids, mint_quantities, data ).buildTransaction( { "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, } ) tx = ledger_api.update_with_gas_estimate(tx) return tx if ledger_api.identifier in [CosmosApi.identifier, FetchAIApi.identifier]: tokens = [] for token_id, quantity in zip(token_ids, mint_quantities): tokens.append({"id": str(token_id), "value": str(quantity)}) msg = { "mint_batch": { "to_address": recipient_address, "data": str(data), "tokens": tokens, } } cosmos_api = cast(CosmosApi, ledger_api) tx = cosmos_api.get_handle_transaction( deployer_address, contract_address, msg, amount=0, tx_fee=0, gas=gas ) return tx raise NotImplementedError
def get_instance(cls, ledger_api: LedgerApi, contract_address: Optional[str] = None) -> Any: """ Get the instance. :param ledger_api: the ledger api we are using. :param contract_address: the contract address. :return: the contract instance """ contract_interface = cls.contract_interface.get( ledger_api.identifier, {}) instance = ledger_api.get_contract_instance(contract_interface, contract_address) return instance
def get_deploy_transaction(cls, ledger_api: LedgerApi, deployer_address: str, **kwargs) -> Optional[JSONLike]: """ Handler method for the 'GET_DEPLOY_TRANSACTION' requests. Implement this method in the sub class if you want to handle the contract requests manually. :param ledger_api: the ledger apis. :param deployer_address: The address that will deploy the contract. :param kwargs: keyword arguments. :return: the tx """ contract_interface = cls.contract_interface.get( ledger_api.identifier, {}) tx = ledger_api.get_deploy_transaction(contract_interface, deployer_address, **kwargs) return tx
def get_transfer_transaction( cls, ledger_api: LedgerApi, contract_address: Address, from_address: Address, receiver: Address, amount: int, gas: int = 0, ) -> None: """ Get transaction to transfer tokens to an account :param ledger_api: the ledger apis. :param contract_address: the contract address. :param from_address: the address of the sender. :param receiver: the address to which to transfer tokens. :param amount: the amount of tokens to transfer. :param gas: the gas limit for the transaction. :return: None """ if ledger_api.identifier == EthereumApi.identifier: nonce = ledger_api.api.eth.getTransactionCount(from_address) instance = cls.get_instance(ledger_api, contract_address) function = instance.functions.transfer intermediate = function(receiver, amount) tx = intermediate.buildTransaction({ "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, }) tx = ledger_api.update_with_gas_estimate(tx) return tx raise NotImplementedError
def get_approve_transaction( cls, ledger_api: LedgerApi, contract_address: Address, from_address: Address, spender: Address, amount: int, gas: int = 0, ) -> None: """ Get transaction to approve oracle client contract transactions on behalf of sender. :param ledger_api: the ledger apis. :param contract_address: the contract address. :param from_address: the address of the approver. :param spender: the address approved to spend on behalf of sender. :param amount: the amount approved to be spent. :param gas: the gas limit for the transaction. :return: None """ if ledger_api.identifier == EthereumApi.identifier: nonce = ledger_api.api.eth.getTransactionCount(from_address) instance = cls.get_instance(ledger_api, contract_address) function = instance.functions.approve intermediate = function(spender, amount) tx = intermediate.buildTransaction({ "gas": gas, "gasPrice": ledger_api.api.toWei("50", "gwei"), "nonce": nonce, }) tx = ledger_api.update_with_gas_estimate(tx) return tx raise NotImplementedError