@argonprotocol/mainchain 1.1.0-rc.8 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/lib/chunk-BQR6FEVP.js +854 -0
- package/lib/chunk-BQR6FEVP.js.map +1 -0
- package/lib/chunk-CHGCEO2U.cjs +2567 -0
- package/lib/chunk-CHGCEO2U.cjs.map +1 -0
- package/lib/chunk-EY3HYZMJ.cjs +854 -0
- package/lib/chunk-EY3HYZMJ.cjs.map +1 -0
- package/lib/chunk-RXCQYVE7.js +2567 -0
- package/lib/chunk-RXCQYVE7.js.map +1 -0
- package/lib/cli.cjs +5 -3346
- package/lib/cli.cjs.map +1 -1
- package/lib/cli.js +5 -3342
- package/lib/cli.js.map +1 -1
- package/lib/clis/index.cjs +4 -3354
- package/lib/clis/index.cjs.map +1 -1
- package/lib/clis/index.d.cts +1 -2
- package/lib/clis/index.d.ts +1 -2
- package/lib/clis/index.js +14 -3342
- package/lib/clis/index.js.map +1 -1
- package/lib/index.cjs +80 -2579
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +851 -666
- package/lib/index.d.ts +851 -666
- package/lib/index.js +43 -2471
- package/lib/index.js.map +1 -1
- package/package.json +10 -7
package/lib/cli.js
CHANGED
|
@@ -1,3347 +1,10 @@
|
|
|
1
1
|
#!/bin/env node
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
19
|
-
|
|
20
|
-
// src/clis/index.ts
|
|
21
|
-
import { Command as Command6, Option as Option2 } from "@commander-js/extra-typings";
|
|
22
|
-
|
|
23
|
-
// src/clis/accountCli.ts
|
|
24
|
-
import { Command } from "@commander-js/extra-typings";
|
|
25
|
-
|
|
26
|
-
// src/index.ts
|
|
27
|
-
var index_exports = {};
|
|
28
|
-
__export(index_exports, {
|
|
29
|
-
AccountMiners: () => AccountMiners,
|
|
30
|
-
AccountRegistry: () => AccountRegistry,
|
|
31
|
-
Accountset: () => Accountset,
|
|
32
|
-
BidPool: () => BidPool,
|
|
33
|
-
BitcoinLocks: () => BitcoinLocks,
|
|
34
|
-
BlockWatch: () => BlockWatch,
|
|
35
|
-
CohortBidder: () => CohortBidder,
|
|
36
|
-
ExtrinsicError: () => ExtrinsicError2,
|
|
37
|
-
JsonExt: () => JsonExt,
|
|
38
|
-
Keyring: () => Keyring,
|
|
39
|
-
MICROGONS_PER_ARGON: () => MICROGONS_PER_ARGON,
|
|
40
|
-
MiningBids: () => MiningBids,
|
|
41
|
-
MiningRotations: () => MiningRotations,
|
|
42
|
-
TxSubmitter: () => TxSubmitter,
|
|
43
|
-
Vault: () => Vault,
|
|
44
|
-
VaultMonitor: () => VaultMonitor,
|
|
45
|
-
WageProtector: () => WageProtector,
|
|
46
|
-
checkForExtrinsicSuccess: () => checkForExtrinsicSuccess,
|
|
47
|
-
convertFixedU128ToBigNumber: () => convertFixedU128ToBigNumber,
|
|
48
|
-
convertPermillToBigNumber: () => convertPermillToBigNumber,
|
|
49
|
-
createKeyringPair: () => createKeyringPair,
|
|
50
|
-
decodeAddress: () => decodeAddress,
|
|
51
|
-
dispatchErrorToExtrinsicError: () => dispatchErrorToExtrinsicError,
|
|
52
|
-
dispatchErrorToString: () => dispatchErrorToString,
|
|
53
|
-
eventDataToJson: () => eventDataToJson,
|
|
54
|
-
filterUndefined: () => filterUndefined,
|
|
55
|
-
formatArgons: () => formatArgons,
|
|
56
|
-
formatPercent: () => formatPercent,
|
|
57
|
-
getAuthorFromHeader: () => getAuthorFromHeader,
|
|
58
|
-
getClient: () => getClient,
|
|
59
|
-
getTickFromHeader: () => getTickFromHeader,
|
|
60
|
-
gettersToObject: () => gettersToObject,
|
|
61
|
-
keyringFromSuri: () => keyringFromSuri,
|
|
62
|
-
mnemonicGenerate: () => mnemonicGenerate,
|
|
63
|
-
waitForLoad: () => waitForLoad
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// src/interfaces/augment-api-consts.ts
|
|
67
|
-
import "@polkadot/api-base/types/consts";
|
|
68
|
-
|
|
69
|
-
// src/interfaces/augment-api-errors.ts
|
|
70
|
-
import "@polkadot/api-base/types/errors";
|
|
71
|
-
|
|
72
|
-
// src/interfaces/augment-api-events.ts
|
|
73
|
-
import "@polkadot/api-base/types/events";
|
|
74
|
-
|
|
75
|
-
// src/interfaces/augment-api-query.ts
|
|
76
|
-
import "@polkadot/api-base/types/storage";
|
|
77
|
-
|
|
78
|
-
// src/interfaces/augment-api-tx.ts
|
|
79
|
-
import "@polkadot/api-base/types/submittable";
|
|
80
|
-
|
|
81
|
-
// src/interfaces/augment-api-rpc.ts
|
|
82
|
-
import "@polkadot/rpc-core/types/jsonrpc";
|
|
83
|
-
|
|
84
|
-
// src/interfaces/augment-api-runtime.ts
|
|
85
|
-
import "@polkadot/api-base/types/calls";
|
|
86
|
-
|
|
87
|
-
// src/interfaces/augment-types.ts
|
|
88
|
-
import "@polkadot/types/types/registry";
|
|
89
|
-
|
|
90
|
-
// src/index.ts
|
|
91
|
-
__reExport(index_exports, types_star);
|
|
92
|
-
import { ApiPromise, HttpProvider, Keyring, WsProvider } from "@polkadot/api";
|
|
93
2
|
import {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
} from "
|
|
98
|
-
import
|
|
99
|
-
|
|
100
|
-
// src/WageProtector.ts
|
|
101
|
-
var WageProtector = class _WageProtector {
|
|
102
|
-
constructor(latestCpi) {
|
|
103
|
-
this.latestCpi = latestCpi;
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Converts the base wage to the current wage using the latest CPI snapshot
|
|
107
|
-
*
|
|
108
|
-
* @param baseWage The base wage to convert
|
|
109
|
-
* @returns The protected wage
|
|
110
|
-
*/
|
|
111
|
-
getProtectedWage(baseWage) {
|
|
112
|
-
return baseWage * this.latestCpi.argonUsdTargetPrice / this.latestCpi.argonUsdPrice;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Subscribes to the current CPI and calls the callback function whenever the CPI changes
|
|
116
|
-
* @param client The ArgonClient to use
|
|
117
|
-
* @param callback The callback function to call when the CPI changes
|
|
118
|
-
* @returns An object with an unsubscribe function that can be called to stop the subscription
|
|
119
|
-
*/
|
|
120
|
-
static async subscribe(client, callback) {
|
|
121
|
-
const unsubscribe = await client.query.priceIndex.current(async (cpi) => {
|
|
122
|
-
if (cpi.isNone) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const finalizedBlock = await client.rpc.chain.getFinalizedHead();
|
|
126
|
-
callback(
|
|
127
|
-
new _WageProtector({
|
|
128
|
-
argonUsdTargetPrice: cpi.value.argonUsdTargetPrice.toBigInt(),
|
|
129
|
-
argonUsdPrice: cpi.value.argonUsdPrice.toBigInt(),
|
|
130
|
-
finalizedBlock: finalizedBlock.toU8a(),
|
|
131
|
-
tick: cpi.value.tick.toBigInt()
|
|
132
|
-
})
|
|
133
|
-
);
|
|
134
|
-
});
|
|
135
|
-
return { unsubscribe };
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Creates a new WageProtector instance by subscribing to the current CPI and waiting for the first value
|
|
139
|
-
* @param client The ArgonClient to use
|
|
140
|
-
*/
|
|
141
|
-
static async create(client) {
|
|
142
|
-
return new Promise(async (resolve, reject) => {
|
|
143
|
-
try {
|
|
144
|
-
const { unsubscribe } = await _WageProtector.subscribe(client, (x) => {
|
|
145
|
-
resolve(x);
|
|
146
|
-
unsubscribe();
|
|
147
|
-
});
|
|
148
|
-
} catch (e) {
|
|
149
|
-
reject(e);
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
// src/TxSubmitter.ts
|
|
156
|
-
function logExtrinsicResult(result) {
|
|
157
|
-
if (process.env.DEBUG) {
|
|
158
|
-
const json = result.status.toJSON();
|
|
159
|
-
const status = Object.keys(json)[0];
|
|
160
|
-
console.debug('Transaction update: "%s"', status, json[status]);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
var TxSubmitter = class {
|
|
164
|
-
constructor(client, tx, pair) {
|
|
165
|
-
this.client = client;
|
|
166
|
-
this.tx = tx;
|
|
167
|
-
this.pair = pair;
|
|
168
|
-
}
|
|
169
|
-
async feeEstimate(tip) {
|
|
170
|
-
const { partialFee } = await this.tx.paymentInfo(this.pair, { tip });
|
|
171
|
-
return partialFee.toBigInt();
|
|
172
|
-
}
|
|
173
|
-
async canAfford(options = {}) {
|
|
174
|
-
const { tip, unavailableBalance } = options;
|
|
175
|
-
const account = await this.client.query.system.account(this.pair.address);
|
|
176
|
-
let availableBalance = account.data.free.toBigInt();
|
|
177
|
-
if (unavailableBalance) {
|
|
178
|
-
availableBalance -= unavailableBalance;
|
|
179
|
-
}
|
|
180
|
-
const existentialDeposit = options.includeExistentialDeposit ? this.client.consts.balances.existentialDeposit.toBigInt() : 0n;
|
|
181
|
-
const fees = await this.feeEstimate(tip);
|
|
182
|
-
const totalCharge = fees + (tip ?? 0n);
|
|
183
|
-
const canAfford = availableBalance > totalCharge + existentialDeposit;
|
|
184
|
-
return { canAfford, availableBalance, txFee: fees };
|
|
185
|
-
}
|
|
186
|
-
async submit(options = {}) {
|
|
187
|
-
const { logResults } = options;
|
|
188
|
-
const result = new TxResult(this.client, logResults);
|
|
189
|
-
let toHuman = this.tx.toHuman().method;
|
|
190
|
-
let txString = [];
|
|
191
|
-
let api = formatCall(toHuman);
|
|
192
|
-
const args = [];
|
|
193
|
-
if (api === "proxy.proxy") {
|
|
194
|
-
toHuman = toHuman.args.call;
|
|
195
|
-
txString.push("Proxy");
|
|
196
|
-
api = formatCall(toHuman);
|
|
197
|
-
}
|
|
198
|
-
if (api.startsWith("utility.batch")) {
|
|
199
|
-
const calls = toHuman.args.calls.map(formatCall).join(", ");
|
|
200
|
-
txString.push(`Batch[${calls}]`);
|
|
201
|
-
} else {
|
|
202
|
-
txString.push(api);
|
|
203
|
-
args.push(toHuman.args);
|
|
204
|
-
}
|
|
205
|
-
args.unshift(txString.join("->"));
|
|
206
|
-
if (options.useLatestNonce && !options.nonce) {
|
|
207
|
-
options.nonce = await this.client.rpc.system.accountNextIndex(
|
|
208
|
-
this.pair.address
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
console.log("Submitting transaction:", ...args);
|
|
212
|
-
await this.tx.signAndSend(this.pair, options, result.onResult.bind(result));
|
|
213
|
-
if (options.waitForBlock) {
|
|
214
|
-
await result.inBlockPromise;
|
|
215
|
-
}
|
|
216
|
-
return result;
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
function formatCall(call) {
|
|
220
|
-
return `${call.section}.${call.method}`;
|
|
221
|
-
}
|
|
222
|
-
var TxResult = class {
|
|
223
|
-
constructor(client, shouldLog = false) {
|
|
224
|
-
this.client = client;
|
|
225
|
-
this.shouldLog = shouldLog;
|
|
226
|
-
this.inBlockPromise = new Promise((resolve, reject) => {
|
|
227
|
-
this.inBlockResolve = resolve;
|
|
228
|
-
this.inBlockReject = reject;
|
|
229
|
-
});
|
|
230
|
-
this.finalizedPromise = new Promise((resolve, reject) => {
|
|
231
|
-
this.finalizedResolve = resolve;
|
|
232
|
-
this.finalizedReject = reject;
|
|
233
|
-
});
|
|
234
|
-
this.inBlockPromise.catch(() => {
|
|
235
|
-
});
|
|
236
|
-
this.finalizedPromise.catch(() => {
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
inBlockPromise;
|
|
240
|
-
finalizedPromise;
|
|
241
|
-
status;
|
|
242
|
-
events = [];
|
|
243
|
-
/**
|
|
244
|
-
* The index of the batch that was interrupted, if any.
|
|
245
|
-
*/
|
|
246
|
-
batchInterruptedIndex;
|
|
247
|
-
includedInBlock;
|
|
248
|
-
/**
|
|
249
|
-
* The final fee paid for the transaction, including the fee tip.
|
|
250
|
-
*/
|
|
251
|
-
finalFee;
|
|
252
|
-
/**
|
|
253
|
-
* The fee tip paid for the transaction.
|
|
254
|
-
*/
|
|
255
|
-
finalFeeTip;
|
|
256
|
-
inBlockResolve;
|
|
257
|
-
inBlockReject;
|
|
258
|
-
finalizedResolve;
|
|
259
|
-
finalizedReject;
|
|
260
|
-
onResult(result) {
|
|
261
|
-
this.status = result.status;
|
|
262
|
-
if (this.shouldLog) {
|
|
263
|
-
logExtrinsicResult(result);
|
|
264
|
-
}
|
|
265
|
-
const { events, status, dispatchError, isFinalized } = result;
|
|
266
|
-
if (status.isInBlock) {
|
|
267
|
-
this.includedInBlock = status.asInBlock.toU8a();
|
|
268
|
-
let encounteredError = dispatchError;
|
|
269
|
-
let batchErrorIndex;
|
|
270
|
-
for (const event of events) {
|
|
271
|
-
this.events.push(event.event);
|
|
272
|
-
if (this.client.events.utility.BatchInterrupted.is(event.event)) {
|
|
273
|
-
batchErrorIndex = event.event.data[0].toNumber();
|
|
274
|
-
this.batchInterruptedIndex = batchErrorIndex;
|
|
275
|
-
encounteredError = event.event.data[1];
|
|
276
|
-
}
|
|
277
|
-
if (this.client.events.transactionPayment.TransactionFeePaid.is(
|
|
278
|
-
event.event
|
|
279
|
-
)) {
|
|
280
|
-
const [_who, actualFee, tip] = event.event.data;
|
|
281
|
-
this.finalFee = actualFee.toBigInt();
|
|
282
|
-
this.finalFeeTip = tip.toBigInt();
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
if (encounteredError) {
|
|
286
|
-
const error = dispatchErrorToExtrinsicError(
|
|
287
|
-
this.client,
|
|
288
|
-
encounteredError,
|
|
289
|
-
batchErrorIndex
|
|
290
|
-
);
|
|
291
|
-
this.reject(error);
|
|
292
|
-
} else {
|
|
293
|
-
this.inBlockResolve(status.asInBlock.toU8a());
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
if (isFinalized) {
|
|
297
|
-
this.finalizedResolve(status.asFinalized);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
reject(error) {
|
|
301
|
-
this.inBlockReject(error);
|
|
302
|
-
this.finalizedReject(error);
|
|
303
|
-
}
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
// src/utils.ts
|
|
307
|
-
import BigNumber2, * as BN from "bignumber.js";
|
|
308
|
-
var { ROUND_FLOOR } = BN;
|
|
309
|
-
var MICROGONS_PER_ARGON = 1e6;
|
|
310
|
-
function formatArgons(x) {
|
|
311
|
-
if (x === void 0 || x === null) return "na";
|
|
312
|
-
const isNegative = x < 0;
|
|
313
|
-
let format = BigNumber2(x.toString()).abs().div(MICROGONS_PER_ARGON).toFormat(2, ROUND_FLOOR);
|
|
314
|
-
if (format.endsWith(".00")) {
|
|
315
|
-
format = format.slice(0, -3);
|
|
316
|
-
}
|
|
317
|
-
return `${isNegative ? "-" : ""}\u20B3${format}`;
|
|
318
|
-
}
|
|
319
|
-
function formatPercent(x) {
|
|
320
|
-
if (!x) return "na";
|
|
321
|
-
return `${x.times(100).decimalPlaces(3)}%`;
|
|
322
|
-
}
|
|
323
|
-
function filterUndefined(obj) {
|
|
324
|
-
return Object.fromEntries(
|
|
325
|
-
Object.entries(obj).filter(
|
|
326
|
-
([_, value]) => value !== void 0 && value !== null
|
|
327
|
-
)
|
|
328
|
-
);
|
|
329
|
-
}
|
|
330
|
-
async function gettersToObject(obj) {
|
|
331
|
-
if (obj === null || obj === void 0 || typeof obj !== "object") return obj;
|
|
332
|
-
const keys = [];
|
|
333
|
-
for (const key in obj) {
|
|
334
|
-
keys.push(key);
|
|
335
|
-
}
|
|
336
|
-
if (Symbol.iterator in obj) {
|
|
337
|
-
const iterableToArray = [];
|
|
338
|
-
for (const item of obj) {
|
|
339
|
-
iterableToArray.push(await gettersToObject(item));
|
|
340
|
-
}
|
|
341
|
-
return iterableToArray;
|
|
342
|
-
}
|
|
343
|
-
const result = {};
|
|
344
|
-
for (const key of keys) {
|
|
345
|
-
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
|
346
|
-
if (descriptor && typeof descriptor.value === "function") {
|
|
347
|
-
continue;
|
|
348
|
-
}
|
|
349
|
-
const value = descriptor && descriptor.get ? descriptor.get.call(obj) : obj[key];
|
|
350
|
-
if (typeof value === "function") continue;
|
|
351
|
-
result[key] = await gettersToObject(value);
|
|
352
|
-
}
|
|
353
|
-
return result;
|
|
354
|
-
}
|
|
355
|
-
function convertFixedU128ToBigNumber(fixedU128) {
|
|
356
|
-
const decimalFactor = new BigNumber2(10).pow(new BigNumber2(18));
|
|
357
|
-
const rawValue = new BigNumber2(fixedU128.toString());
|
|
358
|
-
return rawValue.div(decimalFactor);
|
|
359
|
-
}
|
|
360
|
-
function convertPermillToBigNumber(permill) {
|
|
361
|
-
const decimalFactor = new BigNumber2(1e6);
|
|
362
|
-
const rawValue = new BigNumber2(permill.toString());
|
|
363
|
-
return rawValue.div(decimalFactor);
|
|
364
|
-
}
|
|
365
|
-
function eventDataToJson(event) {
|
|
366
|
-
const obj = {};
|
|
367
|
-
event.data.forEach((data, index) => {
|
|
368
|
-
const name = event.data.names?.[index];
|
|
369
|
-
obj[name ?? `${index}`] = data.toJSON();
|
|
370
|
-
});
|
|
371
|
-
return obj;
|
|
372
|
-
}
|
|
373
|
-
function dispatchErrorToString(client, error) {
|
|
374
|
-
let message = error.toString();
|
|
375
|
-
if (error.isModule) {
|
|
376
|
-
const decoded = client.registry.findMetaError(error.asModule);
|
|
377
|
-
const { docs, name, section } = decoded;
|
|
378
|
-
message = `${section}.${name}: ${docs.join(" ")}`;
|
|
379
|
-
}
|
|
380
|
-
return message;
|
|
381
|
-
}
|
|
382
|
-
var ExtrinsicError2 = class extends Error {
|
|
383
|
-
constructor(errorCode, details, batchInterruptedIndex) {
|
|
384
|
-
super(errorCode);
|
|
385
|
-
this.errorCode = errorCode;
|
|
386
|
-
this.details = details;
|
|
387
|
-
this.batchInterruptedIndex = batchInterruptedIndex;
|
|
388
|
-
}
|
|
389
|
-
toString() {
|
|
390
|
-
if (this.batchInterruptedIndex !== void 0) {
|
|
391
|
-
return `${this.errorCode} ${this.details ?? ""} (Batch interrupted at index ${this.batchInterruptedIndex})`;
|
|
392
|
-
}
|
|
393
|
-
return `${this.errorCode} ${this.details ?? ""}`;
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
function dispatchErrorToExtrinsicError(client, error, batchInterruptedIndex) {
|
|
397
|
-
if (error.isModule) {
|
|
398
|
-
const decoded = client.registry.findMetaError(error.asModule);
|
|
399
|
-
const { docs, name, section } = decoded;
|
|
400
|
-
return new ExtrinsicError2(
|
|
401
|
-
`${section}.${name}`,
|
|
402
|
-
docs.join(" "),
|
|
403
|
-
batchInterruptedIndex
|
|
404
|
-
);
|
|
405
|
-
}
|
|
406
|
-
return new ExtrinsicError2(error.toString(), void 0, batchInterruptedIndex);
|
|
407
|
-
}
|
|
408
|
-
function checkForExtrinsicSuccess(events, client) {
|
|
409
|
-
return new Promise((resolve, reject) => {
|
|
410
|
-
for (const { event } of events) {
|
|
411
|
-
if (client.events.system.ExtrinsicSuccess.is(event)) {
|
|
412
|
-
resolve();
|
|
413
|
-
} else if (client.events.system.ExtrinsicFailed.is(event)) {
|
|
414
|
-
const [dispatchError] = event.data;
|
|
415
|
-
let errorInfo = dispatchError.toString();
|
|
416
|
-
if (dispatchError.isModule) {
|
|
417
|
-
const decoded = client.registry.findMetaError(dispatchError.asModule);
|
|
418
|
-
errorInfo = `${decoded.section}.${decoded.name}`;
|
|
419
|
-
}
|
|
420
|
-
reject(
|
|
421
|
-
new Error(
|
|
422
|
-
`${event.section}.${event.method}:: ExtrinsicFailed:: ${errorInfo}`
|
|
423
|
-
)
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
var JsonExt = class {
|
|
430
|
-
static stringify(obj, space) {
|
|
431
|
-
return JSON.stringify(
|
|
432
|
-
obj,
|
|
433
|
-
(_, v) => typeof v === "bigint" ? `${v}n` : v,
|
|
434
|
-
space
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
static parse(str) {
|
|
438
|
-
return JSON.parse(str, (_, v) => {
|
|
439
|
-
if (typeof v === "string" && v.endsWith("n")) {
|
|
440
|
-
return BigInt(v.slice(0, -1));
|
|
441
|
-
}
|
|
442
|
-
return v;
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
// src/AccountRegistry.ts
|
|
448
|
-
var AccountRegistry = class _AccountRegistry {
|
|
449
|
-
namedAccounts = /* @__PURE__ */ new Map();
|
|
450
|
-
me = "me";
|
|
451
|
-
constructor(name) {
|
|
452
|
-
if (name) {
|
|
453
|
-
this.me = name;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
getName(address) {
|
|
457
|
-
return this.namedAccounts.get(address);
|
|
458
|
-
}
|
|
459
|
-
register(address, name) {
|
|
460
|
-
this.namedAccounts.set(address, name);
|
|
461
|
-
}
|
|
462
|
-
static factory = (name) => new _AccountRegistry(name);
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
// src/Accountset.ts
|
|
466
|
-
import * as process2 from "node:process";
|
|
467
|
-
|
|
468
|
-
// src/BlockWatch.ts
|
|
469
|
-
import { createNanoEvents } from "nanoevents";
|
|
470
|
-
function getTickFromHeader(client, header) {
|
|
471
|
-
for (const x of header.digest.logs) {
|
|
472
|
-
if (x.isPreRuntime) {
|
|
473
|
-
const [engineId, data] = x.asPreRuntime;
|
|
474
|
-
if (engineId.toString() === "aura") {
|
|
475
|
-
return client.createType("u64", data).toNumber();
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
return void 0;
|
|
480
|
-
}
|
|
481
|
-
function getAuthorFromHeader(client, header) {
|
|
482
|
-
for (const x of header.digest.logs) {
|
|
483
|
-
if (x.isPreRuntime) {
|
|
484
|
-
const [engineId, data] = x.asPreRuntime;
|
|
485
|
-
if (engineId.toString() === "pow_") {
|
|
486
|
-
return client.createType("AccountId32", data).toHuman();
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
return void 0;
|
|
491
|
-
}
|
|
492
|
-
var BlockWatch = class {
|
|
493
|
-
constructor(mainchain, options = {}) {
|
|
494
|
-
this.mainchain = mainchain;
|
|
495
|
-
this.options = options;
|
|
496
|
-
this.options.shouldLog ??= true;
|
|
497
|
-
this.options.finalizedBlocks ??= false;
|
|
498
|
-
}
|
|
499
|
-
events = createNanoEvents();
|
|
500
|
-
obligationsById = {};
|
|
501
|
-
obligationIdByUtxoId = {};
|
|
502
|
-
unsubscribe;
|
|
503
|
-
stop() {
|
|
504
|
-
if (this.unsubscribe) {
|
|
505
|
-
this.unsubscribe();
|
|
506
|
-
this.unsubscribe = void 0;
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
async start() {
|
|
510
|
-
await this.watchBlocks();
|
|
511
|
-
}
|
|
512
|
-
async watchBlocks() {
|
|
513
|
-
const client = await this.mainchain;
|
|
514
|
-
const onBlock = async (header) => {
|
|
515
|
-
try {
|
|
516
|
-
await this.processBlock(header);
|
|
517
|
-
} catch (e) {
|
|
518
|
-
console.error("Error processing block", e);
|
|
519
|
-
}
|
|
520
|
-
};
|
|
521
|
-
if (this.options.finalizedBlocks) {
|
|
522
|
-
this.unsubscribe = await client.rpc.chain.subscribeFinalizedHeads(onBlock);
|
|
523
|
-
} else {
|
|
524
|
-
this.unsubscribe = await client.rpc.chain.subscribeNewHeads(onBlock);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
async processBlock(header) {
|
|
528
|
-
const client = await this.mainchain;
|
|
529
|
-
if (this.options.shouldLog) {
|
|
530
|
-
console.log(`-------------------------------------
|
|
531
|
-
BLOCK #${header.number}, ${header.hash.toHuman()}`);
|
|
532
|
-
}
|
|
533
|
-
const blockHash = header.hash;
|
|
534
|
-
const api = await client.at(blockHash);
|
|
535
|
-
const isBlockVote = await api.query.blockSeal.isBlockFromVoteSeal();
|
|
536
|
-
if (!isBlockVote) {
|
|
537
|
-
console.warn("> Compute reactivated!");
|
|
538
|
-
}
|
|
539
|
-
const events = await api.query.system.events();
|
|
540
|
-
const reloadVaults = /* @__PURE__ */ new Set();
|
|
541
|
-
let block = void 0;
|
|
542
|
-
for (const { event, phase } of events) {
|
|
543
|
-
const data = eventDataToJson(event);
|
|
544
|
-
if (data.vaultId) {
|
|
545
|
-
const vaultId = data.vaultId;
|
|
546
|
-
reloadVaults.add(vaultId);
|
|
547
|
-
}
|
|
548
|
-
let logEvent = false;
|
|
549
|
-
if (event.section === "liquidityPools") {
|
|
550
|
-
if (client.events.liquidityPools.BidPoolDistributed.is(event)) {
|
|
551
|
-
const { bidPoolBurned, bidPoolDistributed } = event.data;
|
|
552
|
-
data.burned = formatArgons(bidPoolBurned.toBigInt());
|
|
553
|
-
data.distributed = formatArgons(bidPoolDistributed.toBigInt());
|
|
554
|
-
logEvent = true;
|
|
555
|
-
} else if (client.events.liquidityPools.NextBidPoolCapitalLocked.is(event)) {
|
|
556
|
-
const { totalActivatedCapital } = event.data;
|
|
557
|
-
data.totalActivatedCapital = formatArgons(
|
|
558
|
-
totalActivatedCapital.toBigInt()
|
|
559
|
-
);
|
|
560
|
-
logEvent = true;
|
|
561
|
-
}
|
|
562
|
-
} else if (event.section === "bitcoinLocks") {
|
|
563
|
-
if (client.events.bitcoinLocks.BitcoinLockCreated.is(event)) {
|
|
564
|
-
const { lockPrice, utxoId, accountId, vaultId } = event.data;
|
|
565
|
-
this.obligationsById[utxoId.toNumber()] = {
|
|
566
|
-
vaultId: vaultId.toNumber(),
|
|
567
|
-
amount: lockPrice.toBigInt()
|
|
568
|
-
};
|
|
569
|
-
data.lockPrice = formatArgons(lockPrice.toBigInt());
|
|
570
|
-
data.accountId = accountId.toHuman();
|
|
571
|
-
reloadVaults.add(vaultId.toNumber());
|
|
572
|
-
}
|
|
573
|
-
logEvent = true;
|
|
574
|
-
} else if (event.section === "mint") {
|
|
575
|
-
logEvent = true;
|
|
576
|
-
if (client.events.mint.MiningMint.is(event)) {
|
|
577
|
-
const { amount } = event.data;
|
|
578
|
-
data.amount = formatArgons(amount.toBigInt());
|
|
579
|
-
}
|
|
580
|
-
} else if (event.section === "miningSlot") {
|
|
581
|
-
logEvent = true;
|
|
582
|
-
if (client.events.miningSlot.SlotBidderAdded.is(event)) {
|
|
583
|
-
data.amount = formatArgons(event.data.bidAmount.toBigInt());
|
|
584
|
-
this.events.emit("mining-bid", header, {
|
|
585
|
-
amount: event.data.bidAmount.toBigInt(),
|
|
586
|
-
accountId: event.data.accountId.toString()
|
|
587
|
-
});
|
|
588
|
-
} else if (client.events.miningSlot.SlotBidderDropped.is(event)) {
|
|
589
|
-
this.events.emit("mining-bid-ousted", header, {
|
|
590
|
-
accountId: event.data.accountId.toString(),
|
|
591
|
-
preservedArgonotHold: event.data.preservedArgonotHold.toPrimitive()
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
} else if (event.section === "bitcoinUtxos") {
|
|
595
|
-
logEvent = true;
|
|
596
|
-
if (client.events.bitcoinUtxos.UtxoVerified.is(event)) {
|
|
597
|
-
const { utxoId } = event.data;
|
|
598
|
-
const details = await this.getBitcoinLockDetails(
|
|
599
|
-
utxoId.toNumber(),
|
|
600
|
-
blockHash
|
|
601
|
-
);
|
|
602
|
-
this.events.emit("bitcoin-verified", header, {
|
|
603
|
-
utxoId: utxoId.toNumber(),
|
|
604
|
-
vaultId: details.vaultId,
|
|
605
|
-
amount: details.amount
|
|
606
|
-
});
|
|
607
|
-
data.amount = formatArgons(details.amount);
|
|
608
|
-
reloadVaults.add(details.vaultId);
|
|
609
|
-
}
|
|
610
|
-
} else if (event.section === "system") {
|
|
611
|
-
if (client.events.system.ExtrinsicFailed.is(event)) {
|
|
612
|
-
const { dispatchError } = event.data;
|
|
613
|
-
if (dispatchError.isModule) {
|
|
614
|
-
const decoded = api.registry.findMetaError(dispatchError.asModule);
|
|
615
|
-
const { name, section } = decoded;
|
|
616
|
-
block ??= await client.rpc.chain.getBlock(header.hash);
|
|
617
|
-
const extrinsicIndex = phase.asApplyExtrinsic.toNumber();
|
|
618
|
-
const ext = block.block.extrinsics[extrinsicIndex];
|
|
619
|
-
if (this.options.shouldLog) {
|
|
620
|
-
console.log(
|
|
621
|
-
`> [Failed Tx] ${section}.${name} -> ${ext.method.section}.${ext.method.method} (nonce=${ext.nonce})`,
|
|
622
|
-
ext.toHuman()?.method?.args
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
} else {
|
|
626
|
-
if (this.options.shouldLog) {
|
|
627
|
-
console.log(`x [Failed Tx] ${dispatchError.toJSON()}`);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
if (this.options.shouldLog && logEvent) {
|
|
633
|
-
console.log(`> ${event.section}.${event.method}`, data);
|
|
634
|
-
}
|
|
635
|
-
this.events.emit("event", header, event);
|
|
636
|
-
}
|
|
637
|
-
if (reloadVaults.size)
|
|
638
|
-
this.events.emit("vaults-updated", header, reloadVaults);
|
|
639
|
-
const tick = getTickFromHeader(client, header);
|
|
640
|
-
const author = getAuthorFromHeader(client, header);
|
|
641
|
-
this.events.emit(
|
|
642
|
-
"block",
|
|
643
|
-
header,
|
|
644
|
-
{ tick, author },
|
|
645
|
-
events.map((x) => x.event)
|
|
646
|
-
);
|
|
647
|
-
}
|
|
648
|
-
async getBitcoinLockDetails(utxoId, blockHash) {
|
|
649
|
-
const client = await this.mainchain;
|
|
650
|
-
const api = await client.at(blockHash);
|
|
651
|
-
let obligationId = this.obligationIdByUtxoId[utxoId];
|
|
652
|
-
if (!obligationId) {
|
|
653
|
-
const lock = await api.query.bitcoinLocks.locksByUtxoId(utxoId);
|
|
654
|
-
obligationId = lock.value.obligationId.toNumber();
|
|
655
|
-
this.obligationIdByUtxoId[utxoId] = obligationId;
|
|
656
|
-
this.obligationsById[obligationId] = {
|
|
657
|
-
vaultId: lock.value.vaultId.toNumber(),
|
|
658
|
-
amount: lock.value.lockPrice.toBigInt()
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
return this.obligationsById[obligationId];
|
|
662
|
-
}
|
|
663
|
-
};
|
|
664
|
-
|
|
665
|
-
// src/MiningRotations.ts
|
|
666
|
-
var MiningRotations = class {
|
|
667
|
-
miningConfig;
|
|
668
|
-
genesisTick;
|
|
669
|
-
async getForTick(client, tick) {
|
|
670
|
-
this.miningConfig ??= await client.query.miningSlot.miningConfig().then((x) => ({
|
|
671
|
-
ticksBetweenSlots: x.ticksBetweenSlots.toNumber(),
|
|
672
|
-
slotBiddingStartAfterTicks: x.slotBiddingStartAfterTicks.toNumber()
|
|
673
|
-
}));
|
|
674
|
-
this.genesisTick ??= await client.query.ticks.genesisTick().then((x) => x.toNumber());
|
|
675
|
-
const ticksBetweenSlots = this.miningConfig.ticksBetweenSlots;
|
|
676
|
-
const slot1StartTick = this.genesisTick + this.miningConfig.slotBiddingStartAfterTicks + ticksBetweenSlots;
|
|
677
|
-
if (tick < slot1StartTick) return 0;
|
|
678
|
-
const ticksSinceSlot1 = tick - slot1StartTick;
|
|
679
|
-
return Math.floor(ticksSinceSlot1 / ticksBetweenSlots) + 1;
|
|
680
|
-
}
|
|
681
|
-
async getForHeader(client, header) {
|
|
682
|
-
if (header.number.toNumber() === 0) return 0;
|
|
683
|
-
const tick = getTickFromHeader(client, header);
|
|
684
|
-
if (tick === void 0) return void 0;
|
|
685
|
-
return this.getForTick(client, tick);
|
|
686
|
-
}
|
|
687
|
-
};
|
|
688
|
-
|
|
689
|
-
// src/AccountMiners.ts
|
|
690
|
-
import { createNanoEvents as createNanoEvents2 } from "nanoevents";
|
|
691
|
-
var AccountMiners = class _AccountMiners {
|
|
692
|
-
constructor(accountset, registeredMiners, options = { shouldLog: false }) {
|
|
693
|
-
this.accountset = accountset;
|
|
694
|
-
this.options = options;
|
|
695
|
-
this.miningRotations = new MiningRotations();
|
|
696
|
-
for (const seat of registeredMiners) {
|
|
697
|
-
this.trackedAccountsByAddress[seat.address] = {
|
|
698
|
-
cohortId: seat.cohortId,
|
|
699
|
-
subaccountIndex: seat.subaccountIndex
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
events = createNanoEvents2();
|
|
704
|
-
miningRotations;
|
|
705
|
-
trackedAccountsByAddress = {};
|
|
706
|
-
async watch() {
|
|
707
|
-
const blockWatch = new BlockWatch(this.accountset.client, {
|
|
708
|
-
shouldLog: this.options.shouldLog
|
|
709
|
-
});
|
|
710
|
-
blockWatch.events.on("block", this.onBlock.bind(this));
|
|
711
|
-
await blockWatch.start();
|
|
712
|
-
return blockWatch;
|
|
713
|
-
}
|
|
714
|
-
async onBlock(header, digests, events) {
|
|
715
|
-
const { author, tick } = digests;
|
|
716
|
-
if (author) {
|
|
717
|
-
const voteAuthor = this.trackedAccountsByAddress[author];
|
|
718
|
-
if (voteAuthor && this.options.shouldLog) {
|
|
719
|
-
console.log(
|
|
720
|
-
"> Our vote author",
|
|
721
|
-
this.accountset.accountRegistry.getName(author)
|
|
722
|
-
);
|
|
723
|
-
}
|
|
724
|
-
} else {
|
|
725
|
-
console.warn("> No vote author found");
|
|
726
|
-
}
|
|
727
|
-
const client = await this.accountset.client;
|
|
728
|
-
const rotation = await this.miningRotations.getForTick(client, tick);
|
|
729
|
-
let newMiners;
|
|
730
|
-
const dataByCohort = { rotation };
|
|
731
|
-
for (const event of events) {
|
|
732
|
-
if (client.events.miningSlot.NewMiners.is(event)) {
|
|
733
|
-
newMiners = {
|
|
734
|
-
cohortId: event.data.cohortId.toNumber(),
|
|
735
|
-
addresses: event.data.newMiners.map((x) => x.accountId.toHuman())
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
if (client.events.blockRewards.RewardCreated.is(event)) {
|
|
739
|
-
const { rewards } = event.data;
|
|
740
|
-
for (const reward of rewards) {
|
|
741
|
-
const { argons, ownership } = reward;
|
|
742
|
-
const entry = this.trackedAccountsByAddress[author];
|
|
743
|
-
if (entry) {
|
|
744
|
-
dataByCohort[entry.cohortId] ??= {
|
|
745
|
-
argonsMinted: 0n,
|
|
746
|
-
argonsMined: 0n,
|
|
747
|
-
argonotsMined: 0n
|
|
748
|
-
};
|
|
749
|
-
dataByCohort[entry.cohortId].argonotsMined += ownership.toBigInt();
|
|
750
|
-
dataByCohort[entry.cohortId].argonsMined += argons.toBigInt();
|
|
751
|
-
this.events.emit("mined", header, {
|
|
752
|
-
author,
|
|
753
|
-
argons: argons.toBigInt(),
|
|
754
|
-
argonots: ownership.toBigInt(),
|
|
755
|
-
cohortId: entry.cohortId,
|
|
756
|
-
rotation
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
if (client.events.mint.MiningMint.is(event)) {
|
|
762
|
-
const { perMiner } = event.data;
|
|
763
|
-
const amountPerMiner = perMiner.toBigInt();
|
|
764
|
-
if (amountPerMiner > 0n) {
|
|
765
|
-
for (const [address, info] of Object.entries(
|
|
766
|
-
this.trackedAccountsByAddress
|
|
767
|
-
)) {
|
|
768
|
-
const { cohortId } = info;
|
|
769
|
-
dataByCohort[cohortId] ??= {
|
|
770
|
-
argonsMinted: 0n,
|
|
771
|
-
argonsMined: 0n,
|
|
772
|
-
argonotsMined: 0n
|
|
773
|
-
};
|
|
774
|
-
dataByCohort[cohortId].argonsMinted += amountPerMiner;
|
|
775
|
-
this.events.emit("minted", header, {
|
|
776
|
-
accountId: address,
|
|
777
|
-
argons: amountPerMiner,
|
|
778
|
-
cohortId,
|
|
779
|
-
rotation
|
|
780
|
-
});
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
if (newMiners) {
|
|
786
|
-
this.newCohortMiners(newMiners.cohortId, newMiners.addresses);
|
|
787
|
-
}
|
|
788
|
-
return dataByCohort;
|
|
789
|
-
}
|
|
790
|
-
newCohortMiners(cohortId, addresses) {
|
|
791
|
-
for (const [address, info] of Object.entries(
|
|
792
|
-
this.trackedAccountsByAddress
|
|
793
|
-
)) {
|
|
794
|
-
if (info.cohortId === cohortId - 10) {
|
|
795
|
-
delete this.trackedAccountsByAddress[address];
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
for (const address of addresses) {
|
|
799
|
-
const entry = this.accountset.subAccountsByAddress[address];
|
|
800
|
-
if (entry) {
|
|
801
|
-
this.trackedAccountsByAddress[address] = {
|
|
802
|
-
cohortId,
|
|
803
|
-
subaccountIndex: entry.index
|
|
804
|
-
};
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
static async loadAt(accountset, options = {}) {
|
|
809
|
-
const seats = await accountset.miningSeats(options.blockHash);
|
|
810
|
-
const registered = seats.filter((x) => x.cohortId !== void 0);
|
|
811
|
-
return new _AccountMiners(accountset, registered, {
|
|
812
|
-
shouldLog: options.shouldLog ?? false
|
|
813
|
-
});
|
|
814
|
-
}
|
|
815
|
-
};
|
|
816
|
-
|
|
817
|
-
// src/Accountset.ts
|
|
818
|
-
var Accountset = class {
|
|
819
|
-
txSubmitterPair;
|
|
820
|
-
isProxy = false;
|
|
821
|
-
seedAddress;
|
|
822
|
-
subAccountsByAddress = {};
|
|
823
|
-
accountRegistry;
|
|
824
|
-
client;
|
|
825
|
-
get addresses() {
|
|
826
|
-
return [this.seedAddress, ...Object.keys(this.subAccountsByAddress)];
|
|
827
|
-
}
|
|
828
|
-
get namedAccounts() {
|
|
829
|
-
return this.accountRegistry.namedAccounts;
|
|
830
|
-
}
|
|
831
|
-
sessionKeyMnemonic;
|
|
832
|
-
constructor(options) {
|
|
833
|
-
if ("seedAccount" in options) {
|
|
834
|
-
this.txSubmitterPair = options.seedAccount;
|
|
835
|
-
this.seedAddress = options.seedAccount.address;
|
|
836
|
-
this.isProxy = false;
|
|
837
|
-
} else {
|
|
838
|
-
this.isProxy = options.isProxy;
|
|
839
|
-
this.txSubmitterPair = options.txSubmitter;
|
|
840
|
-
this.seedAddress = options.seedAddress;
|
|
841
|
-
}
|
|
842
|
-
this.sessionKeyMnemonic = options.sessionKeyMnemonic;
|
|
843
|
-
this.accountRegistry = options.accountRegistry ?? AccountRegistry.factory(options.name);
|
|
844
|
-
this.client = options.client;
|
|
845
|
-
const defaultRange = options.subaccountRange ?? getDefaultSubaccountRange();
|
|
846
|
-
this.accountRegistry.register(
|
|
847
|
-
this.seedAddress,
|
|
848
|
-
`${this.accountRegistry.me}//seed`
|
|
849
|
-
);
|
|
850
|
-
for (const i of defaultRange) {
|
|
851
|
-
const pair = this.txSubmitterPair.derive(`//${i}`);
|
|
852
|
-
this.subAccountsByAddress[pair.address] = { pair, index: i };
|
|
853
|
-
this.accountRegistry.register(
|
|
854
|
-
pair.address,
|
|
855
|
-
`${this.accountRegistry.me}//${i}`
|
|
856
|
-
);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
async submitterBalance(blockHash) {
|
|
860
|
-
const client = await this.client;
|
|
861
|
-
const api = blockHash ? await client.at(blockHash) : client;
|
|
862
|
-
const accountData = await api.query.system.account(
|
|
863
|
-
this.txSubmitterPair.address
|
|
864
|
-
);
|
|
865
|
-
return accountData.data.free.toBigInt();
|
|
866
|
-
}
|
|
867
|
-
async balance(blockHash) {
|
|
868
|
-
const client = await this.client;
|
|
869
|
-
const api = blockHash ? await client.at(blockHash) : client;
|
|
870
|
-
const accountData = await api.query.system.account(this.seedAddress);
|
|
871
|
-
return accountData.data.free.toBigInt();
|
|
872
|
-
}
|
|
873
|
-
async totalArgonsAt(blockHash) {
|
|
874
|
-
const client = await this.client;
|
|
875
|
-
const api = blockHash ? await client.at(blockHash) : client;
|
|
876
|
-
const addresses = this.addresses;
|
|
877
|
-
const results = await api.query.system.account.multi(addresses);
|
|
878
|
-
return results.map((account, i) => {
|
|
879
|
-
const address = addresses[i];
|
|
880
|
-
return {
|
|
881
|
-
address,
|
|
882
|
-
amount: account.data.free.toBigInt(),
|
|
883
|
-
index: this.subAccountsByAddress[address]?.index ?? Number.NaN
|
|
884
|
-
};
|
|
885
|
-
});
|
|
886
|
-
}
|
|
887
|
-
async totalArgonotsAt(blockHash) {
|
|
888
|
-
const client = await this.client;
|
|
889
|
-
const api = blockHash ? await client.at(blockHash) : client;
|
|
890
|
-
const addresses = this.addresses;
|
|
891
|
-
const results = await api.query.ownership.account.multi(addresses);
|
|
892
|
-
return results.map((account, i) => {
|
|
893
|
-
const address = addresses[i];
|
|
894
|
-
return {
|
|
895
|
-
address,
|
|
896
|
-
amount: account.free.toBigInt(),
|
|
897
|
-
index: this.subAccountsByAddress[address]?.index ?? Number.NaN
|
|
898
|
-
};
|
|
899
|
-
});
|
|
900
|
-
}
|
|
901
|
-
async getAvailableMinerAccounts(maxSeats) {
|
|
902
|
-
const miningSeats = await this.miningSeats();
|
|
903
|
-
const subaccountRange = [];
|
|
904
|
-
for (const seat of miningSeats) {
|
|
905
|
-
if (seat.hasWinningBid) {
|
|
906
|
-
continue;
|
|
907
|
-
}
|
|
908
|
-
if (seat.isLastDay || seat.seat === void 0) {
|
|
909
|
-
subaccountRange.push({
|
|
910
|
-
index: seat.subaccountIndex,
|
|
911
|
-
isRebid: seat.seat !== void 0,
|
|
912
|
-
address: seat.address
|
|
913
|
-
});
|
|
914
|
-
if (subaccountRange.length >= maxSeats) {
|
|
915
|
-
break;
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
return subaccountRange;
|
|
920
|
-
}
|
|
921
|
-
async loadRegisteredMiners(api) {
|
|
922
|
-
const addresses = Object.keys(this.subAccountsByAddress);
|
|
923
|
-
const rawIndices = await api.query.miningSlot.accountIndexLookup.multi(addresses);
|
|
924
|
-
const addressToMiningIndex = {};
|
|
925
|
-
for (let i = 0; i < addresses.length; i++) {
|
|
926
|
-
const address = addresses[i];
|
|
927
|
-
if (rawIndices[i].isNone) continue;
|
|
928
|
-
addressToMiningIndex[address] = rawIndices[i].value.toNumber();
|
|
929
|
-
}
|
|
930
|
-
const indices = Object.values(addressToMiningIndex).filter(
|
|
931
|
-
(x) => x !== void 0
|
|
932
|
-
);
|
|
933
|
-
const indexRegistrations = indices.length ? await api.query.miningSlot.activeMinersByIndex.multi(indices) : [];
|
|
934
|
-
const registrationBySeatIndex = {};
|
|
935
|
-
for (let i = 0; i < indices.length; i++) {
|
|
936
|
-
const seatIndex = indices[i];
|
|
937
|
-
const registration = indexRegistrations[i];
|
|
938
|
-
if (registration?.isSome) {
|
|
939
|
-
registrationBySeatIndex[seatIndex] = {
|
|
940
|
-
cohortId: registration.value.cohortId.toNumber(),
|
|
941
|
-
bidAmount: registration.value.bid.toBigInt()
|
|
942
|
-
};
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
const nextCohortId = await api.query.miningSlot.nextCohortId();
|
|
946
|
-
return addresses.map((address) => {
|
|
947
|
-
const seat = addressToMiningIndex[address];
|
|
948
|
-
const registration = registrationBySeatIndex[seat];
|
|
949
|
-
let isLastDay = false;
|
|
950
|
-
if (registration?.cohortId) {
|
|
951
|
-
isLastDay = nextCohortId.toNumber() - registration?.cohortId === 10;
|
|
952
|
-
}
|
|
953
|
-
return {
|
|
954
|
-
...registration,
|
|
955
|
-
address,
|
|
956
|
-
seat,
|
|
957
|
-
isLastDay,
|
|
958
|
-
subaccountIndex: this.subAccountsByAddress[address]?.index ?? Number.NaN
|
|
959
|
-
};
|
|
960
|
-
});
|
|
961
|
-
}
|
|
962
|
-
async miningSeats(blockHash) {
|
|
963
|
-
const client = await this.client;
|
|
964
|
-
const api = blockHash ? await client.at(blockHash) : client;
|
|
965
|
-
const miners = await this.loadRegisteredMiners(api);
|
|
966
|
-
const nextCohort = await api.query.miningSlot.nextSlotCohort();
|
|
967
|
-
return miners.map((miner) => {
|
|
968
|
-
const bid = nextCohort.find((x) => x.accountId.toHuman() === miner.address);
|
|
969
|
-
return {
|
|
970
|
-
...miner,
|
|
971
|
-
hasWinningBid: !!bid,
|
|
972
|
-
bidAmount: bid?.bid.toBigInt() ?? miner.bidAmount
|
|
973
|
-
};
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
async bids(blockHash) {
|
|
977
|
-
const client = await this.client;
|
|
978
|
-
const api = blockHash ? await client.at(blockHash) : client;
|
|
979
|
-
const addresses = Object.keys(this.subAccountsByAddress);
|
|
980
|
-
const nextCohort = await api.query.miningSlot.nextSlotCohort();
|
|
981
|
-
const registrationsByAddress = Object.fromEntries(
|
|
982
|
-
nextCohort.map((x, i) => [x.accountId.toHuman(), { ...x, index: i }])
|
|
983
|
-
);
|
|
984
|
-
return addresses.map((address) => {
|
|
985
|
-
const entry = registrationsByAddress[address];
|
|
986
|
-
return {
|
|
987
|
-
address,
|
|
988
|
-
bidPlace: entry?.index,
|
|
989
|
-
bidAmount: entry?.bid?.toBigInt(),
|
|
990
|
-
index: this.subAccountsByAddress[address]?.index ?? Number.NaN
|
|
991
|
-
};
|
|
992
|
-
});
|
|
993
|
-
}
|
|
994
|
-
async consolidate(subaccounts) {
|
|
995
|
-
const client = await this.client;
|
|
996
|
-
const accounts = this.getAccountsInRange(subaccounts);
|
|
997
|
-
const results = [];
|
|
998
|
-
await Promise.allSettled(
|
|
999
|
-
accounts.map(({ pair, index }) => {
|
|
1000
|
-
if (!pair) {
|
|
1001
|
-
results.push({
|
|
1002
|
-
index,
|
|
1003
|
-
failedError: new Error(`No keypair for //${index}`)
|
|
1004
|
-
});
|
|
1005
|
-
return Promise.resolve();
|
|
1006
|
-
}
|
|
1007
|
-
return new Promise((resolve) => {
|
|
1008
|
-
client.tx.utility.batchAll([
|
|
1009
|
-
client.tx.balances.transferAll(this.seedAddress, true),
|
|
1010
|
-
client.tx.ownership.transferAll(this.seedAddress, true)
|
|
1011
|
-
]).signAndSend(pair, (cb) => {
|
|
1012
|
-
logExtrinsicResult(cb);
|
|
1013
|
-
if (cb.dispatchError) {
|
|
1014
|
-
const error = dispatchErrorToString(client, cb.dispatchError);
|
|
1015
|
-
results.push({
|
|
1016
|
-
index,
|
|
1017
|
-
failedError: new Error(
|
|
1018
|
-
`Error consolidating //${index}: ${error}`
|
|
1019
|
-
)
|
|
1020
|
-
});
|
|
1021
|
-
resolve();
|
|
1022
|
-
}
|
|
1023
|
-
if (cb.isInBlock) {
|
|
1024
|
-
results.push({ index, inBlock: cb.status.asInBlock.toHex() });
|
|
1025
|
-
resolve();
|
|
1026
|
-
}
|
|
1027
|
-
}).catch((e) => {
|
|
1028
|
-
results.push({ index, failedError: e });
|
|
1029
|
-
resolve();
|
|
1030
|
-
});
|
|
1031
|
-
});
|
|
1032
|
-
})
|
|
1033
|
-
);
|
|
1034
|
-
return results;
|
|
1035
|
-
}
|
|
1036
|
-
status(opts) {
|
|
1037
|
-
const { argons, argonots, accountSubset, bids, seats } = opts;
|
|
1038
|
-
const accounts = [
|
|
1039
|
-
{
|
|
1040
|
-
index: "main",
|
|
1041
|
-
address: this.seedAddress,
|
|
1042
|
-
argons: formatArgons(
|
|
1043
|
-
argons.find((x) => x.address === this.seedAddress)?.amount ?? 0n
|
|
1044
|
-
),
|
|
1045
|
-
argonots: formatArgons(
|
|
1046
|
-
argonots.find((x) => x.address === this.seedAddress)?.amount ?? 0n
|
|
1047
|
-
)
|
|
1048
|
-
}
|
|
1049
|
-
];
|
|
1050
|
-
for (const [address, { index }] of Object.entries(
|
|
1051
|
-
this.subAccountsByAddress
|
|
1052
|
-
)) {
|
|
1053
|
-
const argonAmount = argons.find((x) => x.address === address)?.amount ?? 0n;
|
|
1054
|
-
const argonotAmount = argonots.find((x) => x.address === address)?.amount ?? 0n;
|
|
1055
|
-
const bid = bids.find((x) => x.address === address);
|
|
1056
|
-
const seat = seats.find((x) => x.address === address)?.seat;
|
|
1057
|
-
const entry = {
|
|
1058
|
-
index: ` //${index}`,
|
|
1059
|
-
address,
|
|
1060
|
-
argons: formatArgons(argonAmount),
|
|
1061
|
-
argonots: formatArgons(argonotAmount),
|
|
1062
|
-
seat,
|
|
1063
|
-
bidPlace: bid?.bidPlace,
|
|
1064
|
-
bidAmount: bid?.bidAmount ?? 0n
|
|
1065
|
-
};
|
|
1066
|
-
if (accountSubset) {
|
|
1067
|
-
entry.isWorkingOn = accountSubset.some((x) => x.address === address);
|
|
1068
|
-
}
|
|
1069
|
-
accounts.push(entry);
|
|
1070
|
-
}
|
|
1071
|
-
return accounts;
|
|
1072
|
-
}
|
|
1073
|
-
async registerKeys(url) {
|
|
1074
|
-
const client = await getClient(url.replace("ws:", "http:"));
|
|
1075
|
-
const keys = this.keys();
|
|
1076
|
-
for (const [name, key] of Object.entries(keys)) {
|
|
1077
|
-
console.log("Registering key", name, key.publicKey);
|
|
1078
|
-
const result = await client.rpc.author.insertKey(
|
|
1079
|
-
name,
|
|
1080
|
-
key.privateKey,
|
|
1081
|
-
key.publicKey
|
|
1082
|
-
);
|
|
1083
|
-
const saved = await client.rpc.author.hasKey(key.publicKey, name);
|
|
1084
|
-
if (!saved) {
|
|
1085
|
-
console.error("Failed to register key", name, key.publicKey);
|
|
1086
|
-
throw new Error(`Failed to register ${name} key ${key.publicKey}`);
|
|
1087
|
-
}
|
|
1088
|
-
console.log(`Registered ${name} key`, result.toHuman());
|
|
1089
|
-
}
|
|
1090
|
-
await client.disconnect();
|
|
1091
|
-
}
|
|
1092
|
-
keys(keysVersion) {
|
|
1093
|
-
let version = keysVersion ?? 0;
|
|
1094
|
-
if (process2.env.KEYS_VERSION) {
|
|
1095
|
-
version = parseInt(process2.env.KEYS_VERSION) ?? 0;
|
|
1096
|
-
}
|
|
1097
|
-
const seedMnemonic = this.sessionKeyMnemonic ?? process2.env.KEYS_MNEMONIC;
|
|
1098
|
-
if (!seedMnemonic) {
|
|
1099
|
-
throw new Error(
|
|
1100
|
-
"KEYS_MNEMONIC environment variable not set. Cannot derive keys."
|
|
1101
|
-
);
|
|
1102
|
-
}
|
|
1103
|
-
const blockSealKey = `${seedMnemonic}//block-seal//${version}`;
|
|
1104
|
-
const granKey = `${seedMnemonic}//grandpa//${version}`;
|
|
1105
|
-
const blockSealAccount = new Keyring().createFromUri(blockSealKey, {
|
|
1106
|
-
type: "ed25519"
|
|
1107
|
-
});
|
|
1108
|
-
const grandpaAccount = new Keyring().createFromUri(granKey, {
|
|
1109
|
-
type: "ed25519"
|
|
1110
|
-
});
|
|
1111
|
-
return {
|
|
1112
|
-
seal: {
|
|
1113
|
-
privateKey: blockSealKey,
|
|
1114
|
-
publicKey: `0x${Buffer.from(blockSealAccount.publicKey).toString("hex")}`,
|
|
1115
|
-
rawPublicKey: blockSealAccount.publicKey
|
|
1116
|
-
},
|
|
1117
|
-
gran: {
|
|
1118
|
-
privateKey: granKey,
|
|
1119
|
-
publicKey: `0x${Buffer.from(grandpaAccount.publicKey).toString("hex")}`,
|
|
1120
|
-
rawPublicKey: grandpaAccount.publicKey
|
|
1121
|
-
}
|
|
1122
|
-
};
|
|
1123
|
-
}
|
|
1124
|
-
async tx(tx) {
|
|
1125
|
-
const client = await this.client;
|
|
1126
|
-
return new TxSubmitter(client, tx, this.txSubmitterPair);
|
|
1127
|
-
}
|
|
1128
|
-
/**
|
|
1129
|
-
* Create but don't submit a mining bid transaction.
|
|
1130
|
-
* @param options
|
|
1131
|
-
*/
|
|
1132
|
-
async createMiningBidTx(options) {
|
|
1133
|
-
const client = await this.client;
|
|
1134
|
-
const { bidAmount, subaccounts, sendRewardsToSeed } = options;
|
|
1135
|
-
const batch = client.tx.utility.batch(
|
|
1136
|
-
subaccounts.map((x) => {
|
|
1137
|
-
const keys = this.keys();
|
|
1138
|
-
const rewards = sendRewardsToSeed ? { Account: this.seedAddress } : { Owner: null };
|
|
1139
|
-
return client.tx.miningSlot.bid(
|
|
1140
|
-
bidAmount,
|
|
1141
|
-
rewards,
|
|
1142
|
-
{
|
|
1143
|
-
grandpa: keys.gran.rawPublicKey,
|
|
1144
|
-
blockSealAuthority: keys.seal.rawPublicKey
|
|
1145
|
-
},
|
|
1146
|
-
x.address
|
|
1147
|
-
);
|
|
1148
|
-
})
|
|
1149
|
-
);
|
|
1150
|
-
let tx = batch;
|
|
1151
|
-
if (this.isProxy) {
|
|
1152
|
-
tx = client.tx.proxy.proxy(this.seedAddress, "MiningBid", batch);
|
|
1153
|
-
}
|
|
1154
|
-
return new TxSubmitter(client, tx, this.txSubmitterPair);
|
|
1155
|
-
}
|
|
1156
|
-
/**
|
|
1157
|
-
* Create a mining bid. This will create a bid for each account in the given range from the seed account as funding.
|
|
1158
|
-
*/
|
|
1159
|
-
async createMiningBids(options) {
|
|
1160
|
-
const accounts = this.getAccountsInRange(options.subaccountRange);
|
|
1161
|
-
const client = await this.client;
|
|
1162
|
-
const submitter = await this.createMiningBidTx({
|
|
1163
|
-
...options,
|
|
1164
|
-
subaccounts: accounts
|
|
1165
|
-
});
|
|
1166
|
-
const { tip = 0n } = options;
|
|
1167
|
-
const txFee = await submitter.feeEstimate(tip);
|
|
1168
|
-
let minBalance = options.bidAmount * BigInt(accounts.length);
|
|
1169
|
-
let totalFees = tip + 1n + txFee;
|
|
1170
|
-
const seedBalance = await client.query.system.account(this.seedAddress).then((x) => x.data.free.toBigInt());
|
|
1171
|
-
if (!this.isProxy) {
|
|
1172
|
-
minBalance += totalFees;
|
|
1173
|
-
}
|
|
1174
|
-
if (seedBalance < minBalance) {
|
|
1175
|
-
throw new Error(
|
|
1176
|
-
`Insufficient balance to create mining bids. Seed account has ${formatArgons(
|
|
1177
|
-
seedBalance
|
|
1178
|
-
)} but needs ${formatArgons(minBalance)}`
|
|
1179
|
-
);
|
|
1180
|
-
}
|
|
1181
|
-
if (this.isProxy) {
|
|
1182
|
-
const { canAfford, availableBalance } = await submitter.canAfford({
|
|
1183
|
-
tip
|
|
1184
|
-
});
|
|
1185
|
-
if (!canAfford) {
|
|
1186
|
-
throw new Error(
|
|
1187
|
-
`Insufficient balance to pay proxy fees. Proxy account has ${formatArgons(
|
|
1188
|
-
availableBalance
|
|
1189
|
-
)} but needs ${formatArgons(totalFees)}`
|
|
1190
|
-
);
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
console.log("Creating bids", {
|
|
1194
|
-
perSeatBid: options.bidAmount,
|
|
1195
|
-
subaccounts: options.subaccountRange,
|
|
1196
|
-
txFee
|
|
1197
|
-
});
|
|
1198
|
-
const txResult = await submitter.submit({
|
|
1199
|
-
tip,
|
|
1200
|
-
useLatestNonce: true
|
|
1201
|
-
});
|
|
1202
|
-
const bidError = await txResult.inBlockPromise.then(() => void 0).catch((x) => x);
|
|
1203
|
-
return {
|
|
1204
|
-
finalFee: txResult.finalFee,
|
|
1205
|
-
bidError,
|
|
1206
|
-
blockHash: txResult.includedInBlock,
|
|
1207
|
-
successfulBids: txResult.batchInterruptedIndex !== void 0 ? txResult.batchInterruptedIndex : accounts.length
|
|
1208
|
-
};
|
|
1209
|
-
}
|
|
1210
|
-
getAccountsInRange(range) {
|
|
1211
|
-
const entries = new Set(range ?? getDefaultSubaccountRange());
|
|
1212
|
-
return Object.entries(this.subAccountsByAddress).filter(([_, account]) => {
|
|
1213
|
-
return entries.has(account.index);
|
|
1214
|
-
}).map(([address, { pair, index }]) => ({ pair, index, address }));
|
|
1215
|
-
}
|
|
1216
|
-
async watchBlocks(shouldLog = false) {
|
|
1217
|
-
const accountMiners = await AccountMiners.loadAt(this, { shouldLog });
|
|
1218
|
-
await accountMiners.watch();
|
|
1219
|
-
return accountMiners;
|
|
1220
|
-
}
|
|
1221
|
-
};
|
|
1222
|
-
function getDefaultSubaccountRange() {
|
|
1223
|
-
try {
|
|
1224
|
-
return parseSubaccountRange(process2.env.SUBACCOUNT_RANGE ?? "0-9");
|
|
1225
|
-
} catch {
|
|
1226
|
-
console.error(
|
|
1227
|
-
"Failed to parse SUBACCOUNT_RANGE environment variable. Defaulting to 0-9. Please check the format of the SUBACCOUNT_RANGE variable."
|
|
1228
|
-
);
|
|
1229
|
-
return Array.from({ length: 10 }, (_, i) => i);
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
function parseSubaccountRange(range) {
|
|
1233
|
-
if (!range) {
|
|
1234
|
-
return void 0;
|
|
1235
|
-
}
|
|
1236
|
-
const indices = [];
|
|
1237
|
-
for (const entry of range.split(",")) {
|
|
1238
|
-
if (entry.includes("-")) {
|
|
1239
|
-
const [start, end] = entry.split("-").map((x) => parseInt(x, 10));
|
|
1240
|
-
for (let i = start; i <= end; i++) {
|
|
1241
|
-
indices.push(i);
|
|
1242
|
-
}
|
|
1243
|
-
continue;
|
|
1244
|
-
}
|
|
1245
|
-
const record = parseInt(entry.trim(), 10);
|
|
1246
|
-
if (Number.isNaN(record) || !Number.isInteger(record)) {
|
|
1247
|
-
throw new Error(`Invalid range entry: ${entry}`);
|
|
1248
|
-
}
|
|
1249
|
-
if (Number.isInteger(record)) {
|
|
1250
|
-
indices.push(record);
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
return indices;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
// src/MiningBids.ts
|
|
1257
|
-
import { printTable } from "console-table-printer";
|
|
1258
|
-
var MiningBids = class {
|
|
1259
|
-
constructor(client, shouldLog = true) {
|
|
1260
|
-
this.client = client;
|
|
1261
|
-
this.shouldLog = shouldLog;
|
|
1262
|
-
}
|
|
1263
|
-
nextCohort = [];
|
|
1264
|
-
async maxCohortSize() {
|
|
1265
|
-
const client = await this.client;
|
|
1266
|
-
return client.consts.miningSlot.maxCohortSize.toNumber();
|
|
1267
|
-
}
|
|
1268
|
-
async onCohortChange(options) {
|
|
1269
|
-
const { onBiddingStart, onBiddingEnd } = options;
|
|
1270
|
-
const client = await this.client;
|
|
1271
|
-
let openCohortId = 0;
|
|
1272
|
-
const unsubscribe = await client.queryMulti(
|
|
1273
|
-
[
|
|
1274
|
-
client.query.miningSlot.isNextSlotBiddingOpen,
|
|
1275
|
-
client.query.miningSlot.nextCohortId
|
|
1276
|
-
],
|
|
1277
|
-
async ([isBiddingOpen, rawNextCohortId]) => {
|
|
1278
|
-
const nextCohortId = rawNextCohortId.toNumber();
|
|
1279
|
-
if (isBiddingOpen.isTrue) {
|
|
1280
|
-
if (openCohortId !== 0) {
|
|
1281
|
-
await onBiddingEnd?.(openCohortId);
|
|
1282
|
-
}
|
|
1283
|
-
openCohortId = nextCohortId;
|
|
1284
|
-
await onBiddingStart?.(nextCohortId);
|
|
1285
|
-
} else {
|
|
1286
|
-
await onBiddingEnd?.(nextCohortId);
|
|
1287
|
-
openCohortId = 0;
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
);
|
|
1291
|
-
return { unsubscribe };
|
|
1292
|
-
}
|
|
1293
|
-
async watch(accountNames, blockHash, printFn) {
|
|
1294
|
-
const client = await this.client;
|
|
1295
|
-
const api = blockHash ? await client.at(blockHash) : client;
|
|
1296
|
-
const unsubscribe = await api.query.miningSlot.nextSlotCohort(
|
|
1297
|
-
async (next) => {
|
|
1298
|
-
this.nextCohort = await Promise.all(
|
|
1299
|
-
next.map((x) => this.toBid(accountNames, x))
|
|
1300
|
-
);
|
|
1301
|
-
if (!this.shouldLog) return;
|
|
1302
|
-
console.clear();
|
|
1303
|
-
const block = await client.query.system.number();
|
|
1304
|
-
if (!printFn) {
|
|
1305
|
-
console.log("At block", block.toNumber());
|
|
1306
|
-
this.print();
|
|
1307
|
-
} else {
|
|
1308
|
-
printFn(block.toNumber());
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
);
|
|
1312
|
-
return { unsubscribe };
|
|
1313
|
-
}
|
|
1314
|
-
async loadAt(accountNames, blockHash) {
|
|
1315
|
-
const client = await this.client;
|
|
1316
|
-
const api = blockHash ? await client.at(blockHash) : client;
|
|
1317
|
-
const nextCohort = await api.query.miningSlot.nextSlotCohort();
|
|
1318
|
-
this.nextCohort = await Promise.all(
|
|
1319
|
-
nextCohort.map((x) => this.toBid(accountNames, x))
|
|
1320
|
-
);
|
|
1321
|
-
}
|
|
1322
|
-
async toBid(accountNames, bid) {
|
|
1323
|
-
return {
|
|
1324
|
-
accountId: bid.accountId.toString(),
|
|
1325
|
-
isOurs: accountNames.get(bid.accountId.toString()) ?? "n",
|
|
1326
|
-
bidAmount: bid.bid.toBigInt()
|
|
1327
|
-
};
|
|
1328
|
-
}
|
|
1329
|
-
print() {
|
|
1330
|
-
const bids = this.nextCohort.map((bid) => {
|
|
1331
|
-
return {
|
|
1332
|
-
account: bid.accountId,
|
|
1333
|
-
isOurs: bid.isOurs,
|
|
1334
|
-
bidAmount: formatArgons(bid.bidAmount)
|
|
1335
|
-
};
|
|
1336
|
-
});
|
|
1337
|
-
if (bids.length) {
|
|
1338
|
-
console.log("\n\nMining Bids:");
|
|
1339
|
-
printTable(bids);
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
};
|
|
1343
|
-
|
|
1344
|
-
// src/Vault.ts
|
|
1345
|
-
import BigNumber3, * as BN2 from "bignumber.js";
|
|
1346
|
-
var { ROUND_FLOOR: ROUND_FLOOR2 } = BN2;
|
|
1347
|
-
var Vault = class {
|
|
1348
|
-
securitization;
|
|
1349
|
-
securitizationRatio;
|
|
1350
|
-
bitcoinLocked;
|
|
1351
|
-
bitcoinPending;
|
|
1352
|
-
terms;
|
|
1353
|
-
operatorAccountId;
|
|
1354
|
-
isClosed;
|
|
1355
|
-
vaultId;
|
|
1356
|
-
pendingTerms;
|
|
1357
|
-
pendingTermsChangeTick;
|
|
1358
|
-
openedDate;
|
|
1359
|
-
constructor(id, vault, tickDuration) {
|
|
1360
|
-
this.securitization = vault.securitization.toBigInt();
|
|
1361
|
-
this.securitizationRatio = convertFixedU128ToBigNumber(
|
|
1362
|
-
vault.securitizationRatio.toBigInt()
|
|
1363
|
-
);
|
|
1364
|
-
this.bitcoinLocked = vault.bitcoinLocked.toBigInt();
|
|
1365
|
-
this.bitcoinPending = vault.bitcoinPending.toBigInt();
|
|
1366
|
-
this.terms = {
|
|
1367
|
-
bitcoinAnnualPercentRate: convertFixedU128ToBigNumber(
|
|
1368
|
-
vault.terms.bitcoinAnnualPercentRate.toBigInt()
|
|
1369
|
-
),
|
|
1370
|
-
bitcoinBaseFee: vault.terms.bitcoinBaseFee.toBigInt(),
|
|
1371
|
-
liquidityPoolProfitSharing: convertPermillToBigNumber(
|
|
1372
|
-
vault.terms.liquidityPoolProfitSharing.toBigInt()
|
|
1373
|
-
)
|
|
1374
|
-
};
|
|
1375
|
-
this.operatorAccountId = vault.operatorAccountId.toString();
|
|
1376
|
-
this.isClosed = vault.isClosed.valueOf();
|
|
1377
|
-
this.vaultId = id;
|
|
1378
|
-
if (vault.pendingTerms.isSome) {
|
|
1379
|
-
const [tickApply, terms] = vault.pendingTerms.value;
|
|
1380
|
-
this.pendingTermsChangeTick = tickApply.toNumber();
|
|
1381
|
-
this.pendingTerms = {
|
|
1382
|
-
bitcoinAnnualPercentRate: convertFixedU128ToBigNumber(
|
|
1383
|
-
terms.bitcoinAnnualPercentRate.toBigInt()
|
|
1384
|
-
),
|
|
1385
|
-
bitcoinBaseFee: terms.bitcoinBaseFee.toBigInt(),
|
|
1386
|
-
liquidityPoolProfitSharing: convertPermillToBigNumber(
|
|
1387
|
-
vault.terms.liquidityPoolProfitSharing.toBigInt()
|
|
1388
|
-
)
|
|
1389
|
-
};
|
|
1390
|
-
}
|
|
1391
|
-
this.openedDate = vault.openedTick ? new Date(vault.openedTick.toNumber() * tickDuration) : /* @__PURE__ */ new Date();
|
|
1392
|
-
}
|
|
1393
|
-
availableBitcoinSpace() {
|
|
1394
|
-
const recoverySecuritization = this.recoverySecuritization();
|
|
1395
|
-
return this.securitization - recoverySecuritization - this.bitcoinLocked;
|
|
1396
|
-
}
|
|
1397
|
-
recoverySecuritization() {
|
|
1398
|
-
const reserved = new BigNumber3(1).div(this.securitizationRatio);
|
|
1399
|
-
return this.securitization - BigInt(
|
|
1400
|
-
reserved.multipliedBy(this.securitization.toString()).toFixed(0, ROUND_FLOOR2)
|
|
1401
|
-
);
|
|
1402
|
-
}
|
|
1403
|
-
minimumSecuritization() {
|
|
1404
|
-
return BigInt(
|
|
1405
|
-
this.securitizationRatio.multipliedBy(this.bitcoinLocked.toString()).decimalPlaces(0, BigNumber3.ROUND_CEIL).toString()
|
|
1406
|
-
);
|
|
1407
|
-
}
|
|
1408
|
-
activatedSecuritization() {
|
|
1409
|
-
const activated = this.bitcoinLocked - this.bitcoinPending;
|
|
1410
|
-
let maxRatio = this.securitizationRatio;
|
|
1411
|
-
if (this.securitizationRatio.toNumber() > 2) {
|
|
1412
|
-
maxRatio = BigNumber3(2);
|
|
1413
|
-
}
|
|
1414
|
-
return BigInt(
|
|
1415
|
-
maxRatio.multipliedBy(activated.toString()).toFixed(0, ROUND_FLOOR2)
|
|
1416
|
-
);
|
|
1417
|
-
}
|
|
1418
|
-
/**
|
|
1419
|
-
* Returns the amount of Argons available to match per liquidity pool
|
|
1420
|
-
*/
|
|
1421
|
-
activatedSecuritizationPerSlot() {
|
|
1422
|
-
const activated = this.activatedSecuritization();
|
|
1423
|
-
return activated / 10n;
|
|
1424
|
-
}
|
|
1425
|
-
calculateBitcoinFee(amount) {
|
|
1426
|
-
const fee = this.terms.bitcoinAnnualPercentRate.multipliedBy(Number(amount)).integerValue(BigNumber3.ROUND_CEIL);
|
|
1427
|
-
return BigInt(fee.toString()) + this.terms.bitcoinBaseFee;
|
|
1428
|
-
}
|
|
1429
|
-
};
|
|
1430
|
-
|
|
1431
|
-
// src/VaultMonitor.ts
|
|
1432
|
-
import { printTable as printTable2 } from "console-table-printer";
|
|
1433
|
-
import { createNanoEvents as createNanoEvents3 } from "nanoevents";
|
|
1434
|
-
var VaultMonitor = class {
|
|
1435
|
-
constructor(accountset, alerts = {}, options = {}) {
|
|
1436
|
-
this.accountset = accountset;
|
|
1437
|
-
this.alerts = alerts;
|
|
1438
|
-
this.options = options;
|
|
1439
|
-
this.mainchain = accountset.client;
|
|
1440
|
-
if (options.vaultOnlyWatchMode !== void 0) {
|
|
1441
|
-
this.vaultOnlyWatchMode = options.vaultOnlyWatchMode;
|
|
1442
|
-
}
|
|
1443
|
-
if (options.shouldLog !== void 0) {
|
|
1444
|
-
this.shouldLog = options.shouldLog;
|
|
1445
|
-
}
|
|
1446
|
-
this.miningBids = new MiningBids(this.mainchain, this.shouldLog);
|
|
1447
|
-
this.blockWatch = new BlockWatch(this.mainchain, {
|
|
1448
|
-
shouldLog: this.shouldLog
|
|
1449
|
-
});
|
|
1450
|
-
this.blockWatch.events.on(
|
|
1451
|
-
"vaults-updated",
|
|
1452
|
-
(header, vaultIds) => this.onVaultsUpdated(header.hash, vaultIds)
|
|
1453
|
-
);
|
|
1454
|
-
this.blockWatch.events.on("mining-bid", async (header, _bid) => {
|
|
1455
|
-
await this.miningBids.loadAt(this.accountset.namedAccounts, header.hash);
|
|
1456
|
-
this.printBids(header.hash);
|
|
1457
|
-
});
|
|
1458
|
-
this.blockWatch.events.on("mining-bid-ousted", async (header) => {
|
|
1459
|
-
await this.miningBids.loadAt(this.accountset.namedAccounts, header.hash);
|
|
1460
|
-
this.printBids(header.hash);
|
|
1461
|
-
});
|
|
1462
|
-
}
|
|
1463
|
-
events = createNanoEvents3();
|
|
1464
|
-
vaultsById = {};
|
|
1465
|
-
blockWatch;
|
|
1466
|
-
mainchain;
|
|
1467
|
-
activatedCapitalByVault = {};
|
|
1468
|
-
lastPrintedBids;
|
|
1469
|
-
miningBids;
|
|
1470
|
-
tickDuration = 0;
|
|
1471
|
-
vaultOnlyWatchMode = false;
|
|
1472
|
-
shouldLog = true;
|
|
1473
|
-
stop() {
|
|
1474
|
-
this.blockWatch.stop();
|
|
1475
|
-
}
|
|
1476
|
-
async monitor(justPrint = false) {
|
|
1477
|
-
const client = await this.mainchain;
|
|
1478
|
-
this.tickDuration = (await client.query.ticks.genesisTicker()).tickDurationMillis.toNumber();
|
|
1479
|
-
const blockHeader = await client.rpc.chain.getHeader();
|
|
1480
|
-
const blockHash = blockHeader.hash.toU8a();
|
|
1481
|
-
console.log(
|
|
1482
|
-
`${justPrint ? "Run" : "Started"} at block ${blockHeader.number} - ${blockHeader.hash.toHuman()}`
|
|
1483
|
-
);
|
|
1484
|
-
await this.miningBids.loadAt(this.accountset.namedAccounts, blockHash);
|
|
1485
|
-
const vaults = await client.query.vaults.vaultsById.entries();
|
|
1486
|
-
for (const [storageKey, rawVault] of vaults) {
|
|
1487
|
-
const vaultId = storageKey.args[0].toNumber();
|
|
1488
|
-
this.updateVault(vaultId, rawVault);
|
|
1489
|
-
}
|
|
1490
|
-
await client.query.liquidityPools.nextLiquidityPoolCapital((x) => {
|
|
1491
|
-
this.activatedCapitalByVault = {};
|
|
1492
|
-
for (const entry of x) {
|
|
1493
|
-
const vaultId = entry.vaultId.toNumber();
|
|
1494
|
-
this.activatedCapitalByVault[vaultId] = entry.activatedCapital.toBigInt();
|
|
1495
|
-
}
|
|
1496
|
-
for (const [vaultId, vault] of Object.entries(this.vaultsById)) {
|
|
1497
|
-
const id = Number(vaultId);
|
|
1498
|
-
this.activatedCapitalByVault[id] ??= 0n;
|
|
1499
|
-
this.checkMiningBondAlerts(id, vault);
|
|
1500
|
-
}
|
|
1501
|
-
});
|
|
1502
|
-
this.printVaults();
|
|
1503
|
-
if (!this.vaultOnlyWatchMode && this.shouldLog) {
|
|
1504
|
-
this.miningBids.print();
|
|
1505
|
-
}
|
|
1506
|
-
if (!justPrint) await this.blockWatch.start();
|
|
1507
|
-
}
|
|
1508
|
-
printVaults() {
|
|
1509
|
-
if (!this.shouldLog) return;
|
|
1510
|
-
const vaults = [];
|
|
1511
|
-
for (const [vaultId, vault] of Object.entries(this.vaultsById)) {
|
|
1512
|
-
vaults.push({
|
|
1513
|
-
id: vaultId,
|
|
1514
|
-
btcSpace: `${formatArgons(vault.availableBitcoinSpace())} (${formatArgons(vault.bitcoinPending)} pending)`,
|
|
1515
|
-
btcDeal: `${formatArgons(vault.terms.bitcoinBaseFee)} + ${formatPercent(vault.terms.bitcoinAnnualPercentRate)}`,
|
|
1516
|
-
securitization: `${formatArgons(vault.securitization)} at ${vault.securitizationRatio.toFormat(1)}x`,
|
|
1517
|
-
securActivated: `${formatArgons(vault.activatedSecuritizationPerSlot())}/slot`,
|
|
1518
|
-
liquidPoolDeal: `${formatPercent(vault.terms.liquidityPoolProfitSharing)} sharing`,
|
|
1519
|
-
operator: `${this.accountset.namedAccounts.has(vault.operatorAccountId) ? ` (${this.accountset.namedAccounts.get(vault.operatorAccountId)})` : vault.operatorAccountId}`,
|
|
1520
|
-
state: vault.isClosed ? "closed" : vault.openedDate < /* @__PURE__ */ new Date() ? "open" : "pending"
|
|
1521
|
-
});
|
|
1522
|
-
}
|
|
1523
|
-
if (vaults.length) {
|
|
1524
|
-
if (this.vaultOnlyWatchMode) {
|
|
1525
|
-
console.clear();
|
|
1526
|
-
}
|
|
1527
|
-
console.log("\n\nVaults:");
|
|
1528
|
-
printTable2(vaults);
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
async recheckAfterActive(vaultId) {
|
|
1532
|
-
const activationDate = this.vaultsById[vaultId].openedDate;
|
|
1533
|
-
if (this.shouldLog) {
|
|
1534
|
-
console.log(`Waiting for vault ${vaultId} to activate ${activationDate}`);
|
|
1535
|
-
}
|
|
1536
|
-
await new Promise(
|
|
1537
|
-
(resolve) => setTimeout(resolve, activationDate.getTime() - Date.now())
|
|
1538
|
-
);
|
|
1539
|
-
const client = await this.mainchain;
|
|
1540
|
-
let isReady = false;
|
|
1541
|
-
while (!isReady) {
|
|
1542
|
-
const rawVault = await client.query.vaults.vaultsById(vaultId);
|
|
1543
|
-
if (!rawVault.isSome) return;
|
|
1544
|
-
const vault = new Vault(vaultId, rawVault.value, this.tickDuration);
|
|
1545
|
-
this.vaultsById[vaultId] = vault;
|
|
1546
|
-
if (vault.isClosed) return;
|
|
1547
|
-
if (vault.openedDate < /* @__PURE__ */ new Date()) {
|
|
1548
|
-
isReady = true;
|
|
1549
|
-
break;
|
|
1550
|
-
}
|
|
1551
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1552
|
-
}
|
|
1553
|
-
this.checkAlerts(vaultId, this.vaultsById[vaultId]);
|
|
1554
|
-
}
|
|
1555
|
-
async onVaultsUpdated(blockHash, vaultIds) {
|
|
1556
|
-
await this.reloadVaultsAt([...vaultIds], blockHash).catch((err) => {
|
|
1557
|
-
console.error(
|
|
1558
|
-
`Failed to reload vault ${[...vaultIds]} at block ${blockHash}:`,
|
|
1559
|
-
err
|
|
1560
|
-
);
|
|
1561
|
-
});
|
|
1562
|
-
this.printVaults();
|
|
1563
|
-
}
|
|
1564
|
-
async reloadVaultsAt(vaultIds, blockHash) {
|
|
1565
|
-
const client = await this.mainchain;
|
|
1566
|
-
const api = await client.at(blockHash);
|
|
1567
|
-
const vaults = await api.query.vaults.vaultsById.multi(vaultIds);
|
|
1568
|
-
for (let i = 0; i < vaultIds.length; i += 1) {
|
|
1569
|
-
this.updateVault(vaultIds[i], vaults[i]);
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
updateVault(vaultId, rawVault) {
|
|
1573
|
-
if (rawVault.isNone) return;
|
|
1574
|
-
const vault = new Vault(vaultId, rawVault.value, this.tickDuration);
|
|
1575
|
-
this.vaultsById[vaultId] = vault;
|
|
1576
|
-
if (vault.openedDate > /* @__PURE__ */ new Date()) {
|
|
1577
|
-
void this.recheckAfterActive(vaultId);
|
|
1578
|
-
} else {
|
|
1579
|
-
this.checkAlerts(vaultId, vault);
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
checkAlerts(vaultId, vault) {
|
|
1583
|
-
if (this.alerts.bitcoinSpaceAvailable !== void 0) {
|
|
1584
|
-
const availableBitcoinSpace = vault.availableBitcoinSpace();
|
|
1585
|
-
if (availableBitcoinSpace >= this.alerts.bitcoinSpaceAvailable) {
|
|
1586
|
-
console.warn(
|
|
1587
|
-
`Vault ${vaultId} has available bitcoins above ${formatArgons(this.alerts.bitcoinSpaceAvailable)}`
|
|
1588
|
-
);
|
|
1589
|
-
this.events.emit("bitcoin-space-above", vaultId, availableBitcoinSpace);
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
checkMiningBondAlerts(vaultId, vault) {
|
|
1594
|
-
if (this.alerts.liquidityPoolSpaceAvailable === void 0) return;
|
|
1595
|
-
const activatedSecuritization = vault.activatedSecuritizationPerSlot();
|
|
1596
|
-
const capitalization = this.activatedCapitalByVault[vaultId] ?? 0n;
|
|
1597
|
-
const available = activatedSecuritization - capitalization;
|
|
1598
|
-
if (available >= this.alerts.liquidityPoolSpaceAvailable) {
|
|
1599
|
-
this.events.emit("liquidity-pool-space-above", vaultId, available);
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
printBids(blockHash) {
|
|
1603
|
-
if (!this.shouldLog) return;
|
|
1604
|
-
if (this.lastPrintedBids === blockHash) return;
|
|
1605
|
-
this.miningBids.print();
|
|
1606
|
-
this.lastPrintedBids = blockHash;
|
|
1607
|
-
}
|
|
1608
|
-
};
|
|
1609
|
-
|
|
1610
|
-
// src/CohortBidderHistory.ts
|
|
1611
|
-
var CohortBidderHistory = class _CohortBidderHistory {
|
|
1612
|
-
constructor(cohortId, subaccounts) {
|
|
1613
|
-
this.cohortId = cohortId;
|
|
1614
|
-
this.subaccounts = subaccounts;
|
|
1615
|
-
this.maxSeatsInPlay = this.subaccounts.length;
|
|
1616
|
-
this.subaccounts.forEach((x) => {
|
|
1617
|
-
this.myAddresses.add(x.address);
|
|
1618
|
-
});
|
|
1619
|
-
}
|
|
1620
|
-
bidHistory = [];
|
|
1621
|
-
stats = {
|
|
1622
|
-
// number of seats won
|
|
1623
|
-
seatsWon: 0,
|
|
1624
|
-
// sum of argons bid in successful bids
|
|
1625
|
-
totalArgonsBid: 0n,
|
|
1626
|
-
// total number of bids placed (includes 1 per seat)
|
|
1627
|
-
bidsAttempted: 0,
|
|
1628
|
-
// fees including the tip
|
|
1629
|
-
fees: 0n,
|
|
1630
|
-
// Max bid per seat
|
|
1631
|
-
maxBidPerSeat: 0n,
|
|
1632
|
-
// The cost in argonots of each seat
|
|
1633
|
-
argonotsPerSeat: 0n,
|
|
1634
|
-
// The argonot price in USD for cost basis
|
|
1635
|
-
argonotUsdPrice: 0,
|
|
1636
|
-
// The cohort expected argons per block
|
|
1637
|
-
cohortArgonsPerBlock: 0n,
|
|
1638
|
-
// The last block that bids are synced to
|
|
1639
|
-
lastBlockNumber: 0
|
|
1640
|
-
};
|
|
1641
|
-
lastBids = [];
|
|
1642
|
-
myAddresses = /* @__PURE__ */ new Set();
|
|
1643
|
-
maxSeatsInPlay = 0;
|
|
1644
|
-
async init(client) {
|
|
1645
|
-
if (!this.stats.argonotsPerSeat) {
|
|
1646
|
-
const startingStats = await _CohortBidderHistory.getStartingData(client);
|
|
1647
|
-
Object.assign(this.stats, startingStats);
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
maybeReducingSeats(maxSeats, reason, historyEntry) {
|
|
1651
|
-
if (this.maxSeatsInPlay > maxSeats) {
|
|
1652
|
-
historyEntry.maxSeatsReductionReason = reason;
|
|
1653
|
-
}
|
|
1654
|
-
this.maxSeatsInPlay = maxSeats;
|
|
1655
|
-
historyEntry.maxSeatsInPlay = maxSeats;
|
|
1656
|
-
}
|
|
1657
|
-
trackChange(next, blockNumber, tick, isLastEntry = false) {
|
|
1658
|
-
let winningBids = 0;
|
|
1659
|
-
let totalArgonsBid = 0n;
|
|
1660
|
-
const nextEntrants = [];
|
|
1661
|
-
for (const x of next) {
|
|
1662
|
-
const bid = x.bid.toBigInt();
|
|
1663
|
-
const address = x.accountId.toHuman();
|
|
1664
|
-
nextEntrants.push({ address, bid });
|
|
1665
|
-
if (this.myAddresses.has(address)) {
|
|
1666
|
-
winningBids++;
|
|
1667
|
-
totalArgonsBid += bid;
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
this.stats.seatsWon = winningBids;
|
|
1671
|
-
this.stats.totalArgonsBid = totalArgonsBid;
|
|
1672
|
-
this.stats.lastBlockNumber = Math.max(
|
|
1673
|
-
blockNumber,
|
|
1674
|
-
this.stats.lastBlockNumber
|
|
1675
|
-
);
|
|
1676
|
-
const historyEntry = {
|
|
1677
|
-
cohortId: this.cohortId,
|
|
1678
|
-
blockNumber,
|
|
1679
|
-
tick,
|
|
1680
|
-
bidChanges: [],
|
|
1681
|
-
winningSeats: winningBids,
|
|
1682
|
-
maxSeatsInPlay: this.maxSeatsInPlay
|
|
1683
|
-
};
|
|
1684
|
-
const hasDiffs = JsonExt.stringify(nextEntrants) !== JsonExt.stringify(this.lastBids);
|
|
1685
|
-
if (!isLastEntry || hasDiffs) {
|
|
1686
|
-
this.bidHistory.unshift(historyEntry);
|
|
1687
|
-
}
|
|
1688
|
-
if (hasDiffs) {
|
|
1689
|
-
nextEntrants.forEach(({ address, bid }, i) => {
|
|
1690
|
-
const prevBidIndex = this.lastBids.findIndex(
|
|
1691
|
-
(y) => y.address === address
|
|
1692
|
-
);
|
|
1693
|
-
const entry = {
|
|
1694
|
-
address,
|
|
1695
|
-
bidAmount: bid,
|
|
1696
|
-
bidPosition: i,
|
|
1697
|
-
prevPosition: prevBidIndex === -1 ? null : prevBidIndex
|
|
1698
|
-
};
|
|
1699
|
-
if (prevBidIndex !== -1) {
|
|
1700
|
-
const prevBidAmount = this.lastBids[prevBidIndex].bid;
|
|
1701
|
-
if (prevBidAmount !== bid) {
|
|
1702
|
-
entry.prevBidAmount = prevBidAmount;
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
historyEntry.bidChanges.push(entry);
|
|
1706
|
-
});
|
|
1707
|
-
this.lastBids.forEach(({ address, bid }, i) => {
|
|
1708
|
-
const nextBid = nextEntrants.some((y) => y.address === address);
|
|
1709
|
-
if (!nextBid) {
|
|
1710
|
-
historyEntry.bidChanges.push({
|
|
1711
|
-
address,
|
|
1712
|
-
bidAmount: bid,
|
|
1713
|
-
bidPosition: null,
|
|
1714
|
-
prevPosition: i
|
|
1715
|
-
});
|
|
1716
|
-
}
|
|
1717
|
-
});
|
|
1718
|
-
this.lastBids = nextEntrants;
|
|
1719
|
-
}
|
|
1720
|
-
return historyEntry;
|
|
1721
|
-
}
|
|
1722
|
-
onBidResult(historyEntry, param) {
|
|
1723
|
-
const {
|
|
1724
|
-
txFeePlusTip,
|
|
1725
|
-
bidPerSeat,
|
|
1726
|
-
bidsAttempted,
|
|
1727
|
-
successfulBids,
|
|
1728
|
-
blockNumber,
|
|
1729
|
-
bidError
|
|
1730
|
-
} = param;
|
|
1731
|
-
this.stats.fees += txFeePlusTip;
|
|
1732
|
-
this.stats.bidsAttempted += bidsAttempted;
|
|
1733
|
-
if (bidPerSeat > this.stats.maxBidPerSeat) {
|
|
1734
|
-
this.stats.maxBidPerSeat = bidPerSeat;
|
|
1735
|
-
}
|
|
1736
|
-
if (blockNumber !== void 0) {
|
|
1737
|
-
this.stats.lastBlockNumber = Math.max(
|
|
1738
|
-
blockNumber,
|
|
1739
|
-
this.stats.lastBlockNumber
|
|
1740
|
-
);
|
|
1741
|
-
}
|
|
1742
|
-
historyEntry.myBidsPlaced.failureReason = bidError;
|
|
1743
|
-
historyEntry.myBidsPlaced.successfulBids = successfulBids;
|
|
1744
|
-
historyEntry.myBidsPlaced.txFeePlusTip = txFeePlusTip;
|
|
1745
|
-
}
|
|
1746
|
-
static async getStartingData(api) {
|
|
1747
|
-
const argonotPrice = await api.query.priceIndex.current();
|
|
1748
|
-
let argonotUsdPrice = 0;
|
|
1749
|
-
if (argonotPrice.isSome) {
|
|
1750
|
-
argonotUsdPrice = convertFixedU128ToBigNumber(
|
|
1751
|
-
argonotPrice.unwrap().argonotUsdPrice.toBigInt()
|
|
1752
|
-
).toNumber();
|
|
1753
|
-
}
|
|
1754
|
-
const argonotsPerSeat = await api.query.miningSlot.argonotsPerMiningSeat().then((x) => x.toBigInt());
|
|
1755
|
-
const cohortArgonsPerBlock = await api.query.blockRewards.argonsPerBlock().then((x) => x.toBigInt());
|
|
1756
|
-
return { argonotsPerSeat, argonotUsdPrice, cohortArgonsPerBlock };
|
|
1757
|
-
}
|
|
1758
|
-
};
|
|
1759
|
-
|
|
1760
|
-
// src/CohortBidder.ts
|
|
1761
|
-
var CohortBidder = class {
|
|
1762
|
-
constructor(accountset, cohortId, subaccounts, options) {
|
|
1763
|
-
this.accountset = accountset;
|
|
1764
|
-
this.cohortId = cohortId;
|
|
1765
|
-
this.subaccounts = subaccounts;
|
|
1766
|
-
this.options = options;
|
|
1767
|
-
this.history = new CohortBidderHistory(cohortId, subaccounts);
|
|
1768
|
-
this.subaccounts.forEach((x) => {
|
|
1769
|
-
this.myAddresses.add(x.address);
|
|
1770
|
-
});
|
|
1771
|
-
}
|
|
1772
|
-
get client() {
|
|
1773
|
-
return this.accountset.client;
|
|
1774
|
-
}
|
|
1775
|
-
get stats() {
|
|
1776
|
-
return this.history.stats;
|
|
1777
|
-
}
|
|
1778
|
-
get bidHistory() {
|
|
1779
|
-
return this.history.bidHistory;
|
|
1780
|
-
}
|
|
1781
|
-
unsubscribe;
|
|
1782
|
-
pendingRequest;
|
|
1783
|
-
retryTimeout;
|
|
1784
|
-
isStopped = false;
|
|
1785
|
-
needsRebid = false;
|
|
1786
|
-
lastBidTime = 0;
|
|
1787
|
-
history;
|
|
1788
|
-
millisPerTick;
|
|
1789
|
-
myAddresses = /* @__PURE__ */ new Set();
|
|
1790
|
-
async stop() {
|
|
1791
|
-
if (this.isStopped) return this.stats;
|
|
1792
|
-
this.isStopped = true;
|
|
1793
|
-
console.log("Stopping bidder for cohort", this.cohortId);
|
|
1794
|
-
clearTimeout(this.retryTimeout);
|
|
1795
|
-
if (this.unsubscribe) {
|
|
1796
|
-
this.unsubscribe();
|
|
1797
|
-
}
|
|
1798
|
-
const client = await this.client;
|
|
1799
|
-
const [nextCohortId, isBiddingOpen] = await client.queryMulti([
|
|
1800
|
-
client.query.miningSlot.nextCohortId,
|
|
1801
|
-
client.query.miningSlot.isNextSlotBiddingOpen
|
|
1802
|
-
]);
|
|
1803
|
-
if (nextCohortId.toNumber() === this.cohortId && isBiddingOpen.isTrue) {
|
|
1804
|
-
console.log("Bidding is still open, waiting for it to close");
|
|
1805
|
-
await new Promise(async (resolve) => {
|
|
1806
|
-
const unsub = await client.query.miningSlot.isNextSlotBiddingOpen(
|
|
1807
|
-
(isOpen) => {
|
|
1808
|
-
if (isOpen.isFalse) {
|
|
1809
|
-
unsub();
|
|
1810
|
-
resolve();
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
);
|
|
1814
|
-
});
|
|
1815
|
-
}
|
|
1816
|
-
void await this.pendingRequest;
|
|
1817
|
-
let header = await client.rpc.chain.getHeader();
|
|
1818
|
-
while (true) {
|
|
1819
|
-
const api2 = await client.at(header.hash);
|
|
1820
|
-
const cohortId = await api2.query.miningSlot.nextCohortId();
|
|
1821
|
-
if (cohortId.toNumber() === this.cohortId) {
|
|
1822
|
-
break;
|
|
1823
|
-
}
|
|
1824
|
-
header = await client.rpc.chain.getHeader(header.parentHash);
|
|
1825
|
-
}
|
|
1826
|
-
const api = await client.at(header.hash);
|
|
1827
|
-
const tick = await api.query.ticks.currentTick().then((x) => x.toNumber());
|
|
1828
|
-
const cohort = await api.query.miningSlot.nextSlotCohort();
|
|
1829
|
-
this.history.trackChange(cohort, header.number.toNumber(), tick, true);
|
|
1830
|
-
console.log("Bidder stopped", {
|
|
1831
|
-
cohortId: this.cohortId,
|
|
1832
|
-
blockNumber: header.number.toNumber(),
|
|
1833
|
-
tick,
|
|
1834
|
-
cohort: cohort.map((x) => ({
|
|
1835
|
-
address: x.accountId.toHuman(),
|
|
1836
|
-
bid: x.bid.toBigInt()
|
|
1837
|
-
}))
|
|
1838
|
-
});
|
|
1839
|
-
return this.stats;
|
|
1840
|
-
}
|
|
1841
|
-
async start() {
|
|
1842
|
-
console.log(`Starting cohort ${this.cohortId} bidder`, {
|
|
1843
|
-
maxBid: formatArgons(this.options.maxBid),
|
|
1844
|
-
minBid: formatArgons(this.options.minBid),
|
|
1845
|
-
bidIncrement: formatArgons(this.options.bidIncrement),
|
|
1846
|
-
maxBudget: formatArgons(this.options.maxBudget),
|
|
1847
|
-
bidDelay: this.options.bidDelay,
|
|
1848
|
-
subaccounts: this.subaccounts
|
|
1849
|
-
});
|
|
1850
|
-
const client = await this.client;
|
|
1851
|
-
await this.history.init(client);
|
|
1852
|
-
this.millisPerTick ??= await client.query.ticks.genesisTicker().then((x) => x.tickDurationMillis.toNumber());
|
|
1853
|
-
this.unsubscribe = await client.queryMulti(
|
|
1854
|
-
[
|
|
1855
|
-
client.query.miningSlot.nextSlotCohort,
|
|
1856
|
-
client.query.miningSlot.nextCohortId
|
|
1857
|
-
],
|
|
1858
|
-
async ([next, nextCohortId]) => {
|
|
1859
|
-
if (nextCohortId.toNumber() === this.cohortId) {
|
|
1860
|
-
await this.checkSeats(next);
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
);
|
|
1864
|
-
}
|
|
1865
|
-
async checkSeats(next) {
|
|
1866
|
-
if (this.isStopped) return;
|
|
1867
|
-
clearTimeout(this.retryTimeout);
|
|
1868
|
-
const client = await this.client;
|
|
1869
|
-
const bestBlock = await client.rpc.chain.getBlockHash();
|
|
1870
|
-
const api = await client.at(bestBlock);
|
|
1871
|
-
const blockNumber = await api.query.system.number().then((x) => x.toNumber());
|
|
1872
|
-
if (this.bidHistory[0]?.blockNumber >= blockNumber) {
|
|
1873
|
-
return;
|
|
1874
|
-
}
|
|
1875
|
-
const tick = await api.query.ticks.currentTick().then((x) => x.toNumber());
|
|
1876
|
-
const historyEntry = this.history.trackChange(next, blockNumber, tick);
|
|
1877
|
-
if (this.pendingRequest) return;
|
|
1878
|
-
const ticksSinceLastBid = Math.floor(
|
|
1879
|
-
(Date.now() - this.lastBidTime) / this.millisPerTick
|
|
1880
|
-
);
|
|
1881
|
-
if (ticksSinceLastBid < this.options.bidDelay) {
|
|
1882
|
-
this.retryTimeout = setTimeout(
|
|
1883
|
-
() => void this.checkCurrentSeats(),
|
|
1884
|
-
this.millisPerTick
|
|
1885
|
-
);
|
|
1886
|
-
return;
|
|
1887
|
-
}
|
|
1888
|
-
console.log(
|
|
1889
|
-
"Checking bids for cohort",
|
|
1890
|
-
this.cohortId,
|
|
1891
|
-
this.subaccounts.map((x) => x.index)
|
|
1892
|
-
);
|
|
1893
|
-
const winningBids = historyEntry.winningSeats;
|
|
1894
|
-
this.needsRebid = winningBids < this.subaccounts.length;
|
|
1895
|
-
if (!this.needsRebid) return;
|
|
1896
|
-
const winningAddresses = new Set(next.map((x) => x.accountId.toHuman()));
|
|
1897
|
-
let lowestBid = -this.options.bidIncrement;
|
|
1898
|
-
if (next.length) {
|
|
1899
|
-
for (let i = next.length - 1; i >= 0; i--) {
|
|
1900
|
-
if (!this.myAddresses.has(next[i].accountId.toHuman())) {
|
|
1901
|
-
lowestBid = next.at(i).bid.toBigInt();
|
|
1902
|
-
break;
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
}
|
|
1906
|
-
const MIN_INCREMENT = 10000n;
|
|
1907
|
-
let nextBid = lowestBid + this.options.bidIncrement;
|
|
1908
|
-
if (nextBid < this.options.minBid) {
|
|
1909
|
-
nextBid = this.options.minBid;
|
|
1910
|
-
}
|
|
1911
|
-
if (nextBid > this.options.maxBid) {
|
|
1912
|
-
nextBid = this.options.maxBid;
|
|
1913
|
-
}
|
|
1914
|
-
const fakeTx = await this.accountset.createMiningBidTx({
|
|
1915
|
-
subaccounts: this.subaccounts,
|
|
1916
|
-
bidAmount: nextBid,
|
|
1917
|
-
sendRewardsToSeed: true
|
|
1918
|
-
});
|
|
1919
|
-
let availableBalanceForBids = await api.query.system.account(this.accountset.txSubmitterPair.address).then((x) => x.data.free.toBigInt());
|
|
1920
|
-
for (const bid of next) {
|
|
1921
|
-
if (this.myAddresses.has(bid.accountId.toHuman())) {
|
|
1922
|
-
availableBalanceForBids += bid.bid.toBigInt();
|
|
1923
|
-
}
|
|
1924
|
-
}
|
|
1925
|
-
const tip = this.options.tipPerTransaction ?? 0n;
|
|
1926
|
-
const feeEstimate = await fakeTx.feeEstimate(tip);
|
|
1927
|
-
const feePlusTip = feeEstimate + tip;
|
|
1928
|
-
let budgetForSeats = this.options.maxBudget - feePlusTip;
|
|
1929
|
-
if (budgetForSeats > availableBalanceForBids) {
|
|
1930
|
-
budgetForSeats = availableBalanceForBids - feePlusTip;
|
|
1931
|
-
}
|
|
1932
|
-
if (nextBid < lowestBid) {
|
|
1933
|
-
console.log(
|
|
1934
|
-
`Can't bid ${formatArgons(nextBid)}. Current lowest bid is ${formatArgons(
|
|
1935
|
-
lowestBid
|
|
1936
|
-
)}.`
|
|
1937
|
-
);
|
|
1938
|
-
this.history.maybeReducingSeats(
|
|
1939
|
-
winningBids,
|
|
1940
|
-
"MaxBidTooLow" /* MaxBidTooLow */,
|
|
1941
|
-
historyEntry
|
|
1942
|
-
);
|
|
1943
|
-
return;
|
|
1944
|
-
}
|
|
1945
|
-
if (nextBid - lowestBid < MIN_INCREMENT) {
|
|
1946
|
-
console.log(
|
|
1947
|
-
`Can't make any more bids for ${this.cohortId} with given constraints.`,
|
|
1948
|
-
{
|
|
1949
|
-
lowestCurrentBid: formatArgons(lowestBid),
|
|
1950
|
-
nextAttemptedBid: formatArgons(nextBid),
|
|
1951
|
-
maxBid: formatArgons(this.options.maxBid)
|
|
1952
|
-
}
|
|
1953
|
-
);
|
|
1954
|
-
this.history.maybeReducingSeats(
|
|
1955
|
-
winningBids,
|
|
1956
|
-
"MaxBidTooLow" /* MaxBidTooLow */,
|
|
1957
|
-
historyEntry
|
|
1958
|
-
);
|
|
1959
|
-
return;
|
|
1960
|
-
}
|
|
1961
|
-
const seatsInBudget = nextBid === 0n ? this.subaccounts.length : Number(budgetForSeats / nextBid);
|
|
1962
|
-
let accountsToUse = [...this.subaccounts];
|
|
1963
|
-
if (accountsToUse.length > seatsInBudget) {
|
|
1964
|
-
const reason = availableBalanceForBids - feePlusTip < nextBid * BigInt(seatsInBudget) ? "InsufficientFunds" /* InsufficientFunds */ : "MaxBudgetTooLow" /* MaxBudgetTooLow */;
|
|
1965
|
-
this.history.maybeReducingSeats(seatsInBudget, reason, historyEntry);
|
|
1966
|
-
accountsToUse.sort((a, b) => {
|
|
1967
|
-
const isWinningA = winningAddresses.has(a.address);
|
|
1968
|
-
const isWinningB = winningAddresses.has(b.address);
|
|
1969
|
-
if (isWinningA && !isWinningB) return -1;
|
|
1970
|
-
if (!isWinningA && isWinningB) return 1;
|
|
1971
|
-
if (a.isRebid && !b.isRebid) return -1;
|
|
1972
|
-
if (!a.isRebid && b.isRebid) return 1;
|
|
1973
|
-
return a.index - b.index;
|
|
1974
|
-
});
|
|
1975
|
-
accountsToUse.length = seatsInBudget;
|
|
1976
|
-
}
|
|
1977
|
-
if (accountsToUse.length > winningBids) {
|
|
1978
|
-
historyEntry.myBidsPlaced = {
|
|
1979
|
-
bids: accountsToUse.length,
|
|
1980
|
-
bidPerSeat: nextBid,
|
|
1981
|
-
txFeePlusTip: feePlusTip,
|
|
1982
|
-
successfulBids: 0
|
|
1983
|
-
};
|
|
1984
|
-
this.pendingRequest = this.bid(nextBid, accountsToUse, historyEntry);
|
|
1985
|
-
} else if (historyEntry.bidChanges.length === 0) {
|
|
1986
|
-
this.history.bidHistory.shift();
|
|
1987
|
-
}
|
|
1988
|
-
this.needsRebid = false;
|
|
1989
|
-
}
|
|
1990
|
-
async bid(bidPerSeat, subaccounts, historyEntry) {
|
|
1991
|
-
const prevLastBidTime = this.lastBidTime;
|
|
1992
|
-
try {
|
|
1993
|
-
this.lastBidTime = Date.now();
|
|
1994
|
-
const submitter = await this.accountset.createMiningBidTx({
|
|
1995
|
-
subaccounts,
|
|
1996
|
-
bidAmount: bidPerSeat,
|
|
1997
|
-
sendRewardsToSeed: true
|
|
1998
|
-
});
|
|
1999
|
-
const tip = this.options.tipPerTransaction ?? 0n;
|
|
2000
|
-
const txResult = await submitter.submit({
|
|
2001
|
-
tip,
|
|
2002
|
-
useLatestNonce: true
|
|
2003
|
-
});
|
|
2004
|
-
const bidError = await txResult.inBlockPromise.then(() => void 0).catch((x) => x);
|
|
2005
|
-
let blockNumber;
|
|
2006
|
-
if (txResult.includedInBlock) {
|
|
2007
|
-
const client = await this.client;
|
|
2008
|
-
const api = await client.at(txResult.includedInBlock);
|
|
2009
|
-
blockNumber = await api.query.system.number().then((x) => x.toNumber());
|
|
2010
|
-
}
|
|
2011
|
-
const successfulBids = txResult.batchInterruptedIndex ?? subaccounts.length;
|
|
2012
|
-
this.history.onBidResult(historyEntry, {
|
|
2013
|
-
blockNumber,
|
|
2014
|
-
successfulBids,
|
|
2015
|
-
bidPerSeat,
|
|
2016
|
-
txFeePlusTip: txResult.finalFee ?? 0n,
|
|
2017
|
-
bidsAttempted: subaccounts.length,
|
|
2018
|
-
bidError
|
|
2019
|
-
});
|
|
2020
|
-
console.log("Done creating bids for cohort", {
|
|
2021
|
-
successfulBids,
|
|
2022
|
-
bidPerSeat,
|
|
2023
|
-
blockNumber
|
|
2024
|
-
});
|
|
2025
|
-
if (bidError) throw bidError;
|
|
2026
|
-
} catch (err) {
|
|
2027
|
-
this.lastBidTime = prevLastBidTime;
|
|
2028
|
-
console.error(`Error bidding for cohort ${this.cohortId}:`, err);
|
|
2029
|
-
clearTimeout(this.retryTimeout);
|
|
2030
|
-
this.retryTimeout = setTimeout(() => void this.checkCurrentSeats(), 1e3);
|
|
2031
|
-
} finally {
|
|
2032
|
-
this.pendingRequest = void 0;
|
|
2033
|
-
}
|
|
2034
|
-
if (this.needsRebid) {
|
|
2035
|
-
this.needsRebid = false;
|
|
2036
|
-
await this.checkCurrentSeats();
|
|
2037
|
-
}
|
|
2038
|
-
}
|
|
2039
|
-
async checkCurrentSeats() {
|
|
2040
|
-
const client = await this.client;
|
|
2041
|
-
const next = await client.query.miningSlot.nextSlotCohort();
|
|
2042
|
-
await this.checkSeats(next);
|
|
2043
|
-
}
|
|
2044
|
-
};
|
|
2045
|
-
|
|
2046
|
-
// src/BidPool.ts
|
|
2047
|
-
import { Table } from "console-table-printer";
|
|
2048
|
-
var EMPTY_TABLE = {
|
|
2049
|
-
headerBottom: { left: " ", mid: " ", other: "\u2500", right: " " },
|
|
2050
|
-
headerTop: { left: " ", mid: " ", other: " ", right: " " },
|
|
2051
|
-
rowSeparator: { left: " ", mid: " ", other: " ", right: " " },
|
|
2052
|
-
tableBottom: { left: " ", mid: " ", other: " ", right: " " },
|
|
2053
|
-
vertical: " "
|
|
2054
|
-
};
|
|
2055
|
-
var BidPool = class {
|
|
2056
|
-
constructor(client, keypair, accountRegistry = AccountRegistry.factory()) {
|
|
2057
|
-
this.client = client;
|
|
2058
|
-
this.keypair = keypair;
|
|
2059
|
-
this.accountRegistry = accountRegistry;
|
|
2060
|
-
this.blockWatch = new BlockWatch(client, { shouldLog: false });
|
|
2061
|
-
}
|
|
2062
|
-
bidPoolAmount = 0n;
|
|
2063
|
-
nextCohortId = 1;
|
|
2064
|
-
poolVaultCapitalByCohort = {};
|
|
2065
|
-
vaultSecuritization = [];
|
|
2066
|
-
printTimeout;
|
|
2067
|
-
blockWatch;
|
|
2068
|
-
vaultsById = {};
|
|
2069
|
-
tickDuration;
|
|
2070
|
-
lastDistributedCohortId;
|
|
2071
|
-
cohortSubscriptions = {};
|
|
2072
|
-
async onVaultsUpdated(blockHash, vaultIdSet) {
|
|
2073
|
-
const client = await this.client;
|
|
2074
|
-
this.tickDuration ??= (await client.query.ticks.genesisTicker()).tickDurationMillis.toNumber();
|
|
2075
|
-
const api = await client.at(blockHash);
|
|
2076
|
-
const vaultIds = [...vaultIdSet];
|
|
2077
|
-
const rawVaults = await api.query.vaults.vaultsById.multi(vaultIds);
|
|
2078
|
-
for (let i = 0; i < vaultIds.length; i += 1) {
|
|
2079
|
-
const rawVault = rawVaults[i];
|
|
2080
|
-
if (rawVault.isNone) continue;
|
|
2081
|
-
const vaultId = vaultIds[i];
|
|
2082
|
-
this.vaultsById[vaultId] = new Vault(
|
|
2083
|
-
vaultId,
|
|
2084
|
-
rawVault.unwrap(),
|
|
2085
|
-
this.tickDuration
|
|
2086
|
-
);
|
|
2087
|
-
}
|
|
2088
|
-
const vaults = Object.entries(this.vaultsById);
|
|
2089
|
-
const newSecuritization = [];
|
|
2090
|
-
for (const [vaultId, vault] of vaults) {
|
|
2091
|
-
const amount = vault.activatedSecuritizationPerSlot();
|
|
2092
|
-
newSecuritization.push({
|
|
2093
|
-
vaultId: Number(vaultId),
|
|
2094
|
-
bitcoinSpace: vault.availableBitcoinSpace(),
|
|
2095
|
-
activatedSecuritization: amount,
|
|
2096
|
-
vaultSharingPercent: vault.terms.liquidityPoolProfitSharing
|
|
2097
|
-
});
|
|
2098
|
-
}
|
|
2099
|
-
newSecuritization.sort((a, b) => {
|
|
2100
|
-
const diff2 = b.activatedSecuritization - a.activatedSecuritization;
|
|
2101
|
-
if (diff2 !== 0n) return Number(diff2);
|
|
2102
|
-
return a.vaultId - b.vaultId;
|
|
2103
|
-
});
|
|
2104
|
-
this.vaultSecuritization = newSecuritization;
|
|
2105
|
-
this.printDebounce();
|
|
2106
|
-
}
|
|
2107
|
-
async getBidPool() {
|
|
2108
|
-
const client = await this.client;
|
|
2109
|
-
const balanceBytes = await client.rpc.state.call(
|
|
2110
|
-
"MiningSlotApi_bid_pool",
|
|
2111
|
-
""
|
|
2112
|
-
);
|
|
2113
|
-
const balance = client.createType("U128", balanceBytes);
|
|
2114
|
-
return balance.toBigInt();
|
|
2115
|
-
}
|
|
2116
|
-
async loadAt(blockHash) {
|
|
2117
|
-
const client = await this.client;
|
|
2118
|
-
blockHash ??= (await client.rpc.chain.getHeader()).hash.toU8a();
|
|
2119
|
-
const api = await client.at(blockHash);
|
|
2120
|
-
const rawVaultIds = await api.query.vaults.vaultsById.keys();
|
|
2121
|
-
const vaultIds = rawVaultIds.map((x) => x.args[0].toNumber());
|
|
2122
|
-
this.bidPoolAmount = await this.getBidPool();
|
|
2123
|
-
this.nextCohortId = (await api.query.miningSlot.nextCohortId()).toNumber();
|
|
2124
|
-
const contributors = await api.query.liquidityPools.liquidityPoolsByCohort.entries();
|
|
2125
|
-
for (const [cohortId, funds] of contributors) {
|
|
2126
|
-
const cohortIdNumber = cohortId.args[0].toNumber();
|
|
2127
|
-
this.loadCohortData(cohortIdNumber, funds);
|
|
2128
|
-
}
|
|
2129
|
-
for (const entrant of await api.query.liquidityPools.openLiquidityPoolCapital()) {
|
|
2130
|
-
this.setVaultCohortData(this.nextCohortId, entrant.vaultId.toNumber(), {
|
|
2131
|
-
activatedCapital: entrant.activatedCapital.toBigInt()
|
|
2132
|
-
});
|
|
2133
|
-
}
|
|
2134
|
-
for (const entrant of await api.query.liquidityPools.nextLiquidityPoolCapital()) {
|
|
2135
|
-
this.setVaultCohortData(this.nextCohortId, entrant.vaultId.toNumber(), {
|
|
2136
|
-
activatedCapital: entrant.activatedCapital.toBigInt()
|
|
2137
|
-
});
|
|
2138
|
-
}
|
|
2139
|
-
await this.onVaultsUpdated(blockHash, new Set(vaultIds));
|
|
2140
|
-
this.print();
|
|
2141
|
-
}
|
|
2142
|
-
async watch() {
|
|
2143
|
-
await this.loadAt();
|
|
2144
|
-
await this.blockWatch.start();
|
|
2145
|
-
this.blockWatch.events.on(
|
|
2146
|
-
"vaults-updated",
|
|
2147
|
-
(b, v) => this.onVaultsUpdated(b.hash, v)
|
|
2148
|
-
);
|
|
2149
|
-
const api = await this.client;
|
|
2150
|
-
this.blockWatch.events.on("event", async (_, event) => {
|
|
2151
|
-
if (api.events.liquidityPools.BidPoolDistributed.is(event)) {
|
|
2152
|
-
const { cohortId: rawCohortId } = event.data;
|
|
2153
|
-
this.lastDistributedCohortId = rawCohortId.toNumber();
|
|
2154
|
-
this.bidPoolAmount = await this.getBidPool();
|
|
2155
|
-
this.cohortSubscriptions[rawCohortId.toNumber()]?.();
|
|
2156
|
-
const entrant = await api.query.liquidityPools.liquidityPoolsByCohort(rawCohortId);
|
|
2157
|
-
this.loadCohortData(rawCohortId.toNumber(), entrant);
|
|
2158
|
-
this.printDebounce();
|
|
2159
|
-
}
|
|
2160
|
-
if (api.events.liquidityPools.NextBidPoolCapitalLocked.is(event)) {
|
|
2161
|
-
const { cohortId } = event.data;
|
|
2162
|
-
for (let inc = 0; inc < 2; inc++) {
|
|
2163
|
-
const id = cohortId.toNumber() + inc;
|
|
2164
|
-
if (!this.cohortSubscriptions[id]) {
|
|
2165
|
-
this.cohortSubscriptions[id] = await api.query.liquidityPools.liquidityPoolsByCohort(
|
|
2166
|
-
id,
|
|
2167
|
-
async (entrant) => {
|
|
2168
|
-
this.loadCohortData(id, entrant);
|
|
2169
|
-
this.printDebounce();
|
|
2170
|
-
}
|
|
2171
|
-
);
|
|
2172
|
-
}
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
});
|
|
2176
|
-
const unsubscribe = await api.queryMulti(
|
|
2177
|
-
[
|
|
2178
|
-
api.query.miningSlot.nextSlotCohort,
|
|
2179
|
-
api.query.miningSlot.nextCohortId,
|
|
2180
|
-
api.query.liquidityPools.openLiquidityPoolCapital,
|
|
2181
|
-
api.query.liquidityPools.nextLiquidityPoolCapital
|
|
2182
|
-
],
|
|
2183
|
-
async ([
|
|
2184
|
-
_nextSlotCohort,
|
|
2185
|
-
nextCohortId,
|
|
2186
|
-
openVaultBidPoolCapital,
|
|
2187
|
-
nextPoolCapital
|
|
2188
|
-
]) => {
|
|
2189
|
-
this.bidPoolAmount = await this.getBidPool();
|
|
2190
|
-
this.nextCohortId = nextCohortId.toNumber();
|
|
2191
|
-
for (const entrant of [
|
|
2192
|
-
...openVaultBidPoolCapital,
|
|
2193
|
-
...nextPoolCapital
|
|
2194
|
-
]) {
|
|
2195
|
-
this.setVaultCohortData(
|
|
2196
|
-
entrant.cohortId.toNumber(),
|
|
2197
|
-
entrant.vaultId.toNumber(),
|
|
2198
|
-
{
|
|
2199
|
-
activatedCapital: entrant.activatedCapital.toBigInt()
|
|
2200
|
-
}
|
|
2201
|
-
);
|
|
2202
|
-
}
|
|
2203
|
-
this.printDebounce();
|
|
2204
|
-
}
|
|
2205
|
-
);
|
|
2206
|
-
return { unsubscribe };
|
|
2207
|
-
}
|
|
2208
|
-
async bondArgons(vaultId, amount, options) {
|
|
2209
|
-
const client = await this.client;
|
|
2210
|
-
const tx = client.tx.liquidityPools.bondArgons(vaultId, amount);
|
|
2211
|
-
const txSubmitter = new TxSubmitter(client, tx, this.keypair);
|
|
2212
|
-
const affordability = await txSubmitter.canAfford({
|
|
2213
|
-
tip: options?.tip,
|
|
2214
|
-
unavailableBalance: amount
|
|
2215
|
-
});
|
|
2216
|
-
if (!affordability.canAfford) {
|
|
2217
|
-
console.warn("Insufficient balance to bond argons to liquidity pool", {
|
|
2218
|
-
...affordability,
|
|
2219
|
-
argonsNeeded: amount
|
|
2220
|
-
});
|
|
2221
|
-
throw new Error("Insufficient balance to bond argons to liquidity pool");
|
|
2222
|
-
}
|
|
2223
|
-
const result = await txSubmitter.submit({
|
|
2224
|
-
tip: options?.tip,
|
|
2225
|
-
useLatestNonce: true
|
|
2226
|
-
});
|
|
2227
|
-
await result.inBlockPromise;
|
|
2228
|
-
return result;
|
|
2229
|
-
}
|
|
2230
|
-
printDebounce() {
|
|
2231
|
-
if (this.printTimeout) {
|
|
2232
|
-
clearTimeout(this.printTimeout);
|
|
2233
|
-
}
|
|
2234
|
-
this.printTimeout = setTimeout(() => {
|
|
2235
|
-
this.print();
|
|
2236
|
-
}, 100);
|
|
2237
|
-
}
|
|
2238
|
-
getOperatorName(vaultId) {
|
|
2239
|
-
const vault = this.vaultsById[vaultId];
|
|
2240
|
-
return this.accountRegistry.getName(vault.operatorAccountId) ?? vault.operatorAccountId;
|
|
2241
|
-
}
|
|
2242
|
-
print() {
|
|
2243
|
-
console.clear();
|
|
2244
|
-
const lastDistributedCohortId = this.lastDistributedCohortId;
|
|
2245
|
-
const distributedCohort = this.poolVaultCapitalByCohort[this.lastDistributedCohortId ?? -1] ?? {};
|
|
2246
|
-
if (Object.keys(distributedCohort).length > 0) {
|
|
2247
|
-
console.log(`
|
|
2248
|
-
|
|
2249
|
-
Distributed (cohort ${lastDistributedCohortId})`);
|
|
2250
|
-
const rows = [];
|
|
2251
|
-
let maxWidth2 = 0;
|
|
2252
|
-
for (const [key, entry] of Object.entries(distributedCohort)) {
|
|
2253
|
-
const { table, width } = this.createBondCapitalTable(
|
|
2254
|
-
entry.earnings ?? 0n,
|
|
2255
|
-
entry.contributors ?? [],
|
|
2256
|
-
`Earnings (shared = ${formatPercent(entry.vaultSharingPercent)})`
|
|
2257
|
-
);
|
|
2258
|
-
if (width > maxWidth2) {
|
|
2259
|
-
maxWidth2 = width;
|
|
2260
|
-
}
|
|
2261
|
-
rows.push({
|
|
2262
|
-
Vault: key,
|
|
2263
|
-
Who: this.getOperatorName(Number(key)),
|
|
2264
|
-
Balances: table
|
|
2265
|
-
});
|
|
2266
|
-
}
|
|
2267
|
-
new Table({
|
|
2268
|
-
columns: [
|
|
2269
|
-
{ name: "Vault", alignment: "left" },
|
|
2270
|
-
{ name: "Who", alignment: "left" },
|
|
2271
|
-
{
|
|
2272
|
-
name: "Balances",
|
|
2273
|
-
title: "Contributor Balances",
|
|
2274
|
-
alignment: "center",
|
|
2275
|
-
minLen: maxWidth2
|
|
2276
|
-
}
|
|
2277
|
-
],
|
|
2278
|
-
rows
|
|
2279
|
-
}).printTable();
|
|
2280
|
-
}
|
|
2281
|
-
console.log(
|
|
2282
|
-
`
|
|
2283
|
-
|
|
2284
|
-
Active Bid Pool: ${formatArgons(this.bidPoolAmount)} (cohort ${this.nextCohortId})`
|
|
2285
|
-
);
|
|
2286
|
-
const cohort = this.poolVaultCapitalByCohort[this.nextCohortId];
|
|
2287
|
-
if (Object.keys(cohort ?? {}).length > 0) {
|
|
2288
|
-
const rows = [];
|
|
2289
|
-
let maxWidth2 = 0;
|
|
2290
|
-
for (const [key, entry] of Object.entries(cohort)) {
|
|
2291
|
-
const { table, width } = this.createBondCapitalTable(
|
|
2292
|
-
entry.activatedCapital,
|
|
2293
|
-
entry.contributors ?? []
|
|
2294
|
-
);
|
|
2295
|
-
if (width > maxWidth2) {
|
|
2296
|
-
maxWidth2 = width;
|
|
2297
|
-
}
|
|
2298
|
-
rows.push({
|
|
2299
|
-
Vault: key,
|
|
2300
|
-
Who: this.getOperatorName(Number(key)),
|
|
2301
|
-
"Pool Capital": table
|
|
2302
|
-
});
|
|
2303
|
-
}
|
|
2304
|
-
new Table({
|
|
2305
|
-
columns: [
|
|
2306
|
-
{ name: "Vault", alignment: "left" },
|
|
2307
|
-
{ name: "Who", alignment: "left" },
|
|
2308
|
-
{ name: "Pool Capital", alignment: "left", minLen: maxWidth2 }
|
|
2309
|
-
],
|
|
2310
|
-
rows
|
|
2311
|
-
}).printTable();
|
|
2312
|
-
}
|
|
2313
|
-
const nextPool = this.poolVaultCapitalByCohort[this.nextCohortId + 1] ?? [];
|
|
2314
|
-
let maxWidth = 0;
|
|
2315
|
-
const nextCapital = [];
|
|
2316
|
-
for (const x of this.vaultSecuritization) {
|
|
2317
|
-
const entry = nextPool[x.vaultId] ?? {};
|
|
2318
|
-
const { table, width } = this.createBondCapitalTable(
|
|
2319
|
-
x.activatedSecuritization,
|
|
2320
|
-
entry.contributors ?? []
|
|
2321
|
-
);
|
|
2322
|
-
if (width > maxWidth) {
|
|
2323
|
-
maxWidth = width;
|
|
2324
|
-
}
|
|
2325
|
-
nextCapital.push({
|
|
2326
|
-
Vault: x.vaultId,
|
|
2327
|
-
Owner: this.getOperatorName(x.vaultId),
|
|
2328
|
-
"Bitcoin Space": formatArgons(x.bitcoinSpace),
|
|
2329
|
-
"Activated Securitization": `${formatArgons(x.activatedSecuritization)} / slot`,
|
|
2330
|
-
"Liquidity Pool": `${formatPercent(x.vaultSharingPercent)} profit sharing${table}`
|
|
2331
|
-
});
|
|
2332
|
-
}
|
|
2333
|
-
if (nextCapital.length) {
|
|
2334
|
-
console.log(`
|
|
2335
|
-
|
|
2336
|
-
Next (cohort ${this.nextCohortId + 1}):`);
|
|
2337
|
-
new Table({
|
|
2338
|
-
columns: [
|
|
2339
|
-
{ name: "Vault", alignment: "left" },
|
|
2340
|
-
{ name: "Owner", alignment: "left" },
|
|
2341
|
-
{ name: "Bitcoin Space", alignment: "right" },
|
|
2342
|
-
{ name: "Activated Securitization", alignment: "right" },
|
|
2343
|
-
{ name: "Liquidity Pool", alignment: "left", minLen: maxWidth }
|
|
2344
|
-
],
|
|
2345
|
-
rows: nextCapital
|
|
2346
|
-
}).printTable();
|
|
2347
|
-
}
|
|
2348
|
-
}
|
|
2349
|
-
setVaultCohortData(cohortId, vaultId, data) {
|
|
2350
|
-
this.poolVaultCapitalByCohort ??= {};
|
|
2351
|
-
this.poolVaultCapitalByCohort[cohortId] ??= {};
|
|
2352
|
-
this.poolVaultCapitalByCohort[cohortId][vaultId] ??= {
|
|
2353
|
-
activatedCapital: data.activatedCapital ?? data.contributors?.reduce((a, b) => a + b.amount, 0n) ?? 0n
|
|
2354
|
-
};
|
|
2355
|
-
Object.assign(
|
|
2356
|
-
this.poolVaultCapitalByCohort[cohortId][vaultId],
|
|
2357
|
-
filterUndefined(data)
|
|
2358
|
-
);
|
|
2359
|
-
}
|
|
2360
|
-
createBondCapitalTable(total, contributors, title = "Total") {
|
|
2361
|
-
const table = new Table({
|
|
2362
|
-
style: EMPTY_TABLE,
|
|
2363
|
-
columns: [
|
|
2364
|
-
{ name: "who", title, minLen: 10, alignment: "right" },
|
|
2365
|
-
{
|
|
2366
|
-
name: "amount",
|
|
2367
|
-
title: formatArgons(total),
|
|
2368
|
-
minLen: 7,
|
|
2369
|
-
alignment: "left"
|
|
2370
|
-
}
|
|
2371
|
-
]
|
|
2372
|
-
});
|
|
2373
|
-
for (const x of contributors) {
|
|
2374
|
-
table.addRow({
|
|
2375
|
-
who: this.accountRegistry.getName(x.address) ?? x.address,
|
|
2376
|
-
amount: formatArgons(x.amount)
|
|
2377
|
-
});
|
|
2378
|
-
}
|
|
2379
|
-
const str = table.render();
|
|
2380
|
-
const width = str.indexOf("\n");
|
|
2381
|
-
return { table: str, width };
|
|
2382
|
-
}
|
|
2383
|
-
loadCohortData(cohortId, vaultFunds) {
|
|
2384
|
-
for (const [vaultId, fund] of vaultFunds) {
|
|
2385
|
-
const vaultIdNumber = vaultId.toNumber();
|
|
2386
|
-
const contributors = fund.contributorBalances.map(([a, b]) => ({
|
|
2387
|
-
address: a.toHuman(),
|
|
2388
|
-
amount: b.toBigInt()
|
|
2389
|
-
}));
|
|
2390
|
-
if (fund.distributedProfits.isSome) {
|
|
2391
|
-
if (cohortId > (this.lastDistributedCohortId ?? 0)) {
|
|
2392
|
-
this.lastDistributedCohortId = cohortId;
|
|
2393
|
-
}
|
|
2394
|
-
}
|
|
2395
|
-
this.setVaultCohortData(cohortId, vaultIdNumber, {
|
|
2396
|
-
earnings: fund.distributedProfits.isSome ? fund.distributedProfits.unwrap().toBigInt() : void 0,
|
|
2397
|
-
vaultSharingPercent: convertPermillToBigNumber(
|
|
2398
|
-
fund.vaultSharingPercent.toBigInt()
|
|
2399
|
-
),
|
|
2400
|
-
contributors
|
|
2401
|
-
});
|
|
2402
|
-
}
|
|
2403
|
-
}
|
|
2404
|
-
};
|
|
2405
|
-
|
|
2406
|
-
// src/BitcoinLocks.ts
|
|
2407
|
-
var SATS_PER_BTC = 100000000n;
|
|
2408
|
-
var BitcoinLocks = class _BitcoinLocks {
|
|
2409
|
-
constructor(client) {
|
|
2410
|
-
this.client = client;
|
|
2411
|
-
}
|
|
2412
|
-
async getMarketRate(satoshis) {
|
|
2413
|
-
const client = await this.client;
|
|
2414
|
-
const sats = client.createType("U64", satoshis.toString());
|
|
2415
|
-
const marketRate = await client.rpc.state.call(
|
|
2416
|
-
"BitcoinApis_market_rate",
|
|
2417
|
-
sats.toHex(true)
|
|
2418
|
-
);
|
|
2419
|
-
const rate = client.createType("Option<U128>", marketRate);
|
|
2420
|
-
if (!rate.isSome) {
|
|
2421
|
-
throw new Error("Market rate not available");
|
|
2422
|
-
}
|
|
2423
|
-
return rate.value.toBigInt();
|
|
2424
|
-
}
|
|
2425
|
-
async buildBitcoinLockTx(args) {
|
|
2426
|
-
const { vaultId, keypair, bitcoinXpub, tip } = args;
|
|
2427
|
-
let amount = args.amount;
|
|
2428
|
-
const marketRatePerBitcoin = await this.getMarketRate(100000000n);
|
|
2429
|
-
const client = await this.client;
|
|
2430
|
-
const account = await client.query.system.account(keypair.address);
|
|
2431
|
-
const freeBalance = account.data.free.toBigInt();
|
|
2432
|
-
let availableBalance = freeBalance;
|
|
2433
|
-
if (args.reducedBalanceBy) {
|
|
2434
|
-
availableBalance -= args.reducedBalanceBy;
|
|
2435
|
-
}
|
|
2436
|
-
const satoshisNeeded = amount * SATS_PER_BTC / marketRatePerBitcoin - 500n;
|
|
2437
|
-
const tx = client.tx.bitcoinLocks.initialize(
|
|
2438
|
-
vaultId,
|
|
2439
|
-
satoshisNeeded,
|
|
2440
|
-
bitcoinXpub
|
|
2441
|
-
);
|
|
2442
|
-
const existentialDeposit = client.consts.balances.existentialDeposit.toBigInt();
|
|
2443
|
-
const finalTip = tip ?? 0n;
|
|
2444
|
-
const fees = await tx.paymentInfo(keypair.address, { tip });
|
|
2445
|
-
const txFee = fees.partialFee.toBigInt();
|
|
2446
|
-
const tickDuration = (await client.query.ticks.genesisTicker()).tickDurationMillis.toNumber();
|
|
2447
|
-
const rawVault = await client.query.vaults.vaultsById(vaultId);
|
|
2448
|
-
const vault = new Vault(vaultId, rawVault.unwrap(), tickDuration);
|
|
2449
|
-
const btcFee = vault.calculateBitcoinFee(amount);
|
|
2450
|
-
const totalCharge = txFee + finalTip + btcFee;
|
|
2451
|
-
if (amount + totalCharge + existentialDeposit > availableBalance) {
|
|
2452
|
-
throw new Error("Insufficient balance to lock bitcoins");
|
|
2453
|
-
}
|
|
2454
|
-
console.log(
|
|
2455
|
-
`Locking ${satoshisNeeded} satoshis in vault ${vaultId} with market rate of ${formatArgons(marketRatePerBitcoin)}/btc. Xpub: ${bitcoinXpub}`
|
|
2456
|
-
);
|
|
2457
|
-
return { tx, txFee, btcFee, satoshis: satoshisNeeded, freeBalance };
|
|
2458
|
-
}
|
|
2459
|
-
static async waitForSpace(accountset, options) {
|
|
2460
|
-
const { argonAmount, bitcoinXpub, maxLockFee, tip = 0n } = options;
|
|
2461
|
-
const vaults = new VaultMonitor(accountset, {
|
|
2462
|
-
bitcoinSpaceAvailable: argonAmount
|
|
2463
|
-
});
|
|
2464
|
-
return new Promise(async (resolve, reject) => {
|
|
2465
|
-
vaults.events.on("bitcoin-space-above", async (vaultId, amount) => {
|
|
2466
|
-
const vault = vaults.vaultsById[vaultId];
|
|
2467
|
-
const fee = vault.calculateBitcoinFee(amount);
|
|
2468
|
-
console.log(
|
|
2469
|
-
`Vault ${vaultId} has ${formatArgons(amount)} argons available for bitcoin. Lock fee is ${formatArgons(fee)}`
|
|
2470
|
-
);
|
|
2471
|
-
if (maxLockFee !== void 0 && fee > maxLockFee) {
|
|
2472
|
-
console.log(
|
|
2473
|
-
`Skipping vault ${vaultId} due to high lock fee: ${formatArgons(maxLockFee)}`
|
|
2474
|
-
);
|
|
2475
|
-
return;
|
|
2476
|
-
}
|
|
2477
|
-
try {
|
|
2478
|
-
const bitcoinLock = new _BitcoinLocks(accountset.client);
|
|
2479
|
-
const { tx, satoshis, btcFee, txFee } = await bitcoinLock.buildBitcoinLockTx({
|
|
2480
|
-
vaultId,
|
|
2481
|
-
keypair: accountset.txSubmitterPair,
|
|
2482
|
-
amount: argonAmount,
|
|
2483
|
-
bitcoinXpub,
|
|
2484
|
-
tip
|
|
2485
|
-
});
|
|
2486
|
-
const result = await accountset.tx(tx).then((x) => x.submit({ waitForBlock: true, tip }));
|
|
2487
|
-
const client = await accountset.client;
|
|
2488
|
-
const utxoId = result.events.find((x) => client.events.bitcoinLocks.BitcoinLockCreated.is(x))?.data.utxoId?.toNumber();
|
|
2489
|
-
if (!utxoId) {
|
|
2490
|
-
throw new Error("Failed to find UTXO ID");
|
|
2491
|
-
}
|
|
2492
|
-
resolve({
|
|
2493
|
-
satoshis,
|
|
2494
|
-
argons: argonAmount,
|
|
2495
|
-
vaultId,
|
|
2496
|
-
btcFee,
|
|
2497
|
-
txFee,
|
|
2498
|
-
finalizedPromise: result.finalizedPromise,
|
|
2499
|
-
utxoId
|
|
2500
|
-
});
|
|
2501
|
-
} catch (err) {
|
|
2502
|
-
console.error("Error submitting bitcoin lock tx:", err);
|
|
2503
|
-
reject(err);
|
|
2504
|
-
} finally {
|
|
2505
|
-
vaults.stop();
|
|
2506
|
-
}
|
|
2507
|
-
});
|
|
2508
|
-
await vaults.monitor();
|
|
2509
|
-
});
|
|
2510
|
-
}
|
|
2511
|
-
};
|
|
2512
|
-
|
|
2513
|
-
// src/keyringUtils.ts
|
|
2514
|
-
function keyringFromSuri(suri, cryptoType = "sr25519") {
|
|
2515
|
-
return new Keyring({ type: cryptoType }).createFromUri(suri);
|
|
2516
|
-
}
|
|
2517
|
-
function createKeyringPair(opts) {
|
|
2518
|
-
const { cryptoType } = opts;
|
|
2519
|
-
const seed = mnemonicGenerate();
|
|
2520
|
-
return keyringFromSuri(seed, cryptoType);
|
|
2521
|
-
}
|
|
2522
|
-
|
|
2523
|
-
// src/index.ts
|
|
2524
|
-
__reExport(index_exports, types_star2);
|
|
2525
|
-
__reExport(index_exports, interfaces_star);
|
|
2526
|
-
import * as types_star2 from "@polkadot/types";
|
|
2527
|
-
import * as interfaces_star from "@polkadot/types/interfaces";
|
|
2528
|
-
async function waitForLoad() {
|
|
2529
|
-
await cryptoWaitReady();
|
|
2530
|
-
}
|
|
2531
|
-
async function getClient(host) {
|
|
2532
|
-
let provider;
|
|
2533
|
-
if (host.startsWith("http:")) {
|
|
2534
|
-
provider = new HttpProvider(host);
|
|
2535
|
-
} else {
|
|
2536
|
-
provider = new WsProvider(host);
|
|
2537
|
-
}
|
|
2538
|
-
return await ApiPromise.create({ provider, noInitWarn: true });
|
|
2539
|
-
}
|
|
2540
|
-
|
|
2541
|
-
// src/clis/accountCli.ts
|
|
2542
|
-
import { printTable as printTable3 } from "console-table-printer";
|
|
2543
|
-
import { cryptoWaitReady as cryptoWaitReady2 } from "@polkadot/util-crypto";
|
|
2544
|
-
import { writeFileSync } from "node:fs";
|
|
2545
|
-
import * as process3 from "node:process";
|
|
2546
|
-
function accountCli() {
|
|
2547
|
-
const program2 = new Command("accounts").description(
|
|
2548
|
-
"Manage subaccounts from a single keypair"
|
|
2549
|
-
);
|
|
2550
|
-
program2.command("watch").description("Watch for blocks closed by subaccounts").action(async () => {
|
|
2551
|
-
const accountset = await accountsetFromCli(program2);
|
|
2552
|
-
const accountMiners = await accountset.watchBlocks();
|
|
2553
|
-
accountMiners.events.on("mined", (_block, mined) => {
|
|
2554
|
-
console.log("Your accounts authored a block", mined);
|
|
2555
|
-
});
|
|
2556
|
-
accountMiners.events.on("minted", (_block, minted) => {
|
|
2557
|
-
console.log("Your accounts minted argons", minted);
|
|
2558
|
-
});
|
|
2559
|
-
});
|
|
2560
|
-
program2.command("list", { isDefault: true }).description("Show subaccounts").option("--addresses", "Just show a list of ids").action(async ({ addresses }) => {
|
|
2561
|
-
const { subaccounts } = globalOptions(program2);
|
|
2562
|
-
const accountset = await accountsetFromCli(program2);
|
|
2563
|
-
if (addresses) {
|
|
2564
|
-
const addresses2 = accountset.addresses;
|
|
2565
|
-
console.log(addresses2.join(","));
|
|
2566
|
-
process3.exit(0);
|
|
2567
|
-
}
|
|
2568
|
-
const [argonots, argons, seats, bids] = await Promise.all([
|
|
2569
|
-
accountset.totalArgonotsAt(),
|
|
2570
|
-
accountset.totalArgonsAt(),
|
|
2571
|
-
accountset.miningSeats(),
|
|
2572
|
-
accountset.bids()
|
|
2573
|
-
]);
|
|
2574
|
-
const accountSubset = subaccounts ? accountset.getAccountsInRange(subaccounts) : void 0;
|
|
2575
|
-
const status = accountset.status({
|
|
2576
|
-
argons,
|
|
2577
|
-
argonots,
|
|
2578
|
-
accountSubset,
|
|
2579
|
-
seats,
|
|
2580
|
-
bids
|
|
2581
|
-
});
|
|
2582
|
-
printTable3(status);
|
|
2583
|
-
process3.exit(0);
|
|
2584
|
-
});
|
|
2585
|
-
program2.command("create").description('Create an account "env" file and optionally register keys').requiredOption(
|
|
2586
|
-
"--path <path>",
|
|
2587
|
-
"The path to an env file to create (convention is .env.<name>)"
|
|
2588
|
-
).option(
|
|
2589
|
-
"--register-keys-to <url>",
|
|
2590
|
-
"Register the keys to a url (normally this is localhost)"
|
|
2591
|
-
).action(async ({ registerKeysTo, path }) => {
|
|
2592
|
-
const { accountPassphrase, accountSuri, accountFilePath } = globalOptions(program2);
|
|
2593
|
-
const accountset = await accountsetFromCli(program2);
|
|
2594
|
-
process3.env.KEYS_MNEMONIC ||= mnemonicGenerate();
|
|
2595
|
-
if (registerKeysTo) {
|
|
2596
|
-
await accountset.registerKeys(registerKeysTo);
|
|
2597
|
-
console.log("Keys registered to", registerKeysTo);
|
|
2598
|
-
}
|
|
2599
|
-
const envData = {
|
|
2600
|
-
ACCOUNT_JSON_PATH: accountFilePath,
|
|
2601
|
-
ACCOUNT_SURI: accountSuri,
|
|
2602
|
-
ACCOUNT_PASSPHRASE: accountPassphrase,
|
|
2603
|
-
KEYS_MNEMONIC: process3.env.KEYS_MNEMONIC,
|
|
2604
|
-
SUBACCOUNT_RANGE: "0-49"
|
|
2605
|
-
};
|
|
2606
|
-
let envfile = "";
|
|
2607
|
-
for (const [key, value] of Object.entries(envData)) {
|
|
2608
|
-
if (key) {
|
|
2609
|
-
const line = `${key}=${String(value)}`;
|
|
2610
|
-
envfile += line + "\n";
|
|
2611
|
-
}
|
|
2612
|
-
}
|
|
2613
|
-
writeFileSync(path, envfile);
|
|
2614
|
-
console.log("Created env file at", path);
|
|
2615
|
-
process3.exit();
|
|
2616
|
-
});
|
|
2617
|
-
program2.command("new-key-seed").description("Create a new mnemonic for runtime keys").action(async () => {
|
|
2618
|
-
await cryptoWaitReady2();
|
|
2619
|
-
const mnemonic = mnemonicGenerate();
|
|
2620
|
-
console.log(
|
|
2621
|
-
"New mnemonic (add this to your .env as KEYS_MNEMONIC):",
|
|
2622
|
-
mnemonic
|
|
2623
|
-
);
|
|
2624
|
-
process3.exit(0);
|
|
2625
|
-
});
|
|
2626
|
-
program2.command("register-keys").description("Create an insert-keys script with curl").argument(
|
|
2627
|
-
"[node-rpc-url]",
|
|
2628
|
-
"The url to your node host (should be installed on machine via localhost)",
|
|
2629
|
-
"http://localhost:9944"
|
|
2630
|
-
).option(
|
|
2631
|
-
"--print-only",
|
|
2632
|
-
"Output as curl commands instead of direct registration"
|
|
2633
|
-
).action(async (nodeRpcUrl, { printOnly }) => {
|
|
2634
|
-
const accountset = await accountsetFromCli(program2);
|
|
2635
|
-
if (printOnly) {
|
|
2636
|
-
const { gran, seal } = accountset.keys();
|
|
2637
|
-
const commands = [];
|
|
2638
|
-
const data = [
|
|
2639
|
-
{
|
|
2640
|
-
jsonrpc: "2.0",
|
|
2641
|
-
id: 0,
|
|
2642
|
-
method: "author_insertKey",
|
|
2643
|
-
params: ["gran", gran.privateKey, gran.publicKey]
|
|
2644
|
-
},
|
|
2645
|
-
{
|
|
2646
|
-
jsonrpc: "2.0",
|
|
2647
|
-
id: 1,
|
|
2648
|
-
method: "author_insertKey",
|
|
2649
|
-
params: ["seal", seal.privateKey, seal.publicKey]
|
|
2650
|
-
}
|
|
2651
|
-
];
|
|
2652
|
-
for (const key of data) {
|
|
2653
|
-
commands.push(
|
|
2654
|
-
`curl -X POST -H "Content-Type: application/json" -d '${JSON.stringify(key)}' ${nodeRpcUrl}`
|
|
2655
|
-
);
|
|
2656
|
-
}
|
|
2657
|
-
console.log(commands.join(" && "));
|
|
2658
|
-
} else {
|
|
2659
|
-
await accountset.registerKeys(nodeRpcUrl);
|
|
2660
|
-
}
|
|
2661
|
-
process3.exit();
|
|
2662
|
-
});
|
|
2663
|
-
program2.command("consolidate").description("Consolidate all argons into parent account").option(
|
|
2664
|
-
"-s, --subaccounts <range>",
|
|
2665
|
-
"Restrict this operation to a subset of the subaccounts (eg, 0-10)",
|
|
2666
|
-
parseSubaccountRange
|
|
2667
|
-
).action(async ({ subaccounts }) => {
|
|
2668
|
-
const accountset = await accountsetFromCli(program2);
|
|
2669
|
-
const result = await accountset.consolidate(subaccounts);
|
|
2670
|
-
printTable3(result);
|
|
2671
|
-
process3.exit(0);
|
|
2672
|
-
});
|
|
2673
|
-
return program2;
|
|
2674
|
-
}
|
|
2675
|
-
|
|
2676
|
-
// src/clis/index.ts
|
|
2677
|
-
import { configDotenv } from "dotenv";
|
|
2678
|
-
import Path from "node:path";
|
|
2679
|
-
|
|
2680
|
-
// src/clis/vaultCli.ts
|
|
2681
|
-
import { Command as Command2 } from "@commander-js/extra-typings";
|
|
2682
|
-
function vaultCli() {
|
|
2683
|
-
const program2 = new Command2("vaults").description(
|
|
2684
|
-
"Monitor vaults and manage securitization"
|
|
2685
|
-
);
|
|
2686
|
-
program2.command("list", { isDefault: true }).description("Show current state of vaults").action(async () => {
|
|
2687
|
-
const accountset = await accountsetFromCli(program2);
|
|
2688
|
-
const vaults = new VaultMonitor(accountset, void 0, {
|
|
2689
|
-
vaultOnlyWatchMode: true
|
|
2690
|
-
});
|
|
2691
|
-
await vaults.monitor(true);
|
|
2692
|
-
process.exit(0);
|
|
2693
|
-
});
|
|
2694
|
-
program2.command("modify-securitization").description("Change the vault securitization ratio").requiredOption("-v, --vault-id <id>", "The vault id to use", parseInt).requiredOption(
|
|
2695
|
-
"-a, --argons <amount>",
|
|
2696
|
-
"The number of argons to set as securitization",
|
|
2697
|
-
parseFloat
|
|
2698
|
-
).option("--ratio <ratio>", "The new securitization ratio", parseFloat).option(
|
|
2699
|
-
"--tip <amount>",
|
|
2700
|
-
"The tip to include with the transaction",
|
|
2701
|
-
parseFloat
|
|
2702
|
-
).action(async ({ tip, argons, vaultId, ratio }) => {
|
|
2703
|
-
const accountset = await accountsetFromCli(program2);
|
|
2704
|
-
const client = await accountset.client;
|
|
2705
|
-
const resolvedTip = tip ? BigInt(tip * MICROGONS_PER_ARGON) : 0n;
|
|
2706
|
-
const microgons = BigInt(argons * MICROGONS_PER_ARGON);
|
|
2707
|
-
const rawVault = (await client.query.vaults.vaultsById(vaultId)).unwrap();
|
|
2708
|
-
if (rawVault.operatorAccountId.toHuman() !== accountset.seedAddress) {
|
|
2709
|
-
console.error("Vault does not belong to this account");
|
|
2710
|
-
process.exit(1);
|
|
2711
|
-
}
|
|
2712
|
-
const existingFunds = rawVault.securitization.toBigInt();
|
|
2713
|
-
const additionalFunds = microgons > existingFunds ? microgons - existingFunds : 0n;
|
|
2714
|
-
const tx = client.tx.vaults.modifyFunding(
|
|
2715
|
-
vaultId,
|
|
2716
|
-
microgons,
|
|
2717
|
-
ratio !== void 0 ? BigNumber(ratio).times(BigNumber(2).pow(64)).toFixed(0) : rawVault.securitizationRatio.toBigInt()
|
|
2718
|
-
);
|
|
2719
|
-
const submit = new TxSubmitter(client, tx, accountset.txSubmitterPair);
|
|
2720
|
-
const canAfford = await submit.canAfford({
|
|
2721
|
-
tip: resolvedTip,
|
|
2722
|
-
unavailableBalance: additionalFunds
|
|
2723
|
-
});
|
|
2724
|
-
if (!canAfford.canAfford) {
|
|
2725
|
-
console.warn("Insufficient balance to modify vault securitization", {
|
|
2726
|
-
...canAfford,
|
|
2727
|
-
addedSecuritization: additionalFunds
|
|
2728
|
-
});
|
|
2729
|
-
process.exit(1);
|
|
2730
|
-
}
|
|
2731
|
-
try {
|
|
2732
|
-
const result = await submit.submit({ tip: resolvedTip });
|
|
2733
|
-
await result.inBlockPromise;
|
|
2734
|
-
console.log("Vault securitization modified");
|
|
2735
|
-
process.exit();
|
|
2736
|
-
} catch (error) {
|
|
2737
|
-
console.error("Error modifying vault securitization", error);
|
|
2738
|
-
process.exit(1);
|
|
2739
|
-
}
|
|
2740
|
-
});
|
|
2741
|
-
program2.command("make-bitcoin-space").description(
|
|
2742
|
-
"Make bitcoin space in a vault and lock it immediately in the same tx."
|
|
2743
|
-
).requiredOption("-v, --vault-id <id>", "The vault id to use", parseInt).requiredOption(
|
|
2744
|
-
"-a, --argons <amount>",
|
|
2745
|
-
"The number of argons to add",
|
|
2746
|
-
parseFloat
|
|
2747
|
-
).requiredOption(
|
|
2748
|
-
"--bitcoin-pubkey <pubkey>",
|
|
2749
|
-
"The pubkey to use for the bitcoin lock"
|
|
2750
|
-
).option(
|
|
2751
|
-
"--tip <amount>",
|
|
2752
|
-
"The tip to include with the transaction",
|
|
2753
|
-
parseFloat
|
|
2754
|
-
).action(async ({ tip, argons, vaultId, bitcoinPubkey }) => {
|
|
2755
|
-
let pubkey = bitcoinPubkey;
|
|
2756
|
-
if (!bitcoinPubkey.startsWith("0x")) {
|
|
2757
|
-
pubkey = `0x${bitcoinPubkey}`;
|
|
2758
|
-
}
|
|
2759
|
-
if (pubkey.length !== 68) {
|
|
2760
|
-
throw new Error(
|
|
2761
|
-
"Bitcoin pubkey must be 66 characters (add 0x in front optionally)"
|
|
2762
|
-
);
|
|
2763
|
-
}
|
|
2764
|
-
const accountset = await accountsetFromCli(program2);
|
|
2765
|
-
const client = await accountset.client;
|
|
2766
|
-
const resolvedTip = tip ? BigInt(tip * MICROGONS_PER_ARGON) : 0n;
|
|
2767
|
-
const microgons = BigInt(argons * MICROGONS_PER_ARGON);
|
|
2768
|
-
const bitcoinLocks = new BitcoinLocks(Promise.resolve(client));
|
|
2769
|
-
const existentialDeposit = client.consts.balances.existentialDeposit.toBigInt();
|
|
2770
|
-
const tickDuration = (await client.query.ticks.genesisTicker()).tickDurationMillis.toNumber();
|
|
2771
|
-
const rawVault = (await client.query.vaults.vaultsById(vaultId)).unwrap();
|
|
2772
|
-
if (rawVault.operatorAccountId.toHuman() !== accountset.seedAddress) {
|
|
2773
|
-
console.error("Vault does not belong to this account");
|
|
2774
|
-
process.exit(1);
|
|
2775
|
-
}
|
|
2776
|
-
const vaultModifyTx = client.tx.vaults.modifyFunding(
|
|
2777
|
-
vaultId,
|
|
2778
|
-
microgons,
|
|
2779
|
-
rawVault.securitizationRatio.toBigInt()
|
|
2780
|
-
);
|
|
2781
|
-
const vaultTxFee = (await vaultModifyTx.paymentInfo(accountset.txSubmitterPair)).partialFee.toBigInt();
|
|
2782
|
-
const vault = new Vault(vaultId, rawVault, tickDuration);
|
|
2783
|
-
const argonsNeeded = microgons - vault.securitization;
|
|
2784
|
-
const argonsAvailable = microgons - vault.availableBitcoinSpace();
|
|
2785
|
-
const account = await client.query.system.account(accountset.seedAddress);
|
|
2786
|
-
const freeBalance = account.data.free.toBigInt();
|
|
2787
|
-
const {
|
|
2788
|
-
tx: lockTx,
|
|
2789
|
-
btcFee,
|
|
2790
|
-
txFee
|
|
2791
|
-
} = await bitcoinLocks.buildBitcoinLockTx({
|
|
2792
|
-
vaultId,
|
|
2793
|
-
keypair: accountset.txSubmitterPair,
|
|
2794
|
-
amount: argonsAvailable,
|
|
2795
|
-
bitcoinXpub: pubkey,
|
|
2796
|
-
tip: resolvedTip,
|
|
2797
|
-
reducedBalanceBy: argonsNeeded + vaultTxFee + resolvedTip
|
|
2798
|
-
});
|
|
2799
|
-
if (argonsNeeded + txFee + vaultTxFee + resolvedTip + btcFee + existentialDeposit > freeBalance) {
|
|
2800
|
-
console.warn(
|
|
2801
|
-
"Insufficient balance to add bitcoin space and use bitcoins",
|
|
2802
|
-
{
|
|
2803
|
-
freeBalance,
|
|
2804
|
-
txFee,
|
|
2805
|
-
vaultTxFee,
|
|
2806
|
-
btcFee,
|
|
2807
|
-
argonsAvailable,
|
|
2808
|
-
vaultMicrogons: microgons,
|
|
2809
|
-
existentialDeposit,
|
|
2810
|
-
neededBalanceAboveED: argonsNeeded + txFee + resolvedTip + btcFee + vaultTxFee
|
|
2811
|
-
}
|
|
2812
|
-
);
|
|
2813
|
-
process.exit(1);
|
|
2814
|
-
}
|
|
2815
|
-
console.log("Adding bitcoin space and locking bitcoins...", {
|
|
2816
|
-
newArgonsAvailable: argonsAvailable,
|
|
2817
|
-
txFee,
|
|
2818
|
-
vaultTxFee,
|
|
2819
|
-
btcFee,
|
|
2820
|
-
resolvedTip
|
|
2821
|
-
});
|
|
2822
|
-
const txSubmitter = new TxSubmitter(
|
|
2823
|
-
client,
|
|
2824
|
-
client.tx.utility.batchAll([vaultModifyTx, lockTx]),
|
|
2825
|
-
accountset.txSubmitterPair
|
|
2826
|
-
);
|
|
2827
|
-
const result = await txSubmitter.submit({ tip: resolvedTip });
|
|
2828
|
-
try {
|
|
2829
|
-
await result.inBlockPromise;
|
|
2830
|
-
console.log("Bitcoin space done");
|
|
2831
|
-
} catch (error) {
|
|
2832
|
-
console.error("Error using bitcoin space", error);
|
|
2833
|
-
process.exit(1);
|
|
2834
|
-
}
|
|
2835
|
-
});
|
|
2836
|
-
return program2;
|
|
2837
|
-
}
|
|
2838
|
-
|
|
2839
|
-
// src/clis/miningCli.ts
|
|
2840
|
-
import { Command as Command3 } from "@commander-js/extra-typings";
|
|
2841
|
-
import { printTable as printTable4 } from "console-table-printer";
|
|
2842
|
-
function miningCli() {
|
|
2843
|
-
const program2 = new Command3("mining").description(
|
|
2844
|
-
"Watch mining seats or setup bidding"
|
|
2845
|
-
);
|
|
2846
|
-
program2.command("list", { isDefault: true }).description("Monitor all miners").action(async () => {
|
|
2847
|
-
const accountset = await accountsetFromCli(program2);
|
|
2848
|
-
const bids = new MiningBids(accountset.client);
|
|
2849
|
-
const api = await accountset.client;
|
|
2850
|
-
let lastMiners = {};
|
|
2851
|
-
function print(blockNumber) {
|
|
2852
|
-
console.clear();
|
|
2853
|
-
const toPrint = Object.entries(lastMiners).map(([seat, miner]) => ({
|
|
2854
|
-
seat,
|
|
2855
|
-
...miner
|
|
2856
|
-
}));
|
|
2857
|
-
if (!toPrint.length) {
|
|
2858
|
-
console.log("No active miners");
|
|
2859
|
-
} else {
|
|
2860
|
-
console.log(`Miners at block ${blockNumber}`);
|
|
2861
|
-
printTable4(
|
|
2862
|
-
toPrint.map((x) => ({
|
|
2863
|
-
...x,
|
|
2864
|
-
bid: x.bid ? formatArgons(x.bid) : "-",
|
|
2865
|
-
cohort: x.cohort,
|
|
2866
|
-
isLastDay: x.isLastDay ? "Y" : "",
|
|
2867
|
-
miner: x.miner
|
|
2868
|
-
}))
|
|
2869
|
-
);
|
|
2870
|
-
}
|
|
2871
|
-
if (!bids.nextCohort.length) {
|
|
2872
|
-
console.log(
|
|
2873
|
-
"-------------------------------------\nNo bids for next cohort"
|
|
2874
|
-
);
|
|
2875
|
-
} else {
|
|
2876
|
-
bids.print();
|
|
2877
|
-
}
|
|
2878
|
-
}
|
|
2879
|
-
const { unsubscribe } = await bids.watch(
|
|
2880
|
-
accountset.namedAccounts,
|
|
2881
|
-
void 0,
|
|
2882
|
-
print
|
|
2883
|
-
);
|
|
2884
|
-
const maxMiners = api.consts.miningSlot.maxMiners.toNumber();
|
|
2885
|
-
const seatIndices = new Array(maxMiners).fill(0).map((_, i) => i);
|
|
2886
|
-
console.log("Watching miners...");
|
|
2887
|
-
const unsub = await api.query.miningSlot.nextCohortId(
|
|
2888
|
-
async (nextCohortId) => {
|
|
2889
|
-
const entries = await api.query.miningSlot.activeMinersByIndex.entries();
|
|
2890
|
-
const block = await api.query.system.number();
|
|
2891
|
-
const seatsWithMiner = new Set(seatIndices);
|
|
2892
|
-
for (const [rawIndex, maybeMiner] of entries) {
|
|
2893
|
-
const index = rawIndex.args[0].toNumber();
|
|
2894
|
-
if (!maybeMiner.isSome) {
|
|
2895
|
-
continue;
|
|
2896
|
-
}
|
|
2897
|
-
seatsWithMiner.delete(index);
|
|
2898
|
-
const miner = maybeMiner.unwrap();
|
|
2899
|
-
const address = miner.accountId.toHuman();
|
|
2900
|
-
const cohortId = miner.cohortId.toNumber();
|
|
2901
|
-
lastMiners[index] = {
|
|
2902
|
-
miner: accountset.namedAccounts.get(address) ?? address,
|
|
2903
|
-
bid: miner.bid.toBigInt(),
|
|
2904
|
-
cohort: cohortId,
|
|
2905
|
-
isLastDay: nextCohortId.toNumber() - cohortId === 10
|
|
2906
|
-
};
|
|
2907
|
-
}
|
|
2908
|
-
for (const index of seatsWithMiner) {
|
|
2909
|
-
lastMiners[index] = {
|
|
2910
|
-
miner: "none"
|
|
2911
|
-
};
|
|
2912
|
-
}
|
|
2913
|
-
print(block.toNumber());
|
|
2914
|
-
}
|
|
2915
|
-
);
|
|
2916
|
-
process.on("SIGINT", () => {
|
|
2917
|
-
unsubscribe();
|
|
2918
|
-
unsub();
|
|
2919
|
-
process.exit(0);
|
|
2920
|
-
});
|
|
2921
|
-
});
|
|
2922
|
-
program2.command("bid").description("Submit mining bids within a range of parameters").option("--min-bid <amount>", "The minimum bid amount to use", parseFloat).option("--max-bid <amount>", "The maximum bid amount to use", parseFloat).option(
|
|
2923
|
-
"--max-seats <n>",
|
|
2924
|
-
"The maximum number of seats to bid on for the slot",
|
|
2925
|
-
parseInt
|
|
2926
|
-
).option(
|
|
2927
|
-
"--max-balance <argons>",
|
|
2928
|
-
"Use a maximum amount of the user's balance for the slot. If this ends in a percent, it will be a percent of the funds"
|
|
2929
|
-
).option("--bid-increment <argons>", "The bid increment", parseFloat, 0.01).option("--bid-delay <ticks>", "Delay between bids in ticks", parseInt, 0).option("--run-continuous", "Keep running and rebid every day").option(
|
|
2930
|
-
"--proxy-for-address <address>",
|
|
2931
|
-
"The seed account to proxy for (eg: 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty)"
|
|
2932
|
-
).action(
|
|
2933
|
-
async ({
|
|
2934
|
-
maxSeats,
|
|
2935
|
-
runContinuous,
|
|
2936
|
-
maxBid,
|
|
2937
|
-
minBid,
|
|
2938
|
-
maxBalance,
|
|
2939
|
-
bidDelay,
|
|
2940
|
-
bidIncrement,
|
|
2941
|
-
proxyForAddress
|
|
2942
|
-
}) => {
|
|
2943
|
-
const accountset = await accountsetFromCli(program2, proxyForAddress);
|
|
2944
|
-
let cohortBidder;
|
|
2945
|
-
const miningBids = new MiningBids(accountset.client, false);
|
|
2946
|
-
const maxCohortSize = await miningBids.maxCohortSize();
|
|
2947
|
-
const stopBidder = async (unsubscribe2) => {
|
|
2948
|
-
if (cohortBidder) {
|
|
2949
|
-
const stats = await cohortBidder.stop();
|
|
2950
|
-
console.log("Final bidding result", {
|
|
2951
|
-
cohortId: cohortBidder.cohortId,
|
|
2952
|
-
...stats
|
|
2953
|
-
});
|
|
2954
|
-
cohortBidder = void 0;
|
|
2955
|
-
if (!runContinuous) {
|
|
2956
|
-
unsubscribe2();
|
|
2957
|
-
process.exit();
|
|
2958
|
-
}
|
|
2959
|
-
}
|
|
2960
|
-
};
|
|
2961
|
-
const { unsubscribe } = await miningBids.onCohortChange({
|
|
2962
|
-
async onBiddingEnd(cohortId) {
|
|
2963
|
-
if (cohortBidder?.cohortId === cohortId) {
|
|
2964
|
-
await stopBidder(unsubscribe);
|
|
2965
|
-
}
|
|
2966
|
-
},
|
|
2967
|
-
async onBiddingStart(cohortId) {
|
|
2968
|
-
const seatsToWin = maxSeats ?? maxCohortSize;
|
|
2969
|
-
const balance = await accountset.balance();
|
|
2970
|
-
const feeWiggleRoom = BigInt(25e3);
|
|
2971
|
-
const amountAvailable = balance - feeWiggleRoom;
|
|
2972
|
-
let maxBidAmount = maxBid ? BigInt(maxBid * MICROGONS_PER_ARGON) : void 0;
|
|
2973
|
-
let maxBalanceToUse = amountAvailable;
|
|
2974
|
-
if (maxBalance !== void 0) {
|
|
2975
|
-
if (maxBalance.endsWith("%")) {
|
|
2976
|
-
let maxBalancePercent = parseInt(maxBalance);
|
|
2977
|
-
let amountToBid = amountAvailable * BigInt(maxBalancePercent) / 100n;
|
|
2978
|
-
if (amountToBid > balance) {
|
|
2979
|
-
amountToBid = balance;
|
|
2980
|
-
}
|
|
2981
|
-
maxBalanceToUse = amountToBid;
|
|
2982
|
-
} else {
|
|
2983
|
-
maxBalanceToUse = BigInt(
|
|
2984
|
-
Math.floor(parseFloat(maxBalance) * MICROGONS_PER_ARGON)
|
|
2985
|
-
);
|
|
2986
|
-
}
|
|
2987
|
-
maxBidAmount ??= maxBalanceToUse / BigInt(seatsToWin);
|
|
2988
|
-
}
|
|
2989
|
-
if (maxBalanceToUse > amountAvailable) {
|
|
2990
|
-
maxBalanceToUse = amountAvailable;
|
|
2991
|
-
}
|
|
2992
|
-
if (!maxBidAmount) {
|
|
2993
|
-
console.error("No max bid amount set");
|
|
2994
|
-
process.exit(1);
|
|
2995
|
-
}
|
|
2996
|
-
const subaccountRange = await accountset.getAvailableMinerAccounts(seatsToWin);
|
|
2997
|
-
if (cohortBidder && cohortBidder?.cohortId !== cohortId) {
|
|
2998
|
-
await stopBidder(unsubscribe);
|
|
2999
|
-
}
|
|
3000
|
-
cohortBidder = new CohortBidder(
|
|
3001
|
-
accountset,
|
|
3002
|
-
cohortId,
|
|
3003
|
-
subaccountRange,
|
|
3004
|
-
{
|
|
3005
|
-
maxBid: maxBidAmount,
|
|
3006
|
-
minBid: BigInt((minBid ?? 0) * MICROGONS_PER_ARGON),
|
|
3007
|
-
bidIncrement: BigInt(
|
|
3008
|
-
Math.floor(bidIncrement * MICROGONS_PER_ARGON)
|
|
3009
|
-
),
|
|
3010
|
-
maxBudget: maxBalanceToUse,
|
|
3011
|
-
bidDelay
|
|
3012
|
-
}
|
|
3013
|
-
);
|
|
3014
|
-
await cohortBidder.start();
|
|
3015
|
-
}
|
|
3016
|
-
});
|
|
3017
|
-
}
|
|
3018
|
-
);
|
|
3019
|
-
program2.command("create-bid-proxy").description("Create a mining-bid proxy account for your main account").requiredOption(
|
|
3020
|
-
"--outfile <path>",
|
|
3021
|
-
"The file to use to store the proxy account json (eg: proxy.json)"
|
|
3022
|
-
).requiredOption(
|
|
3023
|
-
"--fee-argons <argons>",
|
|
3024
|
-
"How many argons should be sent to the proxy account for fees (proxies must pay fees)",
|
|
3025
|
-
parseFloat
|
|
3026
|
-
).option(
|
|
3027
|
-
"--proxy-passphrase <passphrase>",
|
|
3028
|
-
"The passphrase for your proxy account"
|
|
3029
|
-
).action(async ({ outfile, proxyPassphrase, feeArgons }) => {
|
|
3030
|
-
const { mainchainUrl } = globalOptions(program2);
|
|
3031
|
-
const client = await getClient(mainchainUrl);
|
|
3032
|
-
const keyringPair = await saveKeyringPair({
|
|
3033
|
-
filePath: outfile,
|
|
3034
|
-
passphrase: proxyPassphrase
|
|
3035
|
-
});
|
|
3036
|
-
const address = keyringPair.address;
|
|
3037
|
-
console.log(
|
|
3038
|
-
`\u2705 Created proxy account at "${outfile}" with address ${address}`
|
|
3039
|
-
);
|
|
3040
|
-
const tx = client.tx.utility.batchAll([
|
|
3041
|
-
client.tx.proxy.addProxy(address, "MiningBid", 0),
|
|
3042
|
-
client.tx.balances.transferAllowDeath(
|
|
3043
|
-
address,
|
|
3044
|
-
BigInt(feeArgons * MICROGONS_PER_ARGON)
|
|
3045
|
-
)
|
|
3046
|
-
]);
|
|
3047
|
-
let keypair;
|
|
3048
|
-
try {
|
|
3049
|
-
const accountset = await accountsetFromCli(program2);
|
|
3050
|
-
keypair = accountset.txSubmitterPair;
|
|
3051
|
-
} catch (e) {
|
|
3052
|
-
const polkadotLink = `https://polkadot.js.org/apps/?rpc=${mainchainUrl}#/extrinsics/decode/${tx.toHex()}`;
|
|
3053
|
-
console.log(`Complete the registration at this link:`, polkadotLink);
|
|
3054
|
-
process.exit(0);
|
|
3055
|
-
}
|
|
3056
|
-
try {
|
|
3057
|
-
await new TxSubmitter(client, tx, keypair).submit({
|
|
3058
|
-
waitForBlock: true
|
|
3059
|
-
});
|
|
3060
|
-
console.log("Mining bid proxy added and funded.");
|
|
3061
|
-
process.exit();
|
|
3062
|
-
} catch (error) {
|
|
3063
|
-
console.error("Error adding mining proxy", error);
|
|
3064
|
-
process.exit(1);
|
|
3065
|
-
}
|
|
3066
|
-
});
|
|
3067
|
-
return program2;
|
|
3068
|
-
}
|
|
3069
|
-
|
|
3070
|
-
// src/clis/liquidityCli.ts
|
|
3071
|
-
import { Command as Command4 } from "@commander-js/extra-typings";
|
|
3072
|
-
function liquidityCli() {
|
|
3073
|
-
const program2 = new Command4("liquidity-pools").description(
|
|
3074
|
-
"Monitor or bond to liquidity pools"
|
|
3075
|
-
);
|
|
3076
|
-
program2.command("list", { isDefault: true }).description("Show or watch the vault bid pool rewards").action(async () => {
|
|
3077
|
-
const accountset = await accountsetFromCli(program2);
|
|
3078
|
-
const bidPool = new BidPool(
|
|
3079
|
-
accountset.client,
|
|
3080
|
-
accountset.txSubmitterPair
|
|
3081
|
-
);
|
|
3082
|
-
await bidPool.watch();
|
|
3083
|
-
});
|
|
3084
|
-
program2.command("bond").description("Bond argons to a liquidity pool").requiredOption("-v, --vault-id <id>", "The vault id to use", parseInt).requiredOption(
|
|
3085
|
-
"-a, --argons <amount>",
|
|
3086
|
-
"The number of argons to set the vault to",
|
|
3087
|
-
parseFloat
|
|
3088
|
-
).option(
|
|
3089
|
-
"--tip <amount>",
|
|
3090
|
-
"The tip to include with the transaction",
|
|
3091
|
-
parseFloat
|
|
3092
|
-
).action(async ({ tip, argons, vaultId }) => {
|
|
3093
|
-
const accountset = await accountsetFromCli(program2);
|
|
3094
|
-
const resolvedTip = tip ? BigInt(tip * MICROGONS_PER_ARGON) : 0n;
|
|
3095
|
-
const microgons = BigInt(argons * MICROGONS_PER_ARGON);
|
|
3096
|
-
const bidPool = new BidPool(
|
|
3097
|
-
accountset.client,
|
|
3098
|
-
accountset.txSubmitterPair
|
|
3099
|
-
);
|
|
3100
|
-
await bidPool.bondArgons(vaultId, microgons, { tip: resolvedTip });
|
|
3101
|
-
console.log("Bonded argons to liquidity pool bond");
|
|
3102
|
-
process.exit();
|
|
3103
|
-
});
|
|
3104
|
-
program2.command("wait-for-space").description(
|
|
3105
|
-
"Add bonded argons to a liquidity pool when the market rate is favorable"
|
|
3106
|
-
).requiredOption(
|
|
3107
|
-
"--max-argons <amount>",
|
|
3108
|
-
"Max daily argons to use per slot",
|
|
3109
|
-
parseFloat
|
|
3110
|
-
).option(
|
|
3111
|
-
"--min-pct-sharing <percent>",
|
|
3112
|
-
"The minimum profit sharing percent to allow",
|
|
3113
|
-
parseInt,
|
|
3114
|
-
100
|
|
3115
|
-
).option(
|
|
3116
|
-
"--tip <amount>",
|
|
3117
|
-
"The tip to include with the transaction",
|
|
3118
|
-
parseFloat
|
|
3119
|
-
).action(async ({ maxArgons, minPctSharing, tip }) => {
|
|
3120
|
-
const maxAmountPerSlot = BigInt(maxArgons * MICROGONS_PER_ARGON);
|
|
3121
|
-
const accountset = await accountsetFromCli(program2);
|
|
3122
|
-
const vaults = new VaultMonitor(
|
|
3123
|
-
accountset,
|
|
3124
|
-
{
|
|
3125
|
-
liquidityPoolSpaceAvailable: 1000000n
|
|
3126
|
-
},
|
|
3127
|
-
{ shouldLog: false }
|
|
3128
|
-
);
|
|
3129
|
-
const bidPool = new BidPool(
|
|
3130
|
-
accountset.client,
|
|
3131
|
-
accountset.txSubmitterPair
|
|
3132
|
-
);
|
|
3133
|
-
const resolvedTip = tip ? BigInt(tip * MICROGONS_PER_ARGON) : 0n;
|
|
3134
|
-
console.log("Waiting for liquidity pool space...");
|
|
3135
|
-
vaults.events.on(
|
|
3136
|
-
"liquidity-pool-space-above",
|
|
3137
|
-
async (vaultId, amount) => {
|
|
3138
|
-
const vault = vaults.vaultsById[vaultId];
|
|
3139
|
-
if (vault.terms.liquidityPoolProfitSharing.times(100).toNumber() < minPctSharing) {
|
|
3140
|
-
console.info(
|
|
3141
|
-
`Skipping vault ${vaultId} due to lower profit sharing than ${minPctSharing}%`
|
|
3142
|
-
);
|
|
3143
|
-
return;
|
|
3144
|
-
}
|
|
3145
|
-
let amountToAdd = amount;
|
|
3146
|
-
if (amountToAdd > maxAmountPerSlot) {
|
|
3147
|
-
amountToAdd = maxAmountPerSlot;
|
|
3148
|
-
}
|
|
3149
|
-
await bidPool.bondArgons(vaultId, amountToAdd, { tip: resolvedTip });
|
|
3150
|
-
console.log("Bonding argons to vault liquidity pool", {
|
|
3151
|
-
vaultId,
|
|
3152
|
-
amount: formatArgons(amountToAdd)
|
|
3153
|
-
});
|
|
3154
|
-
}
|
|
3155
|
-
);
|
|
3156
|
-
await vaults.monitor();
|
|
3157
|
-
});
|
|
3158
|
-
return program2;
|
|
3159
|
-
}
|
|
3160
|
-
|
|
3161
|
-
// src/clis/bitcoinCli.ts
|
|
3162
|
-
import { Command as Command5 } from "@commander-js/extra-typings";
|
|
3163
|
-
function bitcoinCli() {
|
|
3164
|
-
const program2 = new Command5("bitcoin").description("Wait for bitcoin space");
|
|
3165
|
-
program2.command("watch").requiredOption(
|
|
3166
|
-
"-a, --argons <argons>",
|
|
3167
|
-
"Alert when bitcoin space exceeds this amount",
|
|
3168
|
-
parseFloat
|
|
3169
|
-
).description("Watch for bitcoin space available").action(async ({ argons }) => {
|
|
3170
|
-
const accountset = await accountsetFromCli(program2);
|
|
3171
|
-
const bot = new VaultMonitor(accountset, {
|
|
3172
|
-
bitcoinSpaceAvailable: argons ? BigInt(argons * MICROGONS_PER_ARGON) : 1n
|
|
3173
|
-
});
|
|
3174
|
-
bot.events.on("bitcoin-space-above", async (vaultId, amount) => {
|
|
3175
|
-
const vault = bot.vaultsById[vaultId];
|
|
3176
|
-
const fee = vault.calculateBitcoinFee(amount);
|
|
3177
|
-
const ratio = 100n * fee / amount;
|
|
3178
|
-
console.log(
|
|
3179
|
-
`Vault ${vaultId} has ${formatArgons(amount)} argons available for bitcoin. Fee ratio is ${ratio}%`
|
|
3180
|
-
);
|
|
3181
|
-
});
|
|
3182
|
-
await bot.monitor();
|
|
3183
|
-
});
|
|
3184
|
-
program2.command("wait-for-space").description("Lock bitcoin when available at a given rate").requiredOption(
|
|
3185
|
-
"-a, --argons <amount>",
|
|
3186
|
-
"Bitcoin argons needed. NOTE: your account must have enough to cover fees + tip after this amount.",
|
|
3187
|
-
parseFloat
|
|
3188
|
-
).requiredOption(
|
|
3189
|
-
"--bitcoin-xpub <xpub>",
|
|
3190
|
-
"The xpub key to use for bitcoin locking"
|
|
3191
|
-
).option(
|
|
3192
|
-
"--max-lock-fee <argons>",
|
|
3193
|
-
"The max lock fee you're willing to pay",
|
|
3194
|
-
parseFloat
|
|
3195
|
-
).option(
|
|
3196
|
-
"--tip <amount>",
|
|
3197
|
-
"The tip to include with the transaction",
|
|
3198
|
-
parseFloat,
|
|
3199
|
-
0
|
|
3200
|
-
).action(async ({ argons, bitcoinXpub, maxLockFee, tip }) => {
|
|
3201
|
-
const amountToLock = BigInt(argons * MICROGONS_PER_ARGON);
|
|
3202
|
-
const accountset = await accountsetFromCli(program2);
|
|
3203
|
-
await BitcoinLocks.waitForSpace(accountset, {
|
|
3204
|
-
argonAmount: amountToLock,
|
|
3205
|
-
bitcoinXpub,
|
|
3206
|
-
maxLockFee: maxLockFee !== void 0 ? BigInt(maxLockFee * MICROGONS_PER_ARGON) : void 0,
|
|
3207
|
-
tip: BigInt(tip * MICROGONS_PER_ARGON)
|
|
3208
|
-
}).then(({ vaultId, satoshis, txFee, btcFee }) => {
|
|
3209
|
-
console.log(
|
|
3210
|
-
`Locked ${satoshis} satoshis in vault ${vaultId}. Tx fee=${formatArgons(
|
|
3211
|
-
txFee
|
|
3212
|
-
)}, Lock fee=${formatArgons(btcFee)}.`
|
|
3213
|
-
);
|
|
3214
|
-
process.exit(0);
|
|
3215
|
-
});
|
|
3216
|
-
});
|
|
3217
|
-
return program2;
|
|
3218
|
-
}
|
|
3219
|
-
|
|
3220
|
-
// src/clis/keyringStore.ts
|
|
3221
|
-
import { promises } from "node:fs";
|
|
3222
|
-
import * as os from "node:os";
|
|
3223
|
-
var { readFile, writeFile } = promises;
|
|
3224
|
-
async function keyringFromFile(opts) {
|
|
3225
|
-
if (!opts.filePath) {
|
|
3226
|
-
throw new Error(
|
|
3227
|
-
"No ACCOUNT account loaded (either ACCOUNT_SURI or ACCOUNT_JSON_PATH required)"
|
|
3228
|
-
);
|
|
3229
|
-
}
|
|
3230
|
-
const path = opts.filePath.replace("~", os.homedir());
|
|
3231
|
-
const json = JSON.parse(await readFile(path, "utf-8"));
|
|
3232
|
-
let passphrase = opts.passphrase;
|
|
3233
|
-
if (opts.passphraseFile) {
|
|
3234
|
-
const passphrasePath = opts.passphraseFile.replace("~", os.homedir());
|
|
3235
|
-
passphrase = await readFile(passphrasePath, "utf-8");
|
|
3236
|
-
}
|
|
3237
|
-
const mainAccount = new Keyring().createFromJson(json);
|
|
3238
|
-
mainAccount.decodePkcs8(passphrase);
|
|
3239
|
-
return mainAccount;
|
|
3240
|
-
}
|
|
3241
|
-
async function saveKeyringPair(opts) {
|
|
3242
|
-
const { filePath, passphrase, cryptoType } = opts;
|
|
3243
|
-
const keyring = createKeyringPair({ cryptoType });
|
|
3244
|
-
if (filePath) {
|
|
3245
|
-
const json = keyring.toJson(passphrase);
|
|
3246
|
-
await writeFile(filePath, JSON.stringify(json, null, 2));
|
|
3247
|
-
}
|
|
3248
|
-
return keyring;
|
|
3249
|
-
}
|
|
3250
|
-
|
|
3251
|
-
// src/clis/index.ts
|
|
3252
|
-
function globalOptions(program2) {
|
|
3253
|
-
return program2.optsWithGlobals();
|
|
3254
|
-
}
|
|
3255
|
-
function buildCli() {
|
|
3256
|
-
return new Command6("Argon CLI").option("-e, --env <path>", "The path to the account .env file to load").addOption(
|
|
3257
|
-
new Option2("-u, --mainchain-url <url>", "The mainchain URL to connect to").default("wss://rpc.argon.network").env("MAINCHAIN_URL")
|
|
3258
|
-
).addOption(
|
|
3259
|
-
new Option2(
|
|
3260
|
-
"--account-file-path <jsonPath>",
|
|
3261
|
-
"The path to your json seed file from polkadotjs"
|
|
3262
|
-
).env("ACCOUNT_JSON_PATH")
|
|
3263
|
-
).addOption(
|
|
3264
|
-
new Option2(
|
|
3265
|
-
"--account-suri <secretUri>",
|
|
3266
|
-
"A secret uri (suri) to use for the account"
|
|
3267
|
-
).env("ACCOUNT_SURI")
|
|
3268
|
-
).addOption(
|
|
3269
|
-
new Option2(
|
|
3270
|
-
"--account-passphrase <password>",
|
|
3271
|
-
"The password for your seed file"
|
|
3272
|
-
).env("ACCOUNT_PASSPHRASE")
|
|
3273
|
-
).addOption(
|
|
3274
|
-
new Option2(
|
|
3275
|
-
"--account-passphrase-file <path>",
|
|
3276
|
-
"The path to a password for your seed file"
|
|
3277
|
-
)
|
|
3278
|
-
).addOption(
|
|
3279
|
-
new Option2(
|
|
3280
|
-
"-s, --subaccounts <range>",
|
|
3281
|
-
"Restrict this operation to a subset of the subaccounts (eg, 0-10)"
|
|
3282
|
-
).env("SUBACCOUNT_RANGE").argParser(parseSubaccountRange)
|
|
3283
|
-
).addCommand(accountCli()).addCommand(vaultCli()).addCommand(miningCli()).addCommand(liquidityCli()).addCommand(bitcoinCli());
|
|
3284
|
-
}
|
|
3285
|
-
async function accountsetFromCli(program2, proxyForAddress) {
|
|
3286
|
-
const opts = program2.parent?.optsWithGlobals();
|
|
3287
|
-
let keypair;
|
|
3288
|
-
if (opts.accountSuri) {
|
|
3289
|
-
keypair = keyringFromSuri(opts.accountSuri);
|
|
3290
|
-
}
|
|
3291
|
-
if (opts.accountFilePath) {
|
|
3292
|
-
keypair = await keyringFromFile({
|
|
3293
|
-
filePath: opts.accountFilePath,
|
|
3294
|
-
passphrase: opts.accountPassphrase,
|
|
3295
|
-
passphraseFile: opts.accountPassphraseFile
|
|
3296
|
-
});
|
|
3297
|
-
}
|
|
3298
|
-
if (!keypair) {
|
|
3299
|
-
throw new Error(
|
|
3300
|
-
"No ACCOUNT account loaded (either ACCOUNT_SURI or ACCOUNT_JSON_PATH required)"
|
|
3301
|
-
);
|
|
3302
|
-
}
|
|
3303
|
-
const client = getClient(opts.mainchainUrl);
|
|
3304
|
-
if (proxyForAddress) {
|
|
3305
|
-
return new Accountset({
|
|
3306
|
-
client,
|
|
3307
|
-
isProxy: true,
|
|
3308
|
-
seedAddress: proxyForAddress,
|
|
3309
|
-
txSubmitter: keypair
|
|
3310
|
-
});
|
|
3311
|
-
} else {
|
|
3312
|
-
return new Accountset({
|
|
3313
|
-
seedAccount: keypair,
|
|
3314
|
-
client
|
|
3315
|
-
});
|
|
3316
|
-
}
|
|
3317
|
-
}
|
|
3318
|
-
function addGlobalArgs(program2) {
|
|
3319
|
-
for (const command of program2.commands) {
|
|
3320
|
-
command.configureHelp({
|
|
3321
|
-
showGlobalOptions: true
|
|
3322
|
-
});
|
|
3323
|
-
for (const nested of command.commands) {
|
|
3324
|
-
nested.configureHelp({
|
|
3325
|
-
showGlobalOptions: true
|
|
3326
|
-
});
|
|
3327
|
-
}
|
|
3328
|
-
}
|
|
3329
|
-
}
|
|
3330
|
-
function applyEnv(program2) {
|
|
3331
|
-
program2.parseOptions(process.argv);
|
|
3332
|
-
const { env: env3 } = program2.optsWithGlobals();
|
|
3333
|
-
if (env3) {
|
|
3334
|
-
const envPath = Path.resolve(process.cwd(), env3);
|
|
3335
|
-
const res = configDotenv({ path: envPath });
|
|
3336
|
-
if (res.parsed?.ACCOUNT_JSON_PATH) {
|
|
3337
|
-
process.env.ACCOUNT_JSON_PATH = Path.relative(
|
|
3338
|
-
envPath,
|
|
3339
|
-
process.env.ACCOUNT_JSON_PATH
|
|
3340
|
-
);
|
|
3341
|
-
}
|
|
3342
|
-
}
|
|
3343
|
-
return env3;
|
|
3344
|
-
}
|
|
3
|
+
addGlobalArgs,
|
|
4
|
+
applyEnv,
|
|
5
|
+
buildCli
|
|
6
|
+
} from "./chunk-BQR6FEVP.js";
|
|
7
|
+
import "./chunk-RXCQYVE7.js";
|
|
3345
8
|
|
|
3346
9
|
// src/cli.ts
|
|
3347
10
|
var program = buildCli();
|