1.0.2 • Published 17 days ago

react-native-web3-wallet-bitcoin v1.0.2

Weekly downloads
-
License
MIT
Repository
github
Last release
17 days ago

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.

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;
        }
      }
    }
  });