def transfer(self, txn): """ Make a transaction/transfer on regtest bitcoin """ self.wait_for_node() if not isinstance(txn.coins, list): txn.coins = [txn.coins] if txn.to_address in get_aliases(): txn.to_address = get_alias_address(txn.chain, txn.to_address) if txn.from_address in get_aliases(): txn.from_address = get_alias_address(txn.chain, txn.from_address) # update memo with actual address (over alias name) for alias in get_aliases(): chain = txn.chain asset = txn.get_asset_from_memo() if asset: chain = asset.get_chain() # we use RUNE BNB address to identify a cross chain stake if txn.memo.startswith("STAKE"): chain = RUNE.get_chain() addr = get_alias_address(chain, alias) txn.memo = txn.memo.replace(alias, addr) # create transaction amount = float(txn.coins[0].amount / Coin.ONE) tx_out_dest = {txn.to_address: amount} tx_out_op_return = {"data": txn.memo.encode().hex()} # get unspents UTXOs address = txn.from_address min_amount = float(amount + (self.default_gas / Coin.ONE)) # add more for fee unspents = self.call("listunspent", 1, 9999, [str(address)], True, {"minimumAmount": min_amount}) if len(unspents) == 0: raise Exception( f"Cannot transfer. No BTC UTXO available for {address}") # choose the first UTXO unspent = unspents[0] tx_in = [{"txid": unspent["txid"], "vout": unspent["vout"]}] tx_out = [tx_out_dest] # create change output if needed amount_utxo = float(unspent["amount"]) amount_change = Decimal(amount_utxo) - Decimal(min_amount) if amount_change > 0: tx_out.append({txn.from_address: float(amount_change)}) tx_out.append(tx_out_op_return) tx = self.call("createrawtransaction", tx_in, tx_out) tx = self.call("signrawtransactionwithwallet", tx) txn.id = self.call("sendrawtransaction", tx["hex"]).upper() txn.gas = [Coin("BTC.BTC", self.default_gas)]
def transfer(self, txns): if not isinstance(txns, list): txns = [txns] for txn in txns: if not isinstance(txn.coins, list): txn.coins = [txn.coins] name = txn.from_address txn.gas = [Coin("THOR.RUNE", 100000000)] if txn.from_address in get_aliases(): txn.from_address = get_alias_address(txn.chain, txn.from_address) if txn.to_address in get_aliases(): txn.to_address = get_alias_address(txn.chain, txn.to_address) # update memo with actual address (over alias name) for alias in get_aliases(): chain = txn.chain asset = txn.get_asset_from_memo() if asset: chain = asset.get_chain() addr = get_alias_address(chain, alias) txn.memo = txn.memo.replace(alias, addr) acct = self._get_account(txn.from_address) payload = { "coins": [coin.to_thorchain_fmt() for coin in txn.coins], "memo": txn.memo, "base_req": { "chain_id": "thorchain", "from": txn.from_address }, } payload = self.post("/thorchain/native/tx", payload) msgs = payload["value"]["msg"] fee = payload["value"]["fee"] acct_num = acct["result"]["value"]["account_number"] seq = acct["result"]["value"]["sequence"] sig = self._sign( name, self._get_sign_message("thorchain", acct_num, fee, seq, msgs)) pushable = self.get_pushable(name, msgs, sig, fee, acct_num, seq) result = self.send(pushable) txn.id = result["txhash"]
def transfer(self, txns): """ Make a transaction/transfer on mock binance """ if not isinstance(txns, list): txns = [txns] payload = [] for txn in txns: if not isinstance(txn.coins, list): txn.coins = [txn.coins] if txn.to_address in get_aliases(): txn.to_address = get_alias_address(txn.chain, txn.to_address) if txn.from_address in get_aliases(): txn.from_address = get_alias_address(txn.chain, txn.from_address) # update memo with actual address (over alias name) for alias in get_aliases(): chain = txn.chain asset = txn.get_asset_from_memo() if asset: chain = asset.get_chain() if txn.memo.startswith("STAKE"): if asset and txn.chain == asset.get_chain(): chain = RUNE.get_chain() addr = get_alias_address(chain, alias) txn.memo = txn.memo.replace(alias, addr) payload.append({ "from": txn.from_address, "to": txn.to_address, "memo": txn.memo, "coins": [coin.to_binance_fmt() for coin in txn.coins], }) result = self.post("/broadcast/easy", payload) txn.id = self.get_tx_id_from_block(result["height"])
def transfer(self, txn): """ Make a transaction/transfer on localnet Ethereum """ if not isinstance(txn.coins, list): txn.coins = [txn.coins] if txn.to_address in get_aliases(): txn.to_address = get_alias_address(txn.chain, txn.to_address) if txn.from_address in get_aliases(): txn.from_address = get_alias_address(txn.chain, txn.from_address) # update memo with actual address (over alias name) for alias in get_aliases(): chain = txn.chain asset = txn.get_asset_from_memo() if asset: chain = asset.get_chain() # we use RUNE BNB address to identify a cross chain stake if txn.memo.startswith("STAKE"): chain = RUNE.get_chain() addr = get_alias_address(chain, alias) txn.memo = txn.memo.replace(alias, addr) # create and send transaction tx = { "from": Web3.toChecksumAddress(txn.from_address), "to": Web3.toChecksumAddress(txn.to_address), "value": txn.coins[0].amount, "data": "0x" + txn.memo.encode().hex(), "gas": calculate_gas(txn.memo), } tx_hash = self.web3.geth.personal.send_transaction(tx, self.passphrase) receipt = self.web3.eth.waitForTransactionReceipt(tx_hash) txn.id = receipt["transactionHash"].hex()[2:].upper() txn.gas = [Coin("ETH.ETH", receipt["cumulativeGasUsed"] * self.gas_price)]
def handle_unstake(self, txn): """ handles a unstaking transaction MEMO: WITHDRAW:<asset(req)>:<address(op)>:<basis_points(op)> """ withdraw_basis_points = 10000 # parse memo parts = txn.memo.split(":") if len(parts) < 2: if txn.memo == "": return self.refund(txn, 105, "memo can't be empty") return self.refund(txn, 105, f"invalid tx type: {txn.memo}") # get withdrawal basis points, if it exists in the memo if len(parts) >= 3: withdraw_basis_points = int(parts[2]) # empty asset if parts[1] == "": return self.refund(txn, 105, "Invalid symbol") asset = Asset(parts[1]) # add any rune to the reserve for coin in txn.coins: if coin.asset.is_rune(): self.reserve += coin.amount else: coin.amount = 0 pool = self.get_pool(asset) staker = pool.get_staker(txn.from_address) if staker.is_zero(): # FIXME real world message return self.refund(txn, 105, "refund reason message") # calculate gas prior to update pool in case we empty the pool # and need to subtract gas = None if asset.get_chain() == "BTC": gas = [Bitcoin._calculate_gas(pool, txn)] if asset.get_chain() == "ETH": gas = [Ethereum._calculate_gas(pool, txn)] unstake_units, rune_amt, asset_amt = pool.unstake( txn.from_address, withdraw_basis_points) # if this is our last staker of bnb, subtract a little BNB for gas. if pool.total_units == 0: if pool.asset.is_bnb(): fee_amt = 37500 if RUNE.get_chain() == "BNB": fee_amt *= 2 asset_amt -= fee_amt pool.asset_balance += fee_amt elif pool.asset.is_btc() or pool.asset.is_eth(): asset_amt -= gas[0].amount pool.asset_balance += gas[0].amount self.set_pool(pool) # get from address VAULT cross chain from_address = txn.to_address if from_address != "VAULT": # don't replace for unit tests from_alias = get_alias(txn.chain, from_address) from_address = get_alias_address(asset.get_chain(), from_alias) # get to address cross chain to_address = txn.from_address if to_address not in get_aliases(): # don't replace for unit tests to_alias = get_alias(txn.chain, to_address) to_address = get_alias_address(asset.get_chain(), to_alias) out_txns = [ Transaction( RUNE.get_chain(), txn.to_address, txn.from_address, [Coin(RUNE, rune_amt)], f"OUTBOUND:{txn.id.upper()}", ), Transaction( asset.get_chain(), from_address, to_address, [Coin(asset, asset_amt)], f"OUTBOUND:{txn.id.upper()}", gas=gas, ), ] # generate event for UNSTAKE transaction event = Event( "unstake", [ { "pool": pool.asset }, { "stake_units": unstake_units }, { "basis_points": withdraw_basis_points }, { "asymmetry": "0.000000000000000000" }, *txn.get_attributes(), ], ) self.events.append(event) return out_txns