@verii/endpoints-event-processing 1.0.0-pre.1752076816
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/LICENSE +202 -0
- package/NOTICE +1 -0
- package/README.md +42 -0
- package/package.json +64 -0
- package/src/config/abi.json +1 -0
- package/src/config/config.js +138 -0
- package/src/controllers/events-processing/controller.js +115 -0
- package/src/controllers/health-probes/autohooks.js +6 -0
- package/src/controllers/health-probes/controller.js +117 -0
- package/src/entities/burned-coupons/index.js +3 -0
- package/src/entities/burned-coupons/repo.js +29 -0
- package/src/entities/health-probes/domains/health-states.js +8 -0
- package/src/entities/health-probes/domains/index.js +3 -0
- package/src/entities/health-probes/index.js +3 -0
- package/src/entities/index.js +23 -0
- package/src/entities/oauth/index.js +19 -0
- package/src/entities/oauth/scopes.js +21 -0
- package/src/entities/purchases/domain/constants.js +64 -0
- package/src/entities/purchases/domain/index.js +3 -0
- package/src/entities/purchases/index.js +4 -0
- package/src/entities/purchases/repo.js +39 -0
- package/src/entities/transactions/domain/constants.js +31 -0
- package/src/entities/transactions/domain/index.js +3 -0
- package/src/entities/transactions/index.js +3 -0
- package/src/event-processing-endpoints.js +57 -0
- package/src/handlers/handle-coupons-burned-logging-event.js +58 -0
- package/src/handlers/handle-coupons-burned-verification-event.js +188 -0
- package/src/handlers/handle-coupons-minted-logging-event.js +73 -0
- package/src/handlers/handle-credential-issued-logging-event.js +70 -0
- package/src/handlers/handle-credential-issued-rewards-event.js +218 -0
- package/src/handlers/index.js +7 -0
- package/src/helpers/document-functions.js +28 -0
- package/src/helpers/event-decoding.js +20 -0
- package/src/helpers/index.js +5 -0
- package/src/helpers/map-coupon-burned.js +32 -0
- package/src/index.js +22 -0
- package/src/init-server.js +65 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
const { map, zip } = require('lodash/fp');
|
|
2
|
+
const { initVerificationCoupon } = require('@verii/metadata-registration');
|
|
3
|
+
|
|
4
|
+
const { batchOperations } = require('@verii/fineract-client');
|
|
5
|
+
const { getDidAndAliases } = require('@verii/did-doc');
|
|
6
|
+
|
|
7
|
+
const task = 'coupons-burned-verification';
|
|
8
|
+
|
|
9
|
+
const { initDocumentFunctions, mapCouponBurned } = require('../helpers');
|
|
10
|
+
|
|
11
|
+
const initReadEventsFromBlock = async (context) => {
|
|
12
|
+
const { config } = context;
|
|
13
|
+
const { pullBurnCouponEvents } = await initVerificationCoupon(
|
|
14
|
+
{
|
|
15
|
+
contractAddress: config.couponContractAddress,
|
|
16
|
+
rpcProvider: context.rpcProvider,
|
|
17
|
+
},
|
|
18
|
+
context
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return async (block) => {
|
|
22
|
+
return pullBurnCouponEvents(block);
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
const syncBurnsWithFineract = async (
|
|
26
|
+
{ burnerDidToBundleMap, organizationsMap, burnEvents },
|
|
27
|
+
context
|
|
28
|
+
) => {
|
|
29
|
+
const { log } = context;
|
|
30
|
+
if (burnerDidToBundleMap.size === 0) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const voucherQuantitiesToBurn = map((burnEvent) => {
|
|
35
|
+
const orgOfBurnEvent = organizationsMap.get(burnEvent.burnerDid);
|
|
36
|
+
return {
|
|
37
|
+
clientId: orgOfBurnEvent.ids.fineractClientId,
|
|
38
|
+
quantity: 1,
|
|
39
|
+
submittedOnDate: burnEvent.burnTime,
|
|
40
|
+
};
|
|
41
|
+
}, burnEvents);
|
|
42
|
+
|
|
43
|
+
log.info({ task, voucherQuantitiesToBurn });
|
|
44
|
+
const batchResponses = await batchOperations(
|
|
45
|
+
{ clientVoucherBurns: voucherQuantitiesToBurn, transactionalBatch: false },
|
|
46
|
+
context
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
for (const [payload, batchResponse] of zip(
|
|
50
|
+
voucherQuantitiesToBurn,
|
|
51
|
+
batchResponses
|
|
52
|
+
)) {
|
|
53
|
+
if (batchResponse.statusCode >= 400)
|
|
54
|
+
log.warn({
|
|
55
|
+
code: 'failed-burn-sync',
|
|
56
|
+
batchResponse,
|
|
57
|
+
payload,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return batchResponses;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const writeBurnsToDatabase = async (
|
|
65
|
+
{ burnerDidToBundleMap, organizationsMap },
|
|
66
|
+
context
|
|
67
|
+
) => {
|
|
68
|
+
const { repos } = context;
|
|
69
|
+
if (burnerDidToBundleMap.size === 0) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const now = new Date();
|
|
73
|
+
const burnedCouponsProms = [];
|
|
74
|
+
for (const [burnerDid, bundleToBurnsMap] of burnerDidToBundleMap.entries()) {
|
|
75
|
+
for (const [bundleId, count] of bundleToBurnsMap.entries()) {
|
|
76
|
+
const prom = async () => {
|
|
77
|
+
const purchase = await repos.purchases.findOne({
|
|
78
|
+
filter: {
|
|
79
|
+
'couponBundle.couponBundleId': bundleId,
|
|
80
|
+
$expr: { $lt: ['$couponBundle.used', '$couponBundle.quantity'] },
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (!purchase) return Promise.resolve();
|
|
85
|
+
|
|
86
|
+
await repos.purchases.collection().findOneAndUpdate(
|
|
87
|
+
{ _id: purchase._id },
|
|
88
|
+
{
|
|
89
|
+
$inc: {
|
|
90
|
+
'couponBundle.used': count,
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return repos.burnedCoupons.insert({
|
|
96
|
+
purchaseId: purchase.purchaseId,
|
|
97
|
+
used: count,
|
|
98
|
+
at: now,
|
|
99
|
+
clientId: organizationsMap.get(burnerDid)?.ids.brokerClientId,
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
burnedCouponsProms.push(prom());
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return Promise.all(burnedCouponsProms);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const processEventGenerator = async ({ eventsCursor }, context) => {
|
|
109
|
+
const burnerDidToBundleMap = new Map();
|
|
110
|
+
let numberOfEventsRead = 0;
|
|
111
|
+
let burnEvents = [];
|
|
112
|
+
for await (const selectedEvents of eventsCursor()) {
|
|
113
|
+
const mappedEvents = map(
|
|
114
|
+
(evt) => mapCouponBurned(evt, context),
|
|
115
|
+
selectedEvents
|
|
116
|
+
);
|
|
117
|
+
burnEvents = [...burnEvents, ...mappedEvents];
|
|
118
|
+
numberOfEventsRead += selectedEvents.length;
|
|
119
|
+
for (const evt of mappedEvents) {
|
|
120
|
+
const { burnerDid, bundleIdHex: bundleId } = evt;
|
|
121
|
+
// eslint-disable-next-line max-depth
|
|
122
|
+
if (!burnerDidToBundleMap.has(burnerDid)) {
|
|
123
|
+
burnerDidToBundleMap.set(burnerDid, new Map());
|
|
124
|
+
}
|
|
125
|
+
const bundleToBurnsMap = burnerDidToBundleMap.get(burnerDid);
|
|
126
|
+
// eslint-disable-next-line max-depth
|
|
127
|
+
if (!bundleToBurnsMap.has(bundleId)) {
|
|
128
|
+
bundleToBurnsMap.set(bundleId, 0);
|
|
129
|
+
}
|
|
130
|
+
bundleToBurnsMap.set(bundleId, bundleToBurnsMap.get(bundleId) + 1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
burnEvents,
|
|
135
|
+
numberOfEventsRead,
|
|
136
|
+
burnerDidToBundleMap,
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const handleCouponsBurnedVerificationEvent = async (context) => {
|
|
141
|
+
const { log, repos } = context;
|
|
142
|
+
const { readLastSuccessfulBlock, writeLastSuccessfulBlock } =
|
|
143
|
+
initDocumentFunctions({ eventName: task }, context);
|
|
144
|
+
|
|
145
|
+
const readEventsFromBlock = await initReadEventsFromBlock(context);
|
|
146
|
+
|
|
147
|
+
const lastReadBlock = await readLastSuccessfulBlock();
|
|
148
|
+
log.info({ task, lastReadBlock });
|
|
149
|
+
const initialBlockNumber = lastReadBlock + 1;
|
|
150
|
+
const { eventsCursor, latestBlock } = await readEventsFromBlock(
|
|
151
|
+
initialBlockNumber
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const { numberOfEventsRead, burnEvents, burnerDidToBundleMap } =
|
|
155
|
+
await processEventGenerator({ eventsCursor }, context);
|
|
156
|
+
log.info({
|
|
157
|
+
task,
|
|
158
|
+
lastReadBlock,
|
|
159
|
+
numberOfEventsRead,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const organizations = await repos.organizations.findByDids(
|
|
163
|
+
Array.from(burnerDidToBundleMap.keys())
|
|
164
|
+
);
|
|
165
|
+
const organizationsMap = new Map();
|
|
166
|
+
for (const organization of organizations) {
|
|
167
|
+
const ids = getDidAndAliases(organization?.didDoc);
|
|
168
|
+
for (const id of ids) {
|
|
169
|
+
organizationsMap.set(id, organization);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
await writeBurnsToDatabase(
|
|
174
|
+
{ burnerDidToBundleMap, organizationsMap },
|
|
175
|
+
context
|
|
176
|
+
);
|
|
177
|
+
await syncBurnsWithFineract(
|
|
178
|
+
{ burnerDidToBundleMap, organizationsMap, burnEvents },
|
|
179
|
+
context
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
log.info({ task, latestBlock });
|
|
183
|
+
await writeLastSuccessfulBlock(latestBlock);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
module.exports = {
|
|
187
|
+
handleCouponsBurnedVerificationEvent,
|
|
188
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const { map, forEach } = require('lodash/fp');
|
|
2
|
+
const { toHexString } = require('@verii/blockchain-functions');
|
|
3
|
+
const { initVerificationCoupon } = require('@verii/metadata-registration');
|
|
4
|
+
const { initDocumentFunctions } = require('../helpers');
|
|
5
|
+
|
|
6
|
+
const task = 'coupons-minted-logging';
|
|
7
|
+
|
|
8
|
+
const mapEvent = (event) => ({
|
|
9
|
+
blockNumber: event.blockNumber,
|
|
10
|
+
blockHash: event.blockHash,
|
|
11
|
+
transactionIndex: event.transactionIndex,
|
|
12
|
+
transactionHash: event.transactionHash,
|
|
13
|
+
event: event.fragment.name,
|
|
14
|
+
owner: event.args[0],
|
|
15
|
+
bundleId: `${event.args[1]}`,
|
|
16
|
+
bundleIdHex: toHexString(event.args[1]),
|
|
17
|
+
expirationTime: new Date(Number(`${event.args[2]}`) * 1000),
|
|
18
|
+
quantity: `${event.args[3]}`,
|
|
19
|
+
eventTraceId: event.args[4],
|
|
20
|
+
ownerDid: event.args[5],
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const initReadEventsFromBlock = async (context) => {
|
|
24
|
+
const { config } = context;
|
|
25
|
+
const { pullMintCouponBundleEvents } = await initVerificationCoupon(
|
|
26
|
+
{
|
|
27
|
+
contractAddress: config.couponContractAddress,
|
|
28
|
+
rpcProvider: context.rpcProvider,
|
|
29
|
+
},
|
|
30
|
+
context
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
return async (block) => {
|
|
34
|
+
return pullMintCouponBundleEvents(block);
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const handleCouponsMintedLoggingEvent = async (context) => {
|
|
39
|
+
const { log } = context;
|
|
40
|
+
const { readLastSuccessfulBlock, writeLastSuccessfulBlock } =
|
|
41
|
+
initDocumentFunctions({ eventName: task }, context);
|
|
42
|
+
|
|
43
|
+
const readEventsFromBlock = await initReadEventsFromBlock(context);
|
|
44
|
+
|
|
45
|
+
const lastReadBlock = await readLastSuccessfulBlock();
|
|
46
|
+
|
|
47
|
+
log.info({ task, lastReadBlock });
|
|
48
|
+
|
|
49
|
+
const { eventsCursor, latestBlock } = await readEventsFromBlock(
|
|
50
|
+
lastReadBlock + 1
|
|
51
|
+
);
|
|
52
|
+
let numberOfEventsRead = 0;
|
|
53
|
+
for await (const events of eventsCursor()) {
|
|
54
|
+
numberOfEventsRead += events.length;
|
|
55
|
+
const mappedEvents = map(mapEvent, events);
|
|
56
|
+
forEach((event) => {
|
|
57
|
+
log.info(event);
|
|
58
|
+
}, mappedEvents);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
log.info({
|
|
62
|
+
task,
|
|
63
|
+
lastReadBlock,
|
|
64
|
+
numberOfEventsRead,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
log.info({ task, latestBlock });
|
|
68
|
+
await writeLastSuccessfulBlock(latestBlock);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
handleCouponsMintedLoggingEvent,
|
|
73
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const { map, forEach } = require('lodash/fp');
|
|
2
|
+
const { initMetadataRegistry } = require('@verii/metadata-registration');
|
|
3
|
+
const { initDocumentFunctions, decodeIssuerVc } = require('../helpers');
|
|
4
|
+
|
|
5
|
+
const task = 'credential-issued-logging';
|
|
6
|
+
|
|
7
|
+
const mapEvent = (event) => ({
|
|
8
|
+
blockNumber: event.blockNumber,
|
|
9
|
+
blockHash: event.blockHash,
|
|
10
|
+
transactionIndex: event.transactionIndex,
|
|
11
|
+
transactionHash: event.transactionHash,
|
|
12
|
+
event: event.fragment.name,
|
|
13
|
+
sender: event.args[0],
|
|
14
|
+
issuerDid: decodeIssuerVc(event.args[1]),
|
|
15
|
+
listId: `${event.args[2]}`,
|
|
16
|
+
credentialType: event.args[3],
|
|
17
|
+
index: `${event.args[4]}`,
|
|
18
|
+
eventTraceId: event.args[5],
|
|
19
|
+
caoDid: event.args[6],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const initReadEventsFromBlock = async (context) => {
|
|
23
|
+
const { config } = context;
|
|
24
|
+
const { pullAddedCredentialMetadataEvents } = await initMetadataRegistry(
|
|
25
|
+
{
|
|
26
|
+
contractAddress: config.metadataRegistryContractAddress,
|
|
27
|
+
rpcProvider: context.rpcProvider,
|
|
28
|
+
},
|
|
29
|
+
context
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return async (block) => {
|
|
33
|
+
return pullAddedCredentialMetadataEvents(block);
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handleCredentialIssuedLoggingEvent = async (context) => {
|
|
38
|
+
const { log } = context;
|
|
39
|
+
const { readLastSuccessfulBlock, writeLastSuccessfulBlock } =
|
|
40
|
+
initDocumentFunctions({ eventName: task }, context);
|
|
41
|
+
|
|
42
|
+
const readEventsFromBlock = await initReadEventsFromBlock(context);
|
|
43
|
+
|
|
44
|
+
const lastReadBlock = await readLastSuccessfulBlock();
|
|
45
|
+
|
|
46
|
+
log.info({ task, lastReadBlock });
|
|
47
|
+
|
|
48
|
+
const { eventsCursor, latestBlock } = await readEventsFromBlock(
|
|
49
|
+
lastReadBlock + 1
|
|
50
|
+
);
|
|
51
|
+
let numberOfEventsRead = 0;
|
|
52
|
+
for await (const events of eventsCursor()) {
|
|
53
|
+
numberOfEventsRead += events.length;
|
|
54
|
+
const mappedEvents = map(mapEvent, events);
|
|
55
|
+
forEach((event) => log.info(event), mappedEvents);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
log.info({
|
|
59
|
+
task,
|
|
60
|
+
lastReadBlock,
|
|
61
|
+
numberOfEventsRead,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
log.info({ task, latestBlock });
|
|
65
|
+
await writeLastSuccessfulBlock(latestBlock);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
handleCredentialIssuedLoggingEvent,
|
|
70
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
const {
|
|
2
|
+
partition,
|
|
3
|
+
reduce,
|
|
4
|
+
map,
|
|
5
|
+
size,
|
|
6
|
+
isEmpty,
|
|
7
|
+
uniq,
|
|
8
|
+
flow,
|
|
9
|
+
find,
|
|
10
|
+
} = require('lodash/fp');
|
|
11
|
+
const { initMetadataRegistry } = require('@verii/metadata-registration');
|
|
12
|
+
|
|
13
|
+
const { get2BytesHash } = require('@verii/crypto');
|
|
14
|
+
const { batchTransferCredits } = require('@verii/fineract-client');
|
|
15
|
+
|
|
16
|
+
const { getDidAndAliases } = require('@verii/did-doc');
|
|
17
|
+
const { TransactionReasons } = require('../entities');
|
|
18
|
+
const { decodeIssuerVc, initDocumentFunctions } = require('../helpers');
|
|
19
|
+
|
|
20
|
+
const task = 'credential-issued-rewards';
|
|
21
|
+
|
|
22
|
+
const initReadEventsFromBlock = async (context) => {
|
|
23
|
+
const { config } = context;
|
|
24
|
+
const { pullAddedCredentialMetadataEvents } = await initMetadataRegistry(
|
|
25
|
+
{
|
|
26
|
+
contractAddress: config.metadataRegistryContractAddress,
|
|
27
|
+
rpcProvider: context.rpcProvider,
|
|
28
|
+
},
|
|
29
|
+
context
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return async (block) => {
|
|
33
|
+
return pullAddedCredentialMetadataEvents(block);
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
const executeTransfers = async (
|
|
37
|
+
{ rewardsDictionary, organizations },
|
|
38
|
+
context
|
|
39
|
+
) => {
|
|
40
|
+
const {
|
|
41
|
+
config: { vnfRewardDispersalAccountId },
|
|
42
|
+
log,
|
|
43
|
+
} = context;
|
|
44
|
+
if (isEmpty(rewardsDictionary)) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const transfers = reduce(
|
|
48
|
+
(acc, nextOrganization) => {
|
|
49
|
+
const dids = getDidAndAliases(nextOrganization?.didDoc);
|
|
50
|
+
for (const id of dids) {
|
|
51
|
+
const caoKey = `${id}.caoReward`;
|
|
52
|
+
if (rewardsDictionary[caoKey]) {
|
|
53
|
+
acc.push({
|
|
54
|
+
fromAccount: vnfRewardDispersalAccountId,
|
|
55
|
+
toAccount: nextOrganization.ids.tokenAccountId,
|
|
56
|
+
amount: rewardsDictionary[caoKey],
|
|
57
|
+
description: TransactionReasons.CAO_ISSUING_REWARD,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const issuerKey = `${id}.issuerReward`;
|
|
61
|
+
if (rewardsDictionary[issuerKey]) {
|
|
62
|
+
acc.push({
|
|
63
|
+
fromAccount: vnfRewardDispersalAccountId,
|
|
64
|
+
toAccount: nextOrganization.ids.tokenAccountId,
|
|
65
|
+
amount: rewardsDictionary[issuerKey],
|
|
66
|
+
description: TransactionReasons.ISSUER_ISSUING_REWARD,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return acc;
|
|
72
|
+
},
|
|
73
|
+
[],
|
|
74
|
+
organizations
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
log.info({ task, transfers });
|
|
78
|
+
|
|
79
|
+
return batchTransferCredits({ transfers }, context);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const eventsToOrganizationTransactions = async (
|
|
83
|
+
{ eventsCursor, credentialTypes },
|
|
84
|
+
context
|
|
85
|
+
) => {
|
|
86
|
+
const {
|
|
87
|
+
config: { issuerRewardAmount, caoRewardAmount },
|
|
88
|
+
} = context;
|
|
89
|
+
const rewardsDictionary = {};
|
|
90
|
+
const dids = [];
|
|
91
|
+
const calcIssuerReward = (issuerKey) => {
|
|
92
|
+
const currentIssuerReward = rewardsDictionary[issuerKey] ?? 0;
|
|
93
|
+
return currentIssuerReward + issuerRewardAmount;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const calcCaoReward = (caoKey) => {
|
|
97
|
+
const currentCaoReward = rewardsDictionary[caoKey] ?? 0;
|
|
98
|
+
return currentCaoReward + caoRewardAmount;
|
|
99
|
+
};
|
|
100
|
+
let numberOfEventsRead = 0;
|
|
101
|
+
for await (const events of eventsCursor()) {
|
|
102
|
+
numberOfEventsRead += events.length;
|
|
103
|
+
const validEvents = getRewardableEvents(
|
|
104
|
+
{ events, credentialTypes },
|
|
105
|
+
context
|
|
106
|
+
);
|
|
107
|
+
for (const { issuerKey, caoKey, issuerDid, caoDid } of validEvents) {
|
|
108
|
+
dids.push(issuerDid);
|
|
109
|
+
dids.push(caoDid);
|
|
110
|
+
rewardsDictionary[issuerKey] = calcIssuerReward(issuerKey);
|
|
111
|
+
rewardsDictionary[caoKey] = calcCaoReward(caoKey);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return { rewardsDictionary, numberOfEventsRead, dids: uniq(dids) };
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handleCredentialIssuedRewardsEvent = async (context) => {
|
|
118
|
+
const { log, repos } = context;
|
|
119
|
+
const credentialTypes = await getCredentialTypes(context);
|
|
120
|
+
const { readLastSuccessfulBlock, writeLastSuccessfulBlock } =
|
|
121
|
+
initDocumentFunctions({ eventName: task }, context);
|
|
122
|
+
|
|
123
|
+
const readEventsFromBlock = await initReadEventsFromBlock(context);
|
|
124
|
+
|
|
125
|
+
const lastReadBlock = await readLastSuccessfulBlock();
|
|
126
|
+
log.info({ task, lastReadBlock });
|
|
127
|
+
const { eventsCursor, latestBlock } = await readEventsFromBlock(
|
|
128
|
+
lastReadBlock + 1
|
|
129
|
+
);
|
|
130
|
+
const { rewardsDictionary, numberOfEventsRead, dids } =
|
|
131
|
+
await eventsToOrganizationTransactions(
|
|
132
|
+
{ eventsCursor, credentialTypes },
|
|
133
|
+
context
|
|
134
|
+
);
|
|
135
|
+
log.info({
|
|
136
|
+
task,
|
|
137
|
+
lastReadBlock,
|
|
138
|
+
numberOfEventsRead,
|
|
139
|
+
rewardsDictionary,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const allOrganizations = await repos.organizations.findByDids(dids);
|
|
143
|
+
|
|
144
|
+
const [organizations, rejectedOrganizations] = partition(
|
|
145
|
+
'ids.tokenAccountId',
|
|
146
|
+
allOrganizations
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const rejectOrgsCounts = size(rejectedOrganizations);
|
|
150
|
+
if (rejectOrgsCounts > 0) {
|
|
151
|
+
log.warn('organizations were rejected due to missing ids.tokenAccountId');
|
|
152
|
+
log.warn({
|
|
153
|
+
rejectedOrganizations: map('didDoc.id', rejectedOrganizations),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
await executeTransfers({ rewardsDictionary, organizations }, context);
|
|
158
|
+
|
|
159
|
+
log.info({ task, latestBlock });
|
|
160
|
+
await writeLastSuccessfulBlock(latestBlock);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const getRewardableEvents = ({ events, credentialTypes }, context) => {
|
|
164
|
+
const [rewardableEvents, unRewardableEvents] = flow(
|
|
165
|
+
map(mapEvent),
|
|
166
|
+
partition(isRewardedEvent(credentialTypes))
|
|
167
|
+
)(events);
|
|
168
|
+
context.log.info({
|
|
169
|
+
rewardableEvents,
|
|
170
|
+
unRewardableEvents,
|
|
171
|
+
events,
|
|
172
|
+
credentialTypes,
|
|
173
|
+
});
|
|
174
|
+
return rewardableEvents;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const isRewardedEvent = (credentialTypes) => (event) => {
|
|
178
|
+
const issuerDid = decodeIssuerVc(event.args[1]);
|
|
179
|
+
const caoDid = event.args[6];
|
|
180
|
+
const credentialType = find((ct) => {
|
|
181
|
+
return ct.credentialType2BytesHash === event.credentialType2BytesHash;
|
|
182
|
+
}, credentialTypes);
|
|
183
|
+
|
|
184
|
+
return issuerDid != null && caoDid != null && credentialType?.layer1;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const mapEvent = (event) => {
|
|
188
|
+
const issuerDid = decodeIssuerVc(event.args[1]);
|
|
189
|
+
const caoDid = event.args[6];
|
|
190
|
+
const credentialType2BytesHash = event.args[3];
|
|
191
|
+
const issuerKey = `${issuerDid}.issuerReward`;
|
|
192
|
+
const caoKey = `${caoDid}.caoReward`;
|
|
193
|
+
return {
|
|
194
|
+
issuerDid,
|
|
195
|
+
caoDid,
|
|
196
|
+
issuerKey,
|
|
197
|
+
caoKey,
|
|
198
|
+
credentialType2BytesHash,
|
|
199
|
+
...event,
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const getCredentialTypes = async (context) => {
|
|
204
|
+
const { repos } = context;
|
|
205
|
+
const credentialTypes = await repos.credentialSchemas.find({});
|
|
206
|
+
return map(
|
|
207
|
+
({ credentialType, layer1 }) => ({
|
|
208
|
+
credentialType,
|
|
209
|
+
credentialType2BytesHash: get2BytesHash(credentialType),
|
|
210
|
+
layer1,
|
|
211
|
+
}),
|
|
212
|
+
credentialTypes
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
module.exports = {
|
|
217
|
+
handleCredentialIssuedRewardsEvent,
|
|
218
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
...require('./handle-credential-issued-rewards-event'),
|
|
3
|
+
...require('./handle-coupons-burned-verification-event'),
|
|
4
|
+
...require('./handle-coupons-minted-logging-event'),
|
|
5
|
+
...require('./handle-coupons-burned-logging-event'),
|
|
6
|
+
...require('./handle-credential-issued-logging-event'),
|
|
7
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const { initReadDocument, initWriteDocument } = require('@verii/aws-clients');
|
|
2
|
+
|
|
3
|
+
const initDocumentFunctions = ({ eventName }, context) => {
|
|
4
|
+
const readDocument = initReadDocument(context.config);
|
|
5
|
+
const writeDocument = initWriteDocument(context.config);
|
|
6
|
+
const readLastSuccessfulBlock = async () => {
|
|
7
|
+
const result = await readDocument(context.config.dynamoDbTableEventBlock, {
|
|
8
|
+
EventName: eventName,
|
|
9
|
+
});
|
|
10
|
+
context.log.info({ result });
|
|
11
|
+
|
|
12
|
+
if (!result || !result.Item) {
|
|
13
|
+
return -1;
|
|
14
|
+
}
|
|
15
|
+
return result.Item.BlockNumber;
|
|
16
|
+
};
|
|
17
|
+
const writeLastSuccessfulBlock = (blockNumber) =>
|
|
18
|
+
writeDocument(context.config.dynamoDbTableEventBlock, {
|
|
19
|
+
EventName: eventName,
|
|
20
|
+
BlockNumber: blockNumber,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return { readLastSuccessfulBlock, writeLastSuccessfulBlock };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
module.exports = {
|
|
27
|
+
initDocumentFunctions,
|
|
28
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const { jwtDecode } = require('@verii/jwt');
|
|
2
|
+
|
|
3
|
+
const decodeBigIntToNumber = (bigInt) => Number(bigInt.hex);
|
|
4
|
+
|
|
5
|
+
const decodeBigIntToDate = (bigInt) => new Date(Number(bigInt.hex) * 1000);
|
|
6
|
+
|
|
7
|
+
const hexStringToUtfString = (hexString) =>
|
|
8
|
+
Buffer.from(hexString, 'hex').toString();
|
|
9
|
+
|
|
10
|
+
const decodeIssuerVc = (issuerVc) => {
|
|
11
|
+
const { payload } = jwtDecode(hexStringToUtfString(issuerVc.slice(2)));
|
|
12
|
+
|
|
13
|
+
return payload?.iss;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
decodeBigIntToNumber,
|
|
18
|
+
decodeBigIntToDate,
|
|
19
|
+
decodeIssuerVc,
|
|
20
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const { toHexString, toNumber } = require('@verii/blockchain-functions');
|
|
2
|
+
|
|
3
|
+
const mapCouponBurned = (evt, { log }) => {
|
|
4
|
+
const expirationTime = new Date(toNumber(evt.args[6]) * 1000);
|
|
5
|
+
let burnTime = new Date();
|
|
6
|
+
try {
|
|
7
|
+
burnTime = new Date(toNumber(evt.args[7]) * 1000);
|
|
8
|
+
} catch (err) {
|
|
9
|
+
log.info({ err });
|
|
10
|
+
log.info('burnTime not present on event');
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
blockNumber: evt.blockNumber,
|
|
14
|
+
blockHash: evt.blockHash,
|
|
15
|
+
transactionIndex: evt.transactionIndex,
|
|
16
|
+
transactionHash: evt.transactionHash,
|
|
17
|
+
event: evt.fragment.name,
|
|
18
|
+
owner: evt.args[0],
|
|
19
|
+
bundleId: `${evt.args[1]}`,
|
|
20
|
+
bundleIdHex: evt.args[1] ? toHexString(evt.args[1]) : undefined,
|
|
21
|
+
eventTraceId: evt.args[2],
|
|
22
|
+
caoDid: evt.args[3],
|
|
23
|
+
burnerDid: evt.args[4],
|
|
24
|
+
balance: toNumber(evt.args[5]),
|
|
25
|
+
expirationTime,
|
|
26
|
+
burnTime,
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
module.exports = {
|
|
31
|
+
mapCouponBurned,
|
|
32
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Velocity Team
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/* eslint-disable global-require */
|
|
19
|
+
module.exports = {
|
|
20
|
+
...require('./event-processing-endpoints'),
|
|
21
|
+
...require('./config/config'),
|
|
22
|
+
};
|