@helium/distributor-oracle 0.9.29 → 0.9.31

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.
@@ -0,0 +1,169 @@
1
+ // @ts-ignore
2
+ import { BN } from "@coral-xyz/anchor";
3
+ import { decodeEntityKey, entityCreatorKey, keyToAssetForAsset, } from "@helium/helium-entity-manager-sdk";
4
+ import { lazyDistributorKey, recipientKey } from "@helium/lazy-distributor-sdk";
5
+ import { getAsset, HNT_MINT, searchAssets, } from "@helium/spl-utils";
6
+ import { PublicKey } from "@solana/web3.js";
7
+ import { Op } from "sequelize";
8
+ import { DAO } from "./constants";
9
+ import { DeviceType, } from "./database";
10
+ import { Reward, sequelize, WalletClaimJob } from "./model";
11
+ const ENTITY_CREATOR = entityCreatorKey(DAO)[0];
12
+ const HNT_LAZY_DISTRIBUTOR = lazyDistributorKey(HNT_MINT)[0];
13
+ const getRewardTypeForDevice = (deviceType) => `${deviceType.toString().toLowerCase()}_gateway`;
14
+ export class PgDatabase {
15
+ issuanceProgram;
16
+ lazyDistributorProgram;
17
+ getAssetFn;
18
+ searchAssetsFn;
19
+ constructor(issuanceProgram, lazyDistributorProgram, getAssetFn = getAsset, searchAssetsFn = searchAssets) {
20
+ this.issuanceProgram = issuanceProgram;
21
+ this.lazyDistributorProgram = lazyDistributorProgram;
22
+ this.getAssetFn = getAssetFn;
23
+ this.searchAssetsFn = searchAssetsFn;
24
+ }
25
+ async getOrCreateWalletClaimJob(wallet, batchNumber) {
26
+ if (!batchNumber) {
27
+ await WalletClaimJob.destroy({
28
+ where: { wallet: wallet.toBase58() },
29
+ });
30
+ }
31
+ const job = await WalletClaimJob.findOne({
32
+ where: { wallet: wallet.toBase58() },
33
+ });
34
+ if (!job) {
35
+ let page = 1;
36
+ const limit = 1000;
37
+ let allKtas = [];
38
+ while (true) {
39
+ const assets = (await this.searchAssetsFn(process.env.ASSET_API_URL ||
40
+ this.issuanceProgram.provider.connection.rpcEndpoint, {
41
+ ownerAddress: wallet.toBase58(),
42
+ creatorVerified: true,
43
+ creatorAddress: ENTITY_CREATOR.toBase58(),
44
+ page,
45
+ limit,
46
+ })) || [];
47
+ allKtas = allKtas.concat(assets.map((asset) => keyToAssetForAsset(asset, DAO).toBase58()));
48
+ if (assets.length < limit) {
49
+ break;
50
+ }
51
+ page++;
52
+ }
53
+ const job = await WalletClaimJob.create({
54
+ wallet: wallet.toBase58(),
55
+ remainingKtas: allKtas,
56
+ });
57
+ await job.save();
58
+ return job;
59
+ }
60
+ return job;
61
+ }
62
+ async getRewardableEntities(wallet, limit, batchNumber = 0) {
63
+ const job = await this.getOrCreateWalletClaimJob(wallet, batchNumber);
64
+ const entities = [];
65
+ let nextBatchNumber = batchNumber || 0;
66
+ while (entities.length == 0) {
67
+ const remainingKtas = job.remainingKtas.slice(nextBatchNumber * limit, (nextBatchNumber + 1) * limit);
68
+ if (remainingKtas.length === 0) {
69
+ nextBatchNumber++;
70
+ break;
71
+ }
72
+ const ktas = (await this.issuanceProgram.account.keyToAssetV0.fetchMultiple(remainingKtas)).map((kta, index) => ({
73
+ ...kta,
74
+ address: new PublicKey(remainingKtas[index]),
75
+ }));
76
+ const assets = ktas.map((kta) => kta.asset);
77
+ const recipientKeys = assets.map((a) => recipientKey(HNT_LAZY_DISTRIBUTOR, a)[0]);
78
+ const recipients = (await this.lazyDistributorProgram.account.recipientV0.fetchMultiple(recipientKeys)).map((r, index) => ({
79
+ ...r,
80
+ address: new PublicKey(recipientKeys[index]),
81
+ }));
82
+ const entityKeys = ktas.map((kta) => decodeEntityKey(kta.entityKey, kta.keySerialization));
83
+ const lifetimeRewards = await this.getBulkRewards(entityKeys);
84
+ for (let i = 0; i < recipients.length; i++) {
85
+ const entityKey = entityKeys[i];
86
+ const recipient = recipients[i];
87
+ const kta = ktas[i];
88
+ const lifetimeReward = lifetimeRewards[entityKey];
89
+ const pendingRewards = new BN(lifetimeReward).sub(recipient?.totalRewards || new BN("0"));
90
+ if (!pendingRewards.lte(new BN("0"))) {
91
+ entities.push({
92
+ keyToAsset: kta,
93
+ lifetimeReward: lifetimeReward,
94
+ pendingReward: pendingRewards.toString(),
95
+ recipient: recipient,
96
+ });
97
+ }
98
+ }
99
+ nextBatchNumber++;
100
+ }
101
+ return {
102
+ entities,
103
+ nextBatchNumber,
104
+ };
105
+ }
106
+ async getTotalRewards() {
107
+ const totalRewards = (await Reward.findAll({
108
+ attributes: [
109
+ [sequelize.fn("SUM", sequelize.col("rewards")), "rewards"],
110
+ ],
111
+ }))[0].rewards;
112
+ return totalRewards;
113
+ }
114
+ getActiveDevices(type) {
115
+ const rewardTypes = type
116
+ ? [getRewardTypeForDevice(type)]
117
+ : Object.values(DeviceType)
118
+ .filter((value) => isNaN(Number(value))) // Filter out numeric enum keys
119
+ .map((deviceType) => getRewardTypeForDevice(deviceType));
120
+ return Reward.count({
121
+ where: {
122
+ lastReward: {
123
+ [Op.gte]: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30), // Active within the last 30 days
124
+ },
125
+ rewardType: {
126
+ [Op.in]: rewardTypes,
127
+ },
128
+ },
129
+ });
130
+ }
131
+ async getBulkRewards(entityKeys) {
132
+ const rewards = await Reward.findAll({
133
+ where: {
134
+ address: {
135
+ [Op.in]: entityKeys,
136
+ },
137
+ },
138
+ });
139
+ return rewards
140
+ .map((rew) => [rew.address, rew.rewards])
141
+ .reduce((acc, [key, val]) => {
142
+ acc[key] = new BN(val).toString();
143
+ return acc;
144
+ }, {});
145
+ }
146
+ async getCurrentRewards(assetId) {
147
+ const asset = await this.getAssetFn(process.env.ASSET_API_URL ||
148
+ this.issuanceProgram.provider.connection.rpcEndpoint, assetId);
149
+ if (!asset) {
150
+ console.error("No asset found", assetId.toBase58());
151
+ return "0";
152
+ }
153
+ const keyToAssetKey = keyToAssetForAsset(asset, DAO);
154
+ const keyToAsset = await this.issuanceProgram.account.keyToAssetV0.fetch(keyToAssetKey);
155
+ const entityKey = decodeEntityKey(keyToAsset.entityKey, keyToAsset.keySerialization);
156
+ // Verify the creator is our entity creator, otherwise they could just
157
+ // pass in any NFT with this ecc compact to collect rewards
158
+ if (!asset.creators[0].verified ||
159
+ !new PublicKey(asset.creators[0].address).equals(ENTITY_CREATOR)) {
160
+ throw new Error("Not a valid rewardable entity");
161
+ }
162
+ return this.getCurrentRewardsByEntity(entityKey);
163
+ }
164
+ async getCurrentRewardsByEntity(entityKeyStr) {
165
+ const reward = (await Reward.findByPk(entityKeyStr));
166
+ return new BN(reward?.rewards).toString() || "0";
167
+ }
168
+ }
169
+ //# sourceMappingURL=pgDatabase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pgDatabase.js","sourceRoot":"","sources":["../../../src/pgDatabase.ts"],"names":[],"mappings":"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"}
@@ -1,113 +1,34 @@
1
- import dotenv from "dotenv";
2
- dotenv.config();
3
1
  import { compileTransaction, customSignerKey, init as initTuktuk, RemoteTaskTransactionV0, } from "@helium/tuktuk-sdk";
