public void generateGenesisTransaction(int lockTime) { Transaction genesisTx; try { //generate genesis transaction if our proof is empty if(pm.getValidationPath().size() == 0 && wallet.getBalance(BalanceType.AVAILABLE).isGreaterThan(PSNYMVALUE)) { genesisTx = tg.generateGenesisTransaction(pm, walletFile, lockTime); //TODO register listener before sending tx out, to avoid missing a confidence change genesisTx.getConfidence().addEventListener(new Listener() { @Override public void onConfidenceChanged(TransactionConfidence arg0, ChangeReason arg1) { if (arg0.getConfidenceType() != TransactionConfidence.ConfidenceType.BUILDING) { return; } if(arg0.getDepthInBlocks() == 1) { //enough confidence, write proof message to the file system System.out.println("depth of genesis tx is now 1, consider ready for usage"); } } }); } } catch (InsufficientMoneyException e) { e.printStackTrace(); } }
@Test public void lowerThanDefaultFee() throws InsufficientMoneyException { int feeFactor = 10; Coin fee = Transaction.DEFAULT_TX_FEE.divide(feeFactor); receiveATransactionAmount(wallet, myAddress, Coin.COIN); SendRequest req = SendRequest.to(myAddress, Coin.CENT); req.feePerKb = fee; wallet.completeTx(req); assertEquals(Coin.valueOf(11350).divide(feeFactor), req.tx.getFee()); wallet.commitTx(req.tx); SendRequest emptyReq = SendRequest.emptyWallet(myAddress); emptyReq.feePerKb = fee; emptyReq.ensureMinRequiredFee = true; emptyReq.emptyWallet = true; emptyReq.coinSelector = AllowUnconfirmedCoinSelector.get(); wallet.completeTx(emptyReq); assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, emptyReq.tx.getFee()); wallet.commitTx(emptyReq.tx); }
@Test public void higherThanDefaultFee() throws InsufficientMoneyException { int feeFactor = 10; Coin fee = Transaction.DEFAULT_TX_FEE.multiply(feeFactor); receiveATransactionAmount(wallet, myAddress, Coin.COIN); SendRequest req = SendRequest.to(myAddress, Coin.CENT); req.feePerKb = fee; wallet.completeTx(req); assertEquals(Coin.valueOf(11350).multiply(feeFactor), req.tx.getFee()); wallet.commitTx(req.tx); SendRequest emptyReq = SendRequest.emptyWallet(myAddress); emptyReq.feePerKb = fee; emptyReq.emptyWallet = true; emptyReq.coinSelector = AllowUnconfirmedCoinSelector.get(); wallet.completeTx(emptyReq); assertEquals(Coin.valueOf(171000), emptyReq.tx.getFee()); wallet.commitTx(emptyReq.tx); }
public String sendOffline(String destinationAddress, long amountSatoshis) throws InsufficientMoneyException { Address addressj; try { addressj = new Address(params, destinationAddress); } catch (AddressFormatException e) { e.printStackTrace(); throw new RuntimeException(e); } Coin amount = Coin.valueOf(amountSatoshis); // create a SendRequest of amount to destinationAddress Wallet.SendRequest sendRequest = Wallet.SendRequest.to(addressj, amount); // set dynamic fee sendRequest.feePerKb = getRecommendedFee(); // complete & sign tx kit.wallet().completeTx(sendRequest); kit.wallet().signTransaction(sendRequest); // return tx bytes as hex encoded String return Hex.encodeHexString(sendRequest.tx.bitcoinSerialize()); }
private void listenForCoinsAndRetry(final Coin value, final Address toAddress){ // Wait until the we have enough balance and display a notice. ListenableFuture<Coin> balanceFuture = SPECIEBOX.wallet().getBalanceFuture(value, BalanceType.AVAILABLE); FutureCallback<Coin> retry = new FutureCallback<Coin>() { public void onSuccess(Coin balance) { System.out.println("coins arrived and the wallet now has enough balance"); try { Wallet.SendResult result = SPECIEBOX.wallet().sendCoins(SPECIEBOX.peerGroup(), toAddress, value); System.out.println("Info: COINS RESENT. Transaction hash: " + result.tx.getHashAsString()); } catch (InsufficientMoneyException e) { e.printStackTrace(); System.out.println("Still not enough coins in your wallet, missing " + e.missing.getValue() + " satoshis."); } } public void onFailure(Throwable t) { System.out.println("something went wrong"); } }; Futures.addCallback(balanceFuture, retry); }
@Test public void lowerThanDefaultFee() throws InsufficientMoneyException { int feeFactor = 10; Coin fee = Transaction.DEFAULT_TX_FEE.divide(feeFactor); receiveATransactionAmount(wallet, myAddress, Coin.COIN); SendRequest req = SendRequest.to(myAddress, Coin.CENT); req.feePerKb = fee; wallet.completeTx(req); assertEquals(Coin.valueOf(2270).divide(feeFactor), req.tx.getFee()); wallet.commitTx(req.tx); SendRequest emptyReq = SendRequest.emptyWallet(myAddress); emptyReq.feePerKb = fee; emptyReq.ensureMinRequiredFee = true; emptyReq.emptyWallet = true; emptyReq.coinSelector = AllowUnconfirmedCoinSelector.get(); wallet.completeTx(emptyReq); assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, emptyReq.tx.getFee()); wallet.commitTx(emptyReq.tx); }
@Test public void higherThanDefaultFee() throws InsufficientMoneyException { int feeFactor = 10; Coin fee = Transaction.DEFAULT_TX_FEE.multiply(feeFactor); receiveATransactionAmount(wallet, myAddress, Coin.COIN); SendRequest req = SendRequest.to(myAddress, Coin.CENT); req.feePerKb = fee; wallet.completeTx(req); assertEquals(Coin.valueOf(2270).multiply(feeFactor), req.tx.getFee()); wallet.commitTx(req.tx); SendRequest emptyReq = SendRequest.emptyWallet(myAddress); emptyReq.feePerKb = fee; emptyReq.emptyWallet = true; emptyReq.coinSelector = AllowUnconfirmedCoinSelector.get(); wallet.completeTx(emptyReq); assertEquals(Coin.valueOf(34200), emptyReq.tx.getFee()); wallet.commitTx(emptyReq.tx); }
public static void main(String[] args) throws BlockStoreException, UnknownHostException, InsufficientMoneyException { ConsoleAppender appender = new ConsoleAppender( new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN), ConsoleAppender.SYSTEM_OUT); appender.setThreshold(Level.INFO); Logger.getRootLogger().addAppender(appender); SwingUtilities.invokeLater(new Runnable() { public void run() { final JFrame frame = new JFrame("MainWindow"); final MainWindow mainWindow = new MainWindow(); frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { mainWindow.onClose(); frame.dispose(); } }); frame.setContentPane(mainWindow.panel1); frame.pack(); //frame.setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/de/uni_bonn/bit/wallet_logo.png"))); frame.setTitle("2Factor Wallet"); frame.setVisible(true); mainWindow.startup(); } }); }
@Test public void lowerThanDefaultFee() throws InsufficientMoneyException { int feeFactor = 10; Coin fee = Transaction.DEFAULT_TX_FEE.divide(feeFactor); receiveATransactionAmount(wallet, myAddress, Coin.COIN); SendRequest req = SendRequest.to(myAddress, Coin.CENT); req.feePerKb = fee; wallet.completeTx(req); assertEquals(Coin.valueOf(22700).divide(feeFactor), req.tx.getFee()); wallet.commitTx(req.tx); SendRequest emptyReq = SendRequest.emptyWallet(myAddress); emptyReq.feePerKb = fee; emptyReq.ensureMinRequiredFee = true; emptyReq.emptyWallet = true; emptyReq.coinSelector = AllowUnconfirmedCoinSelector.get(); wallet.completeTx(emptyReq); assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, emptyReq.tx.getFee()); wallet.commitTx(emptyReq.tx); }
@Test public void higherThanDefaultFee() throws InsufficientMoneyException { int feeFactor = 10; Coin fee = Transaction.DEFAULT_TX_FEE.multiply(feeFactor); receiveATransactionAmount(wallet, myAddress, Coin.COIN); SendRequest req = SendRequest.to(myAddress, Coin.CENT); req.feePerKb = fee; wallet.completeTx(req); assertEquals(Coin.valueOf(22700).multiply(feeFactor), req.tx.getFee()); wallet.commitTx(req.tx); SendRequest emptyReq = SendRequest.emptyWallet(myAddress); emptyReq.feePerKb = fee; emptyReq.emptyWallet = true; emptyReq.coinSelector = AllowUnconfirmedCoinSelector.get(); wallet.completeTx(emptyReq); assertEquals(Coin.valueOf(342000), emptyReq.tx.getFee()); wallet.commitTx(emptyReq.tx); }
/** Sends Bitcoin to the specified bitcoin address. * @author Francis Fasola * @param address String representation of the public address. * @param amount The amount of Bitcoin to send. * @throws InsufficientMoneyException Not enough money. * @throws ExecutionException Error during execution. * @throws InterruptedException Error during execution. */ @SuppressWarnings("deprecation") public void sendBitcoin(String address, String amount) throws InsufficientMoneyException, ExecutionException, InterruptedException { Address destinationAddress = Address.fromBase58(params, address); SendRequest request = SendRequest.to(destinationAddress, Coin.parseCoin(amount)); SendResult result = wallet.sendCoins(request); result.broadcastComplete.addListener(() -> { System.out.println("Coins were sent. Transaction hash: " + result.tx.getHashAsString()); }, MoreExecutors.sameThreadExecutor()); }
/** * * @param callbacks */ @Override public void execute(final CoinActionCallback<CurrencyCoin>... callbacks) { this._callbacks = callbacks; this._bitcoin.getWalletManager().wallet().addCoinsSentEventListener(this); Coin balance = _bitcoin.getWalletManager().wallet().getBalance(); Coin amountCoin = Coin.parseCoin(_amount); Address addr = Address.fromBase58(_bitcoin.getWalletManager().wallet().getParams(), _address); SendRequest sendRequest = SendRequest.to(addr, amountCoin); Coin feeCoin = BitcoinManager.CalculateFeeTxSizeBytes(sendRequest.tx, sendRequest.feePerKb.getValue()); long balValue = balance.getValue(); long amountValue = amountCoin.getValue(); long txFeeValue = feeCoin.getValue(); if (amountValue + txFeeValue > balValue) { amountCoin = Coin.valueOf(balValue - txFeeValue); sendRequest = SendRequest.to(addr, amountCoin); } try { _bitcoin.getWalletManager().wallet().sendCoins(sendRequest); } catch (InsufficientMoneyException e) { for (CoinActionCallback<CurrencyCoin> callback : _callbacks) { callback.onError(_bitcoin); } e.printStackTrace(); } }
public void sendBroadcastAnnouncement(BroadcastAnnouncement ba, File f, ProofMessage pm, int lockTime) throws InsufficientMoneyException { //build transaction Transaction tx = new Transaction(params); Script s = ba.buildScript(); System.out.println("Script size is " + s.SIG_SIZE); //System.out.println(s.getScriptType()); ECKey psnymKey = new ECKey(); long unixTime = System.currentTimeMillis() / 1000L; //TODO use bitcoin nets median time tx.setLockTime(CLTVScriptPair.currentBitcoinBIP113Time(bc)-1); CLTVScriptPair sp = new CLTVScriptPair(psnymKey, CLTVScriptPair.currentBitcoinBIP113Time(bc)+lockTime); w.importKey(psnymKey); tx.addOutput(new TransactionOutput(params, tx, pm.getLastTransactionOutput().getValue().subtract(estimateBroadcastFee()), sp.getPubKeyScript().getProgram())); tx.addOutput(Coin.ZERO, s); tx.addInput(pm.getLastTransactionOutput()); tx.getInput(0).setSequenceNumber(3); //the concrete value doesn't matter, this is just for cltv tx.getInput(0).setScriptSig(pm.getScriptPair().calculateSigScript(tx, 0, w)); try { w.commitTx(tx); w.saveToFile(f); } catch (IOException e1) { e1.printStackTrace(); } TransactionBroadcast broadcast = pg.broadcastTransaction(tx); pm.addTransaction(tx, 0, sp); pm.writeToFile(); System.out.println("save broadcast announcement to file"); }
public void sendBroadcastAnnouncement(int lockTime) { try { System.out.println("sendBroadcastAnnouncement"); //if(pm.isEmpty() || pm.getLastTransaction().getConfidence().getDepthInBlocks() == 0) { if(pm.isEmpty()) { //|| !pm.getLastTransaction().getConfidence().getConfidenceType().equals(TransactionConfidence.ConfidenceType.BUILDING)) { return; } else { tg.sendBroadcastAnnouncement(new BroadcastAnnouncement(ptp.getIdentifier().getTorAddress(), pm.getLastTransactionOutput().getValue().getValue(), 10), walletFile, pm, lockTime); } } catch (InsufficientMoneyException e) { e.printStackTrace(); } }
@Test(expected = InsufficientMoneyException.class) public void watchingScriptsConfirmed() throws Exception { Address watchedAddress = new ECKey().toAddress(PARAMS); wallet.addWatchedAddress(watchedAddress); sendMoneyToWallet(BlockChain.NewBlockType.BEST_CHAIN, CENT, watchedAddress); assertEquals(CENT, wallet.getBalance()); // We can't spend watched balances wallet.createSend(OTHER_ADDRESS, CENT); }
@Test public void importAndEncrypt() throws InsufficientMoneyException { Wallet encryptedWallet = new Wallet(PARAMS); encryptedWallet.encrypt(PASSWORD1); final ECKey key = new ECKey(); encryptedWallet.importKeysAndEncrypt(ImmutableList.of(key), PASSWORD1); assertEquals(1, encryptedWallet.getImportedKeys().size()); assertEquals(key.getPubKeyPoint(), encryptedWallet.getImportedKeys().get(0).getPubKeyPoint()); sendMoneyToWallet(encryptedWallet, AbstractBlockChain.NewBlockType.BEST_CHAIN, Coin.COIN, key.toAddress(PARAMS)); assertEquals(Coin.COIN, encryptedWallet.getBalance()); SendRequest req = SendRequest.emptyWallet(OTHER_ADDRESS); req.aesKey = checkNotNull(encryptedWallet.getKeyCrypter()).deriveKey(PASSWORD1); encryptedWallet.sendCoinsOffline(req); }
@Test(expected = Wallet.DustySendRequested.class) public void sendDustTest() throws InsufficientMoneyException { // Tests sending dust, should throw DustySendRequested. Transaction tx = new Transaction(PARAMS); tx.addOutput(Transaction.MIN_NONDUST_OUTPUT.subtract(SATOSHI), OTHER_ADDRESS); SendRequest request = SendRequest.forTx(tx); request.ensureMinRequiredFee = true; wallet.completeTx(request); }
@Test public void sendCoinsWithBroadcasterTest() throws InsufficientMoneyException { ECKey key = ECKey.fromPrivate(BigInteger.TEN); receiveATransactionAmount(wallet, myAddress, Coin.COIN); MockTransactionBroadcaster broadcaster = new MockTransactionBroadcaster(wallet); wallet.setTransactionBroadcaster(broadcaster); SendRequest req = SendRequest.to(OTHER_ADDRESS.getParameters(), key, Coin.CENT); wallet.sendCoins(req); }
/** * Given a spend request containing an incomplete transaction, makes it valid by adding outputs and signed inputs * according to the instructions in the request. The transaction in the request is modified by this method. * * @param req a BitSendRequest that contains the incomplete transaction and details for how to make it valid. * @throws WalletAccountException if the request could not be completed due to not enough balance. * @throws IllegalArgumentException if you try and complete the same SendRequest twice */ public void completeTransaction(BitSendRequest req) throws WalletAccountException { lock.lock(); try { transactionCreator.completeTx(req); } catch (InsufficientMoneyException e) { throw new WalletAccountException(e); } finally { lock.unlock(); } }
private void doEmptyWallet2(KeyParameter aesKey) { emptyWalletButton.setDisable(true); openOfferManager.removeAllOpenOffers(() -> { try { walletService.emptyWallet(addressInputTextField.getText(), aesKey, () -> { closeButton.setText(Res.get("shared.close")); balanceTextField.setText(formatter.formatCoinWithCode(walletService.getAvailableBalance())); emptyWalletButton.setDisable(true); log.debug("wallet empty successful"); onClose(() -> UserThread.runAfter(() -> new Popup<>() .feedback(Res.get("emptyWalletWindow.sent.success")) .show(), Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS)); doClose(); }, (errorMessage) -> { emptyWalletButton.setDisable(false); log.debug("wallet empty failed " + errorMessage); }); } catch (InsufficientMoneyException | AddressFormatException e1) { e1.printStackTrace(); log.error(e1.getMessage()); emptyWalletButton.setDisable(false); } }); }
public void onWithdrawRequest(String toAddress, Coin amount, Coin fee, KeyParameter aesKey, Trade trade, ResultHandler resultHandler, FaultHandler faultHandler) { String fromAddress = btcWalletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(); FutureCallback<Transaction> callback = new FutureCallback<Transaction>() { @Override public void onSuccess(@javax.annotation.Nullable Transaction transaction) { if (transaction != null) { log.debug("onWithdraw onSuccess tx ID:" + transaction.getHashAsString()); addTradeToClosedTrades(trade); trade.setState(Trade.State.WITHDRAW_COMPLETED); resultHandler.handleResult(); } } @Override public void onFailure(@NotNull Throwable t) { t.printStackTrace(); log.error(t.getMessage()); faultHandler.handleFault("An exception occurred at requestWithdraw (onFailure).", t); } }; try { btcWalletService.sendFunds(fromAddress, toAddress, amount, fee, aesKey, AddressEntry.Context.TRADE_PAYOUT, callback); } catch (AddressFormatException | InsufficientMoneyException | AddressEntryException e) { e.printStackTrace(); log.error(e.getMessage()); faultHandler.handleFault("An exception occurred at requestWithdraw.", e); } }
/** * <p>Sends coins according to the given request, via the given {@link TransactionBroadcaster}.</p> * * <p>The returned object provides both the transaction, and a future that can be used to learn when the broadcast * is complete. Complete means, if the PeerGroup is limited to only one connection, when it was written out to * the socket. Otherwise when the transaction is written out and we heard it back from a different peer.</p> * * <p>Note that the sending transaction is committed to the wallet immediately, not when the transaction is * successfully broadcast. This means that even if the network hasn't heard about your transaction you won't be * able to spend those same coins again.</p> * * @param broadcaster the target to use for broadcast. * @param request the SendRequest that describes what to do, get one using static methods on SendRequest itself. * @return An object containing the transaction that was created, and a future for the broadcast of it. * @throws InsufficientMoneyException if the request could not be completed due to not enough balance. * @throws IllegalArgumentException if you try and complete the same SendRequest twice * @throws DustySendRequested if the resultant transaction would violate the dust rules. * @throws CouldNotAdjustDownwards if emptying the wallet was requested and the output can't be shrunk for fees without violating a protocol rule. * @throws ExceededMaxTransactionSize if the resultant transaction is too big for Bitcoin to process. * @throws MultipleOpReturnRequested if there is more than one OP_RETURN output for the resultant transaction. */ public SendResult sendCoins(TransactionBroadcaster broadcaster, SendRequest request) throws InsufficientMoneyException { // Should not be locked here, as we're going to call into the broadcaster and that might want to hold its // own lock. sendCoinsOffline handles everything that needs to be locked. checkState(!lock.isHeldByCurrentThread()); // Commit the TX to the wallet immediately so the spent coins won't be reused. // TODO: We should probably allow the request to specify tx commit only after the network has accepted it. Transaction tx = sendCoinsOffline(request); SendResult result = new SendResult(); result.tx = tx; // The tx has been committed to the pending pool by this point (via sendCoinsOffline -> commitTx), so it has // a txConfidenceListener registered. Once the tx is broadcast the peers will update the memory pool with the // count of seen peers, the memory pool will update the transaction confidence object, that will invoke the // txConfidenceListener which will in turn invoke the wallets event listener onTransactionConfidenceChanged // method. result.broadcast = broadcaster.broadcastTransaction(tx); result.broadcastComplete = result.broadcast.future(); return result; }
@Test(expected = java.lang.IllegalStateException.class) public void sendCoinsNoBroadcasterTest() throws InsufficientMoneyException { ECKey key = ECKey.fromPrivate(BigInteger.TEN); SendRequest req = SendRequest.to(OTHER_ADDRESS.getParameters(), key, SATOSHI.multiply(12)); wallet.sendCoins(req); }
/** * Attempts to open a new connection to and open a payment channel with the given host and port, blocking until the * connection is open. The server is requested to keep the channel open for {@param timeWindow} * seconds. If the server proposes a longer time the channel will be closed. * * @param server The host/port pair where the server is listening. * @param timeoutSeconds The connection timeout and read timeout during initialization. This should be large enough * to accommodate ECDSA signature operations and network latency. * @param wallet The wallet which will be paid from, and where completed transactions will be committed. * Must already have a {@link StoredPaymentChannelClientStates} object in its extensions set. * @param myKey A freshly generated keypair used for the multisig contract and refund output. * @param maxValue The maximum value this channel is allowed to request * @param serverId A unique ID which is used to attempt reopening of an existing channel. * This must be unique to the server, and, if your application is exposing payment channels to some * API, this should also probably encompass some caller UID to avoid applications opening channels * which were created by others. * @param timeWindow The time in seconds, relative to now, on how long this channel should be kept open. * * @throws IOException if there's an issue using the network. * @throws ValueOutOfRangeException if the balance of wallet is lower than maxValue. */ public PaymentChannelClientConnection(InetSocketAddress server, int timeoutSeconds, Wallet wallet, ECKey myKey, Coin maxValue, String serverId, final long timeWindow) throws IOException, ValueOutOfRangeException { // Glue the object which vends/ingests protobuf messages in order to manage state to the network object which // reads/writes them to the wire in length prefixed form. channelClient = new PaymentChannelClient(wallet, myKey, maxValue, Sha256Hash.create(serverId.getBytes()), timeWindow, new PaymentChannelClient.ClientConnection() { @Override public void sendToServer(Protos.TwoWayChannelMessage msg) { wireParser.write(msg); } @Override public void destroyConnection(PaymentChannelCloseException.CloseReason reason) { channelOpenFuture.setException(new PaymentChannelCloseException("Payment channel client requested that the connection be closed: " + reason, reason)); wireParser.closeConnection(); } @Override public boolean acceptExpireTime(long expireTime) { return expireTime <= (timeWindow + Utils.currentTimeSeconds() + 60); // One extra minute to compensate for time skew and latency } @Override public void channelOpen(boolean wasInitiated) { wireParser.setSocketTimeout(0); // Inform the API user that we're done and ready to roll. channelOpenFuture.set(PaymentChannelClientConnection.this); } }); // And glue back in the opposite direction - network to the channelClient. wireParser = new ProtobufParser<Protos.TwoWayChannelMessage>(new ProtobufParser.Listener<Protos.TwoWayChannelMessage>() { @Override public void messageReceived(ProtobufParser<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) { try { channelClient.receiveMessage(msg); } catch (InsufficientMoneyException e) { // We should only get this exception during INITIATE, so channelOpen wasn't called yet. channelOpenFuture.setException(e); } } @Override public void connectionOpen(ProtobufParser<Protos.TwoWayChannelMessage> handler) { channelClient.connectionOpen(); } @Override public void connectionClosed(ProtobufParser<Protos.TwoWayChannelMessage> handler) { channelClient.connectionClosed(); channelOpenFuture.setException(new PaymentChannelCloseException("The TCP socket died", PaymentChannelCloseException.CloseReason.CONNECTION_CLOSED)); } }, Protos.TwoWayChannelMessage.getDefaultInstance(), Short.MAX_VALUE, timeoutSeconds*1000); // Initiate the outbound network connection. We don't need to keep this around. The wireParser object will handle // things from here on out. new NioClient(server, wireParser, timeoutSeconds * 1000); }
@Transactional(isolation = Isolation.SERIALIZABLE) public PayoutResponse payOutVirtualBalance(ECKey accountOwner, String addressAsString) throws UserNotFoundException, InsufficientMoneyException, IOException, BusinessException, InsufficientFunds { final Address toAddress = Address.fromBase58(appConfig.getNetworkParameters(), addressAsString); final Account account = accountService.getByClientPublicKey(accountOwner.getPubKey()); if (account == null) throw new UserNotFoundException(accountOwner.getPublicKeyAsHex()); final Coin virtualBalance = Coin.valueOf(account.virtualBalance()); if (!virtualBalance.isPositive()) throw new InsufficientFunds(); final Coin potValue = getMicroPaymentPotValue(); LOG.info("Micropot value is {}", potValue); if (potValue.isLessThan(virtualBalance)) { eventService.warn(MICRO_PAYMENT_POT_EXHAUSTED, "Not enough coin in pot. " + virtualBalance + " needed " + "but only " + potValue + " available."); return new PayoutResponse(); } Address changeAddress = appConfig.getMicroPaymentPotPrivKey().toAddress(appConfig.getNetworkParameters()); // Estimate fee by creating a send request final Coin feePer1000Bytes = Coin.valueOf(feeService.fee() * 1000); SendRequest estimateRequest = SendRequest.to(toAddress, virtualBalance); estimateRequest.feePerKb = feePer1000Bytes; estimateRequest.changeAddress = changeAddress; walletService.getWallet().completeTx(estimateRequest); final Coin requiredFee = estimateRequest.tx.getFee(); // Make actual request which subtracts fee from virtual balance final Coin coinSendToUser = virtualBalance.minus(requiredFee); SendRequest actualRequest = SendRequest.to(toAddress, coinSendToUser); actualRequest.feePerKb = feePer1000Bytes; actualRequest.changeAddress = changeAddress; Wallet.SendResult sendResult = walletService.getWallet().sendCoins(actualRequest); // At this point we must consider the coins to be gone, even in case of failure as it has been transmitted // to the broadcaster. account.virtualBalance(0L); accountRepository.save(account); // Wait for actual broadcast to succeed Transaction broadcastedTx; try { broadcastedTx = sendResult.broadcastComplete.get(); } catch (InterruptedException | ExecutionException e) { eventService.error(EventType.MICRO_PAYMENT_PAYOUT_ERROR, "Could not broadcast payout request " + "for account " + accountOwner.getPublicKeyAsHex() + ". Transaction: " + DTOUtils.toHex(sendResult.tx.bitcoinSerialize()) + " Reason:" + e.toString()); e.printStackTrace(); return new PayoutResponse(); } PayoutResponse response = new PayoutResponse(); response.transaction = DTOUtils.toHex(broadcastedTx.bitcoinSerialize()); response.valuePaidOut = coinSendToUser.getValue(); return response; }
public void estimateTxSize() { txFeeFromFeeService = feeService.getTxFee(feeTxSize); Address fundingAddress = btcWalletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(); Address reservedForTradeAddress = btcWalletService.getOrCreateAddressEntry(offerId, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(); Address changeAddress = btcWalletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(); Coin reservedFundsForOffer = getSecurityDeposit(); if (!isBuyOffer()) reservedFundsForOffer = reservedFundsForOffer.add(amount.get()); checkNotNull(user.getAcceptedArbitrators(), "user.getAcceptedArbitrators() must not be null"); checkArgument(!user.getAcceptedArbitrators().isEmpty(), "user.getAcceptedArbitrators() must not be empty"); String dummyArbitratorAddress = user.getAcceptedArbitrators().get(0).getBtcAddress(); try { log.info("We create a dummy tx to see if our estimated size is in the accepted range. feeTxSize={}," + " txFee based on feeTxSize: {}, recommended txFee is {} sat/byte", feeTxSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); Transaction tradeFeeTx = tradeWalletService.estimateBtcTradingFeeTxSize( fundingAddress, reservedForTradeAddress, changeAddress, reservedFundsForOffer, true, getMakerFee(), txFeeFromFeeService, dummyArbitratorAddress); final int txSize = tradeFeeTx.bitcoinSerialize().length; // use feeTxSizeEstimationRecursionCounter to avoid risk for endless loop if (txSize > feeTxSize * 1.2 && feeTxSizeEstimationRecursionCounter < 10) { feeTxSizeEstimationRecursionCounter++; log.info("txSize is {} bytes but feeTxSize used for txFee calculation was {} bytes. We try again with an " + "adjusted txFee to reach the target tx fee.", txSize, feeTxSize); feeTxSize = txSize; txFeeFromFeeService = feeService.getTxFee(feeTxSize); // lets try again with the adjusted txSize and fee. estimateTxSize(); } else { log.info("feeTxSize {} bytes", feeTxSize); log.info("txFee based on estimated size: {}, recommended txFee is {} sat/byte", txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); } } catch (InsufficientMoneyException e) { // If we need to fund from an external wallet we can assume we only have 1 input (260 bytes). log.warn("We cannot do the fee estimation because there are not enough funds in the wallet. This is expected " + "if the user pays from an external wallet. In that case we use an estimated tx size of 260 bytes."); feeTxSize = 260; txFeeFromFeeService = feeService.getTxFee(feeTxSize); log.info("feeTxSize {} bytes", feeTxSize); log.info("txFee based on estimated size: {}, recommended txFee is {} sat/byte", txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); } }
public void estimateTxSize() { Address fundingAddress = btcWalletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(); int txSize = 0; if (btcWalletService.getBalance(Wallet.BalanceType.AVAILABLE).isPositive()) { txFeeFromFeeService = getTxFeeBySize(feeTxSize); Address reservedForTradeAddress = btcWalletService.getOrCreateAddressEntry(offer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(); Address changeAddress = btcWalletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(); Coin reservedFundsForOffer = getSecurityDeposit().add(txFeeFromFeeService).add(txFeeFromFeeService); if (isBuyOffer()) reservedFundsForOffer = reservedFundsForOffer.add(amount.get()); checkNotNull(user.getAcceptedArbitrators(), "user.getAcceptedArbitrators() must not be null"); checkArgument(!user.getAcceptedArbitrators().isEmpty(), "user.getAcceptedArbitrators() must not be empty"); String dummyArbitratorAddress = user.getAcceptedArbitrators().get(0).getBtcAddress(); try { log.debug("We create a dummy tx to see if our estimated size is in the accepted range. feeTxSize={}," + " txFee based on feeTxSize: {}, recommended txFee is {} sat/byte", feeTxSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); Transaction tradeFeeTx = tradeWalletService.estimateBtcTradingFeeTxSize( fundingAddress, reservedForTradeAddress, changeAddress, reservedFundsForOffer, true, getTakerFee(), txFeeFromFeeService, dummyArbitratorAddress); txSize = tradeFeeTx.bitcoinSerialize().length; // use feeTxSizeEstimationRecursionCounter to avoid risk for endless loop // We use the tx size for the trade fee tx as target for the fees. // The deposit and payout txs are determined +/- 1 output but the trade fee tx can have either 1 or many inputs // so we need to make sure the trade fee tx gets the correct fee to not get stuck. // We use a 20% tolerance frm out default 320 byte size (typical for deposit and payout) and only if we get a // larger size we increase the fee. Worst case is that we overpay for the other follow up txs, but better than // use a too low fee and get stuck. if (txSize > feeTxSize * 1.2 && feeTxSizeEstimationRecursionCounter < 10) { feeTxSizeEstimationRecursionCounter++; log.info("txSize is {} bytes but feeTxSize used for txFee calculation was {} bytes. We try again with an " + "adjusted txFee to reach the target tx fee.", txSize, feeTxSize); feeTxSize = txSize; txFeeFromFeeService = getTxFeeBySize(txSize); // lets try again with the adjusted txSize and fee. estimateTxSize(); } else { // We are done with estimation iterations if (feeTxSizeEstimationRecursionCounter < 10) log.info("Fee estimation completed:\n" + "txFee based on estimated size of {} bytes. Average tx size = {} bytes. Actual tx size = {} bytes. TxFee is {} ({} sat/byte)", feeTxSize, getAverageSize(feeTxSize), txSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); else log.warn("We could not estimate the fee as the feeTxSizeEstimationRecursionCounter exceeded our limit of 10 recursions.\n" + "txFee based on estimated size of {} bytes. Average tx size = {} bytes. Actual tx size = {} bytes. " + "TxFee is {} ({} sat/byte)", feeTxSize, getAverageSize(feeTxSize), txSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); } } catch (InsufficientMoneyException e) { log.info("We cannot complete the fee estimation because there are not enough funds in the wallet.\n" + "This is expected if the user has not sufficient funds yet.\n" + "In that case we use the latest estimated tx size or the default if none has been calculated yet.\n" + "txFee based on estimated size of {} bytes. Average tx size = {} bytes. Actual tx size = {} bytes. TxFee is {} ({} sat/byte)", feeTxSize, getAverageSize(feeTxSize), txSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); } } else { feeTxSize = 320; txFeeFromFeeService = getTxFeeBySize(feeTxSize); log.info("We cannot do the fee estimation because there are no funds in the wallet.\nThis is expected " + "if the user has not funded his wallet yet.\n" + "In that case we use an estimated tx size of 320 bytes.\n" + "txFee based on estimated size of {} bytes. Average tx size = {} bytes. Actual tx size = {} bytes. TxFee is {} ({} sat/byte)", feeTxSize, getAverageSize(feeTxSize), txSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte()); } }