/** * Execute the console command. * * @return mixed */ public function fire() { $this->comment('Sweeping account'); $address_sender = app('App\\Blockchain\\Sender\\PaymentAddressSender'); $payment_address_repo = app('App\\Repositories\\PaymentAddressRepository'); $payment_address = $payment_address_repo->findByUuid($this->input->getArgument('payment-address-id')); if (!$payment_address) { throw new Exception("Payment address not found", 1); } $destination = $this->input->getArgument('destination-address'); if (!AddressValidator::isValid($destination)) { throw new Exception("The destination address was invalid", 1); } $float_fee = $this->input->getOption('fee'); $api_call = app('App\\Repositories\\APICallRepository')->create(['user_id' => $this->getConsoleUser()['id'], 'details' => ['command' => 'xchain:sweep-address', 'args' => ['payment_address' => $payment_address['uuid'], 'destination' => $destination, 'fee' => $float_fee]]]); // get lock $lock_acquired = AccountHandler::acquirePaymentAddressLock($payment_address); // do the send list($txid, $float_balance_sent) = $address_sender->sweepAllAssets($payment_address, $destination, $float_fee); // clear all balances from all accounts AccountHandler::zeroAllBalances($payment_address, $api_call); // release the account lock if ($lock_acquired) { AccountHandler::releasePaymentAddressLock($payment_address); } $this->comment('done'); }
public function transfer($address_uuid, Guard $auth, AccountTransferRequest $request, APIControllerHelper $helper, PaymentAddressRepository $payment_address_repository, APICallRepository $api_call_repository) { $user = $auth->getUser(); if (!$user) { throw new Exception("User not found", 1); } $payment_address = $helper->requireResourceOwnedByUser($address_uuid, $user, $payment_address_repository); $params = $helper->getAttributesFromRequest($request); $api_call = $api_call_repository->create(['user_id' => $user['id'], 'details' => ['method' => 'api/v1/accounts/transfer/' . $address_uuid, 'args' => $params]]); try { if (isset($params['close']) and $params['close']) { AccountHandler::close($payment_address, $params['from'], $params['to'], $api_call); } else { if (isset($params['quantity']) and isset($params['asset'])) { AccountHandler::transfer($payment_address, $params['from'], $params['to'], $params['quantity'], $params['asset'], isset($params['txid']) ? $params['txid'] : null, $api_call); } else { // transfer all AccountHandler::transferAllByTIXD($payment_address, $params['from'], $params['to'], $params['txid'], $api_call); } } // done return $helper->buildJSONResponse([], 204); } catch (AccountException $e) { return $helper->buildJSONResponse(['message' => $e->getMessage(), 'errorName' => $e->getErrorName()], $e->getStatusCode()); } catch (HttpException $e) { return $helper->buildJSONResponse(['message' => $e->getMessage()], $e->getStatusCode()); } }
public function send(PaymentAddress $payment_address, $parsed_tx, $confirmations) { $is_confirmed = $confirmations >= self::SEND_CONFIRMATIONS_REQUIRED; // check for vins if (!isset($parsed_tx['bitcoinTx']['vin']) or !$parsed_tx['bitcoinTx']['vin']) { EventLog::logError('bitcoinTx.send.noVins', ['txid' => $parsed_tx['txid']]); return; } // get the sending account $account = AccountHandler::getAccount($payment_address); foreach ($parsed_tx['bitcoinTx']['vin'] as $vin) { // update the UTXO record $is_spendable = true; if ($is_spendable) { $spent_txid = isset($vin['txid']) ? $vin['txid'] : null; $spent_n = isset($vin['vout']) ? $vin['vout'] : null; if ($spent_txid and $spent_n !== null) { $type = $is_confirmed ? TXO::SENT : TXO::SENDING; $spent = true; // spend the utxo (updates an existing utxo) Log::debug("new send TXO: {$spent_txid}:{$spent_n}/" . CurrencyUtil::valueToSatoshis($vin['value']) . " " . TXO::typeIntegerToString($type) . " to " . $payment_address['uuid']); $this->txo_repository->updateOrCreate(['txid' => $spent_txid, 'n' => $spent_n, 'type' => $type, 'spent' => $spent, 'amount' => CurrencyUtil::valueToSatoshis($vin['value'])], $payment_address, $account); } } } }
public function testIncomingInvalidatedTransactionUpdatesLedgerEntries() { $provisional_tx_repository = app('App\\Repositories\\ProvisionalTransactionRepository'); $notification_repository = app('App\\Repositories\\NotificationRepository'); $ledger_entry_repository = app('App\\Repositories\\LedgerEntryRepository'); // setup monitors $payment_address_helper = app('PaymentAddressHelper'); $payment_address_one = $payment_address_helper->createSamplePaymentAddressWithoutInitialBalances(null, ['address' => '1JztLWos5K7LsqW5E78EASgiVBaCe6f7cD']); // receive unconfirmed transactions $parsed_txs = $this->receiveUnconfirmedTransactions(1); // check accounts $account_one = AccountHandler::getAccount($payment_address_one, 'default'); $balance = $ledger_entry_repository->accountBalancesByAsset($account_one, null); PHPUnit::assertEquals(0.004, $balance['unconfirmed']['BTC']); // now confirm a malleated version of the transaction $malleated_tx = $parsed_txs[0]; $malleated_tx['txid'] = str_repeat('0', 54) . 'MALLEATED1'; $malleated_txs = [$malleated_tx]; // now confirm the malleated transaction (with 2 confirmations) $this->sendConfirmationEvents(2, $malleated_txs); // check for invalidation notification $balance = $ledger_entry_repository->accountBalancesByAsset($account_one, null); PHPUnit::assertEquals(0.004, $balance['confirmed']['BTC']); PHPUnit::assertArrayNotHasKey('BTC', $balance['unconfirmed']); }
/** * Execute the console command. * * @return mixed */ public function fire() { $payment_address_repo = app('App\\Repositories\\PaymentAddressRepository'); $account_repository = app('App\\Repositories\\AccountRepository'); $user_repository = app('App\\Repositories\\UserRepository'); $payment_address_uuid = $this->input->getArgument('payment-address-uuid'); $name = $this->input->getOption('name'); $show_ledger = $this->input->getOption('ledger'); $show_inactive = $this->input->getOption('inactive'); $payment_address = $payment_address_repo->findByUuid($payment_address_uuid); if (!$payment_address) { throw new Exception("Payment address not found", 1); } $this->info("Showing accounts for payment address {$payment_address['address']} ({$payment_address['uuid']})"); if (strlen($name)) { $account = AccountHandler::getAccount($payment_address, $name); if (!$account) { $this->error("Account not found for {$name}"); return; } $accounts = [$account]; } else { // all accounts, maybe including inactive $accounts = $account_repository->findByAddress($payment_address, $show_inactive ? null : 1); } foreach ($accounts as $account) { $this->line($this->showAccount($account, $show_ledger)); } $this->info('done'); }
public function createSampleTXO($payment_address = null, $overrides = []) { if ($payment_address === null) { $payment_address = app('PaymentAddressHelper')->createSamplePaymentAddress(); } $account = AccountHandler::getAccount($payment_address, 'default'); // build a real script $script = ScriptFactory::scriptPubKey()->payToAddress(AddressFactory::fromString($payment_address['address'])); $attributes = array_merge(['txid' => $this->nextTXID(), 'n' => 0, 'script' => $script->getBuffer()->getHex(), 'amount' => 54321, 'type' => TXO::CONFIRMED, 'spent' => false, 'green' => false], $overrides); $txo_model = $this->txo_repository->create($payment_address, $account, $attributes); return $txo_model; }
/** * Store a newly created resource in storage. * * @return Response */ public function store(APIControllerHelper $helper, CreatePaymentAddressRequest $request, PaymentAddressRepository $payment_address_respository, Guard $auth) { $user = $auth->getUser(); if (!$user) { throw new Exception("User not found", 1); } $attributes = $request->only(array_keys($request->rules())); $attributes['user_id'] = $user['id']; $address = $payment_address_respository->create($attributes); EventLog::log('paymentAddress.created', $address->toArray()); // create a default account AccountHandler::createDefaultAccount($address); return $helper->transformResourceForOutput($address); }
public function addBalancesToPaymentAddressAccount($balances, $payment_address, $with_utxos = true, $account_name = 'default', $txid = 'SAMPLE01') { if (!$balances) { return; } $account = AccountHandler::getAccount($payment_address, $account_name); foreach ($balances as $asset => $quantity) { $this->ledger_entry_repository->addCredit($quantity, $asset, $account, LedgerEntry::CONFIRMED, LedgerEntry::DIRECTION_OTHER, $txid); } if ($with_utxos) { $float_btc_balance = isset($balances['BTC']) ? $balances['BTC'] : 0; return $this->addUTXOToPaymentAddress($float_btc_balance, $payment_address, $account_name, $txid); } return []; }
public function testPrimingTransactionUpdatesBalancesCorrectly() { $user = $this->app->make('\\UserHelper')->createSampleUser(); $payment_address = $this->app->make('\\PaymentAddressHelper')->createSamplePaymentAddress($user); $my_address = $payment_address['address']; $default_account = AccountHandler::getAccount($payment_address); // test balances before $ledger_entry_repo = app('App\\Repositories\\LedgerEntryRepository'); $balances = $ledger_entry_repo->accountBalancesByAsset($default_account, null); PHPUnit::assertEquals(1, $balances['confirmed']['BTC']); // generate a multi priming transaction $parsed_transaction = $this->buildTransaction(0.4, $my_address, [[$my_address, 0.0001], [$my_address, 0.0001], [$my_address, 0.0001], [$my_address, 0.3996]]); // echo "\$parsed_transaction: ".json_encode($parsed_transaction, 192)."\n"; $this->sendTransactionWithConfirmations($parsed_transaction, 0); // test balances after $ledger_entry_repo = app('App\\Repositories\\LedgerEntryRepository'); $balances = $ledger_entry_repo->accountBalancesByAsset($default_account, null); // echo "[after] \$balances: ".json_encode($balances, 192)."\n"; PHPUnit::assertEquals(0.9999, $balances['confirmed']['BTC']); }
/** * Execute the console command. * * @return mixed */ public function fire() { DB::transaction(function () { $payment_address_repo = app('App\\Repositories\\PaymentAddressRepository'); $account_repository = app('App\\Repositories\\AccountRepository'); $payment_address_uuid = $this->input->getArgument('payment-address-uuid'); $payment_address = $payment_address_repo->findByUuid($payment_address_uuid); if (!$payment_address) { throw new Exception("Payment address not found", 1); } $account_name = $this->input->getArgument('account-name'); $api_call = app('App\\Repositories\\APICallRepository')->create(['user_id' => $this->getConsoleUser()['id'], 'details' => ['command' => 'xchain:close-account', 'args' => ['payment_address' => $payment_address['uuid']]]]); $from_account = $account_repository->findByName($account_name, $payment_address['id']); if (!$from_account) { $this->error("Account {$from_account} not found."); return; } $this->comment('closing account ' . $account_name); AccountHandler::close($payment_address, $account_name, 'default', $api_call); }); $this->info('done'); }
public function testUpdateByTXOIdentifiers() { // add one $txo_repository = $this->app->make('App\\Repositories\\TXORepository'); $txid = $this->TXOHelper()->nextTXID(); $txid_2 = $this->TXOHelper()->nextTXID(); $payment_address = app('PaymentAddressHelper')->createSamplePaymentAddressWithoutInitialBalances(); $default_account = AccountHandler::getAccount($payment_address); // create... $attributes = ['txid' => $txid, 'n' => 0, 'type' => TXO::CONFIRMED, 'script' => 'testscript', 'spent' => false]; $txo_model_1 = $txo_repository->updateOrCreate($attributes, $payment_address, $default_account); $txo_model_2 = $txo_repository->updateOrCreate(['n' => 1] + $attributes, $payment_address, $default_account); $txo_model_3 = $txo_repository->updateOrCreate(['txid' => $txid_2] + $attributes, $payment_address, $default_account); $txo_model_4 = $txo_repository->updateOrCreate(['txid' => $txid_2, 'n' => 1] + $attributes, $payment_address, $default_account); // update $update_vars = ['script' => 'testscriptupdated']; $txo_repository->updateByTXOIdentifiers(["{$txid}:1", "{$txid_2}:1"], $update_vars); $reloaded_txo_1 = $txo_repository->findByID($txo_model_1['id']); $reloaded_txo_2 = $txo_repository->findByID($txo_model_2['id']); $reloaded_txo_3 = $txo_repository->findByID($txo_model_3['id']); $reloaded_txo_4 = $txo_repository->findByID($txo_model_4['id']); PHPUnit::assertEquals('testscript', $reloaded_txo_1['script']); PHPUnit::assertEquals('testscriptupdated', $reloaded_txo_2['script']); PHPUnit::assertEquals('testscript', $reloaded_txo_3['script']); PHPUnit::assertEquals('testscriptupdated', $reloaded_txo_4['script']); // update (2) $update_vars = ['script' => 'testscriptupdated222']; $txo_repository->updateByTXOIdentifiers(["{$txid}:0"], $update_vars); $reloaded_txo_1 = $txo_repository->findByID($txo_model_1['id']); $reloaded_txo_2 = $txo_repository->findByID($txo_model_2['id']); $reloaded_txo_3 = $txo_repository->findByID($txo_model_3['id']); $reloaded_txo_4 = $txo_repository->findByID($txo_model_4['id']); PHPUnit::assertEquals('testscriptupdated222', $reloaded_txo_1['script']); PHPUnit::assertEquals('testscriptupdated', $reloaded_txo_2['script']); PHPUnit::assertEquals('testscript', $reloaded_txo_3['script']); PHPUnit::assertEquals('testscriptupdated', $reloaded_txo_4['script']); }
/** * Execute the console command. * * @return mixed */ public function fire() { $txo_repository = app('App\\Repositories\\TXORepository'); $payment_address_repo = app('App\\Repositories\\PaymentAddressRepository'); $bitcoin_payer = app('Tokenly\\BitcoinPayer\\BitcoinPayer'); $payment_address_uuid = $this->input->getArgument('payment-address-uuid'); $should_reconcile = $this->input->getOption('reconcile'); if ($payment_address_uuid) { $payment_address = $payment_address_repo->findByUuid($payment_address_uuid); if (!$payment_address) { throw new Exception("Payment address not found", 1); } $payment_addresses = [$payment_address]; } else { $payment_addresses = $payment_address_repo->findAll(); } foreach ($payment_addresses as $payment_address) { Log::debug("reconciling TXOs for {$payment_address['address']} ({$payment_address['uuid']})"); // get xchain utxos $xchain_utxos_map = []; $db_txos = $txo_repository->findByPaymentAddress($payment_address, null, true); // unspent only foreach ($db_txos as $db_txo) { if ($db_txo['type'] != TXO::CONFIRMED) { continue; } $filtered_utxo = ['txid' => $db_txo['txid'], 'n' => $db_txo['n'], 'script' => $db_txo['script'], 'amount' => $db_txo['amount']]; $xchain_utxos_map[$filtered_utxo['txid'] . ':' . $filtered_utxo['n']] = $filtered_utxo; } // get daemon utxos $daemon_utxos_map = []; $all_utxos = $bitcoin_payer->getAllUTXOs($payment_address['address']); if ($all_utxos) { foreach ($all_utxos as $utxo) { if (!isset($utxo['confirmations']) or $utxo['confirmations'] == 0) { continue; } $filtered_utxo = ['txid' => $utxo['txid'], 'n' => $utxo['vout'], 'script' => $utxo['script'], 'amount' => CurrencyUtil::valueToSatoshis($utxo['amount'])]; $daemon_utxos_map[$utxo['txid'] . ':' . $utxo['vout']] = $filtered_utxo; } } // compare $differences = $this->buildDifferences($xchain_utxos_map, $daemon_utxos_map); if ($differences['any']) { $this->comment("Differences found for {$payment_address['address']} ({$payment_address['uuid']})"); $this->line(json_encode($differences, 192)); $this->line(''); if ($should_reconcile) { $account = AccountHandler::getAccount($payment_address); foreach ($differences['differences'] as $difference) { // delete the xchain txo if it is different in any way if (isset($difference['xchain']) and $difference['xchain']) { $utxo = $difference['xchain']; $this->line('Removing xchain UTXO: ' . json_encode($utxo, 192)); // delete $txo_model = $txo_repository->findByTXIDAndOffset($utxo['txid'], $utxo['n']); if ($txo_model['payment_address_id'] != $payment_address['id']) { throw new Exception("Mismatched payment address id. Expected {$payment_address['id']}. Found {$txo_model['payment_address_id']}", 1); } } if (isset($difference['daemon'])) { $utxo = $difference['daemon']; $this->line('Adding daemon UTXO: ' . json_encode($utxo, 192)); // add the daemon's utxo $txo_repository->create($payment_address, $account, ['txid' => $utxo['txid'], 'n' => $utxo['n'], 'script' => $utxo['script'], 'amount' => $utxo['amount']]); } } } } else { $this->comment("No differences found for {$payment_address['address']} ({$payment_address['uuid']})"); } } $this->info('done'); }
public function updateAccountBalances($found_addresses, $parsed_tx, $confirmations, $block_seq, Block $block = null) { // don't process this now if pre-processing was necessary for the send notification if ($this->willNeedToPreprocessSendNotification($parsed_tx, $confirmations)) { return; } $sources = $parsed_tx['sources'] ? $parsed_tx['sources'] : []; $destinations = $parsed_tx['destinations'] ? $parsed_tx['destinations'] : []; // determine matched payment addresses foreach ($found_addresses['payment_addresses'] as $payment_address) { // Log::debug("upating account balances for payment address {$payment_address['address']}. txid is {$parsed_tx['txid']}"); $is_source = in_array($payment_address['address'], $sources); $is_destination = in_array($payment_address['address'], $destinations); if ($is_source) { // this address sent something $quantity = $this->buildQuantityForEventType('send', $parsed_tx, $payment_address['address']); AccountHandler::send($payment_address, $quantity, $parsed_tx['asset'], $parsed_tx, $confirmations); } if ($is_destination) { // this address received something // Note that the same address might be both the sender and the receiver $quantity = $this->buildQuantityForEventType('receive', $parsed_tx, $payment_address['address']); AccountHandler::receive($payment_address, $quantity, $parsed_tx['asset'], $parsed_tx, $confirmations); } } }
protected function releasePaymentAddressLockWithDelay($payment_address) { if (app()->environment() != 'testing') { $delay = 1500000; Log::debug("delaying for " . $delay / 1000 . " ms before releasing payment address lock"); usleep($delay); } AccountHandler::releasePaymentAddressLock($payment_address); }
protected function clearBalances($balance_type, $balances_by_asset, $payment_address, APICall $api_call) { $ledger = app('App\\Repositories\\LedgerEntryRepository'); // get the default account $account = AccountHandler::getAccount($payment_address, 'default'); foreach ($balances_by_asset as $asset => $quantity) { if ($quantity == 0) { continue; } // debit the {$balance_type} balance $msg = "Balancing {$balance_type} ledger with {$quantity} {$asset}"; $this->info($msg); $ledger_entry_type = $balance_type == 'unconfirmed' ? LedgerEntry::UNCONFIRMED : LedgerEntry::SENDING; if ($quantity < 0) { $ledger->addCredit(0 - $quantity, $asset, $account, $ledger_entry_type, LedgerEntry::DIRECTION_OTHER, null, $api_call); } else { $ledger->addDebit($quantity, $asset, $account, $ledger_entry_type, LedgerEntry::DIRECTION_OTHER, null, $api_call); } } }
protected function loadPaymentAddressInfo($address) { if (!isset($this->payment_address_info_cache)) { $this->payment_address_info_cache = []; } if (!isset($this->payment_address_info_cache[$address])) { $found_payment_address = $this->payment_address_repository->findByAddress($address)->first(); if ($found_payment_address) { $this->payment_address_info_cache[$address] = [$found_payment_address, AccountHandler::getAccount($found_payment_address)]; } else { $this->payment_address_info_cache[$address] = [null, null]; } } return $this->payment_address_info_cache[$address]; }
protected function syncConfirmedBalances($payment_address, $balance_differences, APICall $api_call) { $ledger = app('App\\Repositories\\LedgerEntryRepository'); foreach ($balance_differences as $asset => $qty_by_type) { $xchain_quantity = $qty_by_type['xchain']; $daemon_quantity = $qty_by_type['daemon']; $default_account = AccountHandler::getAccount($payment_address); if ($xchain_quantity > $daemon_quantity) { // debit $quantity = $xchain_quantity - $daemon_quantity; $msg = "Debiting {$quantity} {$asset} from account {$default_account['name']}"; $this->info($msg); $ledger->addDebit($quantity, $asset, $default_account, LedgerEntry::CONFIRMED, LedgerEntry::DIRECTION_OTHER, null, $api_call); } else { // credit $quantity = $daemon_quantity - $xchain_quantity; $msg = "Crediting {$quantity} {$asset} to account {$default_account['name']}"; $this->info($msg); $ledger->addCredit($quantity, $asset, $default_account, LedgerEntry::CONFIRMED, LedgerEntry::DIRECTION_OTHER, null, $api_call); } } }
public function testMoveAccounts() { // receiving a transaction adds TXOs $txo_repository = $this->app->make('App\\Repositories\\TXORepository'); // setup monitors $payment_address_helper = app('PaymentAddressHelper'); $receiving_address_one = $payment_address_helper->createSamplePaymentAddressWithoutInitialBalances(null, ['address' => '1JztLWos5K7LsqW5E78EASgiVBaCe6f7cD']); // create a second account $default_account = AccountHandler::getAccount($receiving_address_one); $account_1 = AccountHandler::createAccount($receiving_address_one, 'account1'); // receive unconfirmed transactions $parsed_txs = $this->receiveUnconfirmedTransactions(1); $loaded_txos = $txo_repository->findAll(); // check that it is in the default account $all_txos = $txo_repository->findAll(); $loaded_txo = $all_txos[0]; PHPUnit::assertEquals($default_account['id'], $loaded_txo['account_id']); // move the utxo to account 1 TXOHandler::moveAccounts([$loaded_txo], $default_account, $account_1); $all_txos = $txo_repository->findAll(); PHPUnit::assertCount(1, $all_txos); $loaded_txo = $all_txos[0]; PHPUnit::assertEquals($account_1['id'], $loaded_txo['account_id']); // check findByAccount $loaded_txos = $txo_repository->findByAccount($default_account, true); PHPUnit::assertCount(0, $loaded_txos); $loaded_txos = $txo_repository->findByAccount($account_1, true); PHPUnit::assertCount(1, $loaded_txos); $loaded_txos = $txo_repository->findByAccount($account_1, false); PHPUnit::assertCount(0, $loaded_txos); }
protected function processTransferEvent($event) { // type: transfer // from: default // to: account1 // quantity: 300 // asset: LTBCOIN // transferType: unconfirmed // txid: cf9d9f4d53d36d9d34f656a6d40bc9dc739178e6ace01bcc42b4b9ea2cbf6741 // get the account (first one) // $account_repository = app('App\Repositories\AccountRepository'); $account = app('App\\Repositories\\AccountRepository')->findAll()->first(); $payment_address = app('App\\Repositories\\PaymentAddressRepository')->findById($account['payment_address_id']); $user = app('App\\Repositories\\UserRepository')->findById($account['user_id']); $txid = isset($event['txid']) ? $event['txid'] : null; $api_call = app('APICallHelper')->newSampleAPICall($user); if (isset($event['close']) and $event['close']) { AccountHandler::close($payment_address, $event['from'], $event['to'], $api_call); } else { AccountHandler::transfer($payment_address, $event['from'], $event['to'], $event['quantity'], $event['asset'], $txid, $api_call); } }