2
+ import dotenv from "dotenv";
4
3
  import { sign } from "tweetnacl";
4
+ dotenv.config();
5
5
  // @ts-ignore
6
6
  import { AnchorProvider, BN, getProvider, setProvider, } from "@coral-xyz/anchor";
7
- import { decodeEntityKey, entityCreatorKey, init as initHeliumEntityManager, keyToAssetKey, keyToAssetForAsset, } from "@helium/helium-entity-manager-sdk";
8
- import { daoKey } from "@helium/helium-sub-daos-sdk";
9
- import { distributeCompressionRewards, init as initLazy, lazyDistributorKey, PROGRAM_ID as LD_PID, recipientKey, } from "@helium/lazy-distributor-sdk";
10
- import { init as initRewards, PROGRAM_ID as RO_PID, } from "@helium/rewards-oracle-sdk";
11
- import { getAsset, HNT_MINT, IOT_MINT, toNumber, } from "@helium/spl-utils";
12
- import { AccountFetchCache } from "@helium/account-fetch-cache";
13
- import { Keypair, PublicKey, ComputeBudgetProgram, VersionedTransaction, } from "@solana/web3.js";
14
- import { Op } from "sequelize";
15
- import fs from "fs";
16
- import { Reward, sequelize } from "./model";
17
- import Fastify from "fastify";
18
7
  import cors from "@fastify/cors";
