-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.ts
212 lines (194 loc) · 9.83 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
import * as fs from 'fs'
import axios from 'axios'
import { Client as hClient, PrivateKey as hPrivateKey } from '@hiveio/dhive'
import { Client as sClient, PrivateKey as sPrivateKey } from 'dsteem'
import voca from 'voca'
(async () => {
if(process.argv.length < 4) {
console.log('Commandline: ./run configfilename amttodistribute [activekey]')
console.log('e.g. ./run token_MPATH.json 17.021')
console.log('Without activekey: print distribution schedule.')
console.log('With activekey: execute the distribution schedule.')
process.exit()
}
// Gather config
let config: any = {}
try {
config = config = JSON.parse(fs.readFileSync(process.argv[2]).toString())
} catch (e) {
console.error(e)
process.exit(-1)
}
//console.log(config)
const tokensymbol: string = config.membershiptokensymbol.toUpperCase()
const mintokens: number = parseFloat(config.minholdingtokens)
const maxtokens: number = parseFloat(config.maxeffectivetokens)
const minpayout: number = parseFloat(config.minpayout)
const mtdp: number = config.membershiptokendecimalplaces ? parseInt(config.membershiptokendecimalplaces) : 3
const excludemembers: string[] = config.excludemembers ? config.excludemembers : []
const daccount: string = config.distributionaccount.toLowerCase()
const dasset: string = config.distributionasset.toUpperCase()
const dassettype: string = config.distributionassettype.toUpperCase()
const dadp: number = config.distributionassetdecimalplaces ? parseInt(config.distributionassetdecimalplaces) : 3
const distributionamt: number = parseFloat(process.argv[3])
excludemembers.push(daccount)
// Check the cmdlines params - not comprehensive
if(
!tokensymbol || tokensymbol.length < 1
|| !daccount || daccount.length < 1
|| !dasset || dasset.length < 1
|| !['HIVE','STEEM','HIVE-ENGINE','STEEM-ENGINE'].includes(dassettype)
) {
console.error('CONFIG ERROR: Missing field in config.')
process.exit(-1)
}
if(mintokens > maxtokens) {
console.error('CONFIG ERROR: minholdingtokens cannot be lower than maxeffectivetokens: ' + mintokens + ' > ' + maxtokens)
process.exit(-1)
}
let activekey: hPrivateKey|sPrivateKey|undefined
try {
activekey = process.argv[4] ? isHive(dassettype) ? hPrivateKey.fromString(process.argv[4]) : sPrivateKey.fromString(process.argv[4]) : undefined
} catch(e:any) {
console.error('ERROR:', e.message)
console.error('COMMAND LINE ERROR: Check that the private active key is correct.')
process.exit(-1)
}
// Some helpers for the axios calls
const hefindtemplate: string = '{ "jsonrpc": "2.0", "method": "find", "params": { "contract": "tokens", "table": "balances", "query": {"symbol": "%s"}}, "id": 1}'
const axoptions: any = { headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }}
const hefindstr: string = voca.sprintf(hefindtemplate, tokensymbol)
// Stores for tokenholder info
const tokenholdersraw: {[accountname: string]: number} = {}
const tokenholdersnorm: {[accountname: string]: number} = {}
const tokenholdersdist: {[accountname: string]: number} = {}
const tokenholdersdistresult: {[accountname: string]: boolean} = {}
let accumulatedtokenbalance: number = 0
let accumulatedpayoutamt: number = 0
//process.exit(0)
try {
// Request membership list from the side-chain
const tb: any = await axios.post(config.sidechainendpoint, hefindstr, axoptions)
await sleep(50)
//console.log(tb.data.result)
// Build member holdings list filtering out those below minholdingtokens
for(const i of tb.data.result) {
if(
!excludemembers.includes(i.account)
&& (parseFloat(i.balance) >= 0.00000001 || parseFloat(i.stake) >= 0.00000001)
) {
// console.log('@' + i.account.padEnd(24) + i.balance.padStart(10) + i.stake.padStart(10))
const amt: number = parseFloat(i.balance) + parseFloat(i.stake)
if(amt >= mintokens) {
tokenholdersraw[i.account] = amt
accumulatedtokenbalance += Math.min(maxtokens, amt)
}
}
}
// Build a proportional (normalised) list of tokenholders and calculate the payouts
// Consider maxeffectivetokens and minpayout
for(const i of Object.keys(tokenholdersraw)) {
tokenholdersnorm[i] = Math.min(maxtokens, tokenholdersraw[i]) / accumulatedtokenbalance
const amt: number = distributionamt * tokenholdersnorm[i]
if(amt >= minpayout) {
tokenholdersdist[i] = amt
accumulatedpayoutamt += amt
}
}
// If the cmdline has no active key, then log the distribution schedule to console in CSV format
if(!activekey) {
console.log('Account, Holding, Share, Dist(' + dasset + ')')
for(const i of Object.keys(tokenholdersdist)) {
console.log(voca.sprintf('@%-25s ' + fp(mtdp) + ', %10.9f, ' + fp(dadp), i + ',', tokenholdersraw[i], tokenholdersnorm[i], tokenholdersdist[i]))
}
console.log(voca.sprintf('%-25s %' + (7 + mtdp) + 's, %10s , ' + fp(dadp), 'TOTAL' + ',', ' ', ' ', accumulatedpayoutamt))
}
// If the cmdline has an active key then attempt to do the distributions
// Log successes first and repeat failures at the end to ease manual followup
else {
const hclient: hClient|sClient = isHive(dassettype) ? new hClient(config.blockchainapinode) : new sClient(config.blockchainapinode)
let hasfails: boolean = false
console.log(dasset + ' distribution to ' + tokensymbol + ' holders.')
console.log('Account, Holding, Share, Dist, Result')
if(dassettype === 'HIVE' || dassettype === 'STEEM') {
for(const i of Object.keys(tokenholdersdist)) {
try {
await hclient.broadcast.transfer(
{
amount: tokenholdersdist[i].toFixed(3) + ' ' + dassettype,
from: daccount,
memo: 'Distribution for ' + tokensymbol,
to: i
},
(<any>activekey)
)
tokenholdersdistresult[i] = true
console.log(voca.sprintf('@%-25s ' + fp(mtdp) + ', %10.9f, ' + fp(dadp) + ', %4s', i + ',', tokenholdersraw[i], tokenholdersnorm[i], tokenholdersdist[i], 'DONE'))
} catch(e:any) {
console.log('@' + i, e.message)
tokenholdersdistresult[i] = false
hasfails = true
}
await sleep(4.25 * 1000)
}
} else if(dassettype === 'HIVE-ENGINE' || dassettype === 'STEEM-ENGINE') {
for(const i of Object.keys(tokenholdersdist)) {
try {
await hclient.broadcast.json(
{
id: (dassettype === 'HIVE-ENGINE' ? 'ssc-mainnet-hive' : 'ssc-mainnet1'),
required_auths: [ daccount ],
required_posting_auths: [],
json: JSON.stringify({
contractName: 'tokens',
contractAction: 'transfer',
contractPayload: {
symbol: dasset,
to: i,
quantity: tokenholdersdist[i].toFixed(dadp),
memo: 'Distribution for ' + tokensymbol
}
})
},
(<any>activekey)
)
tokenholdersdistresult[i] = true
console.log(voca.sprintf('@%-25s ' + fp(mtdp) + ', %10.9f, ' + fp(dadp) + ', %4s', i + ',', tokenholdersraw[i], tokenholdersnorm[i], tokenholdersdist[i], 'DONE'))
} catch(e:any) {
console.log('@' + i, e.message)
tokenholdersdistresult[i] = false
hasfails = true
}
await sleep(4.25 * 1000)
}
} else {
console.error('CONFIG ERROR: Invalid distributionassettype: ' + dassettype)
process.exit(-1)
}
if(hasfails) {
console.log('\n\n\Fails')
for(const i of Object.keys(tokenholdersdistresult)) {
if(!tokenholdersdistresult[i]) {
console.log(voca.sprintf('@%-25s ' + fp(mtdp) + ', %10.9f, ' + fp(dadp) + ', %4s', i + ',', tokenholdersraw[i], tokenholdersnorm[i], tokenholdersdist[i], 'FAIL'))
}
}
} else {
console.log('All successful.')
}
}
} catch (e) {
console.log('ERROR')
console.log(e)
}
})();
function isHive(dassettype: string): boolean {
return dassettype === 'HIVE' || dassettype === 'HIVE-ENGINE'
}
function fp(dp: number): string { return '%' + (7 + dp) + '.' + dp + 'f' }
async function sleep(milliseconds: number): Promise<void> {
return new Promise<void>(resolve => {
setTimeout(() => {
resolve()
}, milliseconds)
})
}