Skip to content

revolutionchain/revo-ethers

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Revo Ethers

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

TLDR for Ethereum developers

import {Contract} from "ethers"
import {
    // import RevoProvider as Provider, replacement for ethers Provider
    RevoProvider as Provider,
    // import RevoWallet as Wallet, replacement for ethers Wallet
    RevoWallet as Wallet,
    // import RevoContractFactory as ContractFactory, replacement for ethers ContractFactory
    RevoContractFactory as ContractFactory,
    // Revo has two bip44 derivation paths, wallets use different ones
    // this is optional, the default is SLIP_BIP44_PATH
    REVO_BIP44_PATH, // Compatible with Revo core wallet and electrum
    SLIP_BIP44_PATH,  // Compatible with 3rd party wallets
    // Revo 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,
    // Revo uses a different hash prefix for messages, use these ethers replacement functions
    hashMessage,
    messagePrefix
} from "revo-ethers-wrapper";
// Revo 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
        // RevoWallet#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 revo-ethers-wrapper

Example

import {Contract} from "ethers"
import {
    RevoProvider as Provider,
    RevoWallet as Wallet,
    RevoContractFactory as ContractFactory,
    REVO_BIP44_PATH, // Compatible with Revo core wallet and electrum
    SLIP_BIP44_PATH  // Compatible with 3rd party wallets
} from "revo-ethers-wrapper";
// point Revo Provider at Janus node https://github.com/revolutionchain/charon/
const mainnetProvider = new Provider("https://charon.qiswap.com/api/");
const testnetProvider = new Provider("https://testnet-charon.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/revop/charon
const regtestProvider = new Provider("http://localhost:23889");
// or register an account with qnode https://qnode.revo.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
        // RevoWallet#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();
// ERC20 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/revolutionchain/charon/blob/master/playground/pet-shop-tutorial/contracts/ERC20Token.sol
const BYTECODE = "0x60806040526100106008600a610141565b61001e90633b9aca00610154565b60005534801561002d57600080fd5b50600080543382526001602052604090912055610173565b634e487b7160e01b600052601160045260246000fd5b600181815b8085111561009657816000190482111561007c5761007c610045565b8085161561008957918102915b93841c9390800290610060565b509250929050565b6000826100ad5750600161013b565b816100ba5750600061013b565b81600181146100d057600281146100da576100f6565b600191505061013b565b60ff8411156100eb576100eb610045565b50506001821b61013b565b5060208310610133831016604e8410600b8410161715610119575081810a61013b565b610123838361005b565b806000190482111561013757610137610045565b0290505b92915050565b600061014d838361009e565b9392505050565b600081600019048311821515161561016e5761016e610045565b500290565b6106e0806101826000396000f3fe6080604052600436106100855760003560e01c806306fdde0314610094578063095ea7b3146100de57806318160ddd1461010e57806323b872dd14610132578063313ce567146101525780635a3b7e421461017957806370a08231146101ae57806395d89b41146101db578063a9059cbb1461020a578063dd62ed3e1461022a57600080fd5b3661008f57600080fd5b600080fd5b3480156100a057600080fd5b506100c860405180604001604052806008815260200167145490c8151154d560c21b81525081565b6040516100d5919061050a565b60405180910390f35b3480156100ea57600080fd5b506100fe6100f936600461057b565b610262565b60405190151581526020016100d5565b34801561011a57600080fd5b5061012460005481565b6040519081526020016100d5565b34801561013e57600080fd5b506100fe61014d3660046105a5565b610315565b34801561015e57600080fd5b50610167600881565b60405160ff90911681526020016100d5565b34801561018557600080fd5b506100c860405180604001604052806009815260200168546f6b656e20302e3160b81b81525081565b3480156101ba57600080fd5b506101246101c93660046105e1565b60016020526000908152604090205481565b3480156101e757600080fd5b506100c86040518060400160405280600381526020016251544360e81b81525081565b34801561021657600080fd5b506100fe61022536600461057b565b61042d565b34801561023657600080fd5b506101246102453660046105fc565b600260209081526000928352604080842090915290825290205481565b6000826001600160a01b03811661027857600080fd5b8215806102a657503360009081526002602090815260408083206001600160a01b0388168452909152902054155b6102af57600080fd5b3360008181526002602090815260408083206001600160a01b03891680855290835292819020879055518681529192917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a35060019392505050565b6000836001600160a01b03811661032b57600080fd5b836001600160a01b03811661033f57600080fd5b6001600160a01b038616600090815260026020908152604080832033845290915290205461036d90856104c8565b6001600160a01b0387166000818152600260209081526040808320338452825280832094909455918152600190915220546103a890856104c8565b6001600160a01b0380881660009081526001602052604080822093909355908716815220546103d790856104eb565b6001600160a01b03808716600081815260016020526040908190209390935591519088169060008051602061068b833981519152906104199088815260200190565b60405180910390a350600195945050505050565b6000826001600160a01b03811661044357600080fd5b3360009081526001602052604090205461045d90846104c8565b33600090815260016020526040808220929092556001600160a01b0386168152205461048990846104eb565b6001600160a01b03851660008181526001602052604090819020929092559051339060008051602061068b833981519152906103039087815260200190565b6000818310156104da576104da61062f565b6104e4828461065b565b9392505050565b6000806104f88385610672565b9050838110156104e4576104e461062f565b600060208083528351808285015260005b818110156105375785810183015185820160400152820161051b565b81811115610549576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b038116811461057657600080fd5b919050565b6000806040838503121561058e57600080fd5b6105978361055f565b946020939093013593505050565b6000806000606084860312156105ba57600080fd5b6105c38461055f565b92506105d16020850161055f565b9150604084013590509250925092565b6000602082840312156105f357600080fd5b6104e48261055f565b6000806040838503121561060f57600080fd5b6106188361055f565b91506106266020840161055f565b90509250929050565b634e487b7160e01b600052600160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60008282101561066d5761066d610645565b500390565b6000821982111561068557610685610645565b50019056feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa264697066735822122002051c03b9e6486c5b21b4c14e6ea0627a710175b0142fc40ce30042333632a464736f6c634300080a0033"
// RevoContractFactory 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 ERC20 token and interact with it 
async function transferToken(contractAddress, from, to, value) {
    const qrc20 = new Contract(contractAddress, ERC_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 REVO
await signer.sendTransaction({
    to: "0x7926223070547D2D15b2eF5e7383E541c338FfE9",
    from: signer.address,
    gasLimit: "0x3d090",
    gasPrice: "0x190",
    // in Satoshis
    value: "0xfffff",
    data: "",
});

Signing/recovering messages

REVO 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 \15REVO Signed Message:\n

VRS Signature format

Ethereum serializes signautres as RSV while Bitcoin/Revo 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 "revo-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 RevoWallet
    inputs: txWithoutInputRequirements.inputs,
    // throw unless inputs match exactly
    nonce: txWithoutInputRequirements.nonce,
});
const txReceipt = await txWithInputRequirement.sendTransaction();

Notes

  • Issues

Revo 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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 100.0%