Skip to content

Commit

Permalink
Merge pull request #344 from EYBlockchain/david/wallet-refactor
Browse files Browse the repository at this point in the history
David/wallet refactor
  • Loading branch information
ChaitanyaKonda authored Dec 10, 2021
2 parents f8f7d66 + 67440b7 commit 2e0ea53
Show file tree
Hide file tree
Showing 125 changed files with 1,268 additions and 298 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/check-PRs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,45 @@ jobs:
npm ci
npm run lint
e2e-test:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@master
- uses: actions/setup-node@v1
with:
node-version: '14.17.0'

- name: Start Containers
run: |
docker-compose build
./start-nightfall -g &> e2e-test.log &disown
- name: wait 1000s for Containers startup and setup completion
run: sleep 1000

- name: debug logs - after container startup
if: always()
run: cat e2e-test.log

- name: Run integration test
run: |
npm ci
npm run test-e2e
- name: debug logs - after integration test run
if: always()
run: cat e2e-test.log

- name: If integration test failed, shutdown the Containers
if: failure()
run: docker-compose -f docker-compose.yml -f docker-compose.ganache.yml down -v

- name: If integration test failed, upload logs files as artifacts
if: failure()
uses: actions/upload-artifact@master
with:
name: e2e-test-logs
path: ./e2e-test.log
ganache-test:
runs-on: ubuntu-18.04
steps:
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ More information can be found [here](https://github.com/EYBlockchain/nightfall_3
1. Open Metamask in browser
2. Settings->Advance->Reset Account

- Transactions with ERC721 and ERC1155 tokens do not work. This is because there is not way yet to recover the token Id.
- Direct transactions are not implemented
- Instant withdraw is selected when doing a withdraw only. Once submitted the instant withdraw request,the wallet requests a simple withdraw and inmediatelly after converts this withdraw into an instant withdraw. Wallet will attempt to send the instant withdraw request up to 10 times, once every 10 seconds. It is likely that during this period, you need to request a simpler transaction (deposit, withdraw or transfer) so that the original withdraw is processed by the processor and the instant withdraw can be carried out.
- Tested with node version v14.18.0
Expand Down
4 changes: 3 additions & 1 deletion cli/lib/abi.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { TOKEN_TYPE } from './constants.mjs';
import ERC20ABI from './abis/ERC20.mjs';
import ERC721ABI from './abis/ERC721.mjs';
import ERC1155ABI from './abis/ERC1155.mjs';
import ERC165 from './abis/ERC165.mjs';

function getAbi(tokenType) {
switch (tokenType) {
Expand All @@ -11,7 +12,8 @@ function getAbi(tokenType) {
return ERC721ABI;
case TOKEN_TYPE.ERC1155:
return ERC1155ABI;

case TOKEN_TYPE.ERC165:
return ERC165;
default:
return null;
}
Expand Down
21 changes: 21 additions & 0 deletions cli/lib/abis/ERC165.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export default [
{
inputs: [
{
internalType: 'bytes4',
name: 'interfaceId',
type: 'bytes4',
},
],
name: 'supportsInterface',
outputs: [
{
internalType: 'bool',
name: '',
type: 'bool',
},
],
stateMutability: 'view',
type: 'function',
},
];
61 changes: 24 additions & 37 deletions cli/lib/nf3.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -396,17 +396,15 @@ class Nf3 {
@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 } = res.data;
if (!block) return null; // block not found
// set the instant withdrawal fee
res = await axios.post(`${this.clientBaseUrl}/set-instant-withdrawal`, {
transactionHash: withdrawTransactionHash,
});
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;
}
}

/**
Expand Down Expand Up @@ -696,43 +694,32 @@ class Nf3 {
}

/**
Get if it's a valid withdraw transaction for finalising the
withdrawal of funds to L1 (only relevant for ERC20).
Returns the balance of tokens held in layer 2
@method
@async
@param {string} withdrawTransactionHash - the hash of the Layer 2 transaction in question
@returns {Promise} This promise rosolves into an object whose properties are the
addresses of the ERC contracts of the tokens held by this account in Layer 2. The
value of each propery is the number of tokens originating from that contract.
*/
async isValidWithdrawal(withdrawTransactionHash) {
let res;
let valid = false;

res = await axios.get(
`${this.optimistBaseUrl}/block/transaction-hash/${withdrawTransactionHash}`,
);
const { block, transactions, index } = res.data;

if (block) {
res = await axios.post(`${this.clientBaseUrl}/valid-withdrawal`, {
block,
transactions,
index,
});
valid = res.data.valid;
}

return valid;
async getLayer2Balances() {
const res = await axios.get(`${this.clientBaseUrl}/commitment/balance`);
return res.data.balance;
}

/**
Returns the balance of tokens held in layer 2
Returns the balance details of tokens held in layer 2
@method
@async
@param {Array} ercList - list of erc contract addresses to filter.
@returns {Promise} This promise rosolves into an object whose properties are the
addresses of the ERC contracts of the tokens held by this account in Layer 2. The
value of each propery is the number of tokens originating from that contract.
*/
async getLayer2Balances() {
const res = await axios.get(`${this.clientBaseUrl}/commitment/balance`);
async getLayer2BalancesDetails(ercList) {
const res = await axios.post(`${this.clientBaseUrl}/commitment/balance-details`, {
compressedPkd: this.zkpKeys.compressedPkd,
ercList,
});
return res.data.balance;
}

Expand Down Expand Up @@ -770,7 +757,7 @@ class Nf3 {
if (typeof window !== 'undefined') {
if (window.ethereum && this.ethereumSigningKey === '') {
this.web3 = new Web3(window.ethereum);
window.ethereum.send('eth_requestAccounts');
window.ethereum.request({ method: 'eth_accounts' });
} else {
// Metamask not available
throw new Error('No Web3 provider found');
Expand Down
140 changes: 119 additions & 21 deletions cli/lib/tokens.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -80,43 +80,141 @@ Get Information of ERC token in ethereum address. Default is in Wei
* @param {object} options - different options for tokens. For ERC20, toEth is boolean
* to return the balance as Ether. For ERC1155, tokenId is required to get balance
* of specific token Id
* @returns {Object} {balance, decimals}
* @returns {Object} {balance, decimals, tokenType, details}
*/
async function getERCInfo(ercAddress, ethereumAddress, provider, options) {
let toEth;
let tokenId;
let details;
let tokenTypeFilter;

if (typeof options !== 'undefined') {
toEth = options.toEth;
tokenId = options.tokenId;
details = options.details;
tokenTypeFilter = options.tokenType;
}

let tokenType;
let balance;
let decimals = 0;
const tokenIds = [];

if (tokenTypeFilter) {
switch (tokenTypeFilter.toUpperCase()) {
case 'ERC721':
tokenType = TOKEN_TYPE.ERC721;
break;
case 'ERC1155':
tokenType = TOKEN_TYPE.ERC1155;
break;
case 'ERC20':
tokenType = TOKEN_TYPE.ERC20;
break;
default:
throw new Error(`Unknown ERC token ${tokenTypeFilter}`);
}
} else {
try {
// Check supportsInterface ERC165 that implements ERC721 and ERC1155
const abi = getAbi(TOKEN_TYPE.ERC165);
const ercContract = new provider.eth.Contract(abi, ercAddress);
const interface721 = await ercContract.methods.supportsInterface('0x80ac58cd').call(); // ERC721 interface
if (interface721) {
tokenType = TOKEN_TYPE.ERC721;
} else {
const interface1155 = await ercContract.methods.supportsInterface('0xd9b67a26').call(); // ERC1155 interface
if (interface1155) tokenType = TOKEN_TYPE.ERC1155;
}
} catch {
// Expected ERC20
tokenType = TOKEN_TYPE.ERC20;
}
}

try {
const abi = getAbi(TOKEN_TYPE.ERC20);
if (tokenType === TOKEN_TYPE.ERC721) {
// ERC721
const abi = getAbi(TOKEN_TYPE.ERC721);
const ercContract = new provider.eth.Contract(abi, ercAddress);
let balance = await ercContract.methods.balanceOf(ethereumAddress).call();
const decimals = await getDecimals(ercAddress, TOKEN_TYPE.ERC20, provider);
if (toEth) {
balance = fromBaseUnit(balance, decimals);
balance = await ercContract.methods.balanceOf(ethereumAddress).call();

if (details) {
const incomingTokenTransferEvents = await ercContract.getPastEvents('Transfer', {
filter: { to: ethereumAddress },
fromBlock: 0,
toBlock: 'latest',
});
const tokenIdsEvents = [];
incomingTokenTransferEvents.forEach(event => {
if (!tokenIdsEvents.includes(event.returnValues.tokenId))
tokenIdsEvents.push(event.returnValues.tokenId);
});

await Promise.all(
tokenIdsEvents.map(async tkId => {
const ownerTokenId = await ercContract.methods.ownerOf(tkId).call();
if (ownerTokenId === ethereumAddress) tokenIds.push({ tokenId: tkId, amount: '1' });
}),
);
}
return { balance, decimals, tokenType: TOKEN_TYPE.ERC20 };
} catch {
} else if (tokenType === TOKEN_TYPE.ERC1155) {
// ERC1155
const abi = getAbi(TOKEN_TYPE.ERC1155);
const ercContract = new provider.eth.Contract(abi, ercAddress);
if (!tokenId) tokenId = 0;
balance = await ercContract.methods.balanceOf(ethereumAddress, tokenId).call();
if (details) {
const incomingTokenTransferBatchEvents = await ercContract.getPastEvents('TransferBatch', {
filter: {},
fromBlock: 0,
toBlock: 'latest',
});
const tokenIdsEvents = [];
incomingTokenTransferBatchEvents.forEach(event => {
event.returnValues.ids.forEach(id => {
if (!tokenIdsEvents.includes(id)) tokenIdsEvents.push(id);
});
});

const incomingTokenTransferSingleEvents = await ercContract.getPastEvents('TransferSingle', {
filter: {},
fromBlock: 0,
toBlock: 'latest',
});
incomingTokenTransferSingleEvents.forEach(event => {
if (!tokenIdsEvents.includes(event.returnValues.id))
tokenIdsEvents.push(event.returnValues.id);
});

await Promise.all(
tokenIdsEvents.map(async Id => {
const amount = await ercContract.methods.balanceOf(ethereumAddress, Id).call();
tokenIds.push({ tokenId: Id, amount });
}),
);

balance = tokenIds
.reduce((partialSum, tokenIdInfo) => partialSum + BigInt(tokenIdInfo.amount), BigInt(0))
.toString();
}
} else {
// expected ERC20
try {
const abi = getAbi(TOKEN_TYPE.ERC1155);
const abi = getAbi(TOKEN_TYPE.ERC20);
const ercContract = new provider.eth.Contract(abi, ercAddress);
const balance = await ercContract.methods.balanceOf(ethereumAddress, tokenId).call();
return { balance, tokenType: TOKEN_TYPE.ERC1155 };
balance = await ercContract.methods.balanceOf(ethereumAddress).call();
decimals = await getDecimals(ercAddress, TOKEN_TYPE.ERC20, provider);
if (toEth) balance = fromBaseUnit(balance, decimals);
if (details) tokenIds.push({ tokenId: 0, amount: balance });
} catch {
try {
const abi = getAbi(TOKEN_TYPE.ERC721);
const ercContract = new provider.eth.Contract(abi, ercAddress);
const balance = await ercContract.methods.balanceOf(ethereumAddress).call();
return { balance, tokenType: TOKEN_TYPE.ERC721 };
} catch {
// TODO
throw new Error('Unknown token type', ercAddress);
}
throw new Error('Unknown token type', ercAddress);
}
}

let result = { balance, tokenType, details: tokenIds };
if (tokenType === TOKEN_TYPE.ERC20) result = { ...result, decimals };
else result = { ...result, decimals: 0 };
return result;
}

export { approve, getDecimals, getERCInfo };
Loading

0 comments on commit 2e0ea53

Please sign in to comment.