@getsafle/vault-bitcoin-controller
Advanced tools
Comparing version 2.0.1 to 2.0.2
@@ -50,2 +50,15 @@ ### 1.0.0 (2021-12-27) | ||
##### Enabled message and transaction signing for imported accounts | ||
##### Enabled message and transaction signing for imported accounts | ||
### 2.0.2 (2023-11-18) | ||
##### Implement get satoshi per byte functionality | ||
- Standardize getFees() to provide transaction fee in sathoshi and transaction size | ||
- Added HD PATH for testnet | ||
- Updated tests to add assertions | ||
- Added constants for base URLs | ||
- Updated node version in CI | ||
- Updated raw transaction paramaters, amount to be accepted in satoshi | ||
- Added badges | ||
- Updated license in package json |
{ | ||
"name": "@getsafle/vault-bitcoin-controller", | ||
"version": "2.0.1", | ||
"version": "2.0.2", | ||
"description": "", | ||
@@ -12,3 +12,8 @@ "engines": { | ||
"lint:fix": "eslint --fix . --ext .js", | ||
"test": "mocha --timeout 15000" | ||
"test": "mocha \"test/**.js\" --timeout 15000", | ||
"test:coverage": "npm run cover:unit && npm run cover:report", | ||
"test:coveragehtml": "npm run cover:unit && npm run cover:reporthtml", | ||
"cover:unit": "nyc --silent npm run test", | ||
"cover:report": "nyc report --reporter=lcov --reporter=text --report-dir='./jscoverage'", | ||
"cover:reporthtml": "nyc report --reporter=html --report-dir='./jscoverage'" | ||
}, | ||
@@ -24,3 +29,3 @@ "repository": { | ||
"author": "", | ||
"license": "ISC", | ||
"license": "MIT", | ||
"bugs": { | ||
@@ -45,4 +50,5 @@ "url": "https://github.com/getsafle/vault-bitcoin-controller/issues" | ||
"eslint-plugin-node": "^11.1.0", | ||
"mocha": "^8.1.3" | ||
"mocha": "^8.1.3", | ||
"nyc": "^15.0.0" | ||
} | ||
} |
@@ -1,3 +0,8 @@ | ||
# bitcoin-controller | ||
# bitcoin-controller<code><a href="https://www.docker.com/" target="_blank"><img height="50" src="https://bitcoin.org/img/icons/logotop.svg?1700824099"></a></code> | ||
[![npm version](https://badge.fury.io/js/@getsafle%2Fvault-bitcoin-controller.svg)](https://badge.fury.io/js/@getsafle%2Fvault-bitcoin-controller) <img alt="Static Badge" src="https://img.shields.io/badge/License-MIT-green"> [![Discussions][discussions-badge]][discussions-link] | ||
<img alt="Static Badge" src="https://img.shields.io/badge/bitcoin_controller-documentation-purple"> | ||
## Install | ||
@@ -58,3 +63,3 @@ | ||
``` | ||
const fees = await bitcoinController.getFee(address, satPerByte); | ||
const fees = await bitcoinController.getFees(rawTransaction); | ||
``` | ||
@@ -67,1 +72,4 @@ | ||
``` | ||
[discussions-badge]: https://img.shields.io/badge/Code_Quality-passing-rgba | ||
[discussions-link]: https://github.com/getsafle/vault-bitcoin-controller/actions |
module.exports = { | ||
bitcoin: { | ||
HD_PATH: `m/84'/0'/0'/0`, | ||
HD_PATH_MAINNET: `m/84'/0'/0'/0`, | ||
HD_PATH_TESTNET: `m/84'/1'/0'/0` | ||
}, | ||
@@ -17,5 +18,3 @@ bitcoin_transaction: { | ||
} | ||
}, | ||
SATOSHI: 100000000, | ||
SOCHAIN_API_KEY: 'F7RKphrzItSQV-MUoPY_S0XIuwTzxFpt' | ||
} | ||
} |
const axios = require('axios') | ||
var sb = require("satoshi-bitcoin"); | ||
const { SATOSHI } = require("../config/index") | ||
async function getFeeAndInput(URL, satPerByte, headers) { | ||
let fee = 0; | ||
async function getTransactionSize(URL, headers){ | ||
let inputCount = 0; | ||
let outputCount = 2; | ||
const utxos = await axios({ | ||
url : `${URL}`, | ||
// url: 'https://sochain.com/api/v3/unspent_outputs/BTC/bc1q7cyrfmck2ffu2ud3rn5l5a8yv6f0chkp0zpemf', | ||
method: 'GET', | ||
headers: headers | ||
}); | ||
let totalAmountAvailable = 0; | ||
@@ -33,9 +30,18 @@ | ||
transactionSize = inputCount * 180 + outputCount * 34 + 10 - inputCount; | ||
let transactionSize = inputCount * 180 + outputCount * 34 + 10 - inputCount; | ||
return { transactionSize, totalAmountAvailable, inputs} | ||
} | ||
async function getFeeAndInput(URL, satPerByte, headers) { | ||
let { transactionSize, totalAmountAvailable, inputs} = await getTransactionSize(URL, headers) | ||
let fee = 0; | ||
// the fees assuming we want to pay 20 satoshis per byte | ||
fee = transactionSize * satPerByte | ||
return { totalAmountAvailable, inputs, fee } | ||
return { totalAmountAvailable, inputs, fee, transactionSize} | ||
} | ||
module.exports = getFeeAndInput | ||
module.exports = { | ||
getFeeAndInput, | ||
getTransactionSize | ||
} |
const signTransaction = require('./signTransaction') | ||
const getFeeAndInput = require('./calculateFeeAndInput') | ||
const {getFeeAndInput, getTransactionSize} = require('./calculateFeeAndInput') | ||
const utils = require('./utils/index') | ||
@@ -8,3 +8,4 @@ | ||
utils, | ||
getFeeAndInput | ||
getFeeAndInput, | ||
getTransactionSize | ||
} |
const bitcore = require("bitcore-lib") | ||
const { SATOSHI, DEFAULT_SATOSHI_PER_BYTE } = require("../config/index") | ||
const getFeeAndInput = require('./calculateFeeAndInput') | ||
const {getFeeAndInput} = require('./calculateFeeAndInput') | ||
async function signTransaction(from, to, amountToSend, URL, privateKey, satPerByte, headers) { | ||
const satoshiToSend = amountToSend * SATOSHI; | ||
const transaction = new bitcore.Transaction(); | ||
@@ -14,3 +11,3 @@ | ||
if (totalAmountAvailable - satoshiToSend - fee < 0) { | ||
if (totalAmountAvailable - amountToSend - fee < 0) { | ||
throw new Error("Balance is too low for this transaction"); | ||
@@ -23,3 +20,3 @@ } | ||
// set the recieving address and the amount to send | ||
transaction.to(to, Math.floor(satoshiToSend)); | ||
transaction.to(to, Math.floor(amountToSend)); | ||
@@ -26,0 +23,0 @@ // Set change address - Address to receive the left over funds after transfer |
const { bitcoin: { HD_PATH } } = require("../../config/index") | ||
function calcBip32ExtendedKey(bip32RootKey) { | ||
function calcBip32ExtendedKey(bip32RootKey, hdPath) { | ||
// Check there's a root key to derive from | ||
@@ -10,3 +10,3 @@ if (!bip32RootKey) { | ||
// Derive the key from the path constant | ||
var pathBits = HD_PATH.split("/"); | ||
var pathBits = hdPath.split("/"); | ||
for (var i = 0; i < pathBits.length; i++) { | ||
@@ -13,0 +13,0 @@ var bit = pathBits[i]; |
@@ -5,7 +5,6 @@ const bitcoinjs = require('bitcoinjs-lib') | ||
let wallet = "NA"; | ||
wallet = bip32ExtendedKey.derive(index); | ||
let wallet = bip32ExtendedKey.derive(index); | ||
const hasPrivkey = !wallet.isNeutered(); | ||
let privkey = "NA"; | ||
let privkey | ||
if (hasPrivkey) { | ||
@@ -12,0 +11,0 @@ privkey = wallet.toWIF(); |
@@ -11,3 +11,4 @@ const ObservableStore = require('obs-store') | ||
const { bitcoin: { HD_PATH }, bitcoin_transaction: { NATIVE_TRANSFER }, bitcoin_network: { MAINNET, TESTNET }, SOCHAIN_API_KEY } = require('./config/index') | ||
const { bitcoin: { HD_PATH_MAINNET, HD_PATH_TESTNET }, bitcoin_transaction: { NATIVE_TRANSFER }, bitcoin_network: { MAINNET, TESTNET }} = require('./config/index') | ||
const { SOCHAIN_API_KEY, SOCHAIN_BASE_URL, BLOCKCYPHER_BASE_URL } = require('./constants/index') | ||
@@ -22,3 +23,3 @@ class KeyringController { | ||
constructor(opts) { | ||
this.store = new ObservableStore({ mnemonic: opts.mnemonic, hdPath: HD_PATH, network: helpers.utils.getNetwork(opts.network), networkType: opts.network ? opts.network : MAINNET.NETWORK, wallet: null, address: [] }) | ||
this.store = new ObservableStore({ mnemonic: opts.mnemonic, hdPath: opts.network === TESTNET.NETWORK ? HD_PATH_TESTNET : HD_PATH_MAINNET, network: helpers.utils.getNetwork(opts.network), networkType: opts.network ? opts.network : MAINNET.NETWORK, wallet: null, address: [] }) | ||
this.generateWallet() | ||
@@ -29,6 +30,6 @@ this.importedWallets = [] | ||
generateWallet() { | ||
const { mnemonic, network } = this.store.getState(); | ||
const { mnemonic, network, hdPath } = this.store.getState(); | ||
const seed = bip39.mnemonicToSeed(mnemonic) | ||
const bip32RootKey = bitcoinjs.bip32.fromSeed(seed, network); | ||
const extendedKeys = helpers.utils.calcBip32ExtendedKeys(bip32RootKey) | ||
const extendedKeys = helpers.utils.calcBip32ExtendedKeys(bip32RootKey, hdPath) | ||
this.updatePersistentStore({ wallet: extendedKeys }) | ||
@@ -93,3 +94,3 @@ return extendedKeys | ||
const URL = `https://sochain.com/api/v3/unspent_outputs/${networkType === TESTNET.NETWORK ? 'BTCTEST' : "BTC"}/${from}` | ||
const URL = SOCHAIN_BASE_URL + `unspent_outputs/${networkType === TESTNET.NETWORK ? 'BTCTEST' : "BTC"}/${from}` | ||
const headers = { "API-KEY": SOCHAIN_API_KEY} | ||
@@ -138,3 +139,3 @@ | ||
method: "POST", | ||
url: `https://chain.so/api/v3/broadcast_transaction/${networkType === TESTNET.NETWORK ? 'BTCTEST' : "BTC"}`, | ||
url: SOCHAIN_BASE_URL + `broadcast_transaction/${networkType === TESTNET.NETWORK ? 'BTCTEST' : "BTC"}`, | ||
data: { | ||
@@ -151,9 +152,35 @@ tx_hex: TransactionHex, | ||
async getFee(address, satPerByte) { | ||
async getFees(rawTransaction) { | ||
const { networkType } = this.store.getState() | ||
const { from } = rawTransaction | ||
try { | ||
const URL = `https://sochain.com/api/v3/unspent_outputs/${networkType === TESTNET.NETWORK ? 'BTCTEST' : "BTC"}/${address}` | ||
const URL = BLOCKCYPHER_BASE_URL + `${networkType === TESTNET.NETWORK ? 'test3' : "main"}/` | ||
const response = await axios({ | ||
url : `${URL}`, | ||
method: 'GET', | ||
}); | ||
let fees = { | ||
slow: { | ||
satPerByte: parseInt(response.data.low_fee_per_kb/1000), | ||
}, | ||
standard: { | ||
satPerByte: parseInt(response.data.medium_fee_per_kb/1000), | ||
}, | ||
fast: { | ||
satPerByte: parseInt(response.data.high_fee_per_kb/1000) | ||
} | ||
} | ||
// get transaction size | ||
const sochainURL = SOCHAIN_BASE_URL + `unspent_outputs/${networkType === TESTNET.NETWORK ? 'BTCTEST' : "BTC"}/${from}` | ||
const headers = { "API-KEY": SOCHAIN_API_KEY} | ||
const { totalAmountAvailable, inputs, fee } = await helpers.getFeeAndInput(URL, satPerByte, headers) | ||
return { transactionFees: fee } | ||
let {transactionSize} = helpers.getTransactionSize(sochainURL, headers) | ||
return { | ||
transactionSize, | ||
fees | ||
} | ||
} catch (err) { | ||
@@ -180,3 +207,3 @@ throw err | ||
try { | ||
const URL = `https://sochain.com/api/v3/balance/${networkType === TESTNET.NETWORK ? 'BTCTEST' : "BTC"}/${address}` | ||
const URL = SOCHAIN_BASE_URL + `balance/${networkType === TESTNET.NETWORK ? 'BTCTEST' : "BTC"}/${address}` | ||
const headers = { "API-KEY": SOCHAIN_API_KEY} | ||
@@ -183,0 +210,0 @@ const balance = await axios({ |
module.exports = { | ||
HD_WALLET_12_MNEMONIC : 'uphold job voyage bunker attract similar ship pear soda security rubber offer', | ||
EXTERNAL_ACCOUNT_PRIVATE_KEY_MAINNET: "L51zqNpZY6pgVz8ggXFghXRSvuub8PvsHLhQy3w4uyX1CHQgurRb", // works on MAINNET ONLY | ||
EXTERNAL_ACCOUNT_ADDRESS_MAINNET: "bc1q7nv22l23wyu4vtq869sgzke3j9swetvytxxcla", | ||
EXTERNAL_ACCOUNT_PRIVATE_KEY: "L2dwGhW8XfBCiJw4GQ9nmumA4APwYZVNRDxXKdPQvAVQ8zHn3VAG", // works on MAINNET ONLY | ||
EXTERNAL_ACCOUNT_ADDRESS: "bc1q7e5yrgec5sq4xz80um9yp5kazahpu20tg9d3ta", | ||
TEST_ADDRESS_1: 'tb1qw3rs6lte05ej6rzm86sd4rg6vt9ss27shx5z8q', | ||
TEST_ADDRESS_2: 'tb1qt3vyrqgy7emqcr8sjr39zcjdca7grfer7lvxhp', | ||
EXTERNAL_ACCOUNT_PRIVATE_KEY: "cQanwmov5nfBHrdqoAAV42i3Xgw8cyebAmKBLRcKXvQH5eutSQzo", // works on TESTNET ONLY | ||
EXTERNAL_ACCOUNT_ADDRESS: "tb1qcen5y7uvp3r9y0c4l4c55hlaks8gef54pr6v68", | ||
EXTERNAL_ACCOUNT_WRONG_PRIVATE_KEY_1: "random_private_key", | ||
@@ -19,4 +20,4 @@ EXTERNAL_ACCOUNT_WRONG_PRIVATE_KEY_2: "0xbcb7a8680126610ca94440b020280f9ef829ad26637bfb5cc", | ||
TRANSFER_BTC: { | ||
BTC_RECEIVER: 'bc1qgkw48n0d74krmvq7d50nechkeqf057p7gyyl7r', // address at index 1 | ||
BTC_AMOUNT: 0.00001 | ||
BTC_RECEIVER: 'tb1qt3vyrqgy7emqcr8sjr39zcjdca7grfer7lvxhp', // address at test index 1 | ||
BTC_AMOUNT: 1000 | ||
}, | ||
@@ -23,0 +24,0 @@ BITCOIN_NETWORK: { |
@@ -6,7 +6,7 @@ var assert = require('assert'); | ||
HD_WALLET_12_MNEMONIC, | ||
TEST_ADDRESS_1, | ||
TEST_ADDRESS_2, | ||
HD_WALLET_12_MNEMONIC_TEST_OTHER, | ||
EXTERNAL_ACCOUNT_PRIVATE_KEY, | ||
EXTERNAL_ACCOUNT_ADDRESS, | ||
EXTERNAL_ACCOUNT_PRIVATE_KEY_MAINNET, | ||
EXTERNAL_ACCOUNT_ADDRESS_MAINNET, | ||
EXTERNAL_ACCOUNT_WRONG_PRIVATE_KEY_1, | ||
@@ -36,3 +36,3 @@ EXTERNAL_ACCOUNT_WRONG_PRIVATE_KEY_2, | ||
mnemonic: HD_WALLET_12_MNEMONIC, | ||
network: MAINNET | ||
network: TESTNET | ||
} | ||
@@ -45,5 +45,5 @@ | ||
const wallet = await bitcoinWallet.addAccount() | ||
console.log("wallet, ", wallet) | ||
assert(wallet.address === TEST_ADDRESS_1, "Added address should be " + TEST_ADDRESS_1) | ||
const wallet2 = await bitcoinWallet.addAccount() | ||
console.log("wallet2, ", wallet2) | ||
assert(wallet2.address === TEST_ADDRESS_2, "Added address should be " + TEST_ADDRESS_2) | ||
}) | ||
@@ -53,3 +53,2 @@ | ||
const acc = await bitcoinWallet.getAccounts() | ||
console.log("acc ", acc) | ||
assert(acc.length === 2, "Should have 2 addresses") | ||
@@ -60,4 +59,4 @@ }) | ||
const acc = await bitcoinWallet.getAccounts() | ||
const privateKey = await bitcoinWallet.exportPrivateKey(acc[0]) | ||
console.log("privateKey, ", privateKey) | ||
const result = await bitcoinWallet.exportPrivateKey(acc[0]) | ||
assert(result.privateKey) | ||
}) | ||
@@ -69,24 +68,43 @@ | ||
const signedMessage1 = await bitcoinWallet.signMessage(TESTING_MESSAGE_1, acc[0]) | ||
console.log("Signed message 1: ", signedMessage1) | ||
assert(bitcoinMessage.verify(TESTING_MESSAGE_1, acc[0], signedMessage1.signedMessage), "Should verify message 1") | ||
const signedMessage2 = await bitcoinWallet.signMessage(TESTING_MESSAGE_2, acc[0]) | ||
console.log("Signed message 2: ", signedMessage2) | ||
assert(bitcoinMessage.verify(TESTING_MESSAGE_2, acc[0], signedMessage2.signedMessage), "Should verify message 2") | ||
const signedMessage3 = await bitcoinWallet.signMessage(TESTING_MESSAGE_3, acc[0]) | ||
console.log("Signed message 3: ", signedMessage3) | ||
assert(bitcoinMessage.verify(TESTING_MESSAGE_3, acc[0], signedMessage3.signedMessage), "Should verify message 3") | ||
}) | ||
it("Get fees will return NaN", async () => { | ||
it("Get fees, invalid argument", async () => { | ||
try { | ||
const acc = await bitcoinWallet.getAccounts() | ||
const result = await bitcoinWallet.getFees(acc[0]); | ||
} catch (err) { | ||
assert.equal(err, "Cannot destructure property 'from' of 'transaction' as it is undefined.", "Should throw TyprError") | ||
} | ||
}) | ||
it("Get fees, valid argument", async () => { | ||
const acc = await bitcoinWallet.getAccounts() | ||
const { transactionFees } = await bitcoinWallet.getFee(acc[0]); | ||
console.log("transactionFees ", transactionFees) | ||
BTC_TXN_PARAM['from'] = acc[0] | ||
const response = await bitcoinWallet.getFees(BTC_TXN_PARAM) | ||
let actualResponse = Object.keys(response) | ||
let expectedResponse = ['transactionSize', 'fees'] | ||
assert.deepEqual(actualResponse, expectedResponse, "Should have transactionSize and fees") | ||
let satPerByte = Object.keys(response.fees); | ||
let expected = [ 'slow', 'standard', 'fast' ] | ||
assert.deepEqual(satPerByte, expected, "Should have slow, standard, fast fees") | ||
}) | ||
it("Get fees with custom satPerByte", async () => { | ||
it("Sign Transaction using estimated fee", async () => { | ||
const acc = await bitcoinWallet.getAccounts() | ||
const { transactionFees } = await bitcoinWallet.getFee(acc[0], 50); | ||
console.log("transactionFees ", transactionFees) | ||
BTC_TXN_PARAM['from'] = acc[0] | ||
let response = await bitcoinWallet.getFees(BTC_TXN_PARAM) | ||
BTC_TXN_PARAM['satPerByte'] = response.fees.slow.satPerByte | ||
const { signedTransaction } = await bitcoinWallet.signTransaction(BTC_TXN_PARAM); | ||
assert(signedTransaction) | ||
}) | ||
@@ -99,5 +117,4 @@ | ||
const { signedTransaction } = await bitcoinWallet.signTransaction(BTC_TXN_PARAM); | ||
console.log("signedTransaction ", signedTransaction) | ||
} catch (err) { | ||
console.log("Catching error ", err) | ||
assert(err.message) | ||
} | ||
@@ -109,15 +126,12 @@ }) | ||
BTC_TXN_PARAM['from'] = acc[0] | ||
BTC_TXN_PARAM['satPerByte'] = 60 | ||
BTC_TXN_PARAM['satPerByte'] = 15 | ||
const { signedTransaction } = await bitcoinWallet.signTransaction(BTC_TXN_PARAM); | ||
console.log("signedTransaction ", signedTransaction) | ||
assert(signedTransaction) | ||
}) | ||
it("Should import correct account ", async () => { | ||
if (opts.network === MAINNET) { | ||
const address = await bitcoinWallet.importWallet(EXTERNAL_ACCOUNT_PRIVATE_KEY_MAINNET) | ||
assert(address.toLowerCase() === EXTERNAL_ACCOUNT_ADDRESS_MAINNET.toLowerCase(), "Wrong address") | ||
} else { | ||
const address = await bitcoinWallet.importWallet(EXTERNAL_ACCOUNT_PRIVATE_KEY) | ||
assert(address.toLowerCase() === EXTERNAL_ACCOUNT_ADDRESS.toLowerCase(), "Wrong address") | ||
} | ||
const address = await bitcoinWallet.importWallet(EXTERNAL_ACCOUNT_PRIVATE_KEY) | ||
assert(address.toLowerCase() === EXTERNAL_ACCOUNT_ADDRESS.toLowerCase(), "Wrong address") | ||
assert(bitcoinWallet.importedWallets.length === 1, "Should have 1 imported wallet") | ||
@@ -128,7 +142,6 @@ }) | ||
const acc = await bitcoinWallet.getAccounts() | ||
const balance = await getBalance(acc[0], opts.network) | ||
console.log("acc ", acc) | ||
console.log("balance ", balance) | ||
const result = await getBalance(acc[0], opts.network) | ||
assert(result.balance) | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
27044
19
0
467
73
7