import axios from 'axios';
+import axios from 'axios';
import Web3 from 'web3';
import WebSocket from 'ws';
import EventEmitter from 'events';
import { Mutex } from 'async-mutex';
-import { generateMnemonic } from 'bip39';
-
+import { approve } from './tokens.mjs';
+import erc20 from './abis/ERC20.mjs';
+import erc721 from './abis/ERC721.mjs';
+import erc1155 from './abis/ERC1155.mjs';
+import { ENVIRONMENTS } from './constants.mjs';
/**
@class
@@ -83,20 +87,20 @@ Source: nf3.mjs
latestWithdrawHash;
- constructor(
- clientBaseUrl,
- optimistBaseUrl,
- optimistWsUrl,
- web3WsUrl,
- ethereumSigningKey,
- zkpKeys,
- ) {
- this.clientBaseUrl = clientBaseUrl;
- this.optimistBaseUrl = optimistBaseUrl;
- this.optimistWsUrl = optimistWsUrl;
+ mnemonic = {};
+
+ contracts = { ERC20: erc20, ERC721: erc721, ERC1155: erc1155 };
+
+ currentEnvironment;
+
+ constructor(web3WsUrl, ethereumSigningKey, environment = ENVIRONMENTS.localhost, zkpKeys) {
+ this.clientBaseUrl = environment.clientApiUrl;
+ this.optimistBaseUrl = environment.optimistApiUrl;
+ this.optimistWsUrl = environment.optimistWsUrl;
this.web3WsUrl = web3WsUrl;
this.ethereumSigningKey = ethereumSigningKey;
this.zkpKeys = zkpKeys;
+ this.currentEnvironment = environment;
}
/**
@@ -104,21 +108,20 @@ Source: nf3.mjs
blockchain.
@returns {Promise}
*/
- async init() {
- this.web3 = new Web3(this.web3WsUrl);
- // Generate a random mnemonic (uses crypto.randomBytes under the hood), defaults to 128-bits of entropy
- const mnemonic = generateMnemonic();
- this.zkpKeys = this.zkpKeys || (await axios.post(`${this.clientBaseUrl}/generate-keys`), { mnemonic, path: `m/44'/60'/0'/0` }).data;
+ async init(mnemonic) {
+ this.setWeb3Provider();
this.shieldContractAddress = await this.getContractAddress('Shield');
this.proposersContractAddress = await this.getContractAddress('Proposers');
this.challengesContractAddress = await this.getContractAddress('Challenges');
this.stateContractAddress = await this.getContractAddress('State');
// set the ethereumAddress iff we have a signing key
- if (this.ethereumSigningKey)
- this.ethereumAddress = this.web3.eth.accounts.privateKeyToAccount(
- this.ethereumSigningKey,
- ).address;
- return this.subscribeToIncomingViewingKeys();
+ if (typeof this.ethereumSigningKey === 'string') {
+ this.ethereumAddress = await this.getAccounts();
+ }
+ // set zkp keys from mnemonic if provided
+ if (typeof mnemonic !== 'undefined') {
+ await this.setzkpKeysFromMnemonic(mnemonic, 0);
+ }
}
/**
@@ -127,11 +130,9 @@ Source: nf3.mjs
@method
@param {string} key - the ethereum private key as a hex string.
*/
- setEthereumSigningKey(key) {
+ async setEthereumSigningKey(key) {
this.ethereumSigningKey = key;
- this.ethereumAddress = this.web3.eth.accounts.privateKeyToAccount(
- this.ethereumSigningKey,
- ).address;
+ this.ethereumAddress = await this.getAccounts();
// clear the nonce as we're using a fresh account
this.nonce = 0;
}
@@ -144,6 +145,28 @@ Source: nf3.mjs
*/
setzkpKeys(keys) {
this.zkpKeys = keys;
+ return this.subscribeToIncomingViewingKeys();
+ }
+
+ /**
+ Setter for the zkp keys by mnemonic, in case it wasn't known at build time and we don't
+ want to use autogenerated ones.
+ @method
+ @param {string} mnemonic - 12 word phrase
+ @param {number} addressIndex - Index used to generate keys combined with mnemonic
+ */
+ async setzkpKeysFromMnemonic(mnemonic, addressIndex) {
+ if (mnemonic !== '') {
+ this.mnemonic.phrase = mnemonic;
+ }
+ this.mnemonic.addressIndex = addressIndex.toString();
+ this.zkpKeys = (
+ await axios.post(`${this.clientBaseUrl}/generate-keys`, {
+ mnemonic: this.mnemonic.phrase,
+ path: `m/44'/60'/0'/${this.mnemonic.addressIndex}`,
+ })
+ ).data;
+ return this.subscribeToIncomingViewingKeys();
}
/**
@@ -164,22 +187,34 @@ Source: nf3.mjs
) {
// We'll manage the nonce ourselves because we can run too fast for the blockchain client to update
// we need a Mutex so that we don't get a nonce-updating race.
+
let tx;
await this.nonceMutex.runExclusive(async () => {
// if we don't have a nonce, we must get one from the ethereum client
if (!this.nonce) this.nonce = await this.web3.eth.getTransactionCount(this.ethereumAddress);
+
+ let gasPrice = 10000000000;
+ const gas = (await this.web3.eth.getBlock('latest')).gasLimit;
+ const blockGasPrice = Number(await this.web3.eth.getGasPrice());
+ if (blockGasPrice > gasPrice) gasPrice = blockGasPrice;
+
tx = {
+ from: this.ethereumAddress,
to: contractAddress,
data: unsignedTransaction,
value: fee,
- gas: 10000000,
- gasPrice: 10000000000,
+ gas,
+ gasPrice,
nonce: this.nonce,
};
this.nonce++;
});
- const signed = await this.web3.eth.accounts.signTransaction(tx, this.ethereumSigningKey);
- return this.web3.eth.sendSignedTransaction(signed.rawTransaction);
+
+ if (this.ethereumSigningKey) {
+ const signed = await this.web3.eth.accounts.signTransaction(tx, this.ethereumSigningKey);
+ return this.web3.eth.sendSignedTransaction(signed.rawTransaction);
+ }
+ return this.web3.eth.sendTransaction(tx);
}
/**
@@ -243,6 +278,15 @@ Source: nf3.mjs
@returns {Promise} Resolves into the Ethereum transaction receipt.
*/
async deposit(ercAddress, tokenType, value, tokenId, fee = this.defaultFee) {
+ await approve(
+ ercAddress,
+ this.ethereumAddress,
+ this.shieldContractAddress,
+ tokenType,
+ value,
+ this.web3,
+ );
+
const res = await axios.post(`${this.clientBaseUrl}/deposit`, {
ercAddress,
tokenId,
@@ -362,14 +406,8 @@ Source: nf3.mjs
*/
async finaliseWithdrawal(withdrawTransactionHash) {
// find the L2 block containing the L2 transaction hash
- let res = await axios.get(
- `${this.optimistBaseUrl}/block/transaction-hash/${withdrawTransactionHash}`,
- );
- const { block, transactions, index } = res.data;
- res = await axios.post(`${this.clientBaseUrl}/finalise-withdrawal`, {
- block,
- transactions,
- index,
+ const res = await axios.post(`${this.clientBaseUrl}/finalise-withdrawal`, {
+ transactionHash: withdrawTransactionHash,
});
return this.submitTransaction(res.data.txDataToSign, this.shieldContractAddress, 0);
}
@@ -383,18 +421,15 @@ Source: nf3.mjs
@param {number} fee - the amount being paid for the instant withdrawal service
*/
async requestInstantWithdrawal(withdrawTransactionHash, fee) {
- // find the L2 block containing the L2 transaction hash
- let res = await axios.get(
- `${this.optimistBaseUrl}/block/transaction-hash/${withdrawTransactionHash}`,
- );
- const { block, transactions, index } = res.data;
- // set the instant withdrawal fee
- res = await axios.post(`${this.clientBaseUrl}/set-instant-withdrawal`, {
- block,
- transactions,
- index,
- });
- return this.submitTransaction(res.data.txDataToSign, this.shieldContractAddress, fee);
+ try {
+ // set the instant withdrawal fee
+ const res = await axios.post(`${this.clientBaseUrl}/set-instant-withdrawal`, {
+ transactionHash: withdrawTransactionHash,
+ });
+ return this.submitTransaction(res.data.txDataToSign, this.shieldContractAddress, fee);
+ } catch {
+ return null;
+ }
}
/**
@@ -500,6 +535,21 @@ Source: nf3.mjs
return this.submitTransaction(res.data.txDataToSign, this.proposersContractAddress, 0);
}
+ /**
+ Change current proposer.
+ It will use the address of the Ethereum Signing key that is holds to change the current
+ proposer.
+ @method
+ @async
+ @returns {Promise} A promise that resolves to the Ethereum transaction receipt.
+ */
+ async changeCurrentProposer() {
+ const res = await axios.get(`${this.optimistBaseUrl}/proposer/change`, {
+ address: this.ethereumAddress,
+ });
+ return this.submitTransaction(res.data.txDataToSign, this.proposersContractAddress, 0);
+ }
+
/**
Withdraw the bond left by the proposer.
It will use the address of the Ethereum Signing key that is holds to withdraw the bond.
@@ -668,39 +718,6 @@ Source: nf3.mjs
return newChallengeEmitter;
}
- /**
- Get if it's a valid withdraw transaction for finalising the
- withdrawal of funds to L1 (only relevant for ERC20).
- @method
- @async
- @param {string} withdrawTransactionHash - the hash of the Layer 2 transaction in question
- */
- async isValidWithdrawal(withdrawTransactionHash) {
- let res;
- let valid = false;
-
- try {
- res = await axios.get(
- `${this.optimistBaseUrl}/block/transaction-hash/${withdrawTransactionHash}`,
- );
- } catch (e) {
- // transaction is not in block yet
- valid = false;
- }
-
- if (res) {
- const { block, transactions, index } = res.data;
- res = await axios.post(`${this.clientBaseUrl}/valid-withdrawal`, {
- block,
- transactions,
- index,
- });
- valid = res.data.valid;
- }
-
- return valid;
- }
-
/**
Returns the balance of tokens held in layer 2
@method
@@ -737,40 +754,22 @@ Source: nf3.mjs
*/
async getPendingWithdraws() {
const res = await axios.get(`${this.clientBaseUrl}/commitment/withdraws`);
- const listWithdrawCommitments = res.data.commitments[this.zkpKeys.compressedPkd];
- const pendingWithdrawCommitments = [];
- if (listWithdrawCommitments) {
- // we loop through all ercaddresses in the wallet
- const ercAddresses = Object.getOwnPropertyNames(listWithdrawCommitments);
-
- for (let i = 0; i < ercAddresses.length; i++) {
- for (let k = 0; k < listWithdrawCommitments[ercAddresses[i]].length; k++) {
- // eslint-disable-next-line no-await-in-loop
- const valid = await this.isValidWithdrawal(
- listWithdrawCommitments[ercAddresses[i]][k].transactionNullified.transactionHash,
- );
- pendingWithdrawCommitments.push({
- commitment: listWithdrawCommitments[ercAddresses[i]][k],
- valid,
- });
- }
- }
- }
- return pendingWithdrawCommitments;
+ return res.data.commitments;
}
/**
Set a Web3 Provider URL
- @param {String|Object} providerData - Network url (i.e, http://localhost:8544) or an Object with the information to set the provider
*/
- setWeb3Provider(providerData) {
- if (typeof providerData === 'string' || typeof window === 'undefined') {
- this.web3 = new Web3(providerData);
- }
-
- if (typeof window !== 'undefined' && window.ethereum) {
- this.web3 = new Web3(window.ethereum);
- window.ethereum.send('eth_requestAccounts');
+ setWeb3Provider() {
+ this.web3 = new Web3(this.web3WsUrl);
+ if (typeof window !== 'undefined') {
+ if (window.ethereum && this.ethereumSigningKey === '') {
+ this.web3 = new Web3(window.ethereum);
+ window.ethereum.send('eth_requestAccounts');
+ } else {
+ // Metamask not available
+ throw new Error('No Web3 provider found');
+ }
}
}
@@ -813,6 +812,9 @@ Source: nf3.mjs
@returns {Promise} - string with the signature
*/
signMessage(msg, account) {
+ if (this.ethereumSigningKey) {
+ return this.web3.eth.accounts.sign(msg, this.ethereumSigningKey).signature;
+ }
return this.web3.eth.personal.sign(msg, account);
}
@@ -826,6 +828,7 @@ Source: nf3.mjs
}
export default Nf3;
+