react-native-web3-wallet-bitcoin
v1.0.2
Published
This is a safe web3 wallet tools, to help with develop wallet applications quickly.
Downloads
10
Maintainers
Readme
React Native Web3 Wallet
Web3 Wallet in React Native (use bitcoinjs-lib)
Welcome
This is a safe web3 wallet tools, to help with develop wallet applications quickly.
- main branch support ethers5.x
- ether6.x branch support ethers6.x
- bitcoin branch support bitcoinjs
Installation
npm install "github:heroims/react-native-web3-wallet#bitcoin" --save
npm install rn-nodeify --save
npm install
(at this point the postinstall
does rn-nodeify --install buffer,stream,assert,events,crypto,vm,process --hack
)
npx pod-install
Usage
Import
import {
bitcoin_lib,
bip371_lib,
psbtutils_lib,
createWallet,
importMnemonic,
importWIF,
importXpriv,
getBitcoinNodeFromMnemonic,
getBitcoinNodeFromWIF,
getBitcoinNodeFromXpriv,
getBitcoinAddress,
createPayment,
getInputData,
toPaddedHexString,
} from '@react-native-web3-wallet/bitcoin';
Wallet
Create Wallet
/**
*
* 0 BTC
*
* Legacy "m/44'/0'/0'/0/0"
* Change "m/44'/0'/0'/1/0"
* SegwitCompatible "m/49'/0'/0'/0/0"
* SegwitNative "m/84'/0'/0'/0/0"
* Taproot "m/86'/0'/0'/0/0"
*
* */
createWallet('password', "m/44'/0'/0'/0/0")
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
});
Print Results
{
"address": ...,
"mnemonic": ...,
"shuffleMnemonic": ...,
"privateKey" : ...,//option
"publicKey" : ...,//option
"WIF" : ...,//option
"xpriv" : ...,//option
"xpub" : ...,//option
}
Get Bitcoin Node
getBitcoinNodeFromMnemonic('mnemonic', 'password', "m/44'/0'/0'/0/0")
getBitcoinNodeFromWIF('wif')
getBitcoinNodeFromXpriv('xpriv')
Get Address
console.log(
'Legacy',
getBitcoinAddress(node.publicKey, bitcoin.networks.bitcoin, 44),
);
console.log(
'SegwitCompatible',
getBitcoinAddress(node.publicKey, bitcoin.networks.bitcoin, 49),
);
console.log(
'SegwitNative',
getBitcoinAddress(node.publicKey, bitcoin.networks.bitcoin, 84),
);
Print Address string
Transcation
Rough logic reference bitcoinjs
const feeValue = 5000; //Please calculate by yourself
const amount = 0.01;
const amountSatoshis = amount * 1e8;
const changeAddress = walletAddress;
const keyPair = getBitcoinNodeFromWIF(
ownerWIF,
bitcoin_lib.networks.testnet,
);
const psbt = new bitcoin_lib.Psbt({
network: bitcoin_lib.networks.testnet,
});
fetch(
`https://api.blockcypher.com/v1/btc/${chainName}/addrs/${walletAddress}?unspentOnly=true&includeScript=true&token=${blockcypherToken}`,
)
.then(response => response.json())
.then(async data => {
console.log(data);
const utxos = data.txrefs;
let use_utxos_values = 0;
for (let index = 0; index < utxos.length; index++) {
const utxo = utxos[index];
use_utxos_values += utxo.value;
// //not segwit
// const response = await fetch(
// `https://api.blockcypher.com/v1/btc/${chainName}/txs/${utxo.tx_hash}/?includeHex=true&token=${blockcypherToken}`,
// );
// const txs = await response.json();
// const inputData = getInputData(
// utxo.tx_hash,
// utxo.tx_output_n,
// false,
// {
// script: utxo.script,
// value: utxo.value,
// txHex: txs.hex,
// },
// );
//segwit
const inputData = getInputData(
utxo.tx_hash,
utxo.tx_output_n,
true,
{script: utxo.script, value: utxo.value},
);
psbt.addInput(inputData);
if (use_utxos_values > amountSatoshis + feeValue) {
break;
}
}
psbt.addOutput({
address: targetAddress,
value: amountSatoshis,
});
psbt.addOutput({
address: changeAddress,
value: use_utxos_values - amountSatoshis,
});
psbt.data.inputs.forEach((value, index) => {
psbt.signInput(index, keyPair);
});
psbt.finalizeAllInputs();
const txHex = psbt.extractTransaction().toHex();
console.log('txHex', txHex);
fetch(
`https://api.blockcypher.com/v1/btc/${chainName}/txs/push?token=${blockcypherToken}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tx: txHex,
}),
},
)
.then(response => response.json())
.then(txData => console.log(txData))
.catch(error => console.error(error));
});
Omni USDT
const amount = 10;
const feeValue = 5000; //Please calculate by yourself
const fundValue = 546; //minimum transfer number
const changeAddress = walletAddress;
const keyPair = getBitcoinNodeFromWIF(
ownerWIF,
bitcoin_lib.networks.testnet,
);
const psbt = new bitcoin_lib.Psbt({
network: bitcoin_lib.networks.testnet,
});
fetch(
`https://api.blockcypher.com/v1/btc/${chainName}/addrs/${walletAddress}?unspentOnly=true&includeScript=true&token=${blockcypherToken}`,
)
.then(response => response.json())
.then(async data => {
console.log(data);
const utxos = data.txrefs;
let use_utxos_values = 0;
for (let index = 0; index < utxos.length; index++) {
const utxo = utxos[index];
use_utxos_values += utxo.value;
//not segwit
const response = await fetch(
`https://api.blockcypher.com/v1/btc/${chainName}/txs/${utxo.tx_hash}/?includeHex=true&token=${blockcypherToken}`,
);
const txs = await response.json();
const inputData = getInputData(
utxo.tx_hash,
utxo.tx_output_n,
false,
{
script: utxo.script,
value: utxo.value,
txHex: txs.hex,
},
);
// //segwit
// const inputData = getInputData(
// utxo.tx_hash,
// utxo.tx_output_n,
// true,
// {script: utxo.script, value: utxo.value},
// );
psbt.addInput(inputData);
if (use_utxos_values > fundValue + feeValue) {
break;
}
}
psbt.addOutput({
address: targetAddress,
value: fundValue,
});
const simple_send = [
'6f6d6e69', // omni
'0000', // version
toPaddedHexString(31, 8), //Property ID: 31 for Tether Property
toPaddedHexString(amount, 16), // amount
].join('');
const omniData = [Buffer.from(simple_send, 'hex')];
const omniOutput = bitcoin_lib.payments.embed({
data: omniData,
}).output;
psbt.addOutput({
script: omniOutput!,
value: 0,
});
psbt.addOutput({
address: changeAddress,
value: use_utxos_values - fundValue,
});
psbt.data.inputs.forEach((value, index) => {
psbt.signInput(index, keyPair);
});
psbt.finalizeAllInputs();
const txHex = psbt.extractTransaction().toHex();
console.log('txHex', txHex);
fetch(
`https://api.blockcypher.com/v1/btc/${chainName}/txs/push?token=${blockcypherToken}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tx: txHex,
}),
},
)
.then(response => response.json())
.then(txData => console.log(txData))
.catch(error => console.error(error));
});
Taproot
const feeValue = 5000; //Please calculate by yourself
const amount = 0.01;
const amountSatoshis = amount * 1e8;
const changeAddress = walletAddress;
const keyPair = getBitcoinNodeFromWIF(
ownerWIF,
bitcoin_lib.networks.testnet,
);
const psbt = new bitcoin_lib.Psbt({
network: bitcoin_lib.networks.testnet,
});
const internalPublickKey = bip371_lib.toXOnly(keyPair.publicKey);
fetch(
`https://api.blockcypher.com/v1/btc/${chainName}/addrs/${walletAddress}?unspentOnly=true&includeScript=true&token=${blockcypherToken}`,
)
.then(response => response.json())
.then(async data => {
console.log(data);
const utxos = data.txrefs;
let use_utxos_values = 0;
for (let index = 0; index < utxos.length; index++) {
const utxo = utxos[index];
use_utxos_values += utxo.value;
psbt.addInput({
hash: utxo.tx_hash,
index: utxo.tx_output_n,
witnessUtxo: {
script: utxo.script,
value: utxo.value,
},
tapInternalKey: internalPublickKey,
});
if (use_utxos_values > amountSatoshis + feeValue) {
break;
}
}
psbt.addOutput({
address: targetAddress,
value: amountSatoshis,
});
psbt.addOutput({
address: changeAddress,
value: use_utxos_values - amountSatoshis,
});
const tweakedChildNode = keyPair.tweak(
bitcoin_lib.crypto.taggedHash(
'TapTweak',
internalPublickKey,
),
);
psbt.data.inputs.forEach((value, index) => {
psbt.signInput(index, tweakedChildNode);
});
psbt.finalizeAllInputs();
const txHex = psbt.extractTransaction().toHex();
console.log('txHex', txHex);
fetch(
`https://api.blockcypher.com/v1/btc/${chainName}/txs/push?token=${blockcypherToken}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tx: txHex,
}),
},
)
.then(response => response.json())
.then(txData => console.log(txData))
.catch(error => console.error(error));
});
Inscription
const fundValue = 549; //minimum transfer number
const changeAddress = walletAddress;
const bitcoinNetwork = bitcoin_lib.networks.testnet;
const keyPair = getBitcoinNodeFromWIF(ownerWIF, bitcoinNetwork);
const psbt = new bitcoin_lib.Psbt({
network: bitcoinNetwork,
});
const internalPublickKey = bip371_lib.toXOnly(keyPair.publicKey);
const encoder = new TextEncoder();
const inscriptionContentType = Buffer.from(
encoder.encode('text/plain;charset=utf-8'),
);
const inscriptionContent = Buffer.from(encoder.encode('123456'));
const inscriptionProtocolId = Buffer.from(encoder.encode('ord'));
const txSize = 600 + Math.floor(inscriptionContent.length / 4);
const feeRate = 2;
const minersFee = txSize * feeRate;
const feeValue = 550 + minersFee;
const inscriptionArray = [
internalPublickKey,
bitcoin_lib.opcodes.OP_CHECKSIG,
bitcoin_lib.opcodes.OP_0,
bitcoin_lib.opcodes.OP_IF,
inscriptionProtocolId,
1,
1, // ISSUE, Buffer.from([1]) is replaced to 05 rather asMinimalOP than 0101 here https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/src/script.js#L53
// this may not be an issue but it generates a different script address. Unsure if ordinals indexer detect 05 as the content type separator
inscriptionContentType,
bitcoin_lib.opcodes.OP_0,
inscriptionContent,
bitcoin_lib.opcodes.OP_ENDIF,
];
const outputScript = bitcoin_lib.script.compile(inscriptionArray);
const scriptTree = {
output: outputScript,
redeemVersion: 192,
};
const scriptTaproot = bitcoin_lib.payments.p2tr({
internalPubkey: internalPublickKey,
scriptTree,
redeem: scriptTree,
network: bitcoinNetwork,
});
const cblock =
scriptTaproot.witness?.[
scriptTaproot.witness!.length - 1
].toString('hex');
const tapLeafScript: {
leafVersion: number;
script: Buffer;
controlBlock: Buffer;
} = {
leafVersion: scriptTaproot.redeemVersion!, // 192 0xc0
script: outputScript,
controlBlock: Buffer.from(cblock ?? '', 'hex'),
};
fetch(
`https://api.blockcypher.com/v1/btc/${chainName}/addrs/${walletAddress}?unspentOnly=true&includeScript=true&token=${blockcypherToken}`,
)
.then(response => response.json())
.then(async data => {
console.log(data);
const utxos = data.txrefs;
let use_utxos_values = 0;
for (let index = 0; index < utxos.length; index++) {
const utxo = utxos[index];
use_utxos_values += utxo.value;
psbt.addInput({
hash: utxo.tx_hash,
index: utxo.tx_output_n,
witnessUtxo: {
value: utxo.value,
script: scriptTaproot.output ?? utxo.script,
},
tapLeafScript: [tapLeafScript],
});
if (use_utxos_values > feeValue + fundValue) {
break;
}
}
psbt.addOutput({
address: targetAddress,
value: fundValue,
});
psbt.addOutput({
address: changeAddress,
value: use_utxos_values - fundValue,
});
psbt.data.inputs.forEach((value, index) => {
psbt.signInput(index, keyPair);
});
psbt.data.inputs.forEach((value, index) => {
const witness = [value.tapScriptSig![0].signature]
.concat(outputScript)
.concat(tapLeafScript.controlBlock);
psbt.finalizeInput(index, () => {
return {
finalScriptWitness:
psbtutils_lib.witnessStackToScriptWitness(witness),
};
});
});
const txHex = psbt.extractTransaction().toHex();
console.log('txHex', txHex);
fetch(
`https://api.blockcypher.com/v1/btc/${chainName}/txs/push?token=${blockcypherToken}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tx: txHex,
}),
},
)
.then(response => response.json())
.then(txData => console.log(txData))
.catch(error => console.error(error));
});
Send Rune
const fundValue = 549; //minimum transfer number
const changeAddress = walletAddress;
const bitcoinNetwork = bitcoin_lib.networks.testnet;
const keyPair = getBitcoinNodeFromWIF(ownerWIF, bitcoinNetwork);
const psbt = new bitcoin_lib.Psbt({
network: bitcoinNetwork,
});
const runeid = 'xxxxx:xxx';
const internalPublickKey = bip371_lib.toXOnly(keyPair.publicKey);
const changedRuneAmount = 0;
const runeAmount = 10;
let payload: any[] = [];
let runeId = RuneId.fromString(runeid);
//Build Runestore
varint.encodeToVec(0, payload);
varint.encodeToVec(runeId.block, payload);
varint.encodeToVec(runeId.tx, payload);
varint.encodeToVec(runeAmount, payload);
varint.encodeToVec(1, payload);
const inscriptionContent = Buffer.from(new Uint8Array(payload));
const txSize = 600 + Math.floor(inscriptionContent.length / 4);
const feeRate = 2;
const minersFee = txSize * feeRate;
const feeValue = 550 + minersFee;
// const outputScript = bitcoin_lib.payments.embed({
// data: [
// bitcoin_lib.opcodes.OP_13,
// inscriptionContent,
// ],
// }).output;
const inscriptionArray = [
bitcoin_lib.opcodes.OP_RETURN,
bitcoin_lib.opcodes.OP_13,
inscriptionContent,
];
const outputScript = bitcoin_lib.script.compile(inscriptionArray);
const scriptTree = {
output: outputScript,
};
const scriptTaproot = bitcoin_lib.payments.p2tr({
internalPubkey: internalPublickKey,
scriptTree,
redeem: scriptTree,
network: bitcoinNetwork,
});
const cblock =
scriptTaproot.witness?.[
scriptTaproot.witness!.length - 1
].toString('hex');
const tapLeafScript: {
leafVersion: number;
script: Buffer;
controlBlock: Buffer;
} = {
leafVersion: scriptTaproot.redeemVersion!, // 192 0xc0
script: outputScript,
controlBlock: Buffer.from(cblock ?? '', 'hex'),
};
fetch(
`https://api.blockcypher.com/v1/btc/${chainName}/addrs/${walletAddress}?unspentOnly=true&includeScript=true&token=${blockcypherToken}`,
)
.then(response => response.json())
.then(async data => {
console.log(data);
const utxos = data.txrefs;
let use_utxos_values = 0;
for (let index = 0; index < utxos.length; index++) {
const utxo = utxos[index];
use_utxos_values += utxo.value;
psbt.addInput({
hash: utxo.tx_hash,
index: utxo.tx_output_n,
witnessUtxo: {
value: utxo.value,
script: utxo.script,
},
tapLeafScript: [tapLeafScript],
});
if (use_utxos_values > feeValue + fundValue) {
break;
}
}
psbt.addOutput({
address: targetAddress,
value: fundValue,
});
psbt.addOutput({
address: changeAddress,
value: use_utxos_values - fundValue,
});
psbt.data.inputs.forEach((value, index) => {
psbt.signInput(index, keyPair);
});
psbt.data.inputs.forEach((value, index) => {
const witness = [value.tapScriptSig![0].signature]
.concat(outputScript)
.concat(tapLeafScript.controlBlock);
psbt.finalizeInput(index, () => {
return {
finalScriptWitness:
psbtutils_lib.witnessStackToScriptWitness(witness),
};
});
});
const txHex = psbt.extractTransaction().toHex();
console.log('txHex', txHex);
fetch(
`https://api.blockcypher.com/v1/btc/${chainName}/txs/push?token=${blockcypherToken}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tx: txHex,
}),
},
)
.then(response => response.json())
.then(txData => console.log(txData))
.catch(error => console.error(error));
});
Get Rune
fetch(
`https://api.blockcypher.com/v1/btc/${chainName}/addrs/${walletAddress}?unspentOnly=true&includeScript=true&token=${blockcypherToken}`,
)
.then(response => response.json())
.then(async data => {
console.log(data);
const utxos = data.txrefs;
let changedRuneAmount = 0;
const runeid = 'xxxxx:xxx';
for (let index = 0; index < utxos.length; index++) {
const utxo = utxos[index];
const script = bitcoin_lib.script.toASM(
Buffer.from(utxo.script, 'hex'),
);
const insts = script.split(' ');
if(insts.length > 2 && insts[0] === 'OP_RETURN' && insts[1] === 'OP_13'){
const bytes = Buffer.from(insts[1], 'hex');
const rune = varint.decode(bytes);
const block = rune[3];
const tx = rune[4];
const amount = rune[5];
if(runeid === `${block}:${tx}`){
changedRuneAmount += rune.value;
}
}
}
});