@@ -5,8 +5,10 @@ const stoManager = require('./sto_manager');
55const transferManager = require ( './transfer_manager' ) ;
66const common = require ( './common/common_functions' ) ;
77const 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
1214const contracts = require ( './helpers/contract_addresses' ) ;
@@ -16,19 +18,10 @@ let securityTokenRegistry;
1618let polyToken ;
1719let featureRegistry ;
1820let securityToken ;
19- let tokenDivisible ;
2021
2122let allModules ;
2223let 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-
3225async function setup ( ) {
3326 try {
3427 let securityTokenRegistryAddress = await contracts . securityTokenRegistry ( ) ;
@@ -289,7 +282,7 @@ async function listInvestorsAtCheckpoint(checkpointId) {
289282}
290283
291284async 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
536405async function withdrawFromContract ( erc20address , value ) {
@@ -824,10 +693,11 @@ async function selectToken() {
824693
825694module . 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}
0 commit comments