Main Focus of a newly startup company is to build a portfolio management system (PMS) that supports both, traditional assets (gold, silver, stocks, etc.) and currently very hot topic - crypto-assets!! But as there are many coins, our task is to understand how HD wallets work, and to build a system that can create them.
hd-wallet-derive - A command line tool that supports BIP32, BIP39 and BIP44 and also supports non-standard derivation paths for the most popular wallets. Unfortunately, there arent many tools available in python and we need to integrate the script in backend with python.
Once the integration is done with this "universal" wallet, one can manage billions of addresses across 300+ coins, giving a serious edge against the competition.
-
PHP must be installed on operating system (any version, 5 or 7). No need to know any PHP
-
Clone the
hd-wallet-derivetool. -
bitPython Bitcoin library. -
web3.pyPython Ethereum library.
Project setup
-
Create a project directory called
walletandcdinto it. -
Clone the
hd-wallet-derivetool into this folder and install it using the instructions on itsREADME.md. -
Create a symlink called
derivefor thehd-wallet-derive/hd-wallet-derive.phpscript into the top level project directory like so:ln -s hd-wallet-derive/hd-wallet-derive.php deriveThis will clean up the command needed to run the script in the code, to call
./deriveinstead of./hd-wallet-derive/hd-wallet-derive.php.exe. -
Test run the
./derivescript properly, use one of the examples on the repo'sREADME.mdNote: If one gets an error running
./derive, as it happens in Windows machine then use:./hd-wallet-derive/hd-wallet-derive.php.exe -
Create a file called
wallet.py-- universal wallet script.
Directory tree for hd-wallet-derive
Setup constants
-
In a separate file,
constants.py, set the following constants:BTC = 'btc'ETH = 'eth'BTCTEST = 'btc-test'
-
In
wallet.py, import all constants:from constants import * -
Use these anytime to reference these strings, both in function calls, and in setting object keys.
Generate a Mnemonic
-
Generate a new 12 word mnemonic using
hd-wallet-deriveor by using this tool. -
Set this mnemonic as an environment variable, and include the one generated as a fallback using:
mnemonic = os.getenv('MNEMONIC', 'insert mnemonic here')
Deriving the wallet keys
-
Use the
subprocesslibrary to call the./derivescript from Python. Make sure to properly wait for the process. -
The following flags must be passed into the shell command as variables:
- Mnemonic (
--mnemonic) must be set from an environment variable, or default to a test mnemonic - Coin (
--coin) - Numderive (
--numderive) to set number of child keys generated
- Mnemonic (
-
Set the
--format=jsonflag, then parse the output into a JSON object usingjson.loads(output) -
Wrap all of this into one function, called
derive_wallets -
Create an object called
coinsthat derivesETHandBTCTESTwallets with this function. When done properly, the final object should look something like this (there are only 3 children each in this image):
Test the coins by calling coins[COINTYPE][INDEX]['privkey'].
Linking the transaction signing libraries
Use bit and web3.py to leverage the keys obtained in the coins object.
Create 3 more funtions:
priv_key_to_account-- this will convert theprivkeystring in a child key to an account object thatbitorweb3.pycan use to transact. This function needs the following parameters:
coin-- the coin type (defined inconstants.py).priv_key-- theprivkeystring will be passed through here.
Check the coin, then return one of the following functions based on the library:
- For
ETH, returnAccount.privateKeyToAccount(priv_key) - For
BTCTEST, returnPrivateKeyTestnet(priv_key)
create_tx-- this will create the raw, unsigned transaction that contains all metadata needed to transact. This function needs the following parameters:
coin-- the coin type (defined inconstants.py).account-- the account object frompriv_key_to_account.to-- the recipient address.amount-- the amount of the coin to send.
Check the coin, then return one of the following functions based on the library:
- For
ETH, return an object containingto,from,value,gas,gasPrice,nonce, andchainID. Make sure to calculate all of these values properly usingweb3.py! - For
BTCTEST, returnPrivateKeyTestnet.prepare_transaction(account.address, [(to, amount, BTC)])
send_tx-- this will callcreate_tx, sign the transaction, then send it to the designated network. This function needs the following parameters:
coin-- the coin type (defined inconstants.py).account-- the account object frompriv_key_to_account.to-- the recipient address.amount-- the amount of the coin to send.
Check the coin, then create a raw_tx object by calling create_tx. Then, sign
the raw_tx using bit or web3.py (hint: the account objects have a sign transaction function within).
Once signed the transaction, send it to the designated blockchain network.
- For
ETH, returnw3.eth.sendRawTransaction(signed.rawTransaction) - For
BTCTEST, returnNetworkAPI.broadcast_tx_testnet(signed)
Execute the transactions
Bitcoin Testnet transaction
-
Fund a
BTCTESTaddress using this testnet faucet. -
Use a block explorer to watch transactions on the address.
BTCTEST Transaction
`btc_acc = priv_key_to_account(BTCTEST,btc_PrivateKey)`
`create_tx(BTCTEST,btc_acc,"n3D8vVvLyD7pPQmWoQgMMERZrDjmBheBpU", 0.1)`
`send_txn(BTCTEST,btc_acc,"mtK73sNPY9CKuzVvpv4W1969AD1UmGGfsX", 0.1)`
Confirmation on executed transaction
ETH Transaction - Local PoA
-
Add one of the
ETHaddresses to the pre-allocated accounts inmtestnet.json -
Initialize using
geth --datadir nodeX init mtestnet.json. -
Add the following middleware to
web3.pyto support the PoA algorithm:from web3.middleware import geth_poa_middleware w3.middleware_onion.inject(geth_poa_middleware, layer=0) -
Connect to HTTP with address private key & check the balance of the account
w3 = Web3(Web3.HTTPProvider("http://127.0.0.1:8545/0x30c2577db89760baa9ba4058b1033b9e103f287e5de1689e35833ee8e7a7c857"))w3.eth.getBalance("0x46BbdBf56ff911A93AdaF0164d0709F78B52765E") -
Due to a bug in
web3.py, send a transaction or two with MyCrypto first, since thew3.eth.generateGasPrice()function does not work with an empty chain. Use one of theETHaddressprivkey, or one of thenodekeystore files. -
Send a transaction from the pre-funded address within the wallet to another, then get the
TxStatusfrom MyCrypto ` eth-test