8
+ import { AccountFetchCache } from "@helium/account-fetch-cache";
9
+ import { decodeEntityKey, init as initHeliumEntityManager, keyToAssetKey, } from "@helium/helium-entity-manager-sdk";
10
+ import { init as initHplCrons } from "@helium/hpl-crons-sdk";
11
+ import { distributeCompressionRewards, initializeCompressionRecipient, init as initLazy, lazyDistributorKey, PROGRAM_ID as LD_PID, recipientKey, } from "@helium/lazy-distributor-sdk";
12
+ import { init as initRewards, PROGRAM_ID as RO_PID, } from "@helium/rewards-oracle-sdk";
13
+ import { getAsset, toNumber } from "@helium/spl-utils";
19
14
  import { getLeafAssetId } from "@metaplex-foundation/mpl-bubblegum";
20
- import { register, totalRewardsGauge } from "./metrics";
15
+ import { createMemoInstruction } from "@solana/spl-memo";
21
16
  import { getAssociatedTokenAddressSync } from "@solana/spl-token";
22
- const HNT = process.env.HNT_MINT
23
- ? new PublicKey(process.env.HNT_MINT)
24
- : HNT_MINT;
25
- const DNT = process.env.DNT_MINT
26
- ? new PublicKey(process.env.DNT_MINT)
27
- : IOT_MINT;
28
- const DAO = daoKey(HNT)[0];
29
- const ENTITY_CREATOR = entityCreatorKey(DAO)[0];
30
- export class PgDatabase {
31
- issuanceProgram;
32
- getAssetFn;
33
- constructor(issuanceProgram, getAssetFn = getAsset) {
34
- this.issuanceProgram = issuanceProgram;
35
- this.getAssetFn = getAssetFn;
36
- }
37
- async getTotalRewards() {
38
- const totalRewards = (await Reward.findAll({
39
- attributes: [
40
- [sequelize.fn("SUM", sequelize.col("rewards")), "rewards"],
41
- ],
42
- }))[0].rewards;
43
- return totalRewards;
44
- }
45
- getActiveDevices() {
46
- return Reward.count({
47
- where: {
48
- [Op.and]: [
49
- {
50
- lastReward: {
51
- [Op.gte]: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30), // Active within the last 30 days
52
- },
53
- },
54
- {
55
- [Op.or]: [
56
- {
57
- rewardType: "mobile_gateway",
58
- },
59
- {
60
- rewardType: "iot_gateway",
61
- },
62
- ],
63
- },
64
- ],
65
- },
66
- });
67
- }
68
- async getBulkRewards(entityKeys) {
69
- const rewards = await Reward.findAll({
70
- where: {
71
- address: {
72
- [Op.in]: entityKeys,
73
- },
74
- },
75
- });
76
- return rewards
77
- .map((rew) => [rew.address, rew.rewards])
78
- .reduce((acc, [key, val]) => {
79
- acc[key] = new BN(val).toString();
80
- return acc;
81
- }, {});
82
- }
83
- async getCurrentRewards(assetId) {
84
- const asset = await this.getAssetFn(process.env.ASSET_API_URL ||
85
- this.issuanceProgram.provider.connection.rpcEndpoint, assetId);
86
- if (!asset) {
87
- console.error("No asset found", assetId.toBase58());
88
- return "0";
89
- }
90
- const keyToAssetKey = keyToAssetForAsset(asset, DAO);
91
- const keyToAsset = await this.issuanceProgram.account.keyToAssetV0.fetch(keyToAssetKey);
92
- const entityKey = decodeEntityKey(keyToAsset.entityKey, keyToAsset.keySerialization);
93
- // Verify the creator is our entity creator, otherwise they could just
94
- // pass in any NFT with this ecc compact to collect rewards
95
- if (!asset.creators[0].verified ||
96
- !new PublicKey(asset.creators[0].address).equals(ENTITY_CREATOR)) {
97
- throw new Error("Not a valid rewardable entity");
98
- }
99
- return this.getCurrentRewardsByEntity(entityKey);
100
- }
101
- async getCurrentRewardsByEntity(entityKeyStr) {
102
- const reward = (await Reward.findByPk(entityKeyStr));
103
- return new BN(reward?.rewards).toString() || "0";
104
- }
105
- }
17
+ import { ComputeBudgetProgram, Keypair, PublicKey, SystemProgram, VersionedTransaction, } from "@solana/web3.js";
18
+ import Fastify from "fastify";
19
+ import fs from "fs";
20
+ import { DAO, DNT, MAX_CLAIMS_PER_TX } from "./constants";
21
+ import { DeviceType } from "./database";
22
+ import { register, totalRewardsGauge } from "./metrics";
23
+ import { PgDatabase } from "./pgDatabase";
24
+ import { Reward, WalletClaimJob } from "./model";
25
+ export * from "./database";
106
26
  export class OracleServer {
107
27
  tuktukProgram;
108
28
  ldProgram;
109
29
  roProgram;
110
30
  hemProgram;
31
+ hplCronsProgram;
111
32
  oracle;
112
33
  db;
113
34
  lazyDistributor;
@@ -117,11 +38,12 @@ export class OracleServer {
117
38
  server;
118
39
  constructor(
119
40
  // tuktuk is on a different version of anchor, so this has to be done.
120
- tuktukProgram, ldProgram, roProgram, hemProgram, oracle, db, lazyDistributor, dao = DAO) {
41
+ tuktukProgram, ldProgram, roProgram, hemProgram, hplCronsProgram, oracle, db, lazyDistributor, dao = DAO) {
121
42
  this.tuktukProgram = tuktukProgram;
122
43
  this.ldProgram = ldProgram;
123
44
  this.roProgram = roProgram;
124
45
  this.hemProgram = hemProgram;
46
+ this.hplCronsProgram = hplCronsProgram;
125
47
  this.oracle = oracle;
126
48
  this.db = db;
127
49
  this.lazyDistributor = lazyDistributor;
@@ -164,6 +86,8 @@ export class OracleServer {
164
86
  await this.app.close();
165
87
  }
166
88
  addRoutes() {
89
+ this.app.get("/active-iot-devices", this.getActiveIotDevicesHandler.bind(this));
90
+ this.app.get("/active-mobile-devices", this.getActiveMobileDevicesHandler.bind(this));
167
91
  this.app.get("/active-devices", this.getActiveDevicesHandler.bind(this));
168
92
  this.app.post("/bulk-rewards", this.getAllRewardsHandler.bind(this));
169
93
  this.app.get("/will-pay-recipient", (_req, res) => {
@@ -174,9 +98,22 @@ export class OracleServer {
174
98
  this.app.get("/", this.getCurrentRewardsHandler.bind(this));
175
99
  this.app.post("/", this.signTransactionHandler.bind(this));
176
100
  this.app.post("/bulk-sign", this.signBulkTransactionsHandler.bind(this));
177
- this.app.post("/v1/tuktuk/:keyToAssetKey", this.tuktukHandler.bind(this));
101
+ this.app.post("/v1/tuktuk/kta/:keyToAssetKey", this.tuktukKtaHandler.bind(this));
102
+ this.app.post("/v1/tuktuk/wallet/:wallet", this.tuktukWalletHandler.bind(this));
178
103
  this.app.post("/v1/sign/:keyToAssetKey", this.signHandler.bind(this));
179
104
  }
105
+ async getActiveIotDevicesHandler(req, res) {
106
+ const count = await this.db.getActiveDevices(DeviceType.IOT);
107
+ res.send({
108
+ count,
109
+ });
110
+ }
111
+ async getActiveMobileDevicesHandler(req, res) {
112
+ const count = await this.db.getActiveDevices(DeviceType.MOBILE);
113
+ res.send({
114
+ count,
115
+ });
116
+ }
180
117
  async getActiveDevicesHandler(req, res) {
181
118
  const count = await this.db.getActiveDevices();
182
119
  res.send({
@@ -429,22 +366,25 @@ export class OracleServer {
429
366
  });
430
367
  }
431
368
  }
432
- async tuktukHandler(request, reply) {
369
+ async tuktukKtaHandler(request, reply) {
433
370
  const taskQueue = new PublicKey(request.body.task_queue);
434
371
  const task = new PublicKey(request.body.task);
435
372
  const taskQueuedAt = new BN(request.body.task_queued_at);
436
373
  try {
374
+ let keyToAsset = await this.hemProgram.account.keyToAssetV0.fetch(new PublicKey(request.params.keyToAssetKey));
375
+ const asset = await getAsset(process.env.ASSET_API_URL ||
376
+ this.ldProgram.provider.connection.rpcEndpoint, keyToAsset.asset);
437
377
  const [wallet, bump] = customSignerKey(taskQueue, [
438
- Buffer.from("oracle"),
378
+ Buffer.from("claim_payer"),
379
+ asset.ownership.owner.toBuffer(),
439
380
  ]);
440
381
  const bumpBuffer = Buffer.alloc(1);
441
382
  bumpBuffer.writeUint8(bump);
442
- let keyToAsset = await this.hemProgram.account.keyToAssetV0.fetch(new PublicKey(request.params.keyToAssetKey));
443
383
  const recipient = recipientKey(this.lazyDistributor, keyToAsset.asset)[0];
444
- let recipientAcc = await this.ldProgram.account.recipientV0.fetch(recipient);
384
+ let recipientAcc = await this.ldProgram.account.recipientV0.fetchNullable(recipient);
445
385
  let distributeIx;
446
- if (recipientAcc.destination &&
447
- !recipientAcc.destination.equals(PublicKey.default)) {
386
+ if (recipientAcc?.destination &&
387
+ !recipientAcc?.destination.equals(PublicKey.default)) {
448
388
  const destination = recipientAcc.destination;
449
389
  distributeIx = await this.ldProgram.methods
450
390
  .distributeCustomDestinationV0()
@@ -460,7 +400,7 @@ export class OracleServer {
460
400
  })
461
401
  .instruction();
462
402
  }
463
- else {
403
+ else if (asset?.compression.compressed) {
464
404
  distributeIx = await (await distributeCompressionRewards({
465
405
  program: this.ldProgram,
466
406
  assetId: keyToAsset.asset,
@@ -469,25 +409,148 @@ export class OracleServer {
469
409
  payer: wallet,
470
410
  })).instruction();
471
411
  }
412
+ else {
413
+ distributeIx = await this.ldProgram.methods
414
+ .distributeRewardsV0()
415
+ .accounts({
416
+ common: {
417
+ payer: wallet,
418
+ recipient: recipient,
419
+ lazyDistributor: this.lazyDistributor,
420
+ rewardsMint: DNT,
421
+ owner: asset.ownership.owner,
422
+ destinationAccount: getAssociatedTokenAddressSync(DNT, asset.ownership.owner, true),
423
+ },
424
+ recipientMintAccount: getAssociatedTokenAddressSync(keyToAsset.asset, asset.ownership.owner, true),
425
+ })
426
+ .instruction();
427
+ }
472
428
  const entityKey = decodeEntityKey(keyToAsset.entityKey, keyToAsset.keySerialization);
429
+ const instructions = [];
430
+ if (!recipientAcc) {
431
+ if (asset?.compression.compressed) {
432
+ instructions.push(await (await initializeCompressionRecipient({
433
+ program: this.ldProgram,
434
+ assetId: keyToAsset.asset,
435
+ lazyDistributor: this.lazyDistributor,
436
+ owner: wallet,
437
+ // Temporarily set oracle as the payer to subsidize new HNT wallets.
438
+ payer: wallet,
439
+ })).instruction());
440
+ }
441
+ else {
442
+ instructions.push(await this.ldProgram.methods
443
+ .initializeRecipientV0()
444
+ .accounts({
445
+ recipient: recipient,
446
+ lazyDistributor: this.lazyDistributor,
447
+ payer: wallet,
448
+ mint: keyToAsset.asset,
449
+ })
450
+ .instruction());
451
+ }
452
+ }
453
+ instructions.push(await this.roProgram.methods
454
+ .setCurrentRewardsWrapperV2({
455
+ currentRewards: new BN(await this.db.getCurrentRewardsByEntity(entityKey)),
456
+ oracleIndex: process.env.ORACLE_INDEX
457
+ ? parseInt(process.env.ORACLE_INDEX)
458
+ : 0,
459
+ })
460
+ .accounts({
461
+ lazyDistributor: this.lazyDistributor,
462
+ recipient,
463
+ payer: wallet,
464
+ keyToAsset: new PublicKey(request.params.keyToAssetKey),
465
+ })
466
+ .instruction());
467
+ instructions.push(distributeIx);
468
+ const { transaction, remainingAccounts } = await compileTransaction(instructions, [
469
+ [
470
+ Buffer.from("claim_payer"),
471
+ asset.ownership.owner.toBuffer(),
472
+ bumpBuffer,
473
+ ],
474
+ ]);
475
+ const remoteTx = new RemoteTaskTransactionV0({
476
+ task,
477
+ taskQueuedAt,
478
+ transaction: {
479
+ ...transaction,
480
+ accounts: remainingAccounts.map((acc) => acc.pubkey),
481
+ },
482
+ });
483
+ const serialized = await RemoteTaskTransactionV0.serialize(this.tuktukProgram.coder.accounts, remoteTx);
484
+ const resp = {
485
+ transaction: serialized.toString("base64"),
486
+ signature: Buffer.from(sign.detached(Uint8Array.from(serialized), this.oracle.secretKey)).toString("base64"),
487
+ remaining_accounts: remainingAccounts.map((acc) => ({
488
+ pubkey: acc.pubkey.toBase58(),
489
+ is_signer: acc.isSigner,
490
+ is_writable: acc.isWritable,
491
+ })),
492
+ };
493
+ reply.status(200).send(resp);
494
+ }
495
+ catch (err) {
496
+ console.error(err);
497
+ reply.status(500).send({
498
+ message: "Request failed",
499
+ });
500
+ }
501
+ }
502
+ async tuktukWalletHandler(request, reply) {
503
+ const wallet = request.params.wallet;
504
+ const batchNumber = request.query.batchNumber;
505
+ const taskQueue = new PublicKey(request.body.task_queue);
506
+ const task = new PublicKey(request.body.task);
507
+ const taskQueuedAt = new BN(request.body.task_queued_at);
508
+ try {
509
+ const [customSignerWallet, bump] = customSignerKey(taskQueue, [
510
+ Buffer.from("claim_payer"),
511
+ new PublicKey(wallet).toBuffer(),
512
+ ]);
513
+ const bumpBuffer = Buffer.alloc(1);
514
+ bumpBuffer.writeUint8(bump);
515
+ const { entities, nextBatchNumber } = await this.db.getRewardableEntities(new PublicKey(wallet), MAX_CLAIMS_PER_TX, Number(batchNumber || 0));
516
+ const taskQueueAcc = await this.tuktukProgram.account.taskQueueV0.fetch(taskQueue);
473
517
  const instructions = [
474
- await this.roProgram.methods
475
- .setCurrentRewardsWrapperV2({
476
- currentRewards: new BN(await this.db.getCurrentRewardsByEntity(entityKey)),
477
- oracleIndex: process.env.ORACLE_INDEX
478
- ? parseInt(process.env.ORACLE_INDEX)
479
- : 0,
518
+ SystemProgram.transfer({
519
+ fromPubkey: customSignerWallet,
520
+ toPubkey: task,
521
+ lamports: taskQueueAcc.minCrankReward.toNumber() * (entities.length + 1),
522
+ }),
523
+ ];
524
+ for (const entity of entities) {
525
+ instructions.push(await this.hplCronsProgram.methods
526
+ .requeueEntityClaimV0()
527
+ .accounts({
528
+ keyToAsset: entity.keyToAsset.address,
529
+ })
530
+ .instruction());
531
+ }
532
+ if (entities.length > 0) {
533
+ instructions.push(await this.hplCronsProgram.methods
534
+ .requeueWalletClaimV0({
535
+ batchNumber: nextBatchNumber,
480
536
  })
481
537
  .accounts({
482
- lazyDistributor: this.lazyDistributor,
483
- recipient,
484
- payer: wallet,
485
- keyToAsset: new PublicKey(request.params.keyToAssetKey),
538
+ wallet: new PublicKey(wallet),
486
539
  })
487
- .instruction(),
488
- distributeIx,
489
- ];
490
- const { transaction, remainingAccounts } = await compileTransaction(instructions, [[Buffer.from("oracle"), bumpBuffer]]);
540
+ .instruction());
541
+ }
542
+ else {
543
+ instructions.push(createMemoInstruction("Finished claiming rewards", [
544
+ customSignerWallet,
545
+ ]));
546
+ }
547
+ const { transaction, remainingAccounts } = await compileTransaction(instructions, [
548
+ [
549
+ Buffer.from("claim_payer"),
550
+ new PublicKey(wallet).toBuffer(),
551
+ bumpBuffer,
552
+ ],
553
+ ]);
491
554
  const remoteTx = new RemoteTaskTransactionV0({
492
555
  task,
493
556
  taskQueuedAt,
@@ -506,7 +569,6 @@ export class OracleServer {
506
569
  is_writable: acc.isWritable,
507
570
  })),
508
571
  };
509
- console.log(resp);
510
572
  reply.status(200).send(resp);
511
573
  }
512
574
  catch (err) {
@@ -572,8 +634,11 @@ export class OracleServer {
572
634
  const ldProgram = await initLazy(provider);
573
635
  const roProgram = await initRewards(provider);
574
636
  const hemProgram = await initHeliumEntityManager(provider);
637
+ const hplCronsProgram = await initHplCrons(provider);
638
+ WalletClaimJob.sync();
639
+ Reward.sync();
575
640
  const LAZY_DISTRIBUTOR = lazyDistributorKey(DNT)[0];
576
- const server = new OracleServer(tuktukProgram, ldProgram, roProgram, hemProgram, oracleKeypair, new PgDatabase(hemProgram), LAZY_DISTRIBUTOR);
641
+ const server = new OracleServer(tuktukProgram, ldProgram, roProgram, hemProgram, hplCronsProgram, oracleKeypair, new PgDatabase(hemProgram, ldProgram), LAZY_DISTRIBUTOR);
577
642
  // For performance
578
643
  new AccountFetchCache({
579
644
  connection: provider.connection,