Skip to content

Commit ee54b6d

Browse files
committed
Multi mint in batches
1 parent e425210 commit ee54b6d

File tree

5 files changed

+75
-206
lines changed

5 files changed

+75
-206
lines changed

CLI/commands/common/constants.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@ module.exports = Object.freeze({
3131
POLY: 1,
3232
DAI: 2
3333
},
34-
DEFAULT_BATCH_SIZE: 75
34+
DEFAULT_BATCH_SIZE: 75,
35+
ADDRESS_ZERO: '0x0000000000000000000000000000000000000000'
3536
});

CLI/commands/sto_manager.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,7 @@ async function changeNonAccreditedLimitsInBatch(currentSTO) {
779779
let batches = common.splitIntoBatches(validData, batchSize);
780780
let [investorArray, limitArray] = common.transposeBatches(batches);
781781
for (let batch = 0; batch < batches.length; batch++) {
782-
console.log(`Batch ${batch + 1} - Attempting to change accredited accounts:\n\n`, investorArray[batch], '\n');
782+
console.log(`Batch ${batch + 1} - Attempting to change non accredited limit to accounts:\n\n`, investorArray[batch], '\n');
783783
let action = await currentSTO.methods.changeNonAccreditedLimit(investorArray[batch], limitArray[batch]);
784784
let receipt = await common.sendTransaction(action);
785785
console.log(chalk.green('Change non accredited limits transaction was successful.'));

CLI/commands/token_manager.js

Lines changed: 61 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ const stoManager = require('./sto_manager');
55
const transferManager = require('./transfer_manager');
66
const common = require('./common/common_functions');
77
const gbl = require('./common/global');
8-
const csv_shared = require('./common/csv_shared');
9-
const BigNumber = require('bignumber.js');
8+
const csvParse = require('./helpers/csv');
9+
10+
// Constants
11+
const MULTIMINT_DATA_CSV = './CLI/data/ST/multi_mint_data.csv';
1012

1113
// Load contract artifacts
1214
const contracts = require('./helpers/contract_addresses');
@@ -16,19 +18,10 @@ let securityTokenRegistry;
1618
let polyToken;
1719
let featureRegistry;
1820
let securityToken;
19-
let tokenDivisible;
2021

2122
let allModules;
2223
let tokenSymbol
2324

24-
/* CSV data control */
25-
let distribData = new Array();
26-
let fullFileData = new Array();
27-
let badData = new Array();
28-
let affiliatesFailedArray = new Array();
29-
let affiliatesKYCInvalidArray = new Array();
30-
/* End CSV data control */
31-
3225
async function setup() {
3326
try {
3427
let securityTokenRegistryAddress = await contracts.securityTokenRegistry();
@@ -289,7 +282,7 @@ async function listInvestorsAtCheckpoint(checkpointId) {
289282
}
290283

291284
async function mintTokens() {
292-
let options = ['Modify whitelist', 'Mint tokens to a single address', `Modify whitelist and mint tokens using data from 'whitelist_data.csv' and 'multi_mint_data.csv'`];
285+
let options = ['Modify whitelist', 'Mint tokens to a single address', `Mint tokens to multiple addresses from CSV`];
293286
let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'Return' });
294287
let selected = index == -1 ? 'Return' : options[index];
295288
console.log('Selected:', selected);
@@ -313,8 +306,9 @@ async function mintTokens() {
313306
let amount = readlineSync.question(`Enter the amount of tokens to mint: `);
314307
await mintToSingleAddress(receiver, amount);
315308
break;
316-
case `Modify whitelist and mint tokens using data from 'whitelist_data.csv' and 'multi_mint_data.csv'`:
317-
await multi_mint_tokens();
309+
case `Mint tokens to multiple addresses from CSV`:
310+
console.log(chalk.yellow(`Investors should be previously whitelisted.`));
311+
await multiMint();
318312
break;
319313
}
320314
}
@@ -345,192 +339,67 @@ async function mintToSingleAddress(_investor, _amount) {
345339
}
346340
}
347341

348-
async function multi_mint_tokens() {
349-
await transferManager.startCSV(tokenSymbol, 75);
350-
console.log(chalk.green(`\nCongratulations! All the affiliates get succssfully whitelisted, Now its time to Mint the tokens\n`));
351-
console.log(chalk.red(`WARNING: `) + `Please make sure all the addresses that get whitelisted are only eligible to hold or get Security token\n`);
352-
353-
await startCSV(tokenSymbol, 75)
354-
console.log(chalk.green(`\nCongratulations! Tokens get successfully Minted and transferred to token holders`));
355-
}
356-
///
357-
358-
async function startCSV(tokenSymbol, batchSize) {
359-
securityToken = await csv_shared.start(tokenSymbol, batchSize);
360-
361-
let result_processing = await csv_shared.read('./CLI/data/multi_mint_data.csv', multimint_processing);
362-
distribData = result_processing.distribData;
363-
fullFileData = result_processing.fullFileData;
364-
badData = result_processing.badData;
365-
366-
tokenDivisible = await securityToken.methods.granularity().call() == 1;
367-
368-
await saveInBlockchain();
369-
await finalResults();
370-
}
371-
372-
function multimint_processing(csv_line) {
373-
let isAddress = web3.utils.isAddress(csv_line[0]);
374-
let validToken = isValidToken(csv_line[1]);
375-
376-
if (isAddress &&
377-
validToken) {
378-
return [true, new Array(web3.utils.toChecksumAddress(csv_line[0]), validToken)]
342+
async function multiMint(_csvFilePath, _batchSize) {
343+
let csvFilePath;
344+
if (typeof _csvFilePath !== 'undefined') {
345+
csvFilePath = _csvFilePath;
379346
} else {
380-
return [false, new Array(csv_line[0], csv_line[1])]
347+
csvFilePath = readlineSync.question(`Enter the path for csv data file (${MULTIMINT_DATA_CSV}): `, {
348+
defaultInput: MULTIMINT_DATA_CSV
349+
});
381350
}
382-
}
383-
384-
function isValidToken(token) {
385-
var tokenAmount = parseFloat(token);
386-
if (tokenDivisible) {
387-
return tokenAmount
351+
let batchSize;
352+
if (typeof _batchSize !== 'undefined') {
353+
batchSize = _batchSize;
388354
} else {
389-
if ((tokenAmount % 1 == 0)) {
390-
return tokenAmount;
391-
}
392-
return false
393-
}
394-
}
395-
396-
async function saveInBlockchain() {
397-
let gtmModules = await securityToken.methods.getModulesByType(3).call();
398-
399-
if (gtmModules.length > 0) {
400-
console.log("Minting of tokens is only allowed before the STO get attached");
401-
process.exit(0);
402-
}
403-
404-
console.log(`
405-
-----------------------------------------
406-
----- Mint the tokens to affiliates -----
407-
-----------------------------------------
408-
`);
409-
410-
for (let i = 0; i < distribData.length; i++) {
411-
try {
412-
let affiliatesVerifiedArray = [], tokensVerifiedArray = [];
413-
414-
// Splitting the user arrays to be organized by input
415-
for (let j = 0; j < distribData[i].length; j++) {
416-
let investorAccount = distribData[i][j][0];
417-
let tokenAmount = web3.utils.toWei((distribData[i][j][1]).toString(), "ether");
418-
let verifiedTransaction = await securityToken.methods.verifyTransfer("0x0000000000000000000000000000000000000000", investorAccount, tokenAmount, web3.utils.fromAscii('')).call();
419-
if (verifiedTransaction) {
420-
affiliatesVerifiedArray.push(investorAccount);
421-
tokensVerifiedArray.push(tokenAmount);
422-
} else {
423-
let gtmModule = await securityToken.methods.getModulesByName(web3.utils.toHex('GeneralTransferManager')).call();
424-
let generalTransferManager = new web3.eth.Contract(abis.generalTransferManager(), gtmModule[0]);
425-
let validKYC = (await generalTransferManager.methods.whitelist(Issuer.address).call()).expiryTime > Math.floor(Date.now()/1000);
426-
if (validKYC) {
427-
affiliatesFailedArray.push(investorAccount);
428-
} else {
429-
affiliatesKYCInvalidArray.push(investorAccount);
430-
}
431-
}
432-
}
433-
434-
let mintMultiAction = await securityToken.methods.mintMulti(affiliatesVerifiedArray, tokensVerifiedArray);
435-
let tx = await common.sendTransaction(mintMultiAction);
436-
console.log(`Batch ${i} - Attempting to send the Minted tokens to affiliates accounts:\n\n`, affiliatesVerifiedArray, "\n\n");
437-
console.log("---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------");
438-
console.log("Multi Mint transaction was successful.", tx.gasUsed, "gas used. Spent:", web3.utils.fromWei(BigNumber(tx.gasUsed * defaultGasPrice).toString(), "ether"), "Ether");
439-
console.log("---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------\n\n");
440-
441-
} catch (err) {
442-
console.log("ERROR", err)
443-
process.exit(0)
444-
}
445-
}
446-
447-
return;
448-
}
449-
450-
async function finalResults() {
451-
let totalInvestors = 0;
452-
let updatedInvestors = 0;
453-
let investorObjectLookup = {};
454-
let investorData_Events = new Array();
455-
456-
let event_data = await securityToken.getPastEvents('Minted', {fromBlock: 0, toBlock: 'latest'}, () => {});
457-
458-
for (var i = 0; i < event_data.length; i++) {
459-
let combineArray = [];
460-
461-
let investorAddress_Event = event_data[i].returnValues._to;
462-
let amount_Event = event_data[i].returnValues._value;
463-
let blockNumber = event_data[i].blockNumber;
464-
465-
combineArray.push(investorAddress_Event);
466-
combineArray.push(amount_Event);
467-
combineArray.push(blockNumber);
468-
469-
investorData_Events.push(combineArray)
470-
471-
// We have already recorded it, so this is an update to our object
472-
if (investorObjectLookup.hasOwnProperty(investorAddress_Event)) {
473-
// The block number form the event we are checking is bigger, so we gotta replace it
474-
if (investorObjectLookup[investorAddress_Event].recordedBlockNumber < blockNumber) {
475-
investorObjectLookup[investorAddress_Event] = {amount: amount_Event, recordedBlockNumber: blockNumber};
476-
updatedInvestors += 1;
477-
}
355+
batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, {
356+
limit: function (input) {
357+
return parseInt(input) > 0;
358+
},
359+
limitMessage: 'Must be greater than 0',
360+
defaultInput: gbl.constants.DEFAULT_BATCH_SIZE
361+
});
362+
}
363+
let parsedData = csvParse(csvFilePath);
364+
let tokenDivisible = await securityToken.methods.granularity().call() == 1;
365+
let validData = parsedData.filter(row =>
366+
web3.utils.isAddress(row[0]) &&
367+
(!isNaN(row[1]) && (tokenDivisible || parseFloat(row[1]) % 1 == 0))
368+
);
369+
let invalidRows = parsedData.filter(row => !validData.includes(row));
370+
if (invalidRows.length > 0) {
371+
console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `));
372+
}
373+
let verifiedData = [];
374+
let unverifiedData = [];
375+
for (const row of validData) {
376+
let investorAccount = row[0];
377+
let tokenAmount = web3.utils.toWei(row[1].toString());
378+
let verifiedTransaction = await securityToken.methods.verifyTransfer(gbl.constants.ADDRESS_ZERO, investorAccount, tokenAmount, web3.utils.fromAscii('')).call();
379+
if (verifiedTransaction) {
380+
verifiedData.push(row);
478381
} else {
479-
investorObjectLookup[investorAddress_Event] = {amount: amount_Event, recordedBlockNumber: blockNumber};
480-
totalInvestors += 1;
382+
unverifiedData.push(row);
481383
}
482384
}
483385

484-
let investorAddress_Events = Object.keys(investorObjectLookup)
485-
486-
console.log(`******************** EVENT LOGS ANALYSIS COMPLETE ********************\n`);
487-
console.log(`A total of ${totalInvestors} affiliated investors get the token\n`);
488-
console.log(`This script in total sent ${fullFileData.length - badData.length - affiliatesFailedArray.length - affiliatesKYCInvalidArray.length} new investors and updated investors to the blockchain.\n`);
489-
console.log(`There were ${badData.length} bad entries that didnt get sent to the blockchain in the script.\n`);
490-
console.log(`There were ${affiliatesKYCInvalidArray.length} accounts with invalid KYC.\n`);
491-
console.log(`There were ${affiliatesFailedArray.length} accounts that didn't get sent to the blockchain as they would fail.\n`);
492-
console.log("************************************************************************************************");
493-
console.log("OBJECT WITH EVERY USER AND THEIR MINTED TOKEN: \n\n", investorObjectLookup)
494-
console.log("************************************************************************************************");
495-
console.log("LIST OF ALL INVESTORS WHO GOT THE MINTED TOKENS: \n\n", investorAddress_Events)
496-
497-
let missingDistribs = [], failedVerificationDistribs = [], invalidKYCDistribs = [];
498-
for (let l = 0; l < fullFileData.length; l++) {
499-
if (affiliatesKYCInvalidArray.includes(fullFileData[l][0])) {
500-
invalidKYCDistribs.push(fullFileData[l]);
501-
} else if (affiliatesFailedArray.includes(fullFileData[l][0])) {
502-
failedVerificationDistribs.push(fullFileData[l]);
503-
} else if (!investorObjectLookup.hasOwnProperty(fullFileData[l][0])) {
504-
missingDistribs.push(fullFileData[l]);
505-
}
386+
let batches = common.splitIntoBatches(verifiedData, batchSize);
387+
let [investorArray, amountArray] = common.transposeBatches(batches);
388+
for (let batch = 0; batch < batches.length; batch++) {
389+
console.log(`Batch ${batch + 1} - Attempting to mint tokens to accounts: \n\n`, investorArray[batch], '\n');
390+
amountArray[batch] = amountArray[batch].map(a => web3.utils.toWei(a.toString()));
391+
let action = await securityToken.methods.mintMulti(investorArray[batch], amountArray[batch]);
392+
let receipt = await common.sendTransaction(action);
393+
console.log(chalk.green('Multi mint transaction was successful.'));
394+
console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`);
506395
}
507396

508-
if (invalidKYCDistribs.length > 0) {
509-
console.log("**************************************************************************************************************************");
510-
console.log("The following data arrays have an invalid KYC. Please review if these accounts are whitelisted and their KYC is not expired\n");
511-
console.log(invalidKYCDistribs);
512-
console.log("**************************************************************************************************************************");
513-
}
514-
if (failedVerificationDistribs.length > 0) {
397+
if (unverifiedData.length > 0) {
515398
console.log("*********************************************************************************************************");
516-
console.log("-- The following data arrays failed at verifyTransfer. Please review if these accounts are whitelisted --\n");
517-
console.log(failedVerificationDistribs);
399+
console.log('The following data arrays failed at verifyTransfer. Please review if these accounts are whitelisted\n');
400+
console.log(chalk.red(unverifiedData.map(d => `${d[0]}, ${d[1]}`).join('\n')));
518401
console.log("*********************************************************************************************************");
519402
}
520-
if (missingDistribs.length > 0) {
521-
console.log("******************************************************************************************");
522-
console.log("-- No Minted event was found for the following data arrays. Please review them manually --\n");
523-
console.log(missingDistribs);
524-
console.log("******************************************************************************************");
525-
}
526-
if (missingDistribs.length == 0 &&
527-
failedVerificationDistribs.length == 0 &&
528-
invalidKYCDistribs.length == 0) {
529-
console.log("\n**************************************************************************************************************************");
530-
console.log("All accounts passed through from the CSV were successfully get the tokens, because we were able to read them all from events");
531-
console.log("****************************************************************************************************************************");
532-
}
533-
534403
}
535404

536405
async function withdrawFromContract(erc20address, value) {
@@ -824,10 +693,11 @@ async function selectToken() {
824693

825694
module.exports = {
826695
executeApp: async function (_tokenSymbol) {
827-
await initialize(_tokenSymbol)
696+
await initialize(_tokenSymbol);
828697
return executeApp();
829698
},
830-
startCSV: async function (tokenSymbol, batchSize) {
831-
return startCSV(tokenSymbol, batchSize);
699+
multiMint: async function (_tokenSymbol, _csvPath, _batchSize) {
700+
await initialize(_tokenSymbol);
701+
return multiMint(_csvPath, _batchSize);
832702
}
833703
}

CLI/commands/transfer_manager.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -483,10 +483,10 @@ async function generalTransferManager() {
483483
}
484484

485485
async function modifyWhitelistInBatch() {
486-
let csvFilePath = readlineSync.question(`Enter the path for csv data file(${WHITELIST_DATA_CSV}): `, {
486+
let csvFilePath = readlineSync.question(`Enter the path for csv data file (${WHITELIST_DATA_CSV}): `, {
487487
defaultInput: WHITELIST_DATA_CSV
488488
});
489-
let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size(${gbl.constants.DEFAULT_BATCH_SIZE}): `, {
489+
let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, {
490490
limit: function (input) {
491491
return parseInt(input) > 0;
492492
},
@@ -508,7 +508,7 @@ async function modifyWhitelistInBatch() {
508508
let batches = common.splitIntoBatches(validData, batchSize);
509509
let [investorArray, fromTimesArray, toTimesArray, expiryTimeArray, canBuyFromSTOArray] = common.transposeBatches(batches);
510510
for (let batch = 0; batch < batches.length; batch++) {
511-
console.log(`Batch ${batch + 1} - Attempting to change accredited accounts: \n\n`, investorArray[batch], '\n');
511+
console.log(`Batch ${batch + 1} - Attempting to modify whitelist to accounts: \n\n`, investorArray[batch], '\n');
512512
let action = await currentTransferManager.methods.modifyWhitelistMulti(investorArray[batch], fromTimesArray[batch], toTimesArray[batch], expiryTimeArray[batch], canBuyFromSTOArray[batch]);
513513
let receipt = await common.sendTransaction(action);
514514
console.log(chalk.green('Whitelist transaction was successful.'));

CLI/polymath-cli.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,17 @@ program
7474
.command('token_manager')
7575
.alias('stm')
7676
.option('-t, --securityToken <tokenSymbol>', 'Selects a ST to manage')
77+
.option('-m, --multiMint <csvFilePath>', 'Distribute tokens to previously whitelisted investors')
78+
.option('-b, --batchSize <batchSize>', 'Max number of records per transaction')
7779
.description('Manage your Security Tokens, mint tokens, add modules and change config')
7880
.action(async function (cmd) {
7981
await gbl.initialize(program.remoteNode);
80-
await token_manager.executeApp(cmd.securityToken);
81-
});
82-
83-
program
84-
.command('multi_mint <tokenSymbol> [batchSize]')
85-
.alias('mi')
86-
.description('Distribute tokens to previously whitelisted investors')
87-
.action(async function (tokenSymbol, batchSize) {
88-
await gbl.initialize(program.remoteNode);
89-
await token_manager.startCSV(tokenSymbol, batchSize);
82+
if (cmd.multiMint) {
83+
let batchSize = cmd.batchSize ? cmd.batchSize : gbl.constants.DEFAULT_BATCH_SIZE;
84+
await token_manager.multiMint(cmd.securityToken, cmd.multiMint, batchSize);
85+
} else {
86+
await token_manager.executeApp(cmd.securityToken);
87+
}
9088
});
9189

9290
program

0 commit comments

Comments
 (0)