Originaler README-Schnappschuss aus dem verknüpften Quell-Repository. Aktuelle Inhalte finden Sie im Repository.
BITCOIN Dart Package
a comprehensive and versatile Dart library that provides robust support for various cryptocurrency transaction types. It is designed to meet your transaction needs for Bitcoin, Dogecoin, Litecoin, Dash, Bitcoin Cash and Bitcoin SV. The library offers features such as spending transactions, address management, Schnorr signatures for Bitcoin, BIP-39 mnemonic phrase generation, hierarchical deterministic (HD) wallet derivation, and Web3 Secret Storage Definition..
For BIP32 HD wallet, BIP39, and Secret storage definitions, please refer to the blockchain_utils package.
This package was inspired by the python-bitcoin-utils package and turned into Dart
Features
Supported Cryptocurrencies
- Bitcoin
- P2PK, P2PKH, P2SH, P2WPKH, P2WSH, P2TR
- Dogecoin
- Litecoin
- P2PK, P2PKH, P2SH, P2WPKH, P2WSH
- Dash
- Bitcoin Cash
- P2PK, P2PKH, P2SH, P2SH32, Token-aware, CashTOKEN
- Bitcoin SV
Transaction Types
ss
This comprehensive package provides robust support for a wide array of Bitcoin transaction types, encompassing the full spectrum of Bitcoin transaction capabilities. Whether you need to execute standard payments, facilitate complex multi-signature wallets, leverage Segregated Witness (SegWit) transactions for lower fees and enhanced scalability, or embrace the privacy and flexibility of Pay-to-Taproot (P2TR) transactions, this package has you covered. Additionally, it empowers users to engage in legacy transactions, create time-locked transactions, and harness the security of multisignature (multisig) transactions. With this package, you can seamlessly navigate the diverse landscape of Bitcoin transactions, ensuring your interactions with the Bitcoin network are secure, efficient, and tailored to your specific needs.
-
P2PKH (Pay-to-Public-Key-Hash): The most common transaction type, it sends funds to a recipient's public key hash. Provides security and anonymity.
-
P2SH (Pay-to-Script-Hash): Allows more complex scripts to be used, enhancing Bitcoin's capabilities by enabling features like multisignature wallets.
-
P2WPKH (Pay-to-Witness-Public-Key-Hash): A Segregated Witness (SegWit) transaction type, it offers reduced fees and improved scalability while maintaining compatibility.
-
P2WSH (Pay-to-Witness-Script-Hash): Another SegWit transaction, it extends the benefits of SegWit to more complex script scenarios, reducing transaction size and fees.
-
P2TR (Pay-to-Taproot): An upgrade aiming to improve privacy and flexibility, allowing users to choose between various scripts and enhance transaction efficiency.
-
Legacy Transactions: Refers to older transaction types used before SegWit, with higher fees and less scalability.
-
Multisignature (Multisig) Transactions: Involves multiple signatures to authorize a Bitcoin transaction, commonly used for security purposes in shared wallets.
-
SegWit Transactions: A collective term for P2WPKH and P2WSH transactions, leveraging segregated witness data to reduce transaction size and fees.
-
Time-Locked Transactions: These transactions have a predetermined time or block height before they can be spent, adding security and functionality to Bitcoin smart contracts.
-
Coinbase Transactions: The first transaction in each block, generating new Bitcoins as a block reward for miners. It includes the miner's payout address.
Tokens on Bitcoin Cash
CashTokens are digital assets that can be created and used on the global, decentralized Bitcoin Cash (BCH) network. These tokens can be issued by any person, organization, or decentralized application.
-
Fungible tokens: Create, sign, spend, and burn.
-
Non-fungible tokens: Create, sign, mint, send, and burn with multiple capabilities (none, mutable, minting).
-
BCMR: Metadata Registries CHIP
Create Transaction
Using this package, you can create a Bitcoin transaction in two ways: either through the BtcTransaction or the BitcoinTransactionBuilder class
-
BtcTransaction: To use the BtcTransaction class, you should have a general understanding of how Bitcoin transactions work, including knowledge of UTXOs, scripts, various types of scripts, Bitcoin addresses, signatures, and more. We created examples and tests to enhance your understanding. An example of this transaction type is explained below, and you can also find numerous examples in the test folder.
-
BitcoinTransactionBuilder: Even with limited prior knowledge, you can utilize this class to send various types of transactions. Below, I've provided an example in which a transaction features 8 distinct input addresses with different types and private keys, as well as 10 different output addresses. Furthermore, additional examples have been prepared, which you can find in the example folder.
PSBT
Find example implementations here
- BIP-0174: Partially Signed Bitcoin Transaction Format
- BIP-0370: PSBT Version 2
- BIP-0371: Taproot Fields for PSBT
- BIP-0373: MuSig2 PSBT Fields
MuSig2 (BIP-327):
Find example implementations here
- Sign/Verify: Supports signing and verifying multisignature transactions using MuSig2
- NonceAgg: Aggregates nonces from multiple participants for secure signature generation.
- KeyAgg: Combines multiple public keys into a single aggregated public key for efficient multisignature verification
Addresses
-
P2PKH A P2PKH (Pay-to-Public-Key-Hash) address in Bitcoin represents ownership of a cryptocurrency wallet by encoding a hashed public key
-
P2WPKH: A P2WPKH (Pay-to-Witness-Public-Key-Hash) address in Bitcoin is a Segregated Witness (SegWit) address that enables more efficient and secure transactions by segregating witness data, enhancing network scalability and security.
-
P2WSH: A P2WSH (Pay-to-Witness-Script-Hash) address in Bitcoin is a Segregated Witness (SegWit) address that allows users to spend bitcoins based on the conditions specified in a witness script, offering improved security and flexibility for complex transaction types.
-
P2TR: A P2TR (Pay-to-Taproot) address in Bitcoin is a type of address that allows users to send and receive bitcoins using the Taproot smart contract, offering enhanced privacy and scalability features.
-
P2SH: A P2SH (Pay-to-Script-Hash) address in Bitcoin is an address type that enables the use of more complex scripting, often associated with multi-signature transactions or other advanced smart contract functionality, enhancing flexibility and security.
-
P2SH(SEGWIT): A P2SH (Pay-to-Script-Hash) Segregated Witness (SegWit) address in Bitcoin combines the benefits of P2SH and SegWit technologies, allowing for enhanced transaction security, reduced fees, and improved scalability.
Addresses specific to Bitcoin Cash
-
P2SH32: Pay to Script Hash 32
-
P2SH32WT: Pay to Script Hash 32 With Token
-
P2PKHWT: Pay to Public Key Hash With Token
Sign
Node Provider
We have integrated three APIs—Mempool, BlockCypher, and Electrum—into the plugin to facilitate network access. These APIs enable seamless retrieval of information such as unspent transactions (UTXO), network fees, sending transactions, receiving transaction details, and fetching account transactions.
EXAMPLES
Key and addresses
-
Private key
final privateKey =
ECPrivate.fromWif("cT33CWKwcV8afBs5NYzeSzeSoGETtAB8izjDjMEuGqyqPoF7fbQR", netVersion: BitcoinNetwork.mainnet.wifNetVer);
final publicKey = privateKey.getPublic();
final signSegwitV0OrLagacy = privateKey.signECDSA();
final signSegwitV1TapprotTransaction = privateKey.signBip340();
final toWif = privateKey.toWif();
final toHex = privateKey.toHex();
-
Public key
final publicKey = ECPublic.fromHex('.....');
final p2pkh = publicKey.toAddress();
final p2wpkh = publicKey.toSegwitAddress();
final p2wsh = publicKey.toP2wshAddress();
final p2tr = publicKey.toTaprootAddress();
final p2pkhInP2sh = publicKey.toP2pkhInP2sh();
final p2wpkhInP2sh = publicKey.toP2wpkhInP2sh();
final p2wshInP2sh = publicKey.toP2wshInP2sh();
final p2pkInP2sh = publicKey.toP2pkInP2sh();
final compressedBytes = publicKey.toCompressedBytes();
final unCompressedBytes = publicKey.toBytes();
final onlyX = publicKey.toXOnlyHex();
final taproot = publicKey.toTapRotHex();
final verify = publicKey.verify();
-
Addresses
final p2pkh = P2pkhAddress.fromAddress(
address: "1Q5odQtVCc4PDmP5ncrp7DSuVbh2ML4Gnb",
network: BitcoinNetwork.mainnet);
final p2wpkh = P2wpkhAddress.fromAddress(
address: "bc1ql5eh45als8sgdkt2drsl344q55g03sj2u9enzz",
network: BitcoinNetwork.mainnet);
final p2wsh = P2wshAddress.fromAddress(
address: "bc1qf90kcg2ktg0wm983cyvhy0jsrj2fmqz26ugf5jz3uw68mtnr8ljsnf8pqe",
network: BitcoinNetwork.mainnet);
final p2tr = P2trAddress.fromAddress(
address: "bc1pmelvn3xz2n3dmcsvk2k99na7kc55ry77zmhg4z39upry05myjthq37f6jk",
network: BitcoinNetwork.mainnet);
final p2sh = P2shAddress.fromAddress(
address: "3HDtvvRMu3yKGFXYFSubTspbhbLagpdKJ7",
network: BitcoinNetwork.mainnet);
final newScript =
Script(script: ["OP_2", public1, public2, "OP_2", "OP_CHECKMULTISIG"]);
final p2wsh3of5Address = P2wshAddress.fromScript(script: newScript);
final p2sh3Of5 =
P2shAddress.fromScript(script: p2wsh3of5Address.toScriptPubKey());
final bitcoinAddress = BitcoinAddress(
"tb1qg07pp4w4q6mzv2uh6n7f32tjq3g2uduwt70krjf0m75xgv9xtmwsp27wuw",
network: BitcoinNetwork.testnet);
final baseAddress = bitcoinAddress.baseAddress;
final type = bitcoinAddress.type;
final litecoin = LitecoinAddress("QQ2nmQTtA4eckgQNhmo7hGERpkBSzffWXK",
network: LitecoinNetwork.testnet);
final bch = BitcoinCashAddress(
"bchtest:qqddwd05aw9etm4pwcmgh5favujyy8uhuu5ypwyh6p",
network: BitcoinCashNetwork.testnet);
final bchp2sh32 = BitcoinCashAddress(
"bchtest:p04ycd9t3pr25jte9tl0vhlamnqqxdhkep6qenrj4fazg4qlxaxevfgeknvm9",
network: BitcoinCashNetwork.testnet);
final bchp2sh32wt = BitcoinCashAddress(
"bchtest:r0nxrdjg297tup5gfe6307mh6u94rn3mnzcq6znepym2ju6ne7ae7kpu9hw64",
network: BitcoinCashNetwork.testnet);
final dash = DashAddress("Xqzs7FnTLc5PqEKWQXjvBLhjC7nMeofkUn",
network: DashNetwork.mainnet);
final doge = DogeAddress("DMovt5M6umoXzs95XNQWSHrHKP5RPwtsJA",
network: DogecoinNetwork.mainnet);
final bitcoinSV = BitcoinSVAddress("15ATt31qva5gkpa6e7ux3if5YSACiAx7s4",
network: BitcoinSVNetwork.mainnet);
Transaction
In the example folder, you'll find various examples tailored for each supported network, including Bitcoin, Dogecoin, Litecoin, Bitcoin Cash, and Dash.
-
With TransactionBuilder
BitcoinTransactionBuilder
supports the Bitcoin, Dogecoin, Dash, and Litecoin networks, allowing for easy creation and signing of various address types.
final service =
await ElectrumWebSocketService.connect("184....");
final provider = ElectrumProvider(service);
final privateKey = ECPrivate.fromHex(
"76257aafc9b954351c7f6445b2d07277f681a5e83d515a1f32ebf54989c2af4f");
final examplePublicKey = privateKey.getPublic();
final spender1 = examplePublicKey.toAddress();
final spender2 = examplePublicKey.toSegwitAddress();
final spender3 = examplePublicKey.toTaprootAddress();
final spender4 = examplePublicKey.toP2pkhInP2sh();
final spender5 = examplePublicKey.toP2pkInP2sh();
final spender6 = examplePublicKey.toP2wshAddress();
final spender7 = examplePublicKey.toP2wpkhInP2sh();
final List<BitcoinBaseAddress> spenders = [
spender1,
spender2,
spender3,
spender4,
spender5,
spender6,
spender7,
];
const network = BitcoinNetwork.testnet;
final List<UtxoWithAddress> accountsUtxos = [];
for (final i in spenders) {
final elctrumUtxos = await provider
.request(ElectrumRequestScriptHashListUnspent(scriptHash: i.pubKeyHash()));
final List<UtxoWithAddress> utxos = elctrumUtxos
.map((e) => UtxoWithAddress(
utxo: e.toUtxo(i.type),
ownerDetails: UtxoAddressDetails(
publicKey: examplePublicKey.toHex(), address: i)))
.toList();
accountsUtxos.addAll(utxos);
}
final sumOfUtxo = accountsUtxos.sumOfUtxosValue();
if (sumOfUtxo == BigInt.zero) {
return;
}
final examplePublicKey2 = ECPublic.fromHex(
"02d82c9860e36f15d7b72aa59e29347f951277c21cd4d34822acdeeadbcff8a546");
final List<BitcoinOutput> outPuts = [
BitcoinOutput(
address: examplePublicKey2.toSegwitAddress(),
value: BtcUtils.toSatoshi("0.00001")),
BitcoinOutput(
address: examplePublicKey2.toTaprootAddress(),
value: BtcUtils.toSatoshi("0.00001")),
BitcoinOutput(
address: examplePublicKey2.toP2pkhInP2sh(),
value: BtcUtils.toSatoshi("0.00001")),
BitcoinOutput(
address: examplePublicKey2.toP2pkInP2sh(),
value: BtcUtils.toSatoshi("0.00001")),
BitcoinOutput(
address: examplePublicKey2.toP2wshAddress(),
value: BtcUtils.toSatoshi("0.00001")),
];
const String memo = "https://github.com/mrtnetwork";
final sumOfOutputs = outPuts.fold(
BigInt.zero, (previousValue, element) => previousValue + element.value);
int transactionSize = BitcoinTransactionBuilder.estimateTransactionSize(
utxos: accountsUtxos,
outputs: [
...outPuts,
BitcoinOutput(
address: examplePublicKey2.toAddress(), value: BigInt.zero)
],
network: network,
memo: memo,
enableRBF: true);
final networkEstimate = await provider.request(ElectrumEstimateFee());
final fee =
BigInt.from(transactionSize) * (networkEstimate ~/ BigInt.from(1000));
final changeValue = sumOfUtxo - (sumOfOutputs + fee);
if (changeValue.isNegative) {
return;
}
if (changeValue > BigInt.zero) {
outPuts.add(BitcoinOutput(
address: examplePublicKey2.toAddress(), value: changeValue));
}
final builder = BitcoinTransactionBuilder(
outPuts: outPuts,
fee: fee,
network: network,
utxos: accountsUtxos,
memo: memo,
inputOrdering: BitcoinOrdering.bip69,
outputOrdering: BitcoinOrdering.bip69,
enableRBF: true);
final transaction =
builder.buildTransaction((trDigest, utxo, publicKey, sighash) {
if (utxo.utxo.isP2tr()) {
return privateKey.signBip340(trDigest, sighash: sighash);
}
return privateKey.signECDSA(trDigest, sighash: sighash);
});
transaction.txId();
final raw = transaction.serialize();
await provider.request(ElectrumRequestBroadCastTransaction(transactionRaw: raw));
-
With ForkedTransactionBuilder
ForkedTransactionBuilder supports the BitcoinCash and bitcoinSV for easy creation and signing of various address types.
For spending network amounts, it functions similarly to TransactionBuilder. However, in this example, the focus is on spending CashToken (BCH Feature). For minting, burning, and creating FTs (Fungible Tokens) and NFTs (Non-Fungible Tokens), you can refer to the example folders
final service = await ElectrumWebSocketService.connect(
"wss://chipnet.imaginary.cash:50004");
final provider = ElectrumProvider(service);
final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString(
"f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b"));
final publicKey = privateKey.getPublic();
const network = BitcoinCashNetwork.testnet;
final p2pkhAddress = BitcoinCashAddress.fromBaseAddress(
publicKey.toP2pkInP2sh(useBCHP2sh32: true));
final receiver1 = P2pkhAddress.fromHash160(
addrHash: publicKey.toAddress().addressProgram,
type: P2pkhAddressType.p2pkhwt);
final elctrumUtxos = await provider.request(ElectrumRequestScriptHashListUnspent(
scriptHash: p2pkhAddress.baseAddress.pubKeyHash(),
includeTokens: true,
));
final List<UtxoWithAddress> utxos = elctrumUtxos
.map((e) => UtxoWithAddress(
utxo: e.toUtxo(p2pkhAddress.type),
ownerDetails: UtxoAddressDetails(
publicKey: publicKey.toHex(), address: p2pkhAddress.baseAddress)))
.toList()
.where((element) =>
element.utxo.token?.category ==
"4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430" ||
element.utxo.token == null)
.toList();
final sumOfUtxo = utxos.sumOfUtxosValue();
if (sumOfUtxo == BigInt.zero) {
return;
}
final CashToken token = elctrumUtxos
.firstWhere((e) =>
e.token?.category ==
"4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430")
.token!;
final sumofTokenUtxos = utxos
.where((element) =>
element.utxo.token?.category ==
"4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430")
.fold(
BigInt.zero,
(previousValue, element) =>
previousValue + element.utxo.token!.amount);
final bchTransaction = ForkedTransactionBuilder(
outPuts: [
BitcoinOutput(
address: p2pkhAddress.baseAddress,
value: sumOfUtxo -
(BtcUtils.toSatoshi("0.00002") + BtcUtils.toSatoshi("0.00003")),
),
BitcoinTokenOutput(
utxoHash: utxos.first.utxo.txHash,
address: receiver1,
value: BtcUtils.toSatoshi("0.00001"),
token: token.copyWith(amount: BigInt.from(15))),
BitcoinTokenOutput(
utxoHash: utxos.first.utxo.txHash,
address: p2pkhAddress.baseAddress,
value: BtcUtils.toSatoshi("0.00001"),
token: token.copyWith(amount: sumofTokenUtxos - BigInt.from(15))),
],
fee: BtcUtils.toSatoshi("0.00003"),
network: network,
utxos: utxos,
);
final transaaction =
bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) {
return privateKey.signECDSA(trDigest, sighash: sighash);
});
transaaction.txId();
transaaction.getSize();
final transactionRaw = transaaction.toHex();
await provider
.request(ElectrumRequestBroadCastTransaction(transactionRaw: transactionRaw));
-
With BtcTransaction
-
Spend P2TR UTXO
final txin = utxo
.map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout))
.toList();
final List<TxOutput> txOut = receiver
.map((e) =>
TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey()))
.toList();
final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true);
final List<TxWitnessInput> witnesses = [];
for (int i = 0; i < txin.length; i++) {
final txDigit = tx.getTransactionTaprootDigset(
txIndex: i,
scriptPubKeys:
utxo.map((e) => e.ownerDetails.address.toScriptPubKey()).toList(),
amounts: utxo.map((e) => e.utxo.value).toList(),
script: const Script(script: []),
sighash: TAPROOT_SIGHASH_ALL,
extFlags: 0,
);
final signedTx = sign(txDigit, utxo[i].public().toHex(), SIGHASH_ALL);
witnesses.add(TxWitnessInput(stack: [signedTx]));
}
tx.witnesses.addAll(witnesses);
tx.TxId()
tx.GetVSize()
tx.Serialize()
-
Spend P2PKH UTXO
final txin = utxo
.map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout))
.toList();
final List<TxOutput> txOut = receiver
.map((e) =>
TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey()))
.toList();
final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false);
for (int i = 0; i < txin.length; i++) {
final txDigit = tx.getTransactionDigest(
txInIndex: i,
script: utxo[i].public().toAddress().toScriptPubKey(),
);
final signedTx = sign(txDigit, utxo[i].public().toHex(), SIGHASH_ALL);
txin[i].scriptSig = Script(script: [signedTx, utxo[i].public().toHex()]);
}
Node provider
I haven't implemented any specific HTTP service or socket service within this plugin. The reason is that different applications may use various plugins or methods to interact with network protocols. However, I have included numerous examples to demonstrate how Electrum and HTTP services can be utilized. You can leverage these examples as a reference to easily create services tailored to your application's specific needs. examples
- Electrum API (Websocket, TCP, SSL)
const network = BitcoinNetwork.mainnet;
final service =
await ElectrumSSLService.connect("testnet.aranguren.org:51002");
final provider = ElectrumProvider(service);
final address = P2trAddress.fromAddress(address: ".....", network: network);
final accountBalance = await provider
.request(ElectrumGetScriptHashBalance(scriptHash: address.pubKeyHash()));
final accountUnspend = await provider
.request(ElectrumRequestScriptHashListUnspent(scriptHash: address.pubKeyHash()));
final accountHistory = await provider
.request(ElectrumScriptHashGetHistory(scriptHash: address.pubKeyHash()));
final broadcastTransaction = await provider
.request(ElectrumRequestBroadCastTransaction(transactionRaw: "txDigest"));
- Explorer API (blockCypher, mempool)
const network = BitcoinNetwork.mainnet;
final service = BitcoinApiService();
final api = ApiProvider.fromBlocCypher(network, service);
final fee = await api.getNetworkFeeRate();
final transactionId = await api.sendRawTransaction("txDigest");
final utxo = await api.getAccountUtxo(address);
final transaction =
await api.getTransaction<BlockCypherTransaction>(transactionId);
final accountTransactions =
await api.getAccountTransactions<MempoolTransaction>('address');
Contributing
Contributions are welcome! Please follow these guidelines:
- Fork the repository and create a new branch.
- Make your changes and ensure tests pass.
- Submit a pull request with a detailed description of your changes.
Feature requests and bugs
Please file feature requests and bugs in the issue tracker.