BCSJS Wallet
This is a client-side wallet library that can generate private keys from a mnemonic, or import private keys from other BCS wallets.
It can sign transactions locally, and submit the raw transaction data to a remote bcs node. The blockchain data is provided by the Insight API (which powers https://explorer.bcs.org/), rather than the raw bcsd RPC calls.
This library makes it possible to run DApp without the users having to run a full bcsd node.
This library is extracted from the official BCS web wallet.
Install
yarn add bcsjs-wallet
Running Tests
The bcsjs-wallet depends on a number of external services. To run the unit tests, you'll need to start the services using docker-compose.
npm run start-services
Then run the tests:
npm run test
Or, to rebuild and rerun tests automatically:
npm run test-watch
If you did not terminate the testing docker services properly, run the clean task first before restart:
npm run clean
Implementation Notes
There are some differences from the original web wallet repo.
- Removed VUE specific code.
- Removed reactive data setters that are intended to trigger view updates, to make this a plain-old JavaScript module.
- Each wallet instance is instantiated with a network explicitly. This allows simultaneous use of different networks.
- TypeScript for type hinting.
- Uses satoshi (1e8) as internal units
- Can represent up to ~90 million BCS accurately.
- Uses coinselect to select utxos.
- Taking into account the size of a transaction, and multiplies that by fee rate per byte.
- Uses blackjack algorithm, and fallbacks to simple accumulative.
- Set tx relay fee automatically from fee rate reported by the network.
- send-to-contract transaction can transfer value to the contract
API
Examples
Create Mnemonic+Password Wallet
; { const network = networkstestnet; const mnemonic = ; const password = "covfefe"; const wallet = network; console; console; console;} ;
Example Output:
mnemonic: hold struggle ready lonely august napkin enforce retire pipe where avoid drip
public address: qLUHmrFGexxpyHwQphLpE1czZNFE5m1xmV
private key (WIF): cNQKccYYQyGX9G9Qxq2DJev9jHygbZpb2UG7EvUapbtDx5XhkhYE
Send Fund
This example restores a wallet from a private key (in WIF format), then sending value to another address.
The transaction is signed locally, and the transaction submitted to a remote API.
The currency unit used is satoshi
. To convert bcs to satoshi you should multiply the amount you want with 1e8
.
; { // Use the test network. Or `networks.mainnet` const network = networkstestnet; const wif = "cU4ficvRNvR7jnbtczCWo5s9rB9Tdg1U4LkArVpGU6cKnDq7LFoP"; const wallet = network; console; const toAddr = "qS3ThpDn4HRH9we2hZUdF3F3uR7TTvpZ9v"; // Sending 0.1 bcs const sendtx = await wallet; console;} ;
Send To Contract
Let's burn some money using the Burn
contract:
pragma solidity ^0.4.18; contract Burn { uint256 public totalburned; event DidBurn(address burnerAddress, uint256 burnedAmount); function burnbabyburn() public payable { totalburned = msg.value; DidBurn(msg.sender, msg.value); }}
The ABI encoding for the burnbabyburn()
invokation is e179b912
. We'll burn 0.05 bcs, expressed in unit of satoshi.
; main.catchconsole.logerr;
Networks
Two networks are predefined:
; // Main Networknetworksmainnet; // Test Networknetworkstestnet;
fromPrivateKey
Alias for fromWIF
.
fromWIF
fromWIF
constructs a wallet from private key (in WIF format).
Suppose you want to import the public address qg3HYD8c4bAVLeEzA9t3Ken3Y3Mni1HZSS
. Use bcs-cli
to dump the private key from wallet:
qcli dumpprivkey qg3HYD8c4bAVLeEzA9t3Ken3Y3Mni1HZSS
cVHzWuEKUxoRKba9ySZFqUKZ9G5W8NkzthRcPaB65amUJs95RM3d
const network = networkstestnet; const privateKey = "cVEwiJ5NMTdnkW4ZW2ykUopawtLPXQWtPDmvpTh5jmXYMtg8itAz"; const wallet = network;console;
Output:
public address: qWAnfBnRNhZBqtgSdgHjSfS2D5Jawmafra
fromMnemonic
fromMnemonic
constructs a wallet from mnemonic. User can optionally specify a password
to add to the mnemonic entropy.
;;; ; console.log"public address:", wallet.address;console.log"private key (WIF):", wallet.toWIF;
Example Output:
public address: qLUHmrFGexxpyHwQphLpE1czZNFE5m1xmV
private key (WIF): cNQKccYYQyGX9G9Qxq2DJev9jHygbZpb2UG7EvUapbtDx5XhkhYE
Wallet
Wallet manages blockchain access for an address. It is able to create and sign transactions locally for sending a payment or interacting with a smart contract.
You would typically construct a Wallet instance using the factory methods provided by Network
.
async wallet.getInfo
Get basic information about the wallet address.
Example:
;console.loginfo;
Output:
{ addrStr: 'qbkJZTKQfcout2joWVmnvUrJUDTg93bhdv',
balance: 128.47960699,
balanceSat: 12847960699,
totalReceived: 599.92142295,
totalReceivedSat: 59992142295,
totalSent: 471.44181596,
totalSentSat: 47144181596,
unconfirmedBalance: 0,
unconfirmedBalanceSat: 0,
unconfirmedTxApperances: 0,
txApperances: 21,
transactions:
[ 'd12ff9cfd76836d8eb5a39bc40f1dc5e6e2032bfa132f66cca638a7e76f2b6e7',
'44fa64f34361cf5460ca116ea396098eb0d20dd43839375c07d69a282d4e29b6',
'ca86c477bc595f08f158eed0d4307ee6e1e674a2c14f808b013b38cb1e929aa0',
'fbf41aaca56dd013934471b4630f8ca52a6216cf791701a07f3e5c0ba16902d5',
'af8fff4a74ff9217d629c17aa84412e8810888983cbc4f6b764740e68b51e5d0',
'e9172194ef9493a2dd8dddd02aa58a1c13dbfb09a7e04cb97558d951e4b93a88',
'3e167a2534d5d18b71ba56bbba8bfdb317711b3f2ef30f10d34941ddc9aa4861',
'bd15b9d9cf4e94915e246a7d78de14cb0a6acec12624902b45717997ef71854e',
'0c99d68c261dd713819c068bd0213bc048bd4928b3d86d71503bb3348d7f42f5',
'd5b823bb524862855181d231e716ff86fa301f701fd4c23b68168debe334da2e',
'7660e89eb45b536b9c7527edafc0884fc2941c0f050625780d3e100c8aeb28f4',
'eddbbac9bb7dae1cf4093d893133eb52c483b13ea66f6354c63302f9127ec1bd',
'6f99149d78ad720591b4cca643fe2599a0a07076f8f3e80b5962cba326772e83',
'0ef2548cceaaa41b7c0127f6e943d103f2fcc236d05e59593e05381f7a8474a0',
'851753842d80e8dea92de643e0f3784cf7cbdbb02ae879593cbeac2c78560bac',
'729c839d63f7426a1f4ada7eb5a35b556556665a4b42e102694674551752bb03',
'ee50d8422dce064d40eb021f4829f5b871e8d2927d93ea136dc0df01b1a72e08',
'caf8b48b9d38c3a27de3d24c5a738f63ec37619d419cfcd061bc991d8369bda3',
'3b59444033d61457fe229a866dc9cb4a60a4b070ea3a73cacba27516fd30cee8',
'5e9ca1c946deaf5458d2b6236145b225eee61ec6991b7df8ee96573b53d82584',
'cfbadf76884ca661816f25487f6493826579afe257517ebd7d1fc2b0020eb289' ] }
async wallet.send
Send payment to a receiving address. The transaction is signed locally using the wallet's private key, and the raw transaction submitted to a remote API (without revealing the wallet's secret).
Method signature:
/** * @param to The receiving address * @param amount The amount to transfer (in satoshi) * @return The raw transaction as hexadecimal string * */public async send to: string, amount: number, opts: ISendTxOptions = ,: Promise<Insight.ISendRawTxResult>
Example:
;; // 0.15 BCS ;console.logtx;
Output:
{ txid: '40fec162e0d4e1377b5e6744eeba562408e22f60399be41e7ba24e1af37f773c' }
async wallet.send options
Setting tx fee rate manually:
;
async wallet.sendEstimateMaxValue
Estimate the maximum value that could be sent from this wallet address.
;
async wallet.generateTx
Generate and sign a payment transaction.
Method signature:
/** * @param to The receiving address * @param amount The amount to transfer (in satoshi) * @param opts * * @returns The raw transaction as hexadecimal string */public async generateTx to: string, amount: number, opts: ISendTxOptions = ,: Promise<string>
Example:
;; ;console.lograwtx;
Example output, the raw transaction as hexadecimal string:
0100000001a09a921ecb383b018b804fc1a274e6e1e67e30d4d0ee58f1085f59bc77c486ca010000006a47304402202fa6106aca6c682ab89b02ad62614462d1ec5e95cb8b4810ce793ad52a4002590220531cf380368cb8f92c7dd03ee375423073a14e5b7da6f48127c63cab17fbf2d7012103c12c73abaccf35b40454e7eb0c4b5760ce7a720d0cd2c9fb7f5423168aaeea03ffffffff02c0e1e400000000001976a914afb616c886f0efd9a9a486ccc07a09ab8d7a4bb288ac49b6ffe0010000001976a914c78300c58ab7c73e1767e3d550464d591ab0a12888ac00000000
You can decode the raw transaction using bcs-cli
:
bcs-cli decoderawtransaction 0100000001a09a921ecb38...
{
// ...
"vout": [
{
"value": 0.15000000,
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 afb616c886f0efd9a9a486ccc07a09ab8d7a4bb2 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a914afb616c886f0efd9a9a486ccc07a09ab8d7a4bb288ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"qZaTYNEimGLuqnBDpP3KvBKsFs3DbCuwnr"
]
}
},
{
"value": 80.69822025,
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 c78300c58ab7c73e1767e3d550464d591ab0a128 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a914c78300c58ab7c73e1767e3d550464d591ab0a12888ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"qbkJZTKQfcout2joWVmnvUrJUDTg93bhdv"
]
}
}
]
}
There are two vouts:
- pubkeyhash 0.15. This is the amount we want to send.
- pubkeyhash 80.69822025. This is the amount we going back to the original owner as change.
async wallet.contractSend
Create a send-to-contract transaction that invokes a contract's method.
/** * @param contractAddress Address of the contract in hexadecimal * @param encodedData The ABI encoded method call, and parameter values. * @param opts */public async contractSend contractAddress: string, encodedData: string, opts: IContractSendTXOptions = ,: Promise<Insight.ISendRawTxResult>
Example:
Invoke the burn()
method, and transfer 5000000 satoshi to the contract.
- The
burn()
method call ABI encodes toe179b912
- The 5000000 is
msg.value
in contract code.
;// ABI encoded data for the send-to-method transaction; // Invoke a contract's method, and transferring 0.05 to it.; console.logtx;
Output:
{ txid: 'd12ff9cfd76836d8eb5a39bc40f1dc5e6e2032bfa132f66cca638a7e76f2b6e7' }
async wallet.contractSendEstimateMaxValue
Estimate the maximum value that could be sent to a contract, substracting the amount reserved for gas.
;
async wallet.generateContractSendTx
Generate a raw a send-to-contract transaction that invokes a contract's method.
Method signature:
/** * @param contractAddress * @param encodedData * @param opts */public async generateContractSendTx contractAddress: string, encodedData: string, opts: IContractSendTXOptions = ,: Promise<string>
Example:
;; ; console.lograwtx;
Example output:
0100000001e7b6f2767e8a63ca6cf632a1bf32206e5edcf140bc395aebd83668d7cff92fd1010000006b483045022100b86c4cbb2aecab44c951f99c0cbbf6115cf80881b39f33b4efd4d296892c1c15022062db1f681e684616e55303556577c9242102ff7a6815894dfb3090a7928fa13a012103c12c73abaccf35b40454e7eb0c4b5760ce7a720d0cd2c9fb7f5423168aaeea03ffffffff0240420f000000000022540390d003012804e179b912141620cd3c24b29d424932ec30c5925f8c0a00941cc2880256e0010000001976a914c78300c58ab7c73e1767e3d550464d591ab0a12888ac00000000
Decode the raw transaction:
bcs-cli decoderawtransaction 0100000001e7b6f2767e8a6...
Decoded Raw TX:
There are two vouts:
- call 0.11. This is the amount we want to send to the contract.
- pubkeyhash 80.58700424. This is the amount we going back to the original owner as change.
async wallet.contractCall
Query a contract's method. It returns the result and logs of a simulated execution of the contract's code.
Method signature:
/** * @param contractAddress Address of the contract in hexadecimal * @param encodedData The ABI encoded method call, and parameter values. * @param opts */public async contractCall contractAddress: string, encodedData: string, opts: IContractSendTXOptions = ,: Promise<Insight.IContractCall>
Example:
;; ; console.logJSON.stringifyresult, null, 2;
Output:
getTransactions
Get transactions about the wallet address.
Method signature:
/** * get transactions by wallet address * @param pageNum page number */public async getTransactionspageNum?: number: Promise<Insight.IRawTransactions>
Example:
; ; ;console.loginfo;
Example output:
wallet address: qbkJZTKQfcout2joWVmnvUrJUDTg93bhdv
toEncryptedPrivateKey
encrypted wip using bip38.
Method signature:
/** * bip38 encrypted wip * @param passphrase * @param params scryptParams, default: { N: 16384, r: 8, p: 8 } */public toEncryptedPrivateKey passphrase: string, params: = scryptParams,: string
Example:
;;; ; console.log"public address:", wallet.address;console.log"private key (WIF):", wallet.toWIF;console.log "encrypted bip38 private key is:", wallet.toEncryptedPrivateKeypassword;
Example output:
public address: qLUHmrFGexxpyHwQphLpE1czZNFE5m1xmVprivate key WIF: cNQKccYYQyGX9G9Qxq2DJev9jHygbZpb2UG7EvUapbtDx5XhkhYEencrypted bip38 private key is: 6PYVKJXXQ7eyTgGizw9NxX4nz1u185GqF28NWudxvyWZUh8QyJ9u2AqxWMencryption takes 2214 seconds
fromEncryptedPrivateKey
fromEncryptedPrivateKey
constructs a wallet from bip38 encrypted private key.
Method signature:
/** * constructs a wallet from bip38 encrypted private key * @param encrypted private key string * @param passhprase password * @param params scryptParams, default: { N: 16384, r: 8, p: 8 } */public fromEncryptedPrivateKey encrypted: string, passhprase: string, params: = scryptParams,: Wallet
Example:
;;; ;;; console.log"public address:", wallet.address;console.log"private key (WIF):", wallet.toWIF;console.log`decryption takes seconds`;
Example output:
public address: qLUHmrFGexxpyHwQphLpE1czZNFE5m1xmVprivate key WIF: cNQKccYYQyGX9G9Qxq2DJev9jHygbZpb2UG7EvUapbtDx5XhkhYEdecryption takes 2258 seconds
deriveChildWallet
Use BIP32 to derive child wallets from the current wallet's keypair.
Example:
Generate as many child wallets as you need:
const childWallet0 = wallet;const childWallet1 = wallet;
Or omit the child wallet index (defaults to 0):
// The default child wallet index is 0const childWallet = wallet;
scrypt (non-stable API)
This is an scrypt helper function, which may be removed in the future.
To use scrypt to hash a secret:
; // by default, the bip38 scrypt parameters are usedconst hash = ; console;
The progress callback is invoked every 1000 rounds. And finally the result hash is a hex string.
status { current: 257000, total: 262144, percent: 98.0377197265625 }
status { current: 258000, total: 262144, percent: 98.419189453125 }
status { current: 259000, total: 262144, percent: 98.8006591796875 }
status { current: 260000, total: 262144, percent: 99.18212890625 }
status { current: 261000, total: 262144, percent: 99.5635986328125 }
status { current: 262000, total: 262144, percent: 99.945068359375 }
8b41dd9e92490b8f2b01cbe1f20e2c57315b5f861da1ede1002bc544d90f7e56
You may also choose to specify your own scrypt parameters:
; const hash = ;