@merkl/api 0.20.42 → 0.20.44
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/dist/src/engine/dynamicData/implementations/Erc20.js +5 -2
- package/dist/src/engine/dynamicData/implementations/EventBased.js +9 -9
- package/dist/src/jobs/dynamic-data.d.ts +2 -2
- package/dist/src/jobs/dynamic-data.js +8 -14
- package/dist/src/jobs/reward-breakdowns.js +3 -0
- package/dist/src/jobs/rewards.js +3 -0
- package/dist/src/jobs/update-dynamic-data.js +12 -10
- package/dist/src/jobs/update-rpc-calls-cache.js +2 -3
- package/dist/src/modules/v4/campaign/campaign.service.js +5 -1
- package/dist/src/modules/v4/merklRoot/merklRoot.repository.d.ts +2 -8
- package/dist/src/modules/v4/merklRoot/merklRoot.repository.js +17 -3
- package/dist/src/modules/v4/merklRoot/merklRoot.service.d.ts +1 -13
- package/dist/src/modules/v4/merklRoot/merklRoot.service.js +9 -30
- package/dist/src/modules/v4/programPayload/programPayload.service.d.ts +2 -1
- package/dist/src/modules/v4/programPayload/programPayload.service.js +21 -7
- package/dist/tsconfig.package.tsbuildinfo +1 -1
- package/package.json +1 -1
@@ -9,7 +9,7 @@ import { TokenRepository } from "@/modules/v4/token/token.repository";
|
|
9
9
|
import { TokenService } from "@/modules/v4/token/token.service";
|
10
10
|
import { log } from "@/utils/logger";
|
11
11
|
import { Pricer } from "@/utils/pricer";
|
12
|
-
import { BN2Number, BalancerPoolInterface, BalancerV3StablePoolInterface, ChainInteractionService, ERC20Interface, EnzymeInterface, FactoryInterface, LayerBankERC20Interface, MetamorphoInterface, YEAR, getEnv, } from "@sdk";
|
12
|
+
import { BN2Number, BalancerPoolInterface, BalancerV3StablePoolInterface, ChainId, ChainInteractionService, ERC20Interface, EnzymeInterface, FactoryInterface, LayerBankERC20Interface, MetamorphoInterface, YEAR, getEnv, } from "@sdk";
|
13
13
|
export class Erc20DynamicData {
|
14
14
|
async build(chainId, campaigns) {
|
15
15
|
const pricer = await Pricer.load();
|
@@ -126,7 +126,7 @@ export class Erc20DynamicData {
|
|
126
126
|
log.warn(`failed to update target token price: ${e}`);
|
127
127
|
}
|
128
128
|
i += callsForCampaign;
|
129
|
-
|
129
|
+
let whitelistedSupplyTargetToken = tokenTypesByCampaign[campaign.campaignId].typeInfo.whitelistedSupplyTargetToken;
|
130
130
|
const totalSupplyTargetToken = whitelistedSupplyTargetToken;
|
131
131
|
const priceTargetToken = tokenTypesByCampaign[campaign.campaignId].typeInfo.priceTargetToken;
|
132
132
|
let lastEligibilityRatio = 1;
|
@@ -158,6 +158,9 @@ export class Erc20DynamicData {
|
|
158
158
|
]);
|
159
159
|
const rewardToken = rewardTokens[0];
|
160
160
|
apr = rewardToken.isPoint ? apr / 365 / 100 : apr;
|
161
|
+
if (campaign.chainId === ChainId.ETHERLINK) {
|
162
|
+
whitelistedSupplyTargetToken = tokenTypesByCampaign[campaign.campaignId].typeInfo.totalSupply;
|
163
|
+
}
|
161
164
|
dynamicData.push({
|
162
165
|
...campaign,
|
163
166
|
apr,
|
@@ -10,7 +10,7 @@ import { Pricer } from "../../../utils/pricer";
|
|
10
10
|
* @dev important: using the most recent state save with current prices
|
11
11
|
* it's only an estimate
|
12
12
|
*/
|
13
|
-
async function
|
13
|
+
async function computeEventBasedPoolRewardsFromMostRecentStateSave(chainId, campaignID, priceCurrency, decimalsCurrency) {
|
14
14
|
let stateSave;
|
15
15
|
let blockNumber;
|
16
16
|
let states = {};
|
@@ -37,23 +37,22 @@ async function computeEventBasedPoolTVLFromMostRecentStateSave(chainId, campaign
|
|
37
37
|
}
|
38
38
|
const { fileName, bucketName } = states;
|
39
39
|
// Bucket service
|
40
|
-
let
|
40
|
+
let distributedRewards = 0;
|
41
41
|
if (!fileName || !bucketName) {
|
42
|
-
return {
|
42
|
+
return { distributedRewards, blockNumber: blockNumber };
|
43
43
|
}
|
44
44
|
try {
|
45
45
|
const bucket = new BucketService("merkl-production-states", "merkl-production");
|
46
46
|
const storedStates = JSON.parse(await bucket.pull(fileName));
|
47
47
|
for (const [_, { value, params: _params }] of Object.entries(storedStates)) {
|
48
|
-
|
48
|
+
distributedRewards += BN2Number(value.allTimeValue, 18);
|
49
49
|
}
|
50
|
-
|
51
|
-
tvl = Math.max(tvl, 1);
|
50
|
+
distributedRewards = Math.max(distributedRewards, 1);
|
52
51
|
}
|
53
52
|
catch {
|
54
53
|
log.warn(`merklDynamic data - failed to decode state of event based on ${NETWORK_LABELS[chainId]}`);
|
55
54
|
}
|
56
|
-
return {
|
55
|
+
return { distributedRewards, blockNumber: blockNumber };
|
57
56
|
}
|
58
57
|
export class EventBasedDynamicData {
|
59
58
|
async build(chainId, campaigns) {
|
@@ -67,7 +66,7 @@ export class EventBasedDynamicData {
|
|
67
66
|
chainId: chainId,
|
68
67
|
symbol: symbolCurrency0,
|
69
68
|
}));
|
70
|
-
const {
|
69
|
+
const { distributedRewards } = await computeEventBasedPoolRewardsFromMostRecentStateSave(chainId, campaign.campaignId, priceToken, decimalsCurrency0);
|
71
70
|
const c = campaign;
|
72
71
|
const amount = BN2Number(c.amount, c.campaignParameters.decimalsRewardToken);
|
73
72
|
const multiplier = BN2Number(c.campaignParameters.topicToData[0].multiplier, 12 + 9);
|
@@ -76,11 +75,12 @@ export class EventBasedDynamicData {
|
|
76
75
|
const isLive = moment().unix() > startTimestamp && moment().unix() < endTimestamp;
|
77
76
|
let distributionMeanAPR = 0;
|
78
77
|
const priceRewardToken = await TokenService.getRewardTokenPrice(campaign);
|
78
|
+
const tvl = distributedRewards / multiplier;
|
79
79
|
if (isLive) {
|
80
80
|
/** Yearly rewards in $ */
|
81
81
|
const fixRewardRate = multiplier * priceRewardToken;
|
82
82
|
distributionMeanAPR = fixRewardRate;
|
83
|
-
if (
|
83
|
+
if (distributedRewards > (amount * (moment().unix() - startTimestamp)) / (endTimestamp - startTimestamp)) {
|
84
84
|
distributionMeanAPR =
|
85
85
|
(fixRewardRate * amount * (moment().unix() - startTimestamp)) / (endTimestamp - startTimestamp) / tvl;
|
86
86
|
}
|
@@ -1,9 +1,9 @@
|
|
1
|
-
import { Campaign, type CampaignDynamicData } from "@sdk";
|
1
|
+
import { Campaign, type CampaignDynamicData, type MerklChainId } from "@sdk";
|
2
2
|
export type CampaignsCacheUpdaterReturnType = {
|
3
3
|
[type_mainParameter: string]: {
|
4
4
|
[campaignId: string]: CampaignDynamicData<Campaign>;
|
5
5
|
};
|
6
6
|
};
|
7
|
-
export declare const main: () => Promise<{
|
7
|
+
export declare const main: (chainId: MerklChainId) => Promise<{
|
8
8
|
success: boolean;
|
9
9
|
}>;
|
@@ -7,25 +7,15 @@ import { dynamicDataBuilderFactory } from "@/engine/dynamicData/factory";
|
|
7
7
|
import { campaignsToOldFormat } from "@/libs/deprecated-merklv3";
|
8
8
|
import { merklChainData } from "@/libs/merklChainData";
|
9
9
|
import { staticCampaignWithCache } from "@/libs/staticCampaigns";
|
10
|
-
import {
|
10
|
+
import { ChainService } from "@/modules/v4/chain/chain.service";
|
11
11
|
import { log } from "@/utils/logger";
|
12
12
|
import { ALL_CAMPAIGNS_FOR_CHAIN_AFTER } from "@/utils/queries/allCampaigns";
|
13
13
|
import { engineDbClient } from "@db";
|
14
|
-
import { Campaign, ChainId, NETWORK_LABELS,
|
14
|
+
import { Campaign, ChainId, NETWORK_LABELS, } from "@sdk";
|
15
15
|
import moment from "moment";
|
16
16
|
const queryCampaignTypes = process.env.CAMPAIGN_TYPES ? JSON.parse(process.env.CAMPAIGN_TYPES) : [];
|
17
17
|
const highCampaignsChains = [ChainId.ARBITRUM, ChainId.POLYGON, ChainId.BLAST, ChainId.BASE];
|
18
|
-
export const main = async () => {
|
19
|
-
const rawChainId = process.env.CHAIN_ID;
|
20
|
-
let chainId;
|
21
|
-
if (typeof rawChainId === "string") {
|
22
|
-
chainId = Number.parseInt(rawChainId);
|
23
|
-
if (!isSupportedChain(chainId, "merkl"))
|
24
|
-
throw new UnsupportedNetwork(chainId);
|
25
|
-
}
|
26
|
-
else {
|
27
|
-
throw new InvalidParameter("Invalid chainId provided");
|
28
|
-
}
|
18
|
+
export const main = async (chainId) => {
|
29
19
|
let success = true;
|
30
20
|
try {
|
31
21
|
await Redis.safeSet(`MerklChainData_${chainId}`, await merklChainData(chainId));
|
@@ -135,7 +125,11 @@ export const main = async () => {
|
|
135
125
|
}
|
136
126
|
return { success };
|
137
127
|
};
|
138
|
-
|
128
|
+
const chains = await ChainService.getSupportedIds();
|
129
|
+
const promises = [];
|
130
|
+
for (const chain of chains)
|
131
|
+
promises.push(main(chain));
|
132
|
+
await Promise.allSettled(promises)
|
139
133
|
.then(success => (success ? process.exit(0) : process.exit(1)))
|
140
134
|
.catch((err) => {
|
141
135
|
console.error(err);
|
@@ -1,4 +1,7 @@
|
|
1
1
|
// ─── Reward Breakdowns ETL ───────────────────────────────────────────────────
|
2
|
+
process.env.ENV = "production";
|
3
|
+
process.env.CHAIN_ID = "42161";
|
4
|
+
process.env.ROOT = "0xfc0aa71421f32507050b193be6d445bf674a774cc4c30ad15657a0e3034bec7a";
|
2
5
|
if (!process.env.DATABASE_API_URL || !process.env.ENV || !process.env.CHAIN_ID || !process.env.ROOT)
|
3
6
|
throw new Error("[ENV]: missing variable");
|
4
7
|
import { BucketService } from "@/modules/v4/bucket/bucket.service";
|
package/dist/src/jobs/rewards.js
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
// ─── Rewards ETL ─────────────────────────────────────────────────────────────
|
2
|
+
process.env.ENV = "production";
|
3
|
+
process.env.CHAIN_ID = "42161";
|
4
|
+
process.env.ROOT = "0xfc0aa71421f32507050b193be6d445bf674a774cc4c30ad15657a0e3034bec7a";
|
2
5
|
if (!process.env.ENV || !process.env.CHAIN_ID || !process.env.ROOT)
|
3
6
|
throw new Error("[ENV]: missing variable");
|
4
7
|
import { log } from "@/utils/logger";
|
@@ -1,14 +1,14 @@
|
|
1
1
|
import { CampaignService } from "@/modules/v4/campaign";
|
2
|
+
import { ChainService } from "@/modules/v4/chain/chain.service";
|
2
3
|
import { DynamicDataService } from "@/modules/v4/dynamicData/dynamicData.service";
|
3
4
|
import { OpportunityService } from "@/modules/v4/opportunity";
|
4
5
|
import { Campaign as CampaignEnum } from "@sdk";
|
5
6
|
import moment from "moment";
|
6
7
|
// ─── Required Env Variables ──────────────────────────────────────────────────
|
7
|
-
const chainId = Number(process.env.CHAIN_ID);
|
8
|
-
if (!chainId)
|
9
|
-
throw new Error("Environment variable CHAIN_ID is required.");
|
8
|
+
// const chainId = Number(process.env.CHAIN_ID);
|
9
|
+
// if (!chainId) throw new Error("Environment variable CHAIN_ID is required.");
|
10
10
|
// ─── Update Dynamic Data (APR / TVL / Daily Rewards) ─────────────────────────
|
11
|
-
const main = async () => {
|
11
|
+
const main = async (chainId) => {
|
12
12
|
const liveCampaigns = (await CampaignService.getLiveCampaigns({ computeChainId: chainId })).map(c => {
|
13
13
|
return {
|
14
14
|
amount: c.amount,
|
@@ -58,11 +58,13 @@ const main = async () => {
|
|
58
58
|
// 4. Update the status of the opportunities associated to live campaigns
|
59
59
|
await OpportunityService.updateMany(liveOpportunityIds, { status: "LIVE" });
|
60
60
|
};
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
61
|
+
const chains = await ChainService.getSupportedIds();
|
62
|
+
const promises = [];
|
63
|
+
for (const chain of chains)
|
64
|
+
promises.push(main(chain));
|
65
|
+
await Promise.allSettled(promises)
|
66
|
+
.then(success => (success ? process.exit(0) : process.exit(1)))
|
67
|
+
.catch((err) => {
|
66
68
|
console.error(err);
|
67
69
|
process.exit(1);
|
68
|
-
}
|
70
|
+
});
|
@@ -3,14 +3,13 @@ import { TTLPresets } from "@/modules/v4/cache/cache.model";
|
|
3
3
|
import { ChainService } from "@/modules/v4/chain/chain.service";
|
4
4
|
import { MerklRootRepository } from "@/modules/v4/merklRoot/merklRoot.repository";
|
5
5
|
import { OpportunityService } from "@/modules/v4/opportunity";
|
6
|
-
import { NETWORK_LABELS, log
|
6
|
+
import { NETWORK_LABELS, log } from "@sdk";
|
7
7
|
const main = async () => {
|
8
8
|
try {
|
9
9
|
const chains = await ChainService.getSupportedIds();
|
10
10
|
const promises = [];
|
11
11
|
for (const chain of chains)
|
12
|
-
promises.push(
|
13
|
-
));
|
12
|
+
promises.push(MerklRootRepository.fetch(chain).catch(_err => log.warn(`RPC calls cache update failed for ${NETWORK_LABELS[chain]}.`)));
|
14
13
|
await Promise.allSettled(promises);
|
15
14
|
// ─── Refresh Cache For GET /opportunities ────
|
16
15
|
await CacheService.set(TTLPresets.MIN_5, OpportunityService.findMany, { items: 25, page: 0 });
|
@@ -279,9 +279,13 @@ export class CampaignService {
|
|
279
279
|
*/
|
280
280
|
static format(campaign) {
|
281
281
|
const { DistributionChain, ComputeChain, Creator, RewardToken, params, CampaignStatus, createdAt, manualOverrides: _, ...c } = campaign;
|
282
|
+
const updatedParams = params;
|
283
|
+
if (ComputeChain.name === "Etherlink") {
|
284
|
+
updatedParams.blacklist = [];
|
285
|
+
}
|
282
286
|
return {
|
283
287
|
...c,
|
284
|
-
params:
|
288
|
+
params: updatedParams,
|
285
289
|
chain: ComputeChain,
|
286
290
|
endTimestamp: Number(c.endTimestamp),
|
287
291
|
startTimestamp: Number(c.startTimestamp),
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { type ChainId } from "@sdk";
|
1
|
+
import { type ChainId, DistributorService } from "@sdk";
|
2
2
|
import type { CreateRootModel, RootByTimestampModel } from "./merklRoot.model";
|
3
3
|
export declare class MerklRootRepository {
|
4
4
|
static firstRoot(chainId: ChainId): Promise<{
|
@@ -13,13 +13,7 @@ export declare class MerklRootRepository {
|
|
13
13
|
root: string;
|
14
14
|
epoch: number;
|
15
15
|
}[]>;
|
16
|
-
static fetch(chainId: ChainId): Promise<
|
17
|
-
live: any;
|
18
|
-
tree: any;
|
19
|
-
lastTree: any;
|
20
|
-
endOfDisputePeriod: any;
|
21
|
-
disputer: any;
|
22
|
-
}>;
|
16
|
+
static fetch(chainId: ChainId): Promise<Partial<Awaited<ReturnType<ReturnType<typeof DistributorService>["fetchUpdateData"]>>>>;
|
23
17
|
static create(x: CreateRootModel): Promise<{
|
24
18
|
chainId: number;
|
25
19
|
timestamp: bigint;
|
@@ -1,5 +1,6 @@
|
|
1
|
+
import { log } from "@/utils/logger";
|
1
2
|
import { apiDbClient } from "@db";
|
2
|
-
import { DistributorService } from "@sdk";
|
3
|
+
import { DistributorService, NETWORK_LABELS, withTimeout } from "@sdk";
|
3
4
|
export class MerklRootRepository {
|
4
5
|
static async firstRoot(chainId) {
|
5
6
|
return await apiDbClient.merklRoot.findFirst({
|
@@ -24,8 +25,21 @@ export class MerklRootRepository {
|
|
24
25
|
return [res[0], res[1]];
|
25
26
|
}
|
26
27
|
static async fetch(chainId) {
|
27
|
-
const
|
28
|
-
|
28
|
+
const RPC_CALL_TIMEOUT = 4_000;
|
29
|
+
try {
|
30
|
+
// Try fetching the data using a RPC
|
31
|
+
const { live, tree, lastTree, endOfDisputePeriod, disputer } = await withTimeout(DistributorService(chainId).fetchUpdateData(), RPC_CALL_TIMEOUT);
|
32
|
+
return { live, tree, lastTree, endOfDisputePeriod, disputer };
|
33
|
+
}
|
34
|
+
catch (e) {
|
35
|
+
// If the error is a timeout, log a warning and return a void object
|
36
|
+
if (e.message === `Timed out after ${RPC_CALL_TIMEOUT}ms`) {
|
37
|
+
const errorMessage = `fetching Merkle Root for chain ${NETWORK_LABELS[chainId]} timed out`;
|
38
|
+
log.warn(errorMessage);
|
39
|
+
return {};
|
40
|
+
}
|
41
|
+
throw e;
|
42
|
+
}
|
29
43
|
}
|
30
44
|
static async create(x) {
|
31
45
|
return await apiDbClient.merklRoot.create({
|
@@ -13,19 +13,7 @@ export declare class MerklRootService {
|
|
13
13
|
root: string;
|
14
14
|
epoch: number;
|
15
15
|
}[]>;
|
16
|
-
|
17
|
-
* Fetch roots for the provided chain
|
18
|
-
* @param chainId to fetch roots for
|
19
|
-
* @returns object with live and last tree roots
|
20
|
-
*/
|
21
|
-
static fetch(chainId: ChainId): Promise<any>;
|
22
|
-
static fetchFromCache(chainId: ChainId): Promise<{
|
23
|
-
live: any;
|
24
|
-
tree: any;
|
25
|
-
lastTree: any;
|
26
|
-
endOfDisputePeriod: any;
|
27
|
-
disputer: any;
|
28
|
-
}>;
|
16
|
+
static fetchFromCache(chainId: ChainId): Promise<any>;
|
29
17
|
/**
|
30
18
|
* Fetch all roots for the provided chains
|
31
19
|
* @param chainIds to fetch roots for
|
@@ -1,6 +1,5 @@
|
|
1
|
-
import { HttpError } from "@/errors";
|
2
1
|
import { log } from "@/utils/logger";
|
3
|
-
import { NETWORK_LABELS
|
2
|
+
import { NETWORK_LABELS } from "@sdk";
|
4
3
|
import { CacheService } from "../cache";
|
5
4
|
import { TTLPresets } from "../cache/cache.model";
|
6
5
|
import { ChainService } from "../chain/chain.service";
|
@@ -12,36 +11,16 @@ export class MerklRootService {
|
|
12
11
|
static async rootForTimestamp(x) {
|
13
12
|
return await MerklRootRepository.rootForTimestamp(x);
|
14
13
|
}
|
15
|
-
/**
|
16
|
-
* Fetch roots for the provided chain
|
17
|
-
* @param chainId to fetch roots for
|
18
|
-
* @returns object with live and last tree roots
|
19
|
-
*/
|
20
|
-
static async fetch(chainId) {
|
21
|
-
try {
|
22
|
-
return await withTimeout(CacheService.wrap(TTLPresets.MIN_1, MerklRootRepository.fetch, chainId), 5_000);
|
23
|
-
}
|
24
|
-
catch (e) {
|
25
|
-
if (e.message === "Timed out after 3000ms") {
|
26
|
-
const errorMessage = `fetching Merkle Root for chain ${NETWORK_LABELS[chainId]} timed out`;
|
27
|
-
log.warn(errorMessage);
|
28
|
-
throw new Error(errorMessage);
|
29
|
-
}
|
30
|
-
throw e;
|
31
|
-
}
|
32
|
-
}
|
33
14
|
static async fetchFromCache(chainId) {
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
}
|
40
|
-
catch (err) {
|
41
|
-
if (err && err instanceof Error && err.message === "DATA_NOT_FOUND")
|
42
|
-
throw new HttpError(`Failed to fetch data for chain ${NETWORK_LABELS[chainId]}.`, 500);
|
43
|
-
return await MerklRootRepository.fetch(chainId);
|
15
|
+
let data = await CacheService.get(MerklRootRepository.fetch, [chainId]);
|
16
|
+
// If the data is null, it means the cache is empty
|
17
|
+
if (data === null) {
|
18
|
+
log.info(`cache is empty for Merkle Root on ${NETWORK_LABELS[chainId]}, using rpc...`);
|
19
|
+
data = await CacheService.wrap(TTLPresets.MIN_1, MerklRootRepository.fetch, chainId);
|
44
20
|
}
|
21
|
+
if (data.lastTree === undefined)
|
22
|
+
throw `fetching Merkle Root on ${NETWORK_LABELS[chainId]} timed out`;
|
23
|
+
return data;
|
45
24
|
}
|
46
25
|
/**
|
47
26
|
* Fetch all roots for the provided chains
|
@@ -16,7 +16,8 @@ export declare class ProgramPayloadService {
|
|
16
16
|
safePayload: safePayload;
|
17
17
|
nonEncodedConfig: any;
|
18
18
|
}>;
|
19
|
-
static checkMinimumAmount(rewardToken: string,
|
19
|
+
static checkMinimumAmount(rewardToken: string, tokenAmount: bigint, numberOfHours: bigint, distributionChainId: ChainId): Promise<boolean>;
|
20
|
+
static getMinimumAmount(rewardToken: string, distributionChainId: ChainId): Promise<bigint>;
|
20
21
|
static createSafePayloadForCampaign(args: CampaignParametersStruct, distributionChainId: ChainId, rewardToken: string, distributionCreator: string, withApproval?: boolean): Promise<[approvalTransaction, createCampaignTransaction] | [createCampaignTransaction]>;
|
21
22
|
static initiateSafePayload(distributionChainId: ChainId, distributionCreator: string, rewardToken: string, approvalAmount?: string): safePayload;
|
22
23
|
static buildPayload(query: CampaignPayloadInputModel, initialCampaignPayload?: safePayload | null, totalAmount?: string): Promise<safePayload>;
|
@@ -71,6 +71,11 @@ export class ProgramPayloadService {
|
|
71
71
|
// Small hack to avoid the need to parse the config again
|
72
72
|
const args = buildCampaignPayload(parsedConfig, distributionChainId).args;
|
73
73
|
const safePayload = ProgramPayloadService.initiateSafePayload(distributionChainId, distributionCreator, rewardToken);
|
74
|
+
const tokenAmount = BigInt(args?.amount.toString());
|
75
|
+
const numberOfHours = BigInt(args?.duration.toString()) / 3600n;
|
76
|
+
if (!(await ProgramPayloadService.checkMinimumAmount(rewardToken, tokenAmount, numberOfHours, distributionChainId))) {
|
77
|
+
throw new Error("Amount is less than minimum");
|
78
|
+
}
|
74
79
|
const transactions = await ProgramPayloadService.createSafePayloadForCampaign(args, distributionChainId, rewardToken, distributionCreator ?? NULL_ADDRESS);
|
75
80
|
safePayload.transactions.push(...transactions);
|
76
81
|
if (debug) {
|
@@ -78,23 +83,25 @@ export class ProgramPayloadService {
|
|
78
83
|
}
|
79
84
|
return safePayload;
|
80
85
|
}
|
81
|
-
static async checkMinimumAmount(rewardToken,
|
86
|
+
static async checkMinimumAmount(rewardToken, tokenAmount, numberOfHours, distributionChainId) {
|
87
|
+
const minimumAmountPerHour = await ProgramPayloadService.getMinimumAmount(rewardToken, distributionChainId);
|
88
|
+
if (!minimumAmountPerHour) {
|
89
|
+
throw new Error("Token not found");
|
90
|
+
}
|
91
|
+
return tokenAmount / numberOfHours >= BigInt(minimumAmountPerHour);
|
92
|
+
}
|
93
|
+
static async getMinimumAmount(rewardToken, distributionChainId) {
|
82
94
|
const tokenList = await TokenService.getValidRewardTokens(distributionChainId);
|
83
95
|
const minimumAmountPerHour = tokenList.find(token => token.address.toLowerCase() === rewardToken.toLowerCase())?.minimumAmountPerHour;
|
84
96
|
if (!minimumAmountPerHour) {
|
85
97
|
throw new Error("Token not found");
|
86
98
|
}
|
87
|
-
|
88
|
-
const numberOfHours = BigInt(args?.duration.toString()) / 3600n;
|
89
|
-
return tokenAmount / numberOfHours >= BigInt(minimumAmountPerHour);
|
99
|
+
return BigInt(minimumAmountPerHour);
|
90
100
|
}
|
91
101
|
static async createSafePayloadForCampaign(args, distributionChainId, rewardToken, distributionCreator, withApproval = true) {
|
92
102
|
const safePayload = _.cloneDeep(safeTemplate);
|
93
103
|
safePayload.chainId = distributionChainId.toString();
|
94
104
|
safePayload.createdAt = Math.floor(Date.now() / 1000);
|
95
|
-
if (!(await ProgramPayloadService.checkMinimumAmount(rewardToken, args, distributionChainId))) {
|
96
|
-
throw new Error("Amount is less than minimum");
|
97
|
-
}
|
98
105
|
safePayload.transactions[0].to = rewardToken;
|
99
106
|
safePayload.transactions[0].contractInputsValues = {
|
100
107
|
amount: args?.amount.toString(),
|
@@ -180,9 +187,16 @@ export class ProgramPayloadService {
|
|
180
187
|
static async buildProgramPayloadWithAmounts(query, body) {
|
181
188
|
let campaignPayloads = null;
|
182
189
|
const totalAmount = Object.values(body).reduce((sum, amount) => sum + BigInt(amount), 0n);
|
190
|
+
const minimumAmountPerHour = await ProgramPayloadService.getMinimumAmount(query.rewardToken, query.distributionChainId);
|
191
|
+
const numberOfHours = (BigInt(query.endTimestamp) - BigInt(query.startTimestamp)) / 3600n;
|
192
|
+
if (!minimumAmountPerHour) {
|
193
|
+
throw new Error("Token not found");
|
194
|
+
}
|
183
195
|
for (const [campaign, amount] of Object.entries(body)) {
|
184
196
|
if (Object.keys(MerklInterfaceCampaigns[query.program]).includes(campaign)) {
|
185
197
|
const queryCampaign = { ...query, campaign, amount: amount };
|
198
|
+
if (BigInt(amount) / numberOfHours < BigInt(minimumAmountPerHour))
|
199
|
+
throw new Error("Amount is less than minimum");
|
186
200
|
campaignPayloads = await ProgramPayloadService.buildPayload(queryCampaign, campaignPayloads, totalAmount.toString());
|
187
201
|
}
|
188
202
|
else {
|