Skip to content

Commit fb4f940

Browse files
committed
Initial commit
0 parents  commit fb4f940

30 files changed

+3664
-0
lines changed

.gitignore

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Miscellaneous
2+
*.class
3+
*.log
4+
*.pyc
5+
*.swp
6+
.DS_Store
7+
.atom/
8+
.buildlog/
9+
.history
10+
.svn/
11+
12+
# IntelliJ related
13+
*.iml
14+
*.ipr
15+
*.iws
16+
.idea/
17+
18+
# Visual Studio Code related
19+
.vscode/
20+
21+
# Flutter/Dart/Pub related
22+
**/doc/api/
23+
.dart_tool/
24+
.flutter-plugins
25+
.packages
26+
.pub-cache/
27+
.pub/
28+
build/
29+
30+
# Android related
31+
**/android/**/gradle-wrapper.jar
32+
**/android/.gradle
33+
**/android/captures/
34+
**/android/gradlew
35+
**/android/gradlew.bat
36+
**/android/local.properties
37+
**/android/**/GeneratedPluginRegistrant.java
38+
39+
# iOS/XCode related
40+
**/ios/**/*.mode1v3
41+
**/ios/**/*.mode2v3
42+
**/ios/**/*.moved-aside
43+
**/ios/**/*.pbxuser
44+
**/ios/**/*.perspectivev3
45+
**/ios/**/*sync/
46+
**/ios/**/.sconsign.dblite
47+
**/ios/**/.tags*
48+
**/ios/**/.vagrant/
49+
**/ios/**/DerivedData/
50+
**/ios/**/Icon?
51+
**/ios/**/Pods/
52+
**/ios/**/.symlinks/
53+
**/ios/**/profile
54+
**/ios/**/xcuserdata
55+
**/ios/.generated/
56+
**/ios/Flutter/App.framework
57+
**/ios/Flutter/Flutter.framework
58+
**/ios/Flutter/Generated.xcconfig
59+
**/ios/Flutter/app.flx
60+
**/ios/Flutter/app.zip
61+
**/ios/Flutter/flutter_assets/
62+
**/ios/ServiceDefinitions.json
63+
**/ios/Runner/GeneratedPluginRegistrant.*
64+
65+
# Exceptions to above rules.
66+
!**/ios/**/default.mode1v3
67+
!**/ios/**/default.mode2v3
68+
!**/ios/**/default.pbxuser
69+
!**/ios/**/default.perspectivev3
70+
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages

.metadata

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# This file tracks properties of this Flutter project.
2+
# Used by Flutter tool to assess capabilities and perform upgrades etc.
3+
#
4+
# This file should be version controlled and should not be manually edited.
5+
6+
version:
7+
revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b
8+
channel: beta
9+
10+
project_type: package

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## [0.0.1] - TODO: Add release date.
2+
3+
* TODO: Describe initial release.

LICENSE

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
TODO: Add your license here.

README.md

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# Bitbox lite
2+
3+
Lite version of [Bitcoin.com's Bitbox JS library](https://developer.bitcoin.com/bitbox/) built for Flutter and with integration to Bitcoin.com's REST API.
4+
Works with mainnet and testnet.
5+
6+
## Getting Started
7+
8+
### 1) Depend on it
9+
After you download the repository, add a local dependency into the pubspec.yaml of your testing or development projet:
10+
11+
```
12+
dependencies:
13+
bitbox_plugin:
14+
path: <path to the directory>/
15+
```
16+
17+
### 2) Import it
18+
19+
```
20+
// There's a good chance your own project will use similar names as some of the
21+
// classes in this library. A simple way to create some order is to import the
22+
// library with Bitbox prefix:
23+
import 'package:bitbox/bitbox.dart' as Bitbox;
24+
```
25+
26+
### 2) Use it
27+
```
28+
// set this to true to use testnet
29+
final testnet = true;
30+
31+
// After running the code for the first time, depositing an amount to the address
32+
// displayed in the console, and waiting for confirmation, paste the generated
33+
// mnemonic here, so the code continues below with address withdrawal
34+
String mnemonic = "";
35+
36+
if (mnemonic == "") {
37+
// generate 12-word (128bit) mnemonic
38+
mnemonic = Bitbox.Mnemonic.generate();
39+
40+
print(mnemonic);
41+
}
42+
43+
// generate a seed from mnemonic
44+
final seed = Bitbox.Mnemonic.toSeed(mnemonic);
45+
46+
// create an instance of Bitbox.HDNode for mainnet
47+
final masterNode = Bitbox.HDNode.fromSeed(seed, testnet);
48+
49+
// This format is compatible with Bitcoin.com wallet.
50+
// Other wallets use Change to m/44'/145'/0'/0
51+
final accountDerivationPath = "m/44'/0'/0'/0";
52+
53+
// create an account node using the provided derivation path
54+
final accountNode = masterNode.derivePath(accountDerivationPath);
55+
56+
// get account's extended private key
57+
final accountXPriv = accountNode.toXPriv();
58+
59+
// create a Bitbox.HDNode instance of the first child in this account
60+
final childNode = accountNode.derive(0);
61+
62+
// get an address of the child
63+
final address = childNode.toCashAddress();
64+
65+
// if you are using testnet, set the appropriate rest api url before making any API
66+
// calls (like getting address or transaction details or broadcasting a transaction
67+
if (testnet) {
68+
Bitbox.Bitbox.setRestUrl(restUrl: Bitbox.Bitbox.trestUrl);
69+
}
70+
71+
// get address details
72+
final addressDetails = await Bitbox.Address.details(address);
73+
74+
print(addressDetails);
75+
76+
// If there is a confirmed balance, attempt to withdraw it
77+
if (addressDetails["balance"] > 0) {
78+
final builder = Bitbox.Bitbox.transactionBuilder(testnet: testnet);
79+
80+
// retrieve address' utxos from the rest api
81+
final utxos = await Bitbox.Address.utxo(address) as List<Bitbox.Utxo>;
82+
83+
// placeholder for input signatures
84+
final signatures = <Map>[];
85+
86+
// placeholder for total input balance
87+
int totalBalance = 0;
88+
89+
// iterate through the list of address utxos and use them as inputs for the
90+
// withdrawal transaction
91+
utxos.forEach((Bitbox.Utxo utxo) {
92+
// add the utxo as an input for the transaction
93+
builder.addInput(utxo.txid, utxo.vout);
94+
95+
// add a signature to the list to be used later
96+
signatures.add({
97+
"vin": signatures.length,
98+
"key_pair": childNode.keyPair,
99+
"original_amount": utxo.satoshis
100+
});
101+
102+
totalBalance += utxo.satoshis;
103+
});
104+
105+
// set an address to send the remaining balance to
106+
final outputAddress = "";
107+
108+
// if there is an unspent balance, create a spending transaction
109+
if (totalBalance > 0 && outputAddress != "") {
110+
// calculate the fee based on number of inputs and one expected output
111+
final fee = Bitbox.BitcoinCash.getByteCount(signatures.length, 1);
112+
113+
// calculate how much balance will be left over to spend after the fee
114+
final sendAmount = totalBalance - fee;
115+
116+
// add the output based on the address provided in the testing data
117+
builder.addOutput(outputAddress, sendAmount);
118+
119+
// sign all inputs
120+
signatures.forEach((signature) {
121+
builder.sign(signature["vin"], signature["key_pair"],
122+
signature["original_amount"]);
123+
});
124+
125+
// build the transaction
126+
final tx = builder.build();
127+
128+
// broadcast the transaction
129+
final txid = await Bitbox.RawTransactions.sendRawTransaction(tx.toHex());
130+
131+
// Yatta!
132+
print("Transaction broadcasted: $txid");
133+
} else if (totalBalance > 0) {
134+
print("Enter an output address to test withdrawal transaction");
135+
}
136+
}
137+
```
138+
139+
For further documentation, refer to apidoc of this repository
140+
141+
## Testing
142+
143+
There are some unit tests in test/bitbox_test.dart. They use data generated from the original [Bitbox for JS](https://developer.bitcoin.com/bitbox/) and compare them with the output of this library.
144+
The following is tested for both testnet and mainnet:
145+
- Generating the master node from mnemonic and comparing both its XPub and XPriv
146+
- Generating an account node and comparing XPub and XPriv
147+
- Generating 10 test childs and comparing their private keys and addresses
148+
- Conversion of cashAddr format to legacy and vice versa
149+
- Fetching address details for the rest API
150+
- Fetching utxos for addresses with balance
151+
- Building a transaction spending the deposited balance and comparing its hash with Bitbox JS output
152+
- Optionally broadcasting the transaction and
153+
154+
To run the test:
155+
156+
1. Generate the testing data by runing create_test_data.js with your local nodeJS engine
157+
2. Update bitbox_test.dart with the path to the generated test_data.json file
158+
3. Run bitbox_test.dart
159+
Optionally between step 1) and 2), send some balance to either testnet or mainnet addresses (or both), wait for confirmations and run create_test_data.js again to update the data and generate testing transactions
160+
161+
162+
## Acknowledgments
163+
164+
This is a port of the original JS-based Bitbox library by Gabriel Cordana and Bitcoin.com, so first of all huge thanks to Gabriel and the whole Bitcoin.com team for doing so much for the BCH ecosystem.
165+
166+
Also I either re-used a lot of code originally wrote for Bitcoin or called some libraries (bip39 and bip32) by [anicdh](https://github.com/anicdh), so Thanks big time to him. Without that it would take me many more weeks!

lib/bitbox.dart

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
library bitbox;
2+
3+
export 'src/account.dart';
4+
export 'src/address.dart';
5+
export 'src/bitbox.dart';
6+
export 'src/bitcoincash.dart';
7+
export 'src/ecpair.dart';
8+
export 'src/hdnode.dart';
9+
export 'src/mnemonic.dart';
10+
export 'src/rawtransactions.dart';
11+
export 'src/transaction.dart';
12+
export 'src/transactionbuilder.dart';
13+
export 'src/varuint.dart';

lib/src/account.dart

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import 'hdnode.dart';
2+
3+
/// BIP32 account with simple helper methods
4+
class Account {
5+
final HDNode accountNode;
6+
7+
int currentChild = 0;
8+
9+
Account(this.accountNode, [this.currentChild]);
10+
11+
/// Returns address at the current position
12+
String getCurrentAddress([legacyFormat = true]) {
13+
if (legacyFormat) {
14+
return accountNode.derive(currentChild).toLegacyAddress();
15+
} else {
16+
return accountNode.derive(currentChild).toCashAddress();
17+
}
18+
}
19+
20+
/// moves the position forward and returns an address from the new position
21+
String getNextAddress([legacyFormat = true]) {
22+
if (legacyFormat) {
23+
return accountNode.derive(++currentChild).toLegacyAddress();
24+
} else {
25+
return accountNode.derive(++currentChild).toCashAddress();
26+
}
27+
}
28+
}

0 commit comments

Comments
 (0)