npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

qtum-ethers-wrapper

v2.1.2

Published

A module for using Qtum through an Ethers compliant library to make it simpler to use Qtum

Downloads

21

Readme

Qtum Ethers

A module for using Qtum through an Ethers compliant library to make it simpler to use Qtum

TLDR for Ethereum developers

import {Contract} from "ethers"
import {
    // import QtumProvider as Provider, replacement for ethers Provider
    QtumProvider as Provider,
    // import QtumWallet as Wallet, replacement for ethers Wallet
    QtumWallet as Wallet,
    // import QtumContractFactory as ContractFactory, replacement for ethers ContractFactory
    QtumContractFactory as ContractFactory,
    // Qtum has two bip44 derivation paths, wallets use different ones
    // this is optional, the default is SLIP_BIP44_PATH
    QTUM_BIP44_PATH, // Compatible with Qtum core wallet and electrum
    SLIP_BIP44_PATH,  // Compatible with 3rd party wallets
    // Qtum uses compressed public/private keys and you need to consider them when doing cryptography
    // these functions are replacements for ethers' ones
    // they have an extra optional parameter to determine whether to use compressed or uncompressed keys (see below)
    computeAddress,
    recoverAddress,
    // Qtum uses a different hash prefix for messages, use these ethers replacement functions
    hashMessage,
    messagePrefix
} from "qtum-ethers-wrapper";
// Qtum does not support nonces since it is a fork of Bitcoin
// there is an equivalent workaround feature built into this library
// it hashes the Bitcoin UTXO inputs and creates a 'nonce'
// you can get this nonce and use it to force usage of specific Bitcoin UTXO inputs
// (see documentation further below if idempotency is required)
const signer = new Wallet(
    privkey,
    provider,
    {
        // optional, will default to true in a future release
        filterDust: true,
        // optional, disable remembering which UTXOs we consume
        // so that we can avoid trying to spend them again while
        // new transactions are in the mempool trying to spend them.
        // having this enabled lets the library send multiple
        // transactions per block.
        disableConsumingUtxos: true,
        // optional, specify inputs to ignore when creating transactions
        // this list can be created from a serialized hex transaction via
        // QtumWallet#getIdempotentNonce.inputs
        ignoreInputs: [''],
        // list of inputs to force, throws if unable to use them (eg they are already spent)
        inputs: [''],
        // hash of inputs, throws if a transaction does not re-use the exact same inputs
        nonce: '',
    }
)

Installation

Open a console and run

npm install qtum-ethers-wrapper

Example

import {Contract} from "ethers"
import {
    QtumProvider as Provider,
    QtumWallet as Wallet,
    QtumContractFactory as ContractFactory,
    QTUM_BIP44_PATH, // Compatible with Qtum core wallet and electrum
    SLIP_BIP44_PATH  // Compatible with 3rd party wallets
} from "qtum-ethers-wrapper";
// point Qtum Provider at Janus node https://github.com/qtumproject/janus/
const mainnetProvider = new Provider("https://janus.qiswap.com/api/");
const testnetProvider = new Provider("https://testnet-janus.qiswap.com/api/");
// or deploy your own node locally with a regtest network
// see for a pre-built docker image https://hub.docker.com/r/qtump/janus
const regtestProvider = new Provider("http://localhost:23889");
// or register an account with qnode https://qnode.qtum.info

const provider = testnetProvider;
// create a wallet
const privkey = "99dda7e1a59655c9e02de8592be3b914df7df320e72ce04ccf0427f9a366ec6e"
const signer = new Wallet(
    privkey,
    provider,
    {
        // optional, will default to true in a future release
        filterDust: true,
        // optional, disable remembering which UTXOs we consume
        // so that we can avoid trying to spend them again while
        // new transactions are in the mempool trying to spend them.
        // having this enabled lets the library send multiple
        // transactions per block.
        disableConsumingUtxos: true,
        // optional, specify inputs to ignore when creating transactions
        // this list can be created from a serialized hex transaction via
        // QtumWallet#getIdempotentNonce.inputs
        ignoreInputs: [''],
        // list of inputs to force, throws if unable to use them (eg they are already spent)
        inputs: [''],
        // hash of inputs, throws if a transaction does not re-use the exact same inputs
        nonce: '',
    }
)
// or create a random account and get the mnemonic
// const signer = Wallet.createRandom(/*{ path = SLIP_BIP44_PATH }*/}).connect(provider);
// const {locale, path, phrase} = signer._mnemonic();
// QRC20 ABI
const ABI = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}];
// https://github.com/qtumproject/janus/blob/master/playground/pet-shop-tutorial/contracts/QRC20Token.sol
const BYTECODE = "0x60806040526100106008600a610141565b61001e90633b9aca00610154565b60005534801561002d57600080fd5b50600080543382526001602052604090912055610173565b634e487b7160e01b600052601160045260246000fd5b600181815b8085111561009657816000190482111561007c5761007c610045565b8085161561008957918102915b93841c9390800290610060565b509250929050565b6000826100ad5750600161013b565b816100ba5750600061013b565b81600181146100d057600281146100da576100f6565b600191505061013b565b60ff8411156100eb576100eb610045565b50506001821b61013b565b5060208310610133831016604e8410600b8410161715610119575081810a61013b565b610123838361005b565b806000190482111561013757610137610045565b0290505b92915050565b600061014d838361009e565b9392505050565b600081600019048311821515161561016e5761016e610045565b500290565b6106e0806101826000396000f3fe6080604052600436106100855760003560e01c806306fdde0314610094578063095ea7b3146100de57806318160ddd1461010e57806323b872dd14610132578063313ce567146101525780635a3b7e421461017957806370a08231146101ae57806395d89b41146101db578063a9059cbb1461020a578063dd62ed3e1461022a57600080fd5b3661008f57600080fd5b600080fd5b3480156100a057600080fd5b506100c860405180604001604052806008815260200167145490c8151154d560c21b81525081565b6040516100d5919061050a565b60405180910390f35b3480156100ea57600080fd5b506100fe6100f936600461057b565b610262565b60405190151581526020016100d5565b34801561011a57600080fd5b5061012460005481565b6040519081526020016100d5565b34801561013e57600080fd5b506100fe61014d3660046105a5565b610315565b34801561015e57600080fd5b50610167600881565b60405160ff90911681526020016100d5565b34801561018557600080fd5b506100c860405180604001604052806009815260200168546f6b656e20302e3160b81b81525081565b3480156101ba57600080fd5b506101246101c93660046105e1565b60016020526000908152604090205481565b3480156101e757600080fd5b506100c86040518060400160405280600381526020016251544360e81b81525081565b34801561021657600080fd5b506100fe61022536600461057b565b61042d565b34801561023657600080fd5b506101246102453660046105fc565b600260209081526000928352604080842090915290825290205481565b6000826001600160a01b03811661027857600080fd5b8215806102a657503360009081526002602090815260408083206001600160a01b0388168452909152902054155b6102af57600080fd5b3360008181526002602090815260408083206001600160a01b03891680855290835292819020879055518681529192917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a35060019392505050565b6000836001600160a01b03811661032b57600080fd5b836001600160a01b03811661033f57600080fd5b6001600160a01b038616600090815260026020908152604080832033845290915290205461036d90856104c8565b6001600160a01b0387166000818152600260209081526040808320338452825280832094909455918152600190915220546103a890856104c8565b6001600160a01b0380881660009081526001602052604080822093909355908716815220546103d790856104eb565b6001600160a01b03808716600081815260016020526040908190209390935591519088169060008051602061068b833981519152906104199088815260200190565b60405180910390a350600195945050505050565b6000826001600160a01b03811661044357600080fd5b3360009081526001602052604090205461045d90846104c8565b33600090815260016020526040808220929092556001600160a01b0386168152205461048990846104eb565b6001600160a01b03851660008181526001602052604090819020929092559051339060008051602061068b833981519152906103039087815260200190565b6000818310156104da576104da61062f565b6104e4828461065b565b9392505050565b6000806104f88385610672565b9050838110156104e4576104e461062f565b600060208083528351808285015260005b818110156105375785810183015185820160400152820161051b565b81811115610549576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b038116811461057657600080fd5b919050565b6000806040838503121561058e57600080fd5b6105978361055f565b946020939093013593505050565b6000806000606084860312156105ba57600080fd5b6105c38461055f565b92506105d16020850161055f565b9150604084013590509250925092565b6000602082840312156105f357600080fd5b6104e48261055f565b6000806040838503121561060f57600080fd5b6106188361055f565b91506106266020840161055f565b90509250929050565b634e487b7160e01b600052600160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60008282101561066d5761066d610645565b500390565b6000821982111561068557610685610645565b50019056feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa264697066735822122002051c03b9e6486c5b21b4c14e6ea0627a710175b0142fc40ce30042333632a464736f6c634300080a0033"
// QtumContractFactory is required to be used instead of standard ethers ContractFactory due to how contract addresses are computed differently
const simpleStore = new ContractFactory(ABI, BYTECODE, signer);

// simpleStore deployment example, returns address
async function deployToken() {
    const deployment = await simpleStore.deploy({
        gasLimit: "0x7a120", // 500,000
        gasPrice: "0x190" // in WEI OR Satoshis
    });
    await deployment.deployed();
    return deployment.address
}
// connect to QRC20 token and interact with it 
async function transferToken(contractAddress, from, to, value) {
    const qrc20 = new Contract(contractAddress, QRC_ABI, signer)
    const name = await qrc20.transfer(from, to, value,
        {
            gasLimit: "0x62521", // 62521
            gasPrice: "0x5d21dba000", // in WEI OR Satoshis (0x190)
        }
    );
}

const contractAddress = await deployToken();
await transferToken(contractAddress, "0x...", "0x...", 1);

// sending QTUM
await signer.sendTransaction({
    to: "0x7926223070547D2D15b2eF5e7383E541c338FfE9",
    from: signer.address,
    gasLimit: "0x3d090",
    gasPrice: "0x190",
    // in Satoshis
    value: "0xfffff",
    data: "",
});

Signing/recovering messages

QTUM uses compressed public keys to generate addresses so you need to use our modified recoverAddress instead of ethers.utils.recoverAddress.

Uncompressed keys are supported as well, it uses the recovery parameter to identify if an uncompressed key was used.

Hash message also uses a different message prefix than Ethereum, it uses \15QTUM Signed Message:\n

VRS Signature format

Ethereum serializes signautres as RSV while Bitcoin/Qtum uses VRS, this library supports both formats. The signatures are identical except for how they are serialized, they reference the same points on the elliptic curve.

import {
    computeAddress,
    hashMessage,
    messagePrefix,
    recoverAddress,
    recoverAddressBtc,
} from "qtum-ethers-wrapper";

const message = "1234";
const digest = hashMessage(message);
const signedMessageRSV = await signer.signMessage(message);
const signedMessageVRS = await signer.signMessageBtc(message);
const recoveredRSV = recoverAddress(digest, signedMessageRSV);
const recoveredVRS = recoverAddressBtc(digest, signedMessageVRS);
if (recoveredRSV !== recoveredVRS) {
    throw new Error("Expected identical addresses");
}

Idempotency

Idempotency in Bitcoin forks involves tying logic to specific UTXO inputs or re-sending the raw serialized transaction and re-crafting a new transaction if that one fails.

This can be done by specifying inputs to use and a special nonce.

The nonce is a hash of each UTXO input in the created transaction.

You will need to keep track of what inputs are attached to what transaction and you can continue sending the transaction

const tx = await signer.sendTransaction({
    to: "0x7926223070547D2D15b2eF5e7383E541c338FfE9",
    from: signer.address,
    gasLimit: "0x3d090",
    gasPrice: "0x190",
    // in Satoshis
    value: "0xfffff",
    data: "",
});
console.log("Generated hash of inputs:", tx.nonce);
console.log("Inputs of transaction:", JSON.stringify(tx.inputs));
console.log("bitcoinjs-lib decoded transaction:", tx.decoded);
console.log("raw serialized signed transaction:", tx.signedTransaction);
// save the signed transaction to your database
// you can re-send the signed transaction as many times as you want and it will always be idempotent
// send the transaction and get a transaction response
const transactionResponse = await tx.sendTransaction();

// re-send the raw signed transaction
const transactionResponse = await provider.sendTransaction(tx.signedTransaction);

// create a transaction while requiring specific inputs
const txWithoutInputRequirements = await signer.sendTransactionIdempotent({
    to: "0x7926223070547D2D15b2eF5e7383E541c338FfE9",
    from: signer.address,
    gasLimit: "0x3d090",
    gasPrice: "0x190",
    // in Satoshis
    value: "0xfffff",
    data: "",
});
console.log("Created transaction that uses these inputs:", JSON.stringify(txWithoutInputRequirements.inputs));
console.log("Use this nonce to throw if the exact same inputs are not used:", txWithoutInputRequirements.nonce);
const txWithInputRequirement = await signer.sendTransactionIdempotent({
    to: "0x7926223070547D2D15b2eF5e7383E541c338FfE9",
    from: signer.address,
    gasLimit: "0x3d090",
    gasPrice: "0x190",
    // in Satoshis
    value: "0xfffff",
    data: "",
    // you can specify inputs here or when creating an instance of QtumWallet
    inputs: txWithoutInputRequirements.inputs,
    // throw unless inputs match exactly
    nonce: txWithoutInputRequirements.nonce,
});
const txReceipt = await txWithInputRequirement.sendTransaction();

Notes

  • Issues

Qtum estimate gas function is not perfect so eth_estimateGas has a 20% buffer for gas limit

Janus doesn't return a transaction receipt for p2pkh tx's

This extension works with p2pk and p2pkh scripts only and asks Janus for p2pk and p2pkh scripts only