@merkl/api 0.16.46 → 0.17.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/dist/src/eden/index.d.ts +5 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/jobs/etl/update-dynamic-data.js +97 -42
- package/dist/src/modules/v4/liquidity/liquidity.service.js +1 -1
- package/dist/src/modules/v4/opportunity/opportunity.controller.d.ts +1 -0
- package/dist/src/modules/v4/opportunity/opportunity.model.d.ts +6 -0
- package/dist/src/modules/v4/opportunity/opportunity.model.js +1 -0
- package/dist/src/modules/v4/opportunity/opportunity.repository.d.ts +2 -1
- package/dist/src/modules/v4/opportunity/opportunity.repository.js +5 -2
- package/dist/src/modules/v4/opportunity/opportunity.service.d.ts +18 -2
- package/dist/src/modules/v4/opportunity/opportunity.service.js +9 -3
- package/dist/src/modules/v4/router.d.ts +1 -0
- package/dist/src/modules/v4/token/token.controller.js +31 -5
- package/dist/tsconfig.package.tsbuildinfo +1 -1
- package/package.json +1 -1
package/dist/src/eden/index.d.ts
CHANGED
@@ -386,6 +386,7 @@ declare const eden: {
|
|
386
386
|
};
|
387
387
|
patch: (body: {
|
388
388
|
name?: string | undefined;
|
389
|
+
status?: "NONE" | "PAST" | "LIVE" | "SOON" | undefined;
|
389
390
|
tags?: string[] | undefined;
|
390
391
|
}, options: {
|
391
392
|
headers: {
|
@@ -3668,6 +3669,7 @@ declare const eden: {
|
|
3668
3669
|
};
|
3669
3670
|
patch: (body: {
|
3670
3671
|
name?: string | undefined;
|
3672
|
+
status?: "NONE" | "PAST" | "LIVE" | "SOON" | undefined;
|
3671
3673
|
tags?: string[] | undefined;
|
3672
3674
|
}, options: {
|
3673
3675
|
headers: {
|
@@ -8242,6 +8244,7 @@ export declare const MerklApi: (domain: string | import("elysia").default<"", fa
|
|
8242
8244
|
patch: {
|
8243
8245
|
body: {
|
8244
8246
|
name?: string | undefined;
|
8247
|
+
status?: "NONE" | "PAST" | "LIVE" | "SOON" | undefined;
|
8245
8248
|
tags?: string[] | undefined;
|
8246
8249
|
};
|
8247
8250
|
params: {
|
@@ -12514,6 +12517,7 @@ export declare const MerklApi: (domain: string | import("elysia").default<"", fa
|
|
12514
12517
|
};
|
12515
12518
|
patch: (body: {
|
12516
12519
|
name?: string | undefined;
|
12520
|
+
status?: "NONE" | "PAST" | "LIVE" | "SOON" | undefined;
|
12517
12521
|
tags?: string[] | undefined;
|
12518
12522
|
}, options: {
|
12519
12523
|
headers: {
|
@@ -15796,6 +15800,7 @@ export declare const MerklApi: (domain: string | import("elysia").default<"", fa
|
|
15796
15800
|
};
|
15797
15801
|
patch: (body: {
|
15798
15802
|
name?: string | undefined;
|
15803
|
+
status?: "NONE" | "PAST" | "LIVE" | "SOON" | undefined;
|
15799
15804
|
tags?: string[] | undefined;
|
15800
15805
|
}, options: {
|
15801
15806
|
headers: {
|
package/dist/src/index.d.ts
CHANGED
@@ -6,22 +6,60 @@ import { OpportunityRepository } from "../../modules/v4/opportunity/opportunity.
|
|
6
6
|
import { RewardService } from "../../modules/v4/reward";
|
7
7
|
import { TvlService } from "../../modules/v4/tvl";
|
8
8
|
import { executeSimple } from "../../utils/execute";
|
9
|
-
import { Campaign } from "@sdk";
|
9
|
+
import { Campaign as CampaignTypesEnum } from "@sdk";
|
10
|
+
import moment from "moment";
|
11
|
+
// ─── Required Env Variables ──────────────────────────────────────────────────
|
10
12
|
const chainId = Number(process.env.CHAIN_ID);
|
11
13
|
if (!chainId)
|
12
14
|
throw new Error("Environment variable CHAIN_ID is required.");
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
// ─── Update Dynamic Data (APR / TVL / Daily Rewards) ─────────────────────────
|
16
|
+
async function updateDynamicData(liveCampaigns, campaignType) {
|
17
|
+
try {
|
18
|
+
const dynamicData = await executeSimple(chainId, campaignsDynamicData(chainId, liveCampaigns, campaignType));
|
19
|
+
const oppMap = {};
|
20
|
+
for (const data of dynamicData) {
|
21
|
+
if (!!data) {
|
22
|
+
// Main Parameter OVERRIDING
|
23
|
+
if (data.campaignType === CampaignTypesEnum.SILO && data.campaignParameters.whitelist?.length === 1)
|
24
|
+
data.mainParameter = `${data.mainParameter}-${data.campaignParameters.whitelist[0]}`;
|
25
|
+
if (!oppMap[`${data.campaignType}_${data.mainParameter}`])
|
26
|
+
oppMap[`${data.campaignType}_${data.mainParameter}`] = {};
|
27
|
+
oppMap[`${data.campaignType}_${data.mainParameter}`][data.campaignId] = data;
|
28
|
+
}
|
29
|
+
}
|
30
|
+
for (const entry of Object.entries(oppMap)) {
|
31
|
+
const [type, mainParameter] = entry[0].split("_");
|
32
|
+
try {
|
33
|
+
const apr = AprService.extractFromDynamicData(+type, Object.values(entry[1]));
|
34
|
+
const tvl = TvlService.extractFromDynamicData(+type, Object.values(entry[1]));
|
35
|
+
const dailyRewards = await RewardService.extractDailyRewardsRecordFromDynamicData(+type, Object.values(entry[1]));
|
36
|
+
const opportunityId = OpportunityService.hashId({
|
37
|
+
chainId,
|
38
|
+
identifier: mainParameter,
|
39
|
+
type: type,
|
40
|
+
});
|
41
|
+
await OpportunityRepository.updateRecords(opportunityId, apr, tvl, dailyRewards);
|
42
|
+
}
|
43
|
+
catch (err) {
|
44
|
+
console.log(mainParameter);
|
45
|
+
console.error(err);
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
catch (err) {
|
50
|
+
console.error(err);
|
51
|
+
}
|
52
|
+
}
|
53
|
+
// ─── Get And Transform Live Campaigns Into A Map ─────────────────────────────
|
54
|
+
const getLiveCampaignsByType = async (chainId) => {
|
55
|
+
const liveCampaigns = (await CampaignService.getLiveCampaigns({ computeChainId: chainId })).map(c => {
|
18
56
|
return {
|
19
57
|
amount: c.amount,
|
20
58
|
campaignId: c.campaignId,
|
21
59
|
mainParameter: c.Opportunity.identifier,
|
22
60
|
campaignParameters: c.params,
|
23
61
|
campaignSubType: c.subType,
|
24
|
-
campaignType,
|
62
|
+
campaignType: c.type,
|
25
63
|
chainId: c.distributionChainId,
|
26
64
|
computeChainId: c.computeChainId,
|
27
65
|
creator: c.creatorAddress,
|
@@ -30,42 +68,59 @@ async function updateDynamicData() {
|
|
30
68
|
startTimestamp: c.startTimestamp,
|
31
69
|
};
|
32
70
|
});
|
33
|
-
const
|
34
|
-
const
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
oppMap[`${data.campaignType}_${data.mainParameter}`][data.campaignId] = data;
|
43
|
-
}
|
71
|
+
const campaignTypeToCampaigns = new Map();
|
72
|
+
for (const campaign of liveCampaigns) {
|
73
|
+
const type = campaign.campaignType;
|
74
|
+
let campaigns = campaignTypeToCampaigns.get(campaign.campaignType);
|
75
|
+
if (!campaigns)
|
76
|
+
campaigns = [campaign];
|
77
|
+
else
|
78
|
+
campaigns.push(campaign);
|
79
|
+
campaignTypeToCampaigns.set(type, campaigns);
|
44
80
|
}
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
81
|
+
return campaignTypeToCampaigns;
|
82
|
+
};
|
83
|
+
// ─── Main function / entry point ─────────────────────────────────────────────
|
84
|
+
const main = async () => {
|
85
|
+
try {
|
86
|
+
// 1. Get a map of campaigns by campaign type
|
87
|
+
const liveCampaigns = await getLiveCampaignsByType(chainId);
|
88
|
+
// 2. Call updateDynamicData with each entries of the map (process by campaign type)
|
89
|
+
for (const [type, campaigns] of liveCampaigns.entries())
|
90
|
+
await updateDynamicData(campaigns, type);
|
91
|
+
// 3. Update status of opportunities
|
92
|
+
// 3.1 Get current live opportunities
|
93
|
+
const liveOpportunities = await OpportunityService.findLiveWithCampaigns(chainId);
|
94
|
+
// 3.2 For each currently live opportunities, infer its updated status by looping through its campaigns
|
95
|
+
const now = moment().unix();
|
96
|
+
for (const opportunity of liveOpportunities) {
|
97
|
+
let status = "NONE";
|
98
|
+
const campaigns = opportunity.campaigns;
|
99
|
+
for (const campaign of campaigns) {
|
100
|
+
if (status !== "LIVE" && campaign.endTimestamp < now)
|
101
|
+
status = "PAST";
|
102
|
+
else if (campaign.startTimestamp < now && campaign.endTimestamp > now)
|
103
|
+
status = "LIVE";
|
104
|
+
else if (status !== "LIVE" && campaign.startTimestamp > now)
|
105
|
+
status = "SOON";
|
106
|
+
}
|
107
|
+
await OpportunityService.updateStatus(opportunity.id, status);
|
57
108
|
}
|
58
|
-
|
59
|
-
|
60
|
-
|
109
|
+
// 4. Compute the opportunity ids of the liveCampaigns
|
110
|
+
const opportunities = new Set();
|
111
|
+
for (const [type, campaigns] of liveCampaigns.entries()) {
|
112
|
+
for (const campaign of campaigns) {
|
113
|
+
const opportunityId = OpportunityService.hashId({ chainId, identifier: campaign.mainParameter, type });
|
114
|
+
opportunities.add(opportunityId);
|
115
|
+
}
|
61
116
|
}
|
117
|
+
// 5. Update the status of the liveCampaigns opportunities
|
118
|
+
await OpportunityService.updateMany(Array.from(opportunities), { status: "LIVE" });
|
119
|
+
process.exit(0);
|
62
120
|
}
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
}
|
68
|
-
|
69
|
-
console.error(err);
|
70
|
-
process.exit(1);
|
71
|
-
}
|
121
|
+
catch (err) {
|
122
|
+
console.error(err);
|
123
|
+
process.exit(1);
|
124
|
+
}
|
125
|
+
};
|
126
|
+
await main();
|
@@ -17,7 +17,7 @@ export class LiquidityService {
|
|
17
17
|
[Campaign.DOLOMITE]: new DolomitePositionFetcher(),
|
18
18
|
};
|
19
19
|
static async fetchPositions(query) {
|
20
|
-
const opportunities = await OpportunityService.
|
20
|
+
const opportunities = await OpportunityService.findLiveWithCampaigns(query.chainId, 1);
|
21
21
|
const promises = [];
|
22
22
|
for (const campaignType of Object.keys(Campaign)) {
|
23
23
|
const fetcher = LiquidityService.#fetchers[Number.parseInt(campaignType)];
|
@@ -340,6 +340,12 @@ export declare const OpportunityIdDto: import("@sinclair/typebox").TObject<{
|
|
340
340
|
export declare const UpdateOpportunityDto: import("@sinclair/typebox").TObject<{
|
341
341
|
name: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
342
342
|
tags: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
343
|
+
status: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TEnum<{
|
344
|
+
NONE: "NONE";
|
345
|
+
PAST: "PAST";
|
346
|
+
LIVE: "LIVE";
|
347
|
+
SOON: "SOON";
|
348
|
+
}>>;
|
343
349
|
}>;
|
344
350
|
export type GetOpportunitiesQueryModel = typeof GetOpportunitiesQueryDto.static;
|
345
351
|
export type GetOpportunityQueryModel = typeof GetOpportunityQueryDto.static;
|
@@ -628,7 +628,7 @@ export declare abstract class OpportunityRepository {
|
|
628
628
|
apr: number;
|
629
629
|
dailyRewards: number;
|
630
630
|
})[]>;
|
631
|
-
static
|
631
|
+
static findLiveWithCampaigns(chainId: MerklChainId, take?: number): Promise<({
|
632
632
|
Chain: {
|
633
633
|
name: string;
|
634
634
|
id: number;
|
@@ -936,4 +936,5 @@ export declare abstract class OpportunityRepository {
|
|
936
936
|
apr: number;
|
937
937
|
dailyRewards: number;
|
938
938
|
}>;
|
939
|
+
static updateMany(ids: string[], data: UpdateOpportunityModel): Promise<import("database/api/.generated/runtime/library").GetBatchResult>;
|
939
940
|
}
|
@@ -257,7 +257,7 @@ export class OpportunityRepository {
|
|
257
257
|
...args,
|
258
258
|
});
|
259
259
|
}
|
260
|
-
static async
|
260
|
+
static async findLiveWithCampaigns(chainId, take) {
|
261
261
|
const now = moment().unix();
|
262
262
|
return await apiDbClient.opportunity.findMany({
|
263
263
|
include: {
|
@@ -274,7 +274,7 @@ export class OpportunityRepository {
|
|
274
274
|
CampaignStatus: true,
|
275
275
|
Creator: true,
|
276
276
|
},
|
277
|
-
take:
|
277
|
+
take: take ? take : undefined,
|
278
278
|
orderBy: { endTimestamp: "desc" },
|
279
279
|
},
|
280
280
|
MainProtocol: true,
|
@@ -406,4 +406,7 @@ export class OpportunityRepository {
|
|
406
406
|
static async update(id, data) {
|
407
407
|
return await apiDbClient.opportunity.update({ where: { id }, data });
|
408
408
|
}
|
409
|
+
static async updateMany(ids, data) {
|
410
|
+
return await apiDbClient.opportunity.updateMany({ where: { id: { in: ids } }, data });
|
411
|
+
}
|
409
412
|
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { type CreateCampaignModel, type GetCampaignQueryModel } from "../campaign";
|
2
|
-
import { Prisma } from "../../../../database/api/.generated";
|
2
|
+
import { Prisma, Status } from "../../../../database/api/.generated";
|
3
3
|
import { type ChainId, type MerklChainId } from "@sdk";
|
4
4
|
import type { CreateOpportunityModel, GetOpportunitiesQueryModel, LightOpportunityFromDB, OpportunityResourceModel, OpportunityUnique, UpdateOpportunityModel } from "./opportunity.model";
|
5
5
|
import { OpportunityRepository } from "./opportunity.repository";
|
@@ -63,6 +63,21 @@ export declare abstract class OpportunityService {
|
|
63
63
|
depositUrl: any;
|
64
64
|
tags: string[];
|
65
65
|
}>;
|
66
|
+
static updateStatus(opportunityId: string, status: Status): Promise<{
|
67
|
+
name: string;
|
68
|
+
type: string;
|
69
|
+
id: string;
|
70
|
+
status: import("../../../../database/api/.generated").$Enums.Status;
|
71
|
+
tags: string[];
|
72
|
+
identifier: string;
|
73
|
+
chainId: number;
|
74
|
+
action: import("../../../../database/api/.generated").$Enums.OpportunityAction;
|
75
|
+
depositUrl: string | null;
|
76
|
+
mainProtocolId: string | null;
|
77
|
+
tvl: number;
|
78
|
+
apr: number;
|
79
|
+
dailyRewards: number;
|
80
|
+
}>;
|
66
81
|
static updateStatusFromCampaign(campaign: Omit<CreateCampaignModel, "id">, upsert?: boolean): Promise<{
|
67
82
|
id: string;
|
68
83
|
chainId: number;
|
@@ -535,7 +550,7 @@ export declare abstract class OpportunityService {
|
|
535
550
|
* @returns the number of opportunities
|
536
551
|
*/
|
537
552
|
static countMany(query: GetOpportunitiesQueryModel): Promise<number>;
|
538
|
-
static
|
553
|
+
static findLiveWithCampaigns(chainId: MerklChainId, take?: number): Promise<{
|
539
554
|
apr: number;
|
540
555
|
aprRecord: {
|
541
556
|
cumulated: number;
|
@@ -882,4 +897,5 @@ export declare abstract class OpportunityService {
|
|
882
897
|
apr: number;
|
883
898
|
dailyRewards: number;
|
884
899
|
}>;
|
900
|
+
static updateMany(ids: string[], data: UpdateOpportunityModel): Promise<import("database/api/.generated/runtime/library").GetBatchResult>;
|
885
901
|
}
|
@@ -28,7 +28,7 @@ import { getMorphoMetadata } from "./subservices/getMorphoMetadata.service";
|
|
28
28
|
import { getRadiantMetadata } from "./subservices/getRadiantMetadata.service";
|
29
29
|
import { getSiloMetadata } from "./subservices/getSiloMetadata.service";
|
30
30
|
import { getUniswapV4Metadata } from "./subservices/getUniswapV4Metadata.service";
|
31
|
-
import { getVestMetadata } from "./subservices/
|
31
|
+
import { getVestMetadata } from "./subservices/getVestMetaData.service";
|
32
32
|
export class OpportunityService {
|
33
33
|
static hashId(opportunity) {
|
34
34
|
return Bun.hash(`${opportunity.chainId}${opportunity.type}${opportunity.identifier}`).toString();
|
@@ -165,6 +165,9 @@ export class OpportunityService {
|
|
165
165
|
await OpportunityRepository.create(opportunity, upsert);
|
166
166
|
return opportunity;
|
167
167
|
}
|
168
|
+
static async updateStatus(opportunityId, status) {
|
169
|
+
return await OpportunityRepository.update(opportunityId, { status });
|
170
|
+
}
|
168
171
|
static async updateStatusFromCampaign(campaign, upsert = true) {
|
169
172
|
const campaignType = CampaignService.getTypeFromV3(campaign.type);
|
170
173
|
const metadata = await OpportunityService.#getMetadata(campaign);
|
@@ -283,10 +286,10 @@ export class OpportunityService {
|
|
283
286
|
static async countMany(query) {
|
284
287
|
return await OpportunityRepository.countMany(query);
|
285
288
|
}
|
286
|
-
static async
|
289
|
+
static async findLiveWithCampaigns(chainId, take) {
|
287
290
|
return record("data-layer.access", async () => {
|
288
291
|
return await CacheService.wrap(TTLPresets.MIN_10, async (chainId) => {
|
289
|
-
const opportunities = await OpportunityRepository.
|
292
|
+
const opportunities = await OpportunityRepository.findLiveWithCampaigns(chainId, take);
|
290
293
|
return opportunities.map(o => {
|
291
294
|
return OpportunityService.formatResponse(o);
|
292
295
|
});
|
@@ -365,4 +368,7 @@ export class OpportunityService {
|
|
365
368
|
static async update(id, data) {
|
366
369
|
return await OpportunityRepository.update(id, data);
|
367
370
|
}
|
371
|
+
static async updateMany(ids, data) {
|
372
|
+
return await OpportunityRepository.updateMany(ids, data);
|
373
|
+
}
|
368
374
|
}
|
@@ -7,7 +7,7 @@ import { ChainDto } from "../accounting";
|
|
7
7
|
import { CreateTokenDto, FindUniqueTokenAllowanceDto, FindUniqueTokenDto, GetTokenBalanceDto, GetTokenQueryDto, NotionWebhookDto, TokenIdDto, UpdateTokenDto, } from "./token.model";
|
8
8
|
import { TokenService } from "./token.service";
|
9
9
|
// ─── Tokens Controller ───────────────────────────────────────────────────────
|
10
|
-
export const TokenController = new Elysia({ prefix: "/tokens", detail: { tags: ["Tokens"]
|
10
|
+
export const TokenController = new Elysia({ prefix: "/tokens", detail: { tags: ["Tokens"] } })
|
11
11
|
// ─── Get A Token By Id ───────────────────────────────────────────────
|
12
12
|
.get("/:id", async ({ params }) => {
|
13
13
|
try {
|
@@ -18,7 +18,12 @@ export const TokenController = new Elysia({ prefix: "/tokens", detail: { tags: [
|
|
18
18
|
if (err.code && err.code === "P2025")
|
19
19
|
throw new NotFoundError();
|
20
20
|
}
|
21
|
-
}, {
|
21
|
+
}, {
|
22
|
+
params: FindUniqueTokenDto,
|
23
|
+
detail: {
|
24
|
+
hide: true,
|
25
|
+
},
|
26
|
+
})
|
22
27
|
.get("/:id/allowance/:owner/:spender", async ({ params: { id, owner, spender } }) => {
|
23
28
|
try {
|
24
29
|
const [chainId, address] = id.split("-");
|
@@ -30,11 +35,19 @@ export const TokenController = new Elysia({ prefix: "/tokens", detail: { tags: [
|
|
30
35
|
if (err.code && err.code === "P2025")
|
31
36
|
throw new NotFoundError();
|
32
37
|
}
|
33
|
-
}, {
|
38
|
+
}, {
|
39
|
+
params: FindUniqueTokenAllowanceDto,
|
40
|
+
detail: {
|
41
|
+
hide: true,
|
42
|
+
},
|
43
|
+
})
|
34
44
|
// ─── Get Valid Reward Token ───────────────────────────────────────────
|
35
45
|
.get("/reward/:chainId", async ({ params }) => TokenService.getValidRewardTokens(params.chainId), {
|
36
46
|
params: ChainDto,
|
37
47
|
beforeHandle: ({ params }) => throwOnUnsupportedChainId(params.chainId),
|
48
|
+
detail: {
|
49
|
+
description: "Get the list of tokens that are accept as reward tokens on a given chain",
|
50
|
+
},
|
38
51
|
})
|
39
52
|
// ─── Get Tokens With Balances ────────────────────────────────────────
|
40
53
|
.get("/balances", async ({ query: { chainId, userAddress, tokenAddress, additionalTokenAddresses } }) => {
|
@@ -43,11 +56,24 @@ export const TokenController = new Elysia({ prefix: "/tokens", detail: { tags: [
|
|
43
56
|
return await TokenService.fetchVerifiedBalances(chainId, userAddress, additionalTokenAddresses);
|
44
57
|
}, {
|
45
58
|
query: GetTokenBalanceDto,
|
59
|
+
detail: {
|
60
|
+
hide: true,
|
61
|
+
},
|
46
62
|
})
|
47
63
|
// ─── Get Many Tokens ─────────────────────────────────────────────────
|
48
|
-
.get("/", async ({ query }) => await TokenService.findMany(query), {
|
64
|
+
.get("/", async ({ query }) => await TokenService.findMany(query), {
|
65
|
+
query: GetTokenQueryDto,
|
66
|
+
detail: {
|
67
|
+
hide: true,
|
68
|
+
},
|
69
|
+
})
|
49
70
|
// ─── Count Tokens ────────────────────────────────────────────────────
|
50
|
-
.get("/count", async ({ query }) => await TokenService.countMany(query), {
|
71
|
+
.get("/count", async ({ query }) => await TokenService.countMany(query), {
|
72
|
+
query: GetTokenQueryDto,
|
73
|
+
detail: {
|
74
|
+
hide: true,
|
75
|
+
},
|
76
|
+
})
|
51
77
|
// ─── Update IsTest Status ────────────────────────────────────────────
|
52
78
|
.post("/", async ({ body }) => await TokenService.fillAndCreate(body), {
|
53
79
|
body: CreateTokenDto,
|