evm-provider
implements a web3 provider which can interact with the Reef chain EVM.
If you only care about developing Solidity contracts on the Reef chain, @reef-chain/evm-provider
is used in our Hardhat Reef environment. The environment simplifies and abstracts all the low-level intricacies, so you can only focus on the Solidity part. See hardhat-reef-examples repo for more examples.
If you need more control, then it can also be used as a Substrate provider to query or to interact with the Reef chain using the same calls as in the Polkadot.js.
Install dependencies with yarn
see issue.
yarn add @reef-chain/evm-provider
To create a Provider
instance, use the following code:
import {
TestAccountSigningKey,
Provider,
Signer,
} from "@reef-chain/evm-provider";
import { WsProvider, Keyring } from "@polkadot/api";
import { createTestPairs } from "@polkadot/keyring/testingPairs";
import { KeyringPair } from "@polkadot/keyring/types";
const WS_URL = process.env.WS_URL || "ws://127.0.0.1:9944";
const seed = process.env.SEED;
const setup = async () => {
const provider = new Provider({
provider: new WsProvider(WS_URL),
});
await provider.api.isReady;
let pair: KeyringPair;
if (seed) {
const keyring = new Keyring({ type: "sr25519" });
pair = keyring.addFromUri(seed);
} else {
const testPairs = createTestPairs();
pair = testPairs.alice;
}
const signingKey = new TestAccountSigningKey(provider.api.registry);
signingKey.addKeyringPair(pair);
const signer = new Signer(provider, pair.address, signingKey);
// Claim default account
if (!(await signer.isClaimed())) {
console.log(
"No claimed EVM account found -> claimed default EVM account: ",
await signer.getAddress()
);
await signer.claimDefaultAccount();
}
return {
signer,
provider,
};
};
export default setup;
with this object you can interact with the Substrate chain.
If you want to interact with injected sources (e.g. from Polkadot{.js}) you can do the following:
import { Provider, Signer, } from "@reef-chain/evm-provider";
import { WsProvider } from "@polkadot/api";
import { web3Accounts, web3Enable } from "@polkadot/extension-dapp";
const WS_URL = process.env.WS_URL || "ws://127.0.0.1:9944";
const seed = process.env.SEED;
const setup = async () => {
// Return an array of all the injected sources
// (this needs to be called first)
const allInjected = await web3Enable('your dapp');
const injected;
if (allInjected[0] && allInjected[0].signer) {
injected = allInjected[0].signer;
}
// Return an array of { address, meta: { name, source } }
// (meta.source contains the name of the extension)
const allAccounts = await web3Accounts();
let account;
if (allAccounts[0] && allAccounts[0].address) {
account = allAccounts[0].address;
}
const provider = new Provider({
provider: new WsProvider(WS_URL)
});
await provider.api.isReady;
const signer = new Signer(provider, account, injected);
// Claim default account
if (!(await signer.isClaimed())) {
console.log(
"No claimed EVM account found -> claimed default EVM account: ",
await signer.getAddress()
);
await signer.claimDefaultAccount();
}
return {
signer,
provider,
};
};
export default setup;
Most, but not all, of evm-provider
API is compatible with ethers.js
. If you are not familiar with ethers.js
, you can start by looking at its documentation. See our Reefswap example on how it uses the above setup
script to deploy and interact with the EVM.
// ethers
let accounts = await this.provider.listAccounts();
let selectedAccount = accounts[0];
// evm-provider
let selectedAccount = await this.signer.queryEvmAddress();
If you would like to inject an evm address that you already own you can do so via the claimAccount
extrinsic. The script below illustrates how this can be done.
import { Keyring, WsProvider } from '@polkadot/api';
import { ethers } from 'ethers';
import { createClaimEvmSignature } from './utils';
import { Provider } from '.';
const WS_URL = process.env.WS_URL || 'ws://127.0.0.1:9944';
// reef address - 5H728gLgx4yuCSVEwGCAfLo3RtzTau9F6cTNqNJtrqqjACWq
const reefPrivKeyRaw = process.env.REEF_PRIV_KEY || "0x0000000000000000000000000000000000000000000000000000000000000000";
const ethPrivKey = process.env.ETH_PRIV_KEY || "0x81376b9868b292a46a1c486d344e427a3088657fda629b5f4a647822d329cd6a";
const main = async (): Promise<void> => {
const provider = new Provider({
provider: new WsProvider(WS_URL)
});
await provider.api.isReady;
const keyring = new Keyring({ type: 'sr25519' });
const reefKey = keyring.addFromUri(reefPrivKeyRaw);
const ethKey = new ethers.Wallet(ethPrivKey);
const msg = createClaimEvmSignature(reefKey.address);
let signature = await ethKey.signMessage(msg);
await provider.api.tx.evmAccounts.claimAccount(
ethKey.address,
signature
).signAndSend(reefKey);
process.exit();
};
main();
This example illustrates how to build a payload to be signed with an EVM account. Once signed, the signature is added to the extrinsic and sent to the chain.
import { PopulatedTransaction } from "ethers";
import { ethers } from "ethers";
import { Provider } from '.';
import { buildPayload, sendSignedTransaction } from './utils';
const buildSignAndSend = async (provider: Provider, signerAddress: string): Promise<void> => {
const contract = new ethers.Contract(
flipperContractAddress,
FlipperAbi,
provider as any
);
const tx: PopulatedTransaction = await contract.populateTransaction.flip();
const { payload, extrinsic } = await buildPayload(provider, signerAddress, tx);
// (...) Add logic to send payload to signer and receive signature
extrinsic.addSignature(signerAddress, signature, payload);
const txResult = await sendSignedTransaction(
provider,
signerAddress,
tx,
payload,
extrinsic,
signature
);
};
The Provider provides an API for interacting with nodes and is an instance of ethers.js
AbstractProvider.
The Signer class can sign transactions and messages using a private key. When using the wallet for the first time, make sure to always claim the EVM account for the wallet you are using:
signer.claimDefaultAccount();
before performing any EVM calls otherwise it may lead to InsufficientBalance
errors.
In addition to the gas limit (processing), the Reef chain also charges a storage fee. When you interact with the EVM, Reef chain will estimate both fees and as such the fees will be invisible to the user. This should work in 99% of cases. It assumes you have at least 60 REEF tokens on the signing account. However, sometimes the heuristics (usually for more complex contracts) are wrong. In this case you can force the values of gasLimit
and storageLimit
by adding them to the options
dictionary at the end of every call, for example:
await factory.deploy(<contract_args>, {
gasLimit: 1000000,
customData: { storageLimit: 1000000 }
});
If you require maximum flexibility evm-provider
exports maximum gas and storage limit:
import { MAX_GAS_LIMIT, MAX_STORAGE_LIMIT } from "@reef-chain/evm-provider";
which default to U64MAX
and U32MAX
respectively.
There is no such API. Substrate does not expose a "query-by-tx-hash" RPC, nor are transactions indexed by hash on the Substrate node. The reason for this is that transaction hashes are non-unique across the chain, although they will generally be unique inside a block.
Please use GraphQL for this purpose.
See above.
- versions 1.*.* work from Reef v8 chain onwards
- no longer requires
resolutions
withethers@5.0.9
- no longer requires
- versions 0.*.* work from Reef v0 to v7