Janus is a web3 proxy adapter that can be used as a web3 provider to interact with Qtum. It supports HTTP(s) and websockets and the current version enables self hosting of keys.
- Quick start
- Public instances
- Requirements
- Installation
- How to use Janus as a Web3 provider
- How to add Janus to Metamask
- Supported ETH methods
- Websocket ETH methods
- Janus methods
- Development methods
- Health checks
- Deploying and Interacting with a contract using RPC calls
- Differences between EVM chains
Mainnet: https://janus.qiswap.com/api/
Testnet: https://testnet-janus.qiswap.com/api/
Regtest: run it locally with make quick-start-regtest
If you need to use eth_sendTransaction, you are going to have to run your own instance pointing to your own QTUM instance
See (Beta) QTUM ethers-js library to generate transactions in the browser so you can use public instances
See Differences between EVM chains below
- Golang
- Docker
- linux commands:
make,curl
$ sudo apt install make git golang docker-compose
# Configure GOPATH if not configured
$ export GOPATH=`go env GOPATH`
$ mkdir -p $GOPATH/src/github.com/qtumproject && \
cd $GOPATH/src/github.com/qtumproject && \
git clone https://github.com/qtumproject/janus
$ cd $GOPATH/src/github.com/qtumproject/janus
# Generate self-signed SSL cert (optional)
# If you do this step, Janus will respond in SSL
# otherwise, Janus will respond unencrypted
$ make docker-configure-https
# Pick a network to quick-start with
$ make quick-start-regtest
$ make quick-start-testnet
$ make quick-start-mainnet
This will build the docker image for the local version of Janus as well as spin up two containers:
-
One named
janusrunning on port 23889 -
Another one named
qtumrunning on port 3889
make quick-start will also fund the tests accounts with QTUM in order for you to start testing and developing locally. Additionally, if you need or want to make changes and or additions to Janus, but don't want to go through the hassle of rebuilding the container, you can run the following command at the project root level:
$ make run-janus
# For https
$ make docker-configure-https && make run-janus-https
Which will run the most current local version of Janus on port 23888, but without rebuilding the image or the local docker container.
Note that Janus will use the hex address for the test base58 Qtum addresses that belong the the local qtum node, for example:
- qUbxboqjBRp96j3La8D1RYkyqx5uQbJPoW (hex 0x7926223070547d2d15b2ef5e7383e541c338ffe9 )
- qLn9vqbr2Gx3TsVR9QyTVB5mrMoh4x43Uf (hex 0x2352be3db3177f0a07efbe6da5857615b8c9901d )
SSL keys and certificates go inside the https folder (mounted at /https in the container) and use --https-key and --https-cert parameters. If the specified files do not exist, it will fall back to http.
To generate self-signed certificates with docker for local development the following script will generate SSL certificates and drop them into the https folder
$ make docker-configure-https
Once Janus is successfully running, all one has to do is point your desired framework to Janus in order to use it as your web3 provider. Lets say you want to use truffle for example, in this case all you have to do is go to your truffle-config.js file and add janus as a network:
module.exports = {
networks: {
janus: {
host: "127.0.0.1",
port: 23889,
network_id: "*",
gasPrice: "0x64"
},
...
},
...
}
Getting Janus to work with Metamask requires two things
- Configuring Metamask to point to Janus
- Locally signing transactions with a Metamask fork
- web3_clientVersion
- web3_sha3
- net_version
- net_listening
- net_peerCount
- eth_protocolVersion
- eth_chainId
- eth_mining
- eth_hashrate
- eth_gasPrice
- eth_accounts
- eth_blockNumber
- eth_getBalance
- eth_getStorageAt
- eth_getTransactionCount
- eth_getCode
- eth_sign
- eth_signTransaction
- eth_sendTransaction
- eth_sendRawTransaction
- eth_call
- eth_estimateGas
- eth_getBlockByHash
- eth_getBlockByNumber
- eth_getTransactionByHash
- eth_getTransactionByBlockHashAndIndex
- eth_getTransactionByBlockNumberAndIndex
- eth_getTransactionReceipt
- eth_getUncleByBlockHashAndIndex
- eth_getCompilers
- eth_newFilter
- eth_newBlockFilter
- eth_uninstallFilter
- eth_getFilterChanges
- eth_getFilterLogs
- eth_getLogs
- (All the above methods)
- eth_subscribe (only 'logs' for now)
- eth_unsubscribe
Use these to speed up development, but don't rely on them in your dapp
- dev_gethexaddress Convert Qtum base58 address to hex
- dev_fromhexaddress Convert from hex to Qtum base58 address for the connected network (strip 0x prefix from address when calling this)
- dev_generatetoaddress Mines blocks in regtest (accepts hex/base58 addresses - keep in mind that to use these coins, you must mine 2000 blocks)
There are two health check endpoints, GET /live and GET /ready they return 200 or 503 depending on health (if they can connect to qtumd)
Assume that you have a contract like this:
pragma solidity ^0.4.18;
contract SimpleStore {
constructor(uint _value) public {
value = _value;
}
function set(uint newValue) public {
value = newValue;
}
function get() public constant returns (uint) {
return value;
}
uint value;
}so that the bytecode is
solc --optimize --bin contracts/SimpleStore.sol
======= contracts/SimpleStore.sol:SimpleStore =======
Binary:
608060405234801561001057600080fd5b506040516020806100f2833981016040525160005560bf806100336000396000f30060806040526004361060485763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166360fe47b18114604d5780636d4ce63c146064575b600080fd5b348015605857600080fd5b5060626004356088565b005b348015606f57600080fd5b506076608d565b60408051918252519081900360200190f35b600055565b600054905600a165627a7a7230582049a087087e1fc6da0b68ca259d45a2e369efcbb50e93f9b7fa3e198de6402b810029
constructor parameters is 0000000000000000000000000000000000000000000000000000000000000001
$ curl --header 'Content-Type: application/json' --data \
'{"id":"10","jsonrpc":"2.0","method":"eth_sendTransaction","params":[{"from":"0x7926223070547d2d15b2ef5e7383e541c338ffe9","gas":"0x6691b7","gasPrice":"0x64","data":"0x608060405234801561001057600080fd5b506040516020806100f2833981016040525160005560bf806100336000396000f30060806040526004361060485763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166360fe47b18114604d5780636d4ce63c146064575b600080fd5b348015605857600080fd5b5060626004356088565b005b348015606f57600080fd5b506076608d565b60408051918252519081900360200190f35b600055565b600054905600a165627a7a7230582049a087087e1fc6da0b68ca259d45a2e369efcbb50e93f9b7fa3e198de6402b8100290000000000000000000000000000000000000000000000000000000000000001"}]}' \
'http://localhost:23889'
{
"jsonrpc": "2.0",
"result": "0xa85cacc6143004139fc68808744ea6125ae984454e0ffa6072ac2f2debb0c2e6",
"id": "10"
}
$ curl --header 'Content-Type: application/json' --data \
'{"id":"10","jsonrpc":"2.0","method":"eth_getTransactionByHash","params":["0xa85cacc6143004139fc68808744ea6125ae984454e0ffa6072ac2f2debb0c2e6"]}' \
'localhost:23889'
{
"jsonrpc":"2.0",
"result": {
"blockHash":"0x1e64595e724ea5161c0597d327072074940f519a6fb285ae60e73a4c996b47a4",
"blockNumber":"0xc9b5",
"transactionIndex":"0x5",
"hash":"0xa85cacc6143004139fc68808744ea6125ae984454e0ffa6072ac2f2debb0c2e6",
"nonce":"0x0",
"value":"0x0",
"input":"0x00",
"from":"0x7926223070547d2d15b2ef5e7383e541c338ffe9",
"to":"",
"gas":"0x363639316237",
"gasPrice":"0x3634"
},
"id":"10"
}
$ curl --header 'Content-Type: application/json' --data \
'{"id":"10","jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0x6da39dc909debf70a536bbc108e2218fd7bce23305ddc00284075df5dfccc21b"]}' \
'localhost:23889'
{
"jsonrpc": "2.0",
"result": {
"transactionHash": "0xa85cacc6143004139fc68808744ea6125ae984454e0ffa6072ac2f2debb0c2e6",
"transactionIndex": "0x5",
"blockHash": "0x1e64595e724ea5161c0597d327072074940f519a6fb285ae60e73a4c996b47a4",
"from":"0x7926223070547d2d15b2ef5e7383e541c338ffe9"
"blockNumber": "0xc9b5",
"cumulativeGasUsed": "0x8c235",
"gasUsed": "0x1c071",
"contractAddress": "0x1286595f8683ae074bc026cf0e587177b36842e2",
"logs": [],
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"status": "0x1"
},
"id": "10"
}
the ABI code of set method with param '["2"]' is 60fe47b10000000000000000000000000000000000000000000000000000000000000002
$ curl --header 'Content-Type: application/json' --data \
'{"id":"10","jsonrpc":"2.0","method":"eth_sendTransaction","params":[{"from":"0x7926223070547d2d15b2ef5e7383e541c338ffe9","gas":"0x6691b7","gasPrice":"0x64","to":"0x1286595f8683ae074bc026cf0e587177b36842e2","data":"60fe47b10000000000000000000000000000000000000000000000000000000000000002"}]}' \
'localhost:23889'
{
"jsonrpc": "2.0",
"result": "0x51a286c3bc68335274b9fd255e3988918a999608e305475105385f7ccf838339",
"id": "10"
}
get method's ABI code is 6d4ce63c
$ curl --header 'Content-Type: application/json' --data \
'{"id":"10","jsonrpc":"2.0","method":"eth_call","params":[{"from":"0x7926223070547d2d15b2ef5e7383e541c338ffe9","gas":"0x6691b7","gasPrice":"0x64","to":"0x1286595f8683ae074bc026cf0e587177b36842e2","data":"6d4ce63c"},"latest"]}' \
'localhost:23889'
{
"jsonrpc": "2.0",
"result": "0x0000000000000000000000000000000000000000000000000000000000000002",
"id": "10"
}
- Transaction signing is incompatible
- QTUM is based on Bitcoin and therefore requires Bitcoin transaction signing
- EVM transactions are done with special opcodes in Bitcoin output scripts (OP_CALL/OP_CREATE)
- Use (Beta) QTUM ethers-js library to sign transactions for use in eth_sendRawTransaction
- Currently, the library only supports sending 1 tx per block due to Bitcoin inputs being re-used so test your code to redo transactions if they are rejected with eth_sendRawTransaction
- This will be fixed in a future version
- Currently, the library only supports sending 1 tx per block due to Bitcoin inputs being re-used so test your code to redo transactions if they are rejected with eth_sendRawTransaction
- QTUM is based on Bitcoin and therefore requires Bitcoin transaction signing
- Solidity
- msg.value is denoted in satoshis, not wei, your dapp needs to handle this correctly
- Sending coins with the creation of a contract will cause a loss of coins
- This is a Qtum intentional deisgn decision and will not change
- Janus will prevent this with eth_sendTransaction but will permit it with eth_sendRawTransaction
- (Beta) QTUM ethers-js library will reject creating such a transaction
- Contract address generation differs from EVM chains
- on EVM chains, the contract address is generated via a hash of the deployer address + the nonce
- QTUM has no concept of a nonce because it is built on Bitcoin
- instead the contract address is generated via a hash of the transaction which will always be different because the Bitcoin inputs will be different
- so, if your app depends on a consistent contract address between deployments on different chains you need to pay special attention to this
- For contract address generation code, see generateContractAddress
- Account address generation differs from EVM chains
- You really only need to worry about this if you need to use the same account address on different chains
- eth_accounts and (Beta) QTUM ethers-js library will abstract this away from you
- For account address generation code, see computeAddress
- Block hash is computed differently from EVM chains
- If you are generating the blockhash from the block header, it will be wrong
- we plan to add a compatiblity layer in Janus to transparently serve the correct block when requesting an Ethereum block hash
- this will eventually require hooking up Janus to a database to keep a map of hash(block header) => QTUM block hash
- we plan to add a compatiblity layer in Janus to transparently serve the correct block when requesting an Ethereum block hash
- If you are generating the blockhash from the block header, it will be wrong
- Remix
- Debug calls are not supported so you will not be able to do any debugging in Remix
- You can use Remix with Janus or (Alpha) QTUM Metamask fork
- It is possible for a QTUM transaction to have more than one EVM transaction in it
- this is because QTUM does EVM transactions inside Bitcoin outputs which there can be multiple of
- Janus and (Beta) QTUM ethers-js library will not generate such a transaction
- Janus will try and work around such a transaction when requesting information about the transaction
- but it is not possible to map to the EVM model perfectly so there will always be some data missing for these transactions
- QTUM is proof of stake and requires coins to be mature (older than 2000 blocks) to be used in a transaction
- this includes staking rewards, gas refunds and block rewards on your local regtest environment
- a gas refund is an output generated by the miner for every EVM transaction in the same block as the EVM transaction takes place in for unused gas
- Janus will try to use mature coins first and will fall back to immature coins if there are no mature coins left
- this can result in transactions being rejected
- (Beta) QTUM ethers-js library will not use immature coins for transactions, but if you end up using high gas limits for your transactions you could quickly run out of usable coins
- if there are no mature coins, the transaction will fail locally
- this includes staking rewards, gas refunds and block rewards on your local regtest environment
- Bitcoin input scripts
- Bitcoin has many different types of scripts
- For a detailed primer on this topic see A breakdown of Bitcoin "standard" script types (crazy long)
- eth_sendTransaction delegates transaction signing to QTUM so most input scripts should be supported
- (Beta) QTUM ethers-js library deals with signing transactions locally and only supports Pay to public key hash (P2PKH) scripts, other script types will be ignored and not selected.
- This can result in your spendable balance being lower than your actual balance.
- Support for Pay to public key (P2PK) input scripts is on the roadmap
- Bitcoin has many different types of scripts
- eth_estimateGas
- Gas estimation on QTUM is not perfect, so a buffer of 10% is added in Janus
- Gas will be refunded in the block that your transaction is mined
- Keep in mind that to re-use this gas refund, you must wait 2000 blocks
- eth_sendTransaction
- When trying to send all your QTUM Balance in a transaction, in EVM you would do value = total - (gas limit * gas price)
- Since QTUM uses Bitcoin transactions, the cost of a transaction differs based on how many bytes are in the transaction
- This means if you have many inputs in a transaction, it will cost more to send
- There is no easy way to send your entire QTUM balance in a single transaction with Janus
- However, (Beta) QTUM ethers-js library supports this via value = total - (gas limit * gas price)
- Adding this to Janus is on the roadmap
- Since QTUM runs on Bitcoin, QTUM has the concept of dust
- Janus delegates transaction signing to QTUM so QTUM will handle dealing with dust
- (Beta) QTUM ethers-js library currently uses dust, but at some point will prevent spending dust by default with a semver change
- On a transfer of Qtum to a Qtum address, there is no receipt generated for such a transfer
- When converting from WEI -> QTUM, precision is lost due to QTUM's smallest demonination being 1 satoshi.
- 1 satoshi = 0.00000001 QTUM = 10000000000 wei
- QTUM's minimum gas price is 40 satoshi
- When specifying a gas price in wei lower than that, the minimum gas price will be used (40 satoshi)
- With the minimum fee per byte being 4 satoshi
- QTUM will reject transactions with very large fees (to prevent accidents)
- Transparently translate eth_sendRawTransaction from an EVM transaction to a QTUM transaction if the same key is hosted
- Transparently serve blocks by their Ethereum block hash
- Send all QTUM support via eth_sendTransaction
- For eth_subscribe only the 'logs' type is supported at the moment