@helium/helium-admin-cli 0.11.6 → 0.11.7
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/lib/cjs/claim-and-close-subscriber-key-to-assets.js +664 -0
- package/lib/cjs/claim-and-close-subscriber-key-to-assets.js.map +1 -0
- package/lib/cjs/close-all-subscriber-recipients.js +329 -0
- package/lib/cjs/close-all-subscriber-recipients.js.map +1 -0
- package/lib/cjs/close-legacy-auto-topoff.js +16 -11
- package/lib/cjs/close-legacy-auto-topoff.js.map +1 -1
- package/lib/cjs/extend-common-lut.js +20 -17
- package/lib/cjs/extend-common-lut.js.map +1 -1
- package/lib/cjs/issue-service-rewards-nft.js +112 -0
- package/lib/cjs/issue-service-rewards-nft.js.map +1 -0
- package/lib/cjs/reschedule-all-mini-fanouts.js +3 -5
- package/lib/cjs/reschedule-all-mini-fanouts.js.map +1 -1
- package/lib/cjs/setup-dc-auto-topoff.js +112 -49
- package/lib/cjs/setup-dc-auto-topoff.js.map +1 -1
- package/lib/cjs/utils.js +34 -18
- package/lib/cjs/utils.js.map +1 -1
- package/lib/esm/src/claim-and-close-subscriber-key-to-assets.js +616 -0
- package/lib/esm/src/claim-and-close-subscriber-key-to-assets.js.map +1 -0
- package/lib/esm/src/close-all-subscriber-recipients.js +287 -0
- package/lib/esm/src/close-all-subscriber-recipients.js.map +1 -0
- package/lib/esm/src/close-legacy-auto-topoff.js +19 -14
- package/lib/esm/src/close-legacy-auto-topoff.js.map +1 -1
- package/lib/esm/src/extend-common-lut.js +21 -18
- package/lib/esm/src/extend-common-lut.js.map +1 -1
- package/lib/esm/src/issue-service-rewards-nft.js +71 -0
- package/lib/esm/src/issue-service-rewards-nft.js.map +1 -0
- package/lib/esm/src/reschedule-all-mini-fanouts.js +3 -5
- package/lib/esm/src/reschedule-all-mini-fanouts.js.map +1 -1
- package/lib/esm/src/setup-dc-auto-topoff.js +121 -57
- package/lib/esm/src/setup-dc-auto-topoff.js.map +1 -1
- package/lib/esm/src/utils.js +35 -19
- package/lib/esm/src/utils.js.map +1 -1
- package/lib/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/lib/types/src/claim-and-close-subscriber-key-to-assets.d.ts +2 -0
- package/lib/types/src/claim-and-close-subscriber-key-to-assets.d.ts.map +1 -0
- package/lib/types/src/close-all-subscriber-recipients.d.ts +2 -0
- package/lib/types/src/close-all-subscriber-recipients.d.ts.map +1 -0
- package/lib/types/src/close-legacy-auto-topoff.d.ts.map +1 -1
- package/lib/types/src/extend-common-lut.d.ts.map +1 -1
- package/lib/types/src/issue-service-rewards-nft.d.ts +2 -0
- package/lib/types/src/issue-service-rewards-nft.d.ts.map +1 -0
- package/lib/types/src/reschedule-all-mini-fanouts.d.ts.map +1 -1
- package/lib/types/src/setup-dc-auto-topoff.d.ts.map +1 -1
- package/lib/types/src/utils.d.ts +2 -1
- package/lib/types/src/utils.d.ts.map +1 -1
- package/package.json +15 -15
|
@@ -0,0 +1,664 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.run = void 0;
|
|
39
|
+
const anchor = __importStar(require("@coral-xyz/anchor"));
|
|
40
|
+
const client = __importStar(require("@helium/distributor-oracle"));
|
|
41
|
+
const helium_entity_manager_sdk_1 = require("@helium/helium-entity-manager-sdk");
|
|
42
|
+
const helium_sub_daos_sdk_1 = require("@helium/helium-sub-daos-sdk");
|
|
43
|
+
const lazy_distributor_sdk_1 = require("@helium/lazy-distributor-sdk");
|
|
44
|
+
const rewards_oracle_sdk_1 = require("@helium/rewards-oracle-sdk");
|
|
45
|
+
const mobile_entity_manager_sdk_1 = require("@helium/mobile-entity-manager-sdk");
|
|
46
|
+
const spl_utils_1 = require("@helium/spl-utils");
|
|
47
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
48
|
+
const bn_js_1 = __importDefault(require("bn.js"));
|
|
49
|
+
const bs58_1 = __importDefault(require("bs58"));
|
|
50
|
+
const os_1 = __importDefault(require("os"));
|
|
51
|
+
const p_limit_1 = __importDefault(require("p-limit"));
|
|
52
|
+
const yargs_1 = __importDefault(require("yargs/yargs"));
|
|
53
|
+
const utils_1 = require("./utils");
|
|
54
|
+
// Hardcoded key_to_asset addresses from initial migration that need special handling
|
|
55
|
+
// These can be closed even if they have iot_info or mobile_info accounts
|
|
56
|
+
const HARDCODED_KEY_TO_ASSETS = [
|
|
57
|
+
new web3_js_1.PublicKey("AcKpRTmy6YKpQaWfLDBUaduQU1kHhNVLrPkW3TmEEqsc"),
|
|
58
|
+
new web3_js_1.PublicKey("3stUgrUq4j5BbamGdy7X2Y3dee24EeY5u1F7RHrrmaoP"),
|
|
59
|
+
new web3_js_1.PublicKey("4v7nfEN2Wj342Zm6V1Jwk9i5YCUHu6zBAJFENk6Gxzvr"),
|
|
60
|
+
new web3_js_1.PublicKey("2RtR6aVt6QgCSdV8LEH6ogWtDXGJpL73aB72DevJKgFC"),
|
|
61
|
+
];
|
|
62
|
+
function run(args = process.argv) {
|
|
63
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
64
|
+
const yarg = (0, yargs_1.default)(args).options({
|
|
65
|
+
wallet: {
|
|
66
|
+
alias: "k",
|
|
67
|
+
describe: "Anchor wallet keypair",
|
|
68
|
+
default: `${os_1.default.homedir()}/.config/solana/id.json`,
|
|
69
|
+
},
|
|
70
|
+
url: {
|
|
71
|
+
alias: "u",
|
|
72
|
+
default: "http://127.0.0.1:8899",
|
|
73
|
+
describe: "The solana url",
|
|
74
|
+
},
|
|
75
|
+
authority: {
|
|
76
|
+
type: "string",
|
|
77
|
+
describe: "Path to the authority keypair. Defaults to wallet.",
|
|
78
|
+
},
|
|
79
|
+
commit: {
|
|
80
|
+
type: "boolean",
|
|
81
|
+
describe: "Actually claim and close accounts. Otherwise dry-run",
|
|
82
|
+
default: false,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
const argv = yield yarg.argv;
|
|
86
|
+
process.env.ANCHOR_WALLET = argv.wallet;
|
|
87
|
+
process.env.ANCHOR_PROVIDER_URL = argv.url;
|
|
88
|
+
anchor.setProvider(anchor.AnchorProvider.local(argv.url));
|
|
89
|
+
const provider = anchor.getProvider();
|
|
90
|
+
const lazyProgram = yield (0, lazy_distributor_sdk_1.init)(provider);
|
|
91
|
+
const hemProgram = yield (0, helium_entity_manager_sdk_1.init)(provider);
|
|
92
|
+
const rewardsOracleProgram = yield (0, rewards_oracle_sdk_1.init)(provider);
|
|
93
|
+
const memProgram = yield (0, mobile_entity_manager_sdk_1.init)(provider);
|
|
94
|
+
const authority = argv.authority
|
|
95
|
+
? (0, utils_1.loadKeypair)(argv.authority)
|
|
96
|
+
: (0, utils_1.loadKeypair)(argv.wallet);
|
|
97
|
+
const dao = (0, helium_sub_daos_sdk_1.daoKey)(spl_utils_1.HNT_MINT)[0];
|
|
98
|
+
const mobileSubDao = (0, helium_sub_daos_sdk_1.subDaoKey)(spl_utils_1.MOBILE_MINT)[0];
|
|
99
|
+
const iotSubDao = (0, helium_sub_daos_sdk_1.subDaoKey)(spl_utils_1.IOT_MINT)[0];
|
|
100
|
+
const mobileConfig = (0, helium_entity_manager_sdk_1.rewardableEntityConfigKey)(mobileSubDao, "MOBILE")[0];
|
|
101
|
+
const iotConfig = (0, helium_entity_manager_sdk_1.rewardableEntityConfigKey)(iotSubDao, "IOT")[0];
|
|
102
|
+
const lazyDistributor = (0, lazy_distributor_sdk_1.lazyDistributorKey)(spl_utils_1.MOBILE_MINT)[0];
|
|
103
|
+
// ========== STEP 1: Find all subscriber assets AND hardcoded key_to_assets ==========
|
|
104
|
+
console.log("=== STEP 1: FINDING KEY_TO_ASSET ACCOUNTS ===\n");
|
|
105
|
+
// Build list of key_to_asset info
|
|
106
|
+
const keyToAssetInfos = [];
|
|
107
|
+
// Process hardcoded key_to_assets FIRST
|
|
108
|
+
// Note: We fetch these to get assetId and asset data (for entityKey)
|
|
109
|
+
console.log(`Checking ${HARDCODED_KEY_TO_ASSETS.length} hardcoded accounts...`);
|
|
110
|
+
const hardcodedAccounts = yield Promise.all(HARDCODED_KEY_TO_ASSETS.map((keyToAssetAddr) => __awaiter(this, void 0, void 0, function* () {
|
|
111
|
+
try {
|
|
112
|
+
const keyToAssetAcc = yield hemProgram.account.keyToAssetV0.fetch(keyToAssetAddr);
|
|
113
|
+
return {
|
|
114
|
+
keyToAsset: keyToAssetAddr,
|
|
115
|
+
assetId: keyToAssetAcc.asset,
|
|
116
|
+
entityKey: Buffer.from(keyToAssetAcc.entityKey),
|
|
117
|
+
isHardcoded: true,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
return null; // Already closed
|
|
122
|
+
}
|
|
123
|
+
})));
|
|
124
|
+
keyToAssetInfos.push(...hardcodedAccounts.filter((a) => a !== null));
|
|
125
|
+
if (keyToAssetInfos.length > 0) {
|
|
126
|
+
console.log(` Found ${keyToAssetInfos.length} hardcoded key_to_assets\n`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
console.log(` All hardcoded accounts already closed\n`);
|
|
130
|
+
}
|
|
131
|
+
// Find all subscriber assets using DAS
|
|
132
|
+
// Strategy: Fetch all MakerV0 accounts, identify subscriber collections, search by those
|
|
133
|
+
console.log("Finding all subscriber assets via DAS...");
|
|
134
|
+
const allSubscriberAssets = new Map(); // Dedupe by asset ID
|
|
135
|
+
try {
|
|
136
|
+
// Fetch all CarrierV0 accounts to get subscriber collections
|
|
137
|
+
console.log("Fetching CarrierV0 accounts...");
|
|
138
|
+
const carriers = yield memProgram.account.carrierV0.all();
|
|
139
|
+
const subscriberCollections = new Set();
|
|
140
|
+
for (const carrier of carriers) {
|
|
141
|
+
subscriberCollections.add(carrier.account.collection.toBase58());
|
|
142
|
+
}
|
|
143
|
+
console.log(` Found ${subscriberCollections.size} subscriber collection(s) from ${carriers.length} carriers`);
|
|
144
|
+
if (subscriberCollections.size === 0) {
|
|
145
|
+
console.log(" No subscriber collections found");
|
|
146
|
+
}
|
|
147
|
+
// Search each subscriber collection using cursor-based pagination
|
|
148
|
+
console.log("Searching DAS for subscriber assets...");
|
|
149
|
+
// Retry wrapper for DAS calls with exponential backoff
|
|
150
|
+
function getAssetsWithRetry(endpoint, params, maxRetries = 5) {
|
|
151
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
152
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
153
|
+
try {
|
|
154
|
+
return yield (0, spl_utils_1.getAssetsByGroup)(endpoint, params);
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
if (attempt === maxRetries - 1) {
|
|
158
|
+
throw err;
|
|
159
|
+
}
|
|
160
|
+
// Exponential backoff: 2s, 4s, 8s, 16s, 30s
|
|
161
|
+
const delay = Math.min(2000 * Math.pow(2, attempt), 30000);
|
|
162
|
+
const errorMsg = err.message || err.toString();
|
|
163
|
+
console.log(` Error: ${errorMsg.substring(0, 80)}... retrying in ${delay / 1000}s (attempt ${attempt + 1}/${maxRetries})`);
|
|
164
|
+
yield new Promise((resolve) => setTimeout(resolve, delay));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
throw new Error("Max retries exceeded");
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
for (const collection of subscriberCollections) {
|
|
171
|
+
const limit = 1000;
|
|
172
|
+
const startSize = allSubscriberAssets.size;
|
|
173
|
+
let cursor = undefined;
|
|
174
|
+
let fetchCount = 0;
|
|
175
|
+
const startTime = Date.now();
|
|
176
|
+
console.log(` Collection ${collection}: Starting...`);
|
|
177
|
+
while (true) {
|
|
178
|
+
const result = yield getAssetsWithRetry(provider.connection.rpcEndpoint, {
|
|
179
|
+
groupValue: collection,
|
|
180
|
+
limit,
|
|
181
|
+
cursor,
|
|
182
|
+
});
|
|
183
|
+
for (const asset of result.items) {
|
|
184
|
+
allSubscriberAssets.set(asset.id.toBase58(), asset);
|
|
185
|
+
}
|
|
186
|
+
fetchCount++;
|
|
187
|
+
const collectionTotal = allSubscriberAssets.size - startSize;
|
|
188
|
+
// Show progress: first 3 fetches, then every 25 fetches
|
|
189
|
+
if (fetchCount <= 3 || fetchCount % 25 === 0) {
|
|
190
|
+
console.log(` ${fetchCount} fetches: ${collectionTotal.toLocaleString()} assets`);
|
|
191
|
+
}
|
|
192
|
+
// Stop if no cursor returned (indicates end of data)
|
|
193
|
+
if (!result.cursor) {
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
cursor = result.cursor;
|
|
197
|
+
// Small delay to avoid rate limits (100ms between fetches)
|
|
198
|
+
yield new Promise((resolve) => setTimeout(resolve, 100));
|
|
199
|
+
}
|
|
200
|
+
const thisCollectionTotal = allSubscriberAssets.size - startSize;
|
|
201
|
+
const totalTime = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
202
|
+
console.log(` Complete: ${thisCollectionTotal.toLocaleString()} assets in ${fetchCount} fetches (${totalTime}s, cumulative: ${allSubscriberAssets.size.toLocaleString()})`);
|
|
203
|
+
}
|
|
204
|
+
console.log(`\nTotal unique subscriber assets found: ${allSubscriberAssets.size}`);
|
|
205
|
+
console.log("Processing assets into key_to_asset accounts...");
|
|
206
|
+
const existingKeyToAssetSet = new Set(keyToAssetInfos.map((k) => k.keyToAsset.toBase58()));
|
|
207
|
+
let processed = 0;
|
|
208
|
+
for (const asset of allSubscriberAssets.values()) {
|
|
209
|
+
const keyToAssetAddr = (0, helium_entity_manager_sdk_1.keyToAssetForAsset)(asset, dao);
|
|
210
|
+
// Ensure keyToAssetAddr is a PublicKey
|
|
211
|
+
const keyToAssetPubkey = typeof keyToAssetAddr === "string"
|
|
212
|
+
? new web3_js_1.PublicKey(keyToAssetAddr)
|
|
213
|
+
: keyToAssetAddr;
|
|
214
|
+
// Skip if already in list (from hardcoded)
|
|
215
|
+
const keyToAssetStr = keyToAssetPubkey.toBase58();
|
|
216
|
+
if (!existingKeyToAssetSet.has(keyToAssetStr)) {
|
|
217
|
+
keyToAssetInfos.push({
|
|
218
|
+
keyToAsset: keyToAssetPubkey,
|
|
219
|
+
assetId: asset.id,
|
|
220
|
+
asset: asset,
|
|
221
|
+
isHardcoded: false,
|
|
222
|
+
});
|
|
223
|
+
existingKeyToAssetSet.add(keyToAssetStr);
|
|
224
|
+
}
|
|
225
|
+
processed++;
|
|
226
|
+
// Show progress every 100k assets
|
|
227
|
+
if (processed % 100000 === 0) {
|
|
228
|
+
console.log(` Processed ${processed.toLocaleString()} / ${allSubscriberAssets.size.toLocaleString()} assets...`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
console.log(` Processed all ${allSubscriberAssets.size.toLocaleString()} assets\n`);
|
|
232
|
+
// Free memory - no longer need full asset objects or dedup set
|
|
233
|
+
allSubscriberAssets.clear();
|
|
234
|
+
existingKeyToAssetSet.clear();
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
console.log(`\n⚠️ DAS search failed: ${err.message}`);
|
|
238
|
+
console.log(` Continuing with ${keyToAssetInfos.length} hardcoded key_to_assets only.`);
|
|
239
|
+
}
|
|
240
|
+
const hardcodedCount = keyToAssetInfos.filter((k) => k.isHardcoded).length;
|
|
241
|
+
const subscriberCount = keyToAssetInfos.filter((k) => !k.isHardcoded).length;
|
|
242
|
+
console.log(`\nTotal: ${keyToAssetInfos.length.toLocaleString()} key_to_asset accounts (${hardcodedCount} hardcoded, ${subscriberCount.toLocaleString()} subscribers)`);
|
|
243
|
+
// Extract entity keys (try URI first, fetch as fallback)
|
|
244
|
+
console.log("Extracting entity keys from asset data...");
|
|
245
|
+
const existingKeyToAssets = [];
|
|
246
|
+
const needsFetch = [];
|
|
247
|
+
// Try to extract from URI first
|
|
248
|
+
keyToAssetInfos.forEach((info) => {
|
|
249
|
+
var _a, _b;
|
|
250
|
+
if (info.entityKey) {
|
|
251
|
+
// Hardcoded account - already have entity key
|
|
252
|
+
existingKeyToAssets.push({
|
|
253
|
+
keyToAsset: info.keyToAsset,
|
|
254
|
+
assetId: info.assetId,
|
|
255
|
+
entityKey: info.entityKey,
|
|
256
|
+
isHardcoded: info.isHardcoded,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
else if ((_b = (_a = info.asset) === null || _a === void 0 ? void 0 : _a.content) === null || _b === void 0 ? void 0 : _b.json_uri) {
|
|
260
|
+
// Try to extract from URI
|
|
261
|
+
const entityKeyStr = info.asset.content.json_uri.split("/").slice(-1)[0];
|
|
262
|
+
const cleanEntityKeyStr = entityKeyStr
|
|
263
|
+
.split(".")[0]
|
|
264
|
+
.split("?")[0]
|
|
265
|
+
.split("#")[0];
|
|
266
|
+
try {
|
|
267
|
+
const entityKey = Buffer.from(bs58_1.default.decode(cleanEntityKeyStr));
|
|
268
|
+
existingKeyToAssets.push({
|
|
269
|
+
keyToAsset: info.keyToAsset,
|
|
270
|
+
assetId: info.assetId,
|
|
271
|
+
entityKey,
|
|
272
|
+
isHardcoded: info.isHardcoded,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
// URI extraction failed - need to fetch
|
|
277
|
+
needsFetch.push(info);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
// No URI - need to fetch
|
|
282
|
+
needsFetch.push(info);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
console.log(` Extracted ${existingKeyToAssets.length.toLocaleString()} entity keys from URIs`);
|
|
286
|
+
// Fetch the ones that failed URI extraction
|
|
287
|
+
if (needsFetch.length > 0) {
|
|
288
|
+
console.log(` Fetching ${needsFetch.length.toLocaleString()} keyToAssetV0 accounts (URI extraction failed)...`);
|
|
289
|
+
function fetchKeyToAssetsWithRetry(chunk, maxRetries = 3) {
|
|
290
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
291
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
292
|
+
try {
|
|
293
|
+
const accountInfos = yield hemProgram.account.keyToAssetV0.fetchMultiple(chunk.map((k) => k.keyToAsset));
|
|
294
|
+
return { accountInfos, chunk };
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
if (attempt === maxRetries - 1)
|
|
298
|
+
throw err;
|
|
299
|
+
const delay = Math.min(1000 * Math.pow(2, attempt), 5000);
|
|
300
|
+
yield new Promise((resolve) => setTimeout(resolve, delay));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
throw new Error("Max retries exceeded");
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
const keyToAssetChunks = (0, spl_utils_1.chunks)(needsFetch, 100);
|
|
307
|
+
const keyToAssetLimiter = (0, p_limit_1.default)(10);
|
|
308
|
+
let keyToAssetFetchedCount = 0;
|
|
309
|
+
let keyToAssetBatchIndex = 0;
|
|
310
|
+
const keyToAssetBatchResults = yield Promise.all(keyToAssetChunks.map((chunk) => keyToAssetLimiter(() => __awaiter(this, void 0, void 0, function* () {
|
|
311
|
+
const result = yield fetchKeyToAssetsWithRetry(chunk);
|
|
312
|
+
keyToAssetFetchedCount += chunk.length;
|
|
313
|
+
keyToAssetBatchIndex++;
|
|
314
|
+
// Show progress
|
|
315
|
+
if (keyToAssetBatchIndex <= 3 ||
|
|
316
|
+
keyToAssetBatchIndex % 10 === 0 ||
|
|
317
|
+
keyToAssetFetchedCount === needsFetch.length) {
|
|
318
|
+
console.log(` ${keyToAssetBatchIndex} batches: ${keyToAssetFetchedCount.toLocaleString()} accounts`);
|
|
319
|
+
}
|
|
320
|
+
return result;
|
|
321
|
+
}))));
|
|
322
|
+
// Process fetched accounts
|
|
323
|
+
keyToAssetBatchResults.forEach(({ accountInfos, chunk }) => {
|
|
324
|
+
accountInfos.forEach((account, index) => {
|
|
325
|
+
if (account) {
|
|
326
|
+
existingKeyToAssets.push({
|
|
327
|
+
keyToAsset: chunk[index].keyToAsset,
|
|
328
|
+
assetId: chunk[index].assetId,
|
|
329
|
+
entityKey: Buffer.from(account.entityKey),
|
|
330
|
+
isHardcoded: chunk[index].isHardcoded,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
console.log(` Fetched ${keyToAssetFetchedCount.toLocaleString()} entity keys from accounts`);
|
|
336
|
+
}
|
|
337
|
+
console.log(` Total: ${existingKeyToAssets.length.toLocaleString()} entity keys\n`);
|
|
338
|
+
// Free memory - no longer need original key_to_asset infos or assets
|
|
339
|
+
keyToAssetInfos.length = 0;
|
|
340
|
+
// ========== STEP 2: Claim all rewards until none remain ==========
|
|
341
|
+
console.log("\n=== STEP 2: CLAIMING REWARDS ===\n");
|
|
342
|
+
let claimIteration = 0;
|
|
343
|
+
let totalClaimedAmount = new bn_js_1.default(0);
|
|
344
|
+
let totalClaimedTransactions = 0;
|
|
345
|
+
// Build recipient lookup
|
|
346
|
+
console.log(`Fetching ${existingKeyToAssets.length.toLocaleString()} recipient accounts...`);
|
|
347
|
+
const existingRecipients = new Set();
|
|
348
|
+
const recipientKeys = existingKeyToAssets.map(({ assetId }) => (0, lazy_distributor_sdk_1.recipientKey)(lazyDistributor, assetId)[0]);
|
|
349
|
+
function fetchRecipientsWithRetry(chunk, maxRetries = 3) {
|
|
350
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
351
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
352
|
+
try {
|
|
353
|
+
const accountInfos = yield lazyProgram.account.recipientV0.fetchMultiple(chunk);
|
|
354
|
+
return { accountInfos, chunk };
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
if (attempt === maxRetries - 1)
|
|
358
|
+
throw err;
|
|
359
|
+
const delay = Math.min(1000 * Math.pow(2, attempt), 5000);
|
|
360
|
+
yield new Promise((resolve) => setTimeout(resolve, delay));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
throw new Error("Max retries exceeded");
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
// Fetch recipients with concurrency limit
|
|
367
|
+
const recipientChunks = (0, spl_utils_1.chunks)(recipientKeys, 1000);
|
|
368
|
+
const limiter = (0, p_limit_1.default)(10);
|
|
369
|
+
let fetchedCount = 0;
|
|
370
|
+
let batchIndex = 0;
|
|
371
|
+
// Build assetsWithRecipients directly while fetching (no extra loops!)
|
|
372
|
+
const assetsWithRecipients = [];
|
|
373
|
+
yield Promise.all(recipientChunks.map((chunk, chunkIndex) => limiter(() => __awaiter(this, void 0, void 0, function* () {
|
|
374
|
+
const result = yield fetchRecipientsWithRetry(chunk);
|
|
375
|
+
fetchedCount += chunk.length;
|
|
376
|
+
batchIndex++;
|
|
377
|
+
// Calculate starting index for this chunk
|
|
378
|
+
const chunkStartIndex = chunkIndex * 1000;
|
|
379
|
+
// Build assetsWithRecipients directly from results
|
|
380
|
+
result.accountInfos.forEach((info, index) => {
|
|
381
|
+
if (info) {
|
|
382
|
+
const recipientAddr = chunk[index].toBase58();
|
|
383
|
+
existingRecipients.add(recipientAddr);
|
|
384
|
+
// Use index to get corresponding asset (no map lookup!)
|
|
385
|
+
assetsWithRecipients.push(existingKeyToAssets[chunkStartIndex + index]);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
// Show progress: first 3 batches, then every 25 batches
|
|
389
|
+
if (batchIndex <= 3 ||
|
|
390
|
+
batchIndex % 25 === 0 ||
|
|
391
|
+
fetchedCount === recipientKeys.length) {
|
|
392
|
+
console.log(` ${batchIndex} batches: ${fetchedCount.toLocaleString()} accounts`);
|
|
393
|
+
}
|
|
394
|
+
return result;
|
|
395
|
+
}))));
|
|
396
|
+
console.log(` Found ${existingRecipients.size.toLocaleString()} existing recipients\n`);
|
|
397
|
+
console.log(`Checking rewards for ${assetsWithRecipients.length.toLocaleString()} assets with recipients (skipping ${(existingKeyToAssets.length - assetsWithRecipients.length).toLocaleString()} without recipients)\n`);
|
|
398
|
+
while (true) {
|
|
399
|
+
claimIteration++;
|
|
400
|
+
console.log(`--- Claim Iteration ${claimIteration} ---`);
|
|
401
|
+
// Check pending rewards using bulk API
|
|
402
|
+
let assetsWithRewards = 0;
|
|
403
|
+
let assetsWithZeroRewards = 0;
|
|
404
|
+
let totalPendingRewards = new bn_js_1.default(0);
|
|
405
|
+
const assetsToClaim = [];
|
|
406
|
+
// Create map of entityKey -> asset info for lookup
|
|
407
|
+
const entityKeyToAsset = new Map();
|
|
408
|
+
assetsWithRecipients.forEach(({ assetId, entityKey }) => {
|
|
409
|
+
// Decode entity key to string format that oracle expects (b58 for subscriber assets)
|
|
410
|
+
const entityKeyStr = (0, helium_entity_manager_sdk_1.decodeEntityKey)(entityKey, { b58: {} });
|
|
411
|
+
if (entityKeyStr) {
|
|
412
|
+
entityKeyToAsset.set(entityKeyStr, {
|
|
413
|
+
assetId,
|
|
414
|
+
entityKey,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
// Get bulk rewards in chunks
|
|
419
|
+
const entityKeys = Array.from(entityKeyToAsset.keys());
|
|
420
|
+
const rewardsChunks = (0, spl_utils_1.chunks)(entityKeys, 5000);
|
|
421
|
+
const rewardsLimiter = (0, p_limit_1.default)(5);
|
|
422
|
+
let checkedCount = 0;
|
|
423
|
+
let rewardsBatchIndex = 0;
|
|
424
|
+
const bulkRewardsResults = yield Promise.all(rewardsChunks.map((chunk) => rewardsLimiter(() => __awaiter(this, void 0, void 0, function* () {
|
|
425
|
+
try {
|
|
426
|
+
const bulkRewards = yield client.getBulkRewards(lazyProgram, lazyDistributor, chunk);
|
|
427
|
+
rewardsBatchIndex++;
|
|
428
|
+
checkedCount += chunk.length;
|
|
429
|
+
// Show progress
|
|
430
|
+
if (rewardsBatchIndex <= 3 ||
|
|
431
|
+
rewardsBatchIndex % 5 === 0 ||
|
|
432
|
+
checkedCount === entityKeys.length) {
|
|
433
|
+
console.log(` ${rewardsBatchIndex} batches: ${checkedCount.toLocaleString()} assets`);
|
|
434
|
+
}
|
|
435
|
+
return { chunk, bulkRewards };
|
|
436
|
+
}
|
|
437
|
+
catch (err) {
|
|
438
|
+
console.error(`Error fetching bulk rewards: ${err.message}`);
|
|
439
|
+
return { chunk, bulkRewards: [] };
|
|
440
|
+
}
|
|
441
|
+
}))));
|
|
442
|
+
// Merge bulk rewards from all chunks
|
|
443
|
+
// Each chunk has the same oracle keys but different entity key data
|
|
444
|
+
const mergedBulkRewards = [];
|
|
445
|
+
if (bulkRewardsResults.length > 0 &&
|
|
446
|
+
bulkRewardsResults[0].bulkRewards.length > 0) {
|
|
447
|
+
const numOracles = bulkRewardsResults[0].bulkRewards.length;
|
|
448
|
+
for (let oracleIdx = 0; oracleIdx < numOracles; oracleIdx++) {
|
|
449
|
+
const mergedRewards = {};
|
|
450
|
+
// Merge rewards from all chunks for this oracle
|
|
451
|
+
bulkRewardsResults.forEach(({ bulkRewards }) => {
|
|
452
|
+
if (bulkRewards[oracleIdx]) {
|
|
453
|
+
Object.assign(mergedRewards, bulkRewards[oracleIdx].currentRewards);
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
mergedBulkRewards.push({
|
|
457
|
+
currentRewards: mergedRewards,
|
|
458
|
+
oracleKey: bulkRewardsResults[0].bulkRewards[oracleIdx].oracleKey,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
// Process rewards using merged bulk rewards
|
|
463
|
+
entityKeys.forEach((entityKey) => {
|
|
464
|
+
const assetInfo = entityKeyToAsset.get(entityKey);
|
|
465
|
+
if (!assetInfo)
|
|
466
|
+
return;
|
|
467
|
+
// Aggregate rewards across all oracles for this entity
|
|
468
|
+
const rewards = mergedBulkRewards.map((oracle) => ({
|
|
469
|
+
currentRewards: oracle.currentRewards[entityKey] || "0",
|
|
470
|
+
oracleKey: oracle.oracleKey,
|
|
471
|
+
}));
|
|
472
|
+
const totalRewards = rewards.reduce((sum, r) => {
|
|
473
|
+
return sum.add(new bn_js_1.default(r.currentRewards));
|
|
474
|
+
}, new bn_js_1.default(0));
|
|
475
|
+
if (totalRewards.gt(new bn_js_1.default(0))) {
|
|
476
|
+
assetsWithRewards++;
|
|
477
|
+
totalPendingRewards = totalPendingRewards.add(totalRewards);
|
|
478
|
+
assetsToClaim.push({
|
|
479
|
+
asset: assetInfo.assetId,
|
|
480
|
+
rewards,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
assetsWithZeroRewards++;
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
const pendingMobile = totalPendingRewards.div(new bn_js_1.default(100000000)).toString();
|
|
488
|
+
console.log(`\nPending: ${assetsWithRewards} assets with ${pendingMobile} MOBILE (${assetsWithZeroRewards} with zero)`);
|
|
489
|
+
// If no more rewards, break out of loop
|
|
490
|
+
if (assetsWithRewards === 0) {
|
|
491
|
+
console.log("\n✓ All rewards have been claimed!");
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
if (!argv.commit) {
|
|
495
|
+
console.log(`\nDry run: would claim rewards for ${assetsWithRewards} assets`);
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
console.log(`\nClaiming rewards for ${assetsWithRewards} assets...`);
|
|
499
|
+
// Batch claim rewards using formBulkTransactions (up to 100 assets per call)
|
|
500
|
+
console.log(`Preparing ${assetsToClaim.length} claim transactions...`);
|
|
501
|
+
const claimChunks = (0, spl_utils_1.chunks)(assetsToClaim, 100); // formBulkTransactions supports up to 100
|
|
502
|
+
const claimTxLimiter = (0, p_limit_1.default)(10);
|
|
503
|
+
let preparedCount = 0;
|
|
504
|
+
const allBatchTxns = yield Promise.all(claimChunks.map((chunk) => claimTxLimiter(() => __awaiter(this, void 0, void 0, function* () {
|
|
505
|
+
try {
|
|
506
|
+
const assets = chunk.map((c) => c.asset);
|
|
507
|
+
// Use formBulkTransactions which is more efficient than individual formTransaction calls
|
|
508
|
+
const txns = yield client.formBulkTransactions({
|
|
509
|
+
program: lazyProgram,
|
|
510
|
+
rewardsOracleProgram: rewardsOracleProgram,
|
|
511
|
+
rewards: mergedBulkRewards,
|
|
512
|
+
assets,
|
|
513
|
+
lazyDistributor,
|
|
514
|
+
wallet: authority.publicKey,
|
|
515
|
+
});
|
|
516
|
+
preparedCount += chunk.length;
|
|
517
|
+
// Show progress every 100 or at end
|
|
518
|
+
if (preparedCount % 100 === 0 ||
|
|
519
|
+
preparedCount === assetsToClaim.length) {
|
|
520
|
+
console.log(` Prepared ${preparedCount}/${assetsToClaim.length} transactions`);
|
|
521
|
+
}
|
|
522
|
+
return txns;
|
|
523
|
+
}
|
|
524
|
+
catch (err) {
|
|
525
|
+
console.error(`Error forming bulk transactions for batch: ${err.message}`);
|
|
526
|
+
return [];
|
|
527
|
+
}
|
|
528
|
+
}))));
|
|
529
|
+
const txns = allBatchTxns.flat().filter(spl_utils_1.truthy);
|
|
530
|
+
console.log(`Sending ${txns.length} transactions...`);
|
|
531
|
+
for (const tx of txns) {
|
|
532
|
+
tx.sign([authority]);
|
|
533
|
+
}
|
|
534
|
+
yield (0, spl_utils_1.bulkSendRawTransactions)(provider.connection, txns.map((tx) => Buffer.from(tx.serialize())), (status) => {
|
|
535
|
+
// Only show every 10 batches for mainnet scale
|
|
536
|
+
if (status.totalProgress % 10 === 0 ||
|
|
537
|
+
status.totalProgress === txns.length) {
|
|
538
|
+
console.log(` Sent ${status.totalProgress} / ${txns.length}`);
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
totalClaimedAmount = totalClaimedAmount.add(totalPendingRewards);
|
|
542
|
+
totalClaimedTransactions += txns.length;
|
|
543
|
+
console.log(`Iteration ${claimIteration} complete. Claimed ${totalPendingRewards
|
|
544
|
+
.div(new bn_js_1.default(100000000))
|
|
545
|
+
.toString()} MOBILE across ${txns.length} transactions.`);
|
|
546
|
+
// Free memory for next iteration
|
|
547
|
+
assetsToClaim.length = 0;
|
|
548
|
+
txns.length = 0;
|
|
549
|
+
// Wait a bit before next iteration to let chain settle
|
|
550
|
+
console.log("\nWaiting 5 seconds before next check...");
|
|
551
|
+
yield new Promise((resolve) => setTimeout(resolve, 5000));
|
|
552
|
+
}
|
|
553
|
+
const claimedMobile = totalClaimedAmount.div(new bn_js_1.default(100000000)).toString();
|
|
554
|
+
console.log(`\nClaimed ${claimedMobile} MOBILE in ${claimIteration} iteration(s), ${totalClaimedTransactions} tx(s)`);
|
|
555
|
+
// Free memory - no longer need recipient data
|
|
556
|
+
existingRecipients.clear();
|
|
557
|
+
recipientKeys.length = 0;
|
|
558
|
+
// ========== STEP 3: Close all KeyToAssetV0 accounts ==========
|
|
559
|
+
console.log("\n=== STEP 3: CLOSING ACCOUNTS ===\n");
|
|
560
|
+
if (!argv.commit) {
|
|
561
|
+
const closingHardcoded = existingKeyToAssets.filter((k) => k.isHardcoded).length;
|
|
562
|
+
const closingSubscribers = existingKeyToAssets.filter((k) => !k.isHardcoded).length;
|
|
563
|
+
console.log(`Dry run: would close ${existingKeyToAssets.length} accounts (${closingHardcoded} hardcoded, ${closingSubscribers} subscribers)`);
|
|
564
|
+
console.log(`\nRe-run with --commit to execute`);
|
|
565
|
+
console.log(`Then run close-all-subscriber-recipients to close RecipientV0 accounts`);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
console.log(`Closing ${existingKeyToAssets.length.toLocaleString()} accounts...`);
|
|
569
|
+
// Build close instructions
|
|
570
|
+
const instructions = [];
|
|
571
|
+
// Separate hardcoded (need info checks) from subscribers (no checks needed)
|
|
572
|
+
const hardcodedAccountsToClose = existingKeyToAssets.filter((k) => k.isHardcoded);
|
|
573
|
+
const subscriberAccounts = existingKeyToAssets.filter((k) => !k.isHardcoded);
|
|
574
|
+
console.log(` Hardcoded: ${hardcodedAccountsToClose.length}, Subscribers: ${subscriberAccounts.length.toLocaleString()}`);
|
|
575
|
+
// Process hardcoded accounts (check info accounts)
|
|
576
|
+
if (hardcodedAccountsToClose.length > 0) {
|
|
577
|
+
console.log("Processing hardcoded accounts...");
|
|
578
|
+
for (const { keyToAsset, assetId, entityKey } of hardcodedAccountsToClose) {
|
|
579
|
+
const [mobileInfo] = (0, helium_entity_manager_sdk_1.mobileInfoKey)(mobileConfig, entityKey);
|
|
580
|
+
const [iotInfo] = (0, helium_entity_manager_sdk_1.iotInfoKey)(iotConfig, entityKey);
|
|
581
|
+
const [mobileInfoAcc, iotInfoAcc] = yield Promise.all([
|
|
582
|
+
provider.connection.getAccountInfo(mobileInfo),
|
|
583
|
+
provider.connection.getAccountInfo(iotInfo),
|
|
584
|
+
]);
|
|
585
|
+
console.log(` ${keyToAsset.toBase58()}`);
|
|
586
|
+
console.log(` Mobile Info: ${mobileInfoAcc ? "exists (will close)" : "does not exist"}`);
|
|
587
|
+
console.log(` IoT Info: ${iotInfoAcc ? "exists (will close)" : "does not exist"}`);
|
|
588
|
+
const ix = yield hemProgram.methods
|
|
589
|
+
.tempCloseKeyToAssetV0()
|
|
590
|
+
.accountsPartial({
|
|
591
|
+
keyToAsset,
|
|
592
|
+
dao,
|
|
593
|
+
authority: authority.publicKey,
|
|
594
|
+
asset: assetId,
|
|
595
|
+
mobileConfig,
|
|
596
|
+
iotConfig,
|
|
597
|
+
mobileInfo,
|
|
598
|
+
iotInfo,
|
|
599
|
+
iotSubDao,
|
|
600
|
+
mobileSubDao,
|
|
601
|
+
})
|
|
602
|
+
.instruction();
|
|
603
|
+
instructions.push(ix);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
// Process subscriber accounts (no info checks - they shouldn't have info accounts)
|
|
607
|
+
if (subscriberAccounts.length > 0) {
|
|
608
|
+
console.log(`Processing ${subscriberAccounts.length.toLocaleString()} subscriber accounts...`);
|
|
609
|
+
let processed = 0;
|
|
610
|
+
const subscriberChunks = (0, spl_utils_1.chunks)(subscriberAccounts, 100);
|
|
611
|
+
const instructionLimiter = (0, p_limit_1.default)(50);
|
|
612
|
+
const allBatchInstructions = yield Promise.all(subscriberChunks.map((chunk) => instructionLimiter(() => __awaiter(this, void 0, void 0, function* () {
|
|
613
|
+
const batchInstructions = yield Promise.all(chunk.map(({ keyToAsset, assetId, entityKey }) => __awaiter(this, void 0, void 0, function* () {
|
|
614
|
+
const [mobileInfo] = (0, helium_entity_manager_sdk_1.mobileInfoKey)(mobileConfig, entityKey);
|
|
615
|
+
const [iotInfo] = (0, helium_entity_manager_sdk_1.iotInfoKey)(iotConfig, entityKey);
|
|
616
|
+
return yield hemProgram.methods
|
|
617
|
+
.tempCloseKeyToAssetV0()
|
|
618
|
+
.accountsPartial({
|
|
619
|
+
keyToAsset,
|
|
620
|
+
dao,
|
|
621
|
+
authority: authority.publicKey,
|
|
622
|
+
asset: assetId,
|
|
623
|
+
mobileConfig,
|
|
624
|
+
iotConfig,
|
|
625
|
+
mobileInfo,
|
|
626
|
+
iotInfo,
|
|
627
|
+
iotSubDao,
|
|
628
|
+
mobileSubDao,
|
|
629
|
+
})
|
|
630
|
+
.instruction();
|
|
631
|
+
})));
|
|
632
|
+
processed += chunk.length;
|
|
633
|
+
// Show progress every 10k instructions
|
|
634
|
+
if (processed % 10000 === 0 ||
|
|
635
|
+
processed === subscriberAccounts.length) {
|
|
636
|
+
console.log(` Prepared ${processed.toLocaleString()} / ${subscriberAccounts.length.toLocaleString()} instructions...`);
|
|
637
|
+
}
|
|
638
|
+
return batchInstructions;
|
|
639
|
+
}))));
|
|
640
|
+
instructions.push(...allBatchInstructions.flat().filter(spl_utils_1.truthy));
|
|
641
|
+
}
|
|
642
|
+
console.log(`\nBatching ${instructions.length.toLocaleString()} instructions into transactions...`);
|
|
643
|
+
// Free memory - no longer need existingKeyToAssets
|
|
644
|
+
existingKeyToAssets.length = 0;
|
|
645
|
+
const closeTxns = yield (0, spl_utils_1.batchInstructionsToTxsWithPriorityFee)(provider, instructions, {
|
|
646
|
+
useFirstEstimateForAll: true,
|
|
647
|
+
computeUnitLimit: 600000,
|
|
648
|
+
});
|
|
649
|
+
// Free memory - instructions now in transactions
|
|
650
|
+
instructions.length = 0;
|
|
651
|
+
console.log(`\nSending ${closeTxns.length} transactions...`);
|
|
652
|
+
// Sign with authority and send
|
|
653
|
+
yield (0, spl_utils_1.bulkSendTransactions)(provider, closeTxns, (status) => {
|
|
654
|
+
console.log(`Sending ${status.currentBatchProgress} / ${status.currentBatchSize} in batch. ${status.totalProgress} / ${closeTxns.length}`);
|
|
655
|
+
}, 10, [authority]);
|
|
656
|
+
const finalClaimedMobile = totalClaimedAmount
|
|
657
|
+
.div(new bn_js_1.default(100000000))
|
|
658
|
+
.toString();
|
|
659
|
+
console.log(`\n✓ Complete: Claimed ${finalClaimedMobile} MOBILE, closed ${instructions.length} accounts`);
|
|
660
|
+
console.log(`Next: run close-all-subscriber-recipients`);
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
exports.run = run;
|
|
664
|
+
//# sourceMappingURL=claim-and-close-subscriber-key-to-assets.js.map
|