Skip to content

Commit

Permalink
Merge pull request #369 from EYBlockchain/david/liquidity-provider
Browse files Browse the repository at this point in the history
David/liquidity provider
  • Loading branch information
ChaitanyaKonda authored Jan 14, 2022
2 parents 55ddebf + 453be62 commit a62df52
Show file tree
Hide file tree
Showing 23 changed files with 366 additions and 106 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ More information can be found [here](https://github.com/EYBlockchain/nightfall_3

- 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.
- Doing a transfer to a third account and an instant withdraw in the same block makes the instant withdraw fail.
- Tested with node version v14.18.0

# Acknowledgements
Expand Down
38 changes: 32 additions & 6 deletions cli/lib/nf3.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,7 @@ class Nf3 {
@param {string} tokenId - The ID of an ERC721 or ERC1155 token. In the case of
an 'ERC20' coin, this should be set to '0x00'.
@param {object} keys - The ZKP private key set of the sender.
@param {array} pkd - The transmission key of the recipient (this is a curve point
represented as an array of two hex strings).
@param {string} compressedPkd - The compressed transmission key of the recipient
@returns {Promise} Resolves into the Ethereum transaction receipt.
*/
async transfer(
Expand All @@ -300,7 +299,7 @@ class Nf3 {
tokenType,
value,
tokenId,
pkd,
compressedPkd,
fee = this.defaultFee,
) {
const res = await axios.post(`${this.clientBaseUrl}/transfer`, {
Expand All @@ -309,7 +308,7 @@ class Nf3 {
tokenId,
recipientData: {
values: [value],
recipientPkds: [pkd],
recipientCompressedPkds: [compressedPkd],
},
nsk: this.zkpKeys.nsk,
ask: this.zkpKeys.ask,
Expand Down Expand Up @@ -705,7 +704,7 @@ class Nf3 {
Returns the balance of tokens held in layer 2
@method
@async
@returns {Promise} This promise rosolves into an object whose properties are the
@returns {Promise} This promise resolves 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.
*/
Expand All @@ -719,7 +718,7 @@ class Nf3 {
@method
@async
@param {Array} ercList - list of erc contract addresses to filter.
@returns {Promise} This promise rosolves into an object whose properties are the
@returns {Promise} This promise resolves 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.
*/
Expand All @@ -731,6 +730,33 @@ class Nf3 {
return res.data.balance;
}

/**
Returns the balance of tokens held in layer 2
@method
@async
@returns {Promise} This promise resolves 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 pending deposit from that contract.
*/
async getLayer2PendingDepositBalances() {
const res = await axios.get(`${this.clientBaseUrl}/commitment/pending-deposit`);
return res.data.balance;
}

/**
Returns the balance of tokens held in layer 2
@method
@async
@returns {Promise} This promise resolves 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 pending spent (transfer & withdraw)
from that contract.
*/
async getLayer2PendingSpentBalances() {
const res = await axios.get(`${this.clientBaseUrl}/commitment/pending-spent`);
return res.data.balance;
}

/**
Returns the commitments of tokens held in layer 2
@method
Expand Down
47 changes: 26 additions & 21 deletions cli/src/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,16 @@ async function askQuestions(nf3) {
name: 'task',
type: 'list',
message: 'What would you like to do?',
choices: ['Deposit', 'Transfer', 'Withdraw', 'Instant-Withdraw', 'View my wallet', 'Exit'],
choices: [
'Deposit',
'Transfer',
'Withdraw',
'Instant-Withdraw',
'View my wallet',
'View my pending deposits',
'View my pending spent',
'Exit',
],
},
{
name: 'recipientAddress',
Expand All @@ -61,19 +70,11 @@ async function askQuestions(nf3) {
validate: input => web3.utils.isAddress(input),
},
{
name: 'pkdX',
name: 'compressedPkd',
type: 'input',
message: "Provide the x coordinate of the recipient's transmission key",
default: 'my key',
message: "Provide the compressed recipient's transmission key",
default: nf3.zkpKeys.compressedPkd,
when: answers => answers.task === 'Transfer',
validate: input => web3.utils.isHexStrict(input) || input === 'my key',
},
{
name: 'pkdY',
type: 'input',
message: "Provide the y coordinate of the recipient's transmission key",
default: 'my key',
when: answers => answers.task === 'Transfer' && answers.pkdX !== 'my key',
validate: input => web3.utils.isHexStrict(input),
},
{
Expand Down Expand Up @@ -133,18 +134,19 @@ async function askQuestions(nf3) {
/**
Simple function to print out the balances object
*/
function printBalances(balances) {
function printBalances(balances, type) {
console.log(`${type} BALANCES ${balances}`);
if (Object.keys(balances).length === 0) {
console.log('You have no balances yet - try depositing some tokens into Layer 2 from Layer 1');
return;
}
// eslint-disable-next-line guard-for-in
for (const compressedPkd in balances) {
const table = new Table({ head: ['ERC Contract Address', 'Layer 2 Balance'] });
const table = new Table({ head: ['ERC Contract Address', `${type} Layer 2 Balance`] });
Object.keys(balances[compressedPkd]).forEach(ercAddress =>
table.push({ [ercAddress]: balances[compressedPkd][ercAddress] }),
);
console.log(chalk.yellow('Balances of user', compressedPkd));
console.log(chalk.yellow(`${type} Balances of user ${compressedPkd}`));
console.log(table.toString());
}
}
Expand All @@ -162,12 +164,10 @@ async function loop(nf3, ercAddress) {
value = 0,
tokenId = '0x00',
privateKey,
pkdX,
pkdY,
compressedPkd,
withdrawTransactionHash,
offchain,
} = await askQuestions(nf3);
let [x, y] = [pkdX, pkdY]; // make these variable - we may need to change them
if (privateKey) {
await nf3.setEthereumSigningKey(privateKey); // we'll remember the key so we don't keep asking for it
nf3.addPeer('http://optimist1:80'); // add a Proposer for direct transfers and withdraws
Expand All @@ -187,15 +187,14 @@ async function loop(nf3, ercAddress) {
value.toString(),
await getDecimals(ercAddress[tokenType], tokenType, web3),
);
if (x === 'my key') [x, y] = nf3.zkpKeys.pkd;
try {
receiptPromise = nf3.transfer(
offchain,
ercAddress[tokenType],
tokenType,
valueWei,
tokenId,
[x, y], // this holds the recipient's pkd point.
compressedPkd,
fee,
);
} catch (err) {
Expand Down Expand Up @@ -246,7 +245,13 @@ async function loop(nf3, ercAddress) {
}
break;
case 'View my wallet':
printBalances(await nf3.getLayer2Balances());
printBalances(await nf3.getLayer2Balances(), '');
return [false, null];
case 'View my pending deposits':
printBalances(await nf3.getLayer2PendingDepositBalances(), 'Pending Deposit');
return [false, null];
case 'View my pending spent':
printBalances(await nf3.getLayer2PendingSpentBalances(), 'Pending Spent');
return [false, null];
case 'Exit':
return [true, null];
Expand Down
12 changes: 12 additions & 0 deletions cli/src/liquidity-provider.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import clear from 'clear';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import Nf3 from '../lib/nf3.mjs';
import { APPROVE_AMOUNT, TOKEN_TYPE } from '../lib/constants.mjs';
import { setEnvironment, getCurrentEnvironment } from '../lib/environment.mjs';
import { approve } from '../lib/tokens.mjs';

const defaultKey = '0xfbc1ee1c7332e2e5a76a99956f50b3ba2639aff73d56477e877ef8390c41e0c6';
const defaultMnemonic = 'toy vivid real shove evolve kidney captain flock hungry evoke lawn plunge';
Expand Down Expand Up @@ -39,6 +41,16 @@ async function startProvider(testEnvironment) {
await nf3.init(defaultMnemonic);
if (await nf3.healthcheck('optimist')) console.log('Healthcheck passed');
else throw new Error('Healthcheck failed');
const erc20Address = await nf3.getContractAddress('ERC20Mock');

await approve(
erc20Address,
nf3.ethereumAddress,
nf3.shieldContractAddress,
TOKEN_TYPE.ERC20,
APPROVE_AMOUNT,
nf3.web3,
);
// set up a listener to service requests for an instant withdrawal
const emitter = await nf3.getInstantWithdrawalRequestedEmitter();
emitter.on('data', async (withdrawTransactionHash, paidBy, amount) => {
Expand Down
24 changes: 24 additions & 0 deletions nightfall-client/src/routes/commitment.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
getWalletCommitments,
getWithdrawCommitments,
getWalletBalanceDetails,
getWalletPendingDepositBalance,
getWalletPendingSpentBalance,
} from '../services/commitment-storage.mjs';

const router = express.Router();
Expand Down Expand Up @@ -51,6 +53,28 @@ router.post('/balance-details', async (req, res, next) => {
}
});

router.get('/pending-deposit', async (req, res, next) => {
logger.debug('commitment/pending-deposit endpoint received GET');
try {
const balance = await getWalletPendingDepositBalance();
res.json({ balance });
} catch (err) {
logger.error(err);
next(err);
}
});

router.get('/pending-spent', async (req, res, next) => {
logger.debug('commitment/pending-spent endpoint received GET');
try {
const balance = await getWalletPendingSpentBalance();
res.json({ balance });
} catch (err) {
logger.error(err);
next(err);
}
});

router.get('/commitments', async (req, res, next) => {
logger.debug('commitment/commitments endpoint received GET');
try {
Expand Down
80 changes: 80 additions & 0 deletions nightfall-client/src/services/commitment-storage.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,86 @@ export async function getWalletBalance() {
}, {});
}

// function to get the balance of pending deposits commitments for each ERC address
export async function getWalletPendingDepositBalance() {
const connection = await mongo.connection(MONGO_URL);
const db = connection.db(COMMITMENTS_DB);
const query = { isDeposited: true, isNullified: false, isOnChain: { $eq: -1 } };
const options = {
projection: {
preimage: { ercAddress: 1, compressedPkd: 1, tokenId: 1, value: 1 },
_id: 0,
},
};
const wallet = await db.collection(COMMITMENTS_COLLECTION).find(query, options).toArray();
// the below is a little complex. First we extract the ercAddress, tokenId and value
// from the preimage. Then we format them nicely. We don't care about the value of the
// tokenId, other than if it's zero or not (indicating the token type). Then we filter
// any commitments of zero value and tokenId (meaningless commitments), then we
// work out the balance contribution of each commitment - a 721 token has no value field in the
// commitment but each 721 token counts as a balance of 1. Then finally add up the individual
// commitment balances to get a balance for each erc address.
return wallet
.map(e => ({
ercAddress: `0x${BigInt(e.preimage.ercAddress).toString(16).padStart(40, '0')}`, // Pad this to actual address length
compressedPkd: e.preimage.compressedPkd,
tokenId: !!BigInt(e.preimage.tokenId),
value: Number(BigInt(e.preimage.value)),
}))
.filter(e => e.tokenId || e.value > 0) // there should be no commitments with tokenId and value of ZERO
.map(e => ({
compressedPkd: e.compressedPkd,
ercAddress: e.ercAddress,
balance: e.tokenId ? 1 : e.value,
}))
.reduce((acc, e) => {
if (!acc[e.compressedPkd]) acc[e.compressedPkd] = {};
if (!acc[e.compressedPkd][e.ercAddress]) acc[e.compressedPkd][e.ercAddress] = 0;
acc[e.compressedPkd][e.ercAddress] += e.balance;
return acc;
}, {});
}

// function to get the balance of pending spent commitments from transfer and withdraw for each ERC address
export async function getWalletPendingSpentBalance() {
const connection = await mongo.connection(MONGO_URL);
const db = connection.db(COMMITMENTS_DB);
const query = { isNullified: true, isNullifiedOnChain: { $eq: -1 } };
const options = {
projection: {
preimage: { ercAddress: 1, compressedPkd: 1, tokenId: 1, value: 1 },
_id: 0,
},
};
const wallet = await db.collection(COMMITMENTS_COLLECTION).find(query, options).toArray();
// the below is a little complex. First we extract the ercAddress, tokenId and value
// from the preimage. Then we format them nicely. We don't care about the value of the
// tokenId, other than if it's zero or not (indicating the token type). Then we filter
// any commitments of zero value and tokenId (meaningless commitments), then we
// work out the balance contribution of each commitment - a 721 token has no value field in the
// commitment but each 721 token counts as a balance of 1. Then finally add up the individual
// commitment balances to get a balance for each erc address.
return wallet
.map(e => ({
ercAddress: `0x${BigInt(e.preimage.ercAddress).toString(16).padStart(40, '0')}`, // Pad this to actual address length
compressedPkd: e.preimage.compressedPkd,
tokenId: !!BigInt(e.preimage.tokenId),
value: Number(BigInt(e.preimage.value)),
}))
.filter(e => e.tokenId || e.value > 0) // there should be no commitments with tokenId and value of ZERO
.map(e => ({
compressedPkd: e.compressedPkd,
ercAddress: e.ercAddress,
balance: e.tokenId ? 1 : e.value,
}))
.reduce((acc, e) => {
if (!acc[e.compressedPkd]) acc[e.compressedPkd] = {};
if (!acc[e.compressedPkd][e.ercAddress]) acc[e.compressedPkd][e.ercAddress] = 0;
acc[e.compressedPkd][e.ercAddress] += e.balance;
return acc;
}, {});
}

// function to get the balance of commitments for each ERC address
export async function getWalletBalanceDetails(compressedPkd, ercList) {
let ercAddressList = ercList || [];
Expand Down
1 change: 1 addition & 0 deletions nightfall-client/src/services/finalise-withdrawal.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export async function finaliseWithdrawal({ transactionHash }) {
index,
)
.encodeABI();

// store the commitment on successful computation of the transaction
return { rawTransaction };
} catch (err) {
Expand Down
10 changes: 9 additions & 1 deletion nightfall-client/src/services/keys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import config from 'config';
import mimcHash from 'common-files/utils/crypto/mimc/mimc.mjs';
import bip39Pkg from 'bip39';
import pkg from 'ethereumjs-wallet';
import { scalarMult, edwardsCompress } from '../utils/crypto/encryption/elgamal.mjs';
import {
scalarMult,
edwardsCompress,
edwardsDecompress,
} from '../utils/crypto/encryption/elgamal.mjs';

const { hdkey } = pkg;
const { validateMnemonic, mnemonicToSeed } = bip39Pkg;
Expand Down Expand Up @@ -31,6 +35,10 @@ export function compressPublicKey(publicKey) {
return new GN(edwardsCompress([publicKey[0].bigInt, publicKey[1].bigInt]));
}

export function decompressKey(key) {
return generalise(edwardsDecompress(key.bigInt));
}

// path structure is m / purpose' / coin_type' / account' / change / address_index
// the path we use is m/44'/60'/account'/0/address_index
// address will change incrementally for every new set of keys to be created from the same seed
Expand Down
Loading

0 comments on commit a62df52

Please sign in to comment.