/** * this test requires / asumes that the test wallet it uses contains a balance * * we keep the wallet topped off with some coins, * but if some funny guy ever empties it or if you use your own API key to run the test then it needs to be topped off again * * @throws \Exception */ public function testWalletTransaction() { $client = $this->setupBlocktrailSDK(); /** @var Wallet $wallet */ $wallet = $client->initWallet(["identifier" => "unittest-transaction", "passphrase" => "password"]); $this->assertEquals("give pause forget seed dance crawl situate hole keen", $wallet->getPrimaryMnemonic()); $this->assertEquals("unittest-transaction", $wallet->getIdentifier()); $this->assertEquals("M/9999'", $wallet->getBlocktrailPublicKeys()[9999][1]); $this->assertEquals("tpubD9q6vq9zdP3gbhpjs7n2TRvT7h4PeBhxg1Kv9jEc1XAss7429VenxvQTsJaZhzTk54gnsHRpgeeNMbm1QTag4Wf1QpQ3gy221GDuUCxgfeZ", $wallet->getBlocktrailPublicKeys()[9999][0]); $this->assertEquals("tpubD9q6vq9zdP3gbhpjs7n2TRvT7h4PeBhxg1Kv9jEc1XAss7429VenxvQTsJaZhzTk54gnsHRpgeeNMbm1QTag4Wf1QpQ3gy221GDuUCxgfeZ", $wallet->getBlocktrailPublicKey("m/9999'")->key()); $this->assertEquals("tpubD9q6vq9zdP3gbhpjs7n2TRvT7h4PeBhxg1Kv9jEc1XAss7429VenxvQTsJaZhzTk54gnsHRpgeeNMbm1QTag4Wf1QpQ3gy221GDuUCxgfeZ", $wallet->getBlocktrailPublicKey("M/9999'")->key()); list($confirmed, $unconfirmed) = $wallet->getBalance(); $this->assertGreaterThan(0, $confirmed + $unconfirmed, "positive unconfirmed balance"); $this->assertGreaterThan(0, $confirmed, "positive confirmed balance"); list($path, $address) = $wallet->getNewAddressPair(); $this->assertTrue(strpos($path, "M/9999'/0/") === 0); $this->assertTrue(BitcoinLib::validate_address($address, false, null)); ///* $value = BlocktrailSDK::toSatoshi(0.0002); $txHash = $wallet->pay([$address => $value], null, false, true, Wallet::FEE_STRATEGY_BASE_FEE); $this->assertTrue(!!$txHash); sleep(1); // sleep to wait for the TX to be processed try { $tx = $client->transaction($txHash); } catch (ObjectNotFound $e) { $this->fail("404 for tx[{$txHash}] [" . gmdate('Y-m-d H:i:s') . "]"); } $this->assertTrue(!!$tx, "check for tx[{$txHash}] [" . gmdate('Y-m-d H:i:s') . "]"); $this->assertEquals($txHash, $tx['hash']); $this->assertEquals(BlocktrailSDK::toSatoshi(0.0001), $tx['total_fee']); $this->assertTrue(count($tx['outputs']) <= 2); $this->assertTrue(in_array($value, array_column($tx['outputs'], 'value'))); //*/ /* * do another TX but with a LOW_PRIORITY_FEE */ $value = BlocktrailSDK::toSatoshi(0.0001); $txHash = $wallet->pay([$address => $value], null, false, true, Wallet::FEE_STRATEGY_LOW_PRIORITY); $this->assertTrue(!!$txHash); sleep(1); // sleep to wait for the TX to be processed try { $tx = $client->transaction($txHash); } catch (ObjectNotFound $e) { $this->fail("404 for tx[{$txHash}] [" . gmdate('Y-m-d H:i:s') . "]"); } $this->assertTrue(!!$tx, "check for tx[{$txHash}] [" . gmdate('Y-m-d H:i:s') . "]"); $this->assertEquals($txHash, $tx['hash']); $this->assertTrue(count($tx['outputs']) <= 2); $this->assertTrue(in_array($value, array_column($tx['outputs'], 'value'))); /* * do another TX but with a custom - high - fee */ $value = BlocktrailSDK::toSatoshi(0.0001); $forceFee = BlocktrailSDK::toSatoshi(0.001); $txHash = $wallet->pay([$address => $value], null, false, true, Wallet::FEE_STRATEGY_BASE_FEE, $forceFee); $this->assertTrue(!!$txHash); sleep(1); // sleep to wait for the TX to be processed try { $tx = $client->transaction($txHash); } catch (ObjectNotFound $e) { $this->fail("404 for tx[{$txHash}] [" . gmdate('Y-m-d H:i:s') . "]"); } $this->assertTrue(!!$tx, "check for tx[{$txHash}] [" . gmdate('Y-m-d H:i:s') . "]"); $this->assertEquals($txHash, $tx['hash']); $this->assertEquals($forceFee, $tx['total_fee']); $this->assertTrue(count($tx['outputs']) <= 2); $this->assertTrue(in_array($value, array_column($tx['outputs'], 'value'))); /* * do another TX with OP_RETURN using TxBuilder */ $value = BlocktrailSDK::toSatoshi(0.0002); $moon = "MOOOOOOOOOOOOON!"; $txBuilder = new TransactionBuilder(); $txBuilder->randomizeChangeOutput(false); $txBuilder->addRecipient($address, $value); $txBuilder->addOpReturn($moon); $txBuilder = $wallet->coinSelectionForTxBuilder($txBuilder); $txHash = $wallet->sendTx($txBuilder); $this->assertTrue(!!$txHash); sleep(1); // sleep to wait for the TX to be processed try { $tx = $client->transaction($txHash); } catch (ObjectNotFound $e) { $this->fail("404 for tx[{$txHash}] [" . gmdate('Y-m-d H:i:s') . "]"); } $this->assertTrue(!!$tx, "check for tx[{$txHash}] [" . gmdate('Y-m-d H:i:s') . "]"); $this->assertEquals($txHash, $tx['hash']); $this->assertTrue(count($tx['outputs']) <= 3); $this->assertEquals($value, $tx['outputs'][0]['value']); $this->assertEquals(0, $tx['outputs'][1]['value']); $this->assertEquals("6a" . "10" . bin2hex($moon), $tx['outputs'][1]['script_hex']); }
// uncomment to create a couple of 0.001 BTC UTXOs (and 1 big output remains) // after doing this run the example twice and the 2nd time around you can see it only using a single UTXO //var_dump($wallet->pay([$paymentAddress => $wallet->getMaxSpendable()['max']])); //var_dump($wallet->pay([ // $wallet->getNewAddress() => $amount, // $wallet->getNewAddress() => $amount, // $wallet->getNewAddress() => $amount, // $wallet->getNewAddress() => $amount, // $wallet->getNewAddress() => $amount, // $wallet->getNewAddress() => $amount, // $wallet->getNewAddress() => $amount, //])); //exit(1); $optimalFeePerKB = $wallet->getOptimalFeePerKB(); $lowPriorityFeePerKB = $wallet->getLowPriorityFeePerKB(); $txBuilder = new TransactionBuilder(); $txBuilder->addRecipient($paymentAddress, $amount); // get coinselection for the payment we want to make $txBuilder = $wallet->coinSelectionForTxBuilder($txBuilder); // debug info echo "--------------------------------------------\n"; var_dump("utxos", $txBuilder->getUtxos()); echo "--------------------------------------------\n"; var_dump("outputs", $txBuilder->getOutputs()); list($fee, $change) = $wallet->determineFeeAndChange($txBuilder, $optimalFeePerKB, $lowPriorityFeePerKB); var_dump("fee", $fee); var_dump("change", $change); // set the fee and update the output value $txBuilder->setFee($fee); $txBuilder->updateOutputValue(0, $amount - $fee); // EXTRA: remove any UTXOs we don't need to minize the fee of the TX
use Blocktrail\SDK\Blocktrail; use Blocktrail\SDK\BlocktrailSDK; use Blocktrail\SDK\Connection\Exceptions\ObjectNotFound; use Blocktrail\SDK\TransactionBuilder; use Blocktrail\SDK\Wallet; use Blocktrail\SDK\WalletInterface; require_once __DIR__ . "/../vendor/autoload.php"; $client = new BlocktrailSDK("MY_APIKEY", "MY_APISECRET", "BTC", true, 'v1'); // $client->setVerboseErrors(); // $client->setCurlDebugging(); /** * @var $wallet \Blocktrail\SDK\WalletInterface * @var $backupMnemonic string */ try { /** @var Wallet $wallet */ $wallet = $client->initWallet(["identifier" => "example-wallet", "passphrase" => "example-strong-password"]); } catch (ObjectNotFound $e) { /** @var Wallet $wallet */ list($wallet, $primaryMnemonic, $backupMnemonic, $blocktrailPublicKeys) = $client->createNewWallet(["identifier" => "example-wallet", "passphrase" => "example-strong-password", "key_index" => 9999]); $wallet->doDiscovery(); } // var_dump($wallet->deleteWebhook()); // var_dump($wallet->setupWebhook("http://www.example.com/wallet/webhook/example-wallet")); // $utxos = $wallet->utxos()['data']; // $utxo = $utxos[array_rand($utxos)]; $txBuilder = new TransactionBuilder(); $txBuilder->addRecipient($wallet->getNewAddress(), Blocktrail::DUST + 1); $txBuilder->addOpReturn("TO DAAAA MOOOON@!的"); $txBuilder = $wallet->coinSelectionForTxBuilder($txBuilder); echo $wallet->sendTx($txBuilder);
public function determineFeeAndChange(TransactionBuilder $txBuilder) { $send = $txBuilder->getOutputs(); $utxos = $txBuilder->getUtxos(); $fee = $txBuilder->getFee(); $change = null; // if the fee is fixed we just need to calculate the change if ($fee !== null) { $change = $this->determineChange($utxos, $send, $fee); // if change is not dust we need to add a change output if ($change > Blocktrail::DUST) { $send[] = ['address' => 'change', 'value' => $change]; } else { // if change is dust we do nothing (implicitly it's added to the fee) $change = 0; } } else { $fee = $this->determineFee($utxos, $send); $change = $this->determineChange($utxos, $send, $fee); if ($change > 0) { $changeIdx = count($send); // set dummy change output $send[$changeIdx] = ['address' => 'change', 'value' => $change]; // recaculate fee now that we know that we have a change output $fee2 = $this->determineFee($utxos, $send); // unset dummy change output unset($send[$changeIdx]); // if adding the change output made the fee bump up and the change is smaller than the fee // then we're not doing change if ($fee2 > $fee && $fee2 > $change) { $change = 0; } else { $change = $this->determineChange($utxos, $send, $fee2); // if change is not dust we need to add a change output if ($change > Blocktrail::DUST) { $send[$changeIdx] = ['address' => 'change', 'value' => $change]; } else { // if change is dust we do nothing (implicitly it's added to the fee) $change = 0; } } } } $fee = $this->determineFee($utxos, $send); return [$fee, $change]; }
<?php use Blocktrail\SDK\BlocktrailSDK; use Blocktrail\SDK\Connection\Exceptions\ObjectNotFound; use Blocktrail\SDK\TransactionBuilder; use Blocktrail\SDK\Wallet; use Blocktrail\SDK\WalletInterface; require_once __DIR__ . "/../vendor/autoload.php"; $client = new BlocktrailSDK("MY_APIKEY", "MY_APISECRET", "BTC", true, 'v1'); // $client->setVerboseErrors(); // $client->setCurlDebugging(); /** * @var $wallet \Blocktrail\SDK\WalletInterface * @var $backupMnemonic string */ try { /** @var Wallet $wallet */ $wallet = $client->initWallet(["identifier" => "example-wallet", "passphrase" => "example-strong-password"]); } catch (ObjectNotFound $e) { list($wallet, $primaryMnemonic, $backupMnemonic, $blocktrailPublicKeys) = $client->createNewWallet(["identifier" => "example-wallet", "passphrase" => "example-strong-password", "key_index" => 9999]); $wallet->doDiscovery(); } // var_dump($wallet->deleteWebhook()); // var_dump($wallet->setupWebhook("http://www.example.com/wallet/webhook/example-wallet")); $utxos = $wallet->utxos()['data']; $utxo = $utxos[array_rand($utxos)]; $txBuilder = new TransactionBuilder(); var_dump($utxo['hash'], $utxo['idx'], $utxo['value'], $utxo['address'], $utxo['scriptpubkey_hex'], $utxo['path'], $utxo['redeem_script']); $txBuilder->spendOutput($utxo['hash'], $utxo['idx']); $txBuilder->addRecipient($wallet->getNewAddress(), $utxo['value'] / 2); var_dump($wallet->sendTx($txBuilder));