@xyo-network/payment-plugin 3.0.18

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.
Files changed (52) hide show
  1. package/LICENSE +165 -0
  2. package/README.md +13 -0
  3. package/dist/neutral/Discount/Diviner.d.ts +19 -0
  4. package/dist/neutral/Discount/Diviner.d.ts.map +1 -0
  5. package/dist/neutral/Discount/index.d.ts +3 -0
  6. package/dist/neutral/Discount/index.d.ts.map +1 -0
  7. package/dist/neutral/Discount/lib/applyCoupons.d.ts +4 -0
  8. package/dist/neutral/Discount/lib/applyCoupons.d.ts.map +1 -0
  9. package/dist/neutral/Discount/lib/index.d.ts +2 -0
  10. package/dist/neutral/Discount/lib/index.d.ts.map +1 -0
  11. package/dist/neutral/Invoice/getInvoiceForEscrow.d.ts +6 -0
  12. package/dist/neutral/Invoice/getInvoiceForEscrow.d.ts.map +1 -0
  13. package/dist/neutral/Invoice/index.d.ts +2 -0
  14. package/dist/neutral/Invoice/index.d.ts.map +1 -0
  15. package/dist/neutral/Subtotal/Diviner.d.ts +12 -0
  16. package/dist/neutral/Subtotal/Diviner.d.ts.map +1 -0
  17. package/dist/neutral/Subtotal/index.d.ts +2 -0
  18. package/dist/neutral/Subtotal/index.d.ts.map +1 -0
  19. package/dist/neutral/Subtotal/lib/appraisalValidators.d.ts +3 -0
  20. package/dist/neutral/Subtotal/lib/appraisalValidators.d.ts.map +1 -0
  21. package/dist/neutral/Subtotal/lib/durationValidators.d.ts +3 -0
  22. package/dist/neutral/Subtotal/lib/durationValidators.d.ts.map +1 -0
  23. package/dist/neutral/Subtotal/lib/index.d.ts +3 -0
  24. package/dist/neutral/Subtotal/lib/index.d.ts.map +1 -0
  25. package/dist/neutral/Subtotal/lib/termsValidators.d.ts +4 -0
  26. package/dist/neutral/Subtotal/lib/termsValidators.d.ts.map +1 -0
  27. package/dist/neutral/Total/Diviner.d.ts +16 -0
  28. package/dist/neutral/Total/Diviner.d.ts.map +1 -0
  29. package/dist/neutral/Total/index.d.ts +2 -0
  30. package/dist/neutral/Total/index.d.ts.map +1 -0
  31. package/dist/neutral/index.d.ts +5 -0
  32. package/dist/neutral/index.d.ts.map +1 -0
  33. package/dist/neutral/index.mjs +347 -0
  34. package/dist/neutral/index.mjs.map +1 -0
  35. package/package.json +61 -0
  36. package/src/Discount/Diviner.ts +155 -0
  37. package/src/Discount/index.ts +2 -0
  38. package/src/Discount/lib/applyCoupons.ts +58 -0
  39. package/src/Discount/lib/index.ts +1 -0
  40. package/src/Invoice/getInvoiceForEscrow.ts +39 -0
  41. package/src/Invoice/index.ts +1 -0
  42. package/src/Subtotal/Diviner.ts +65 -0
  43. package/src/Subtotal/index.ts +1 -0
  44. package/src/Subtotal/lib/appraisalValidators.ts +45 -0
  45. package/src/Subtotal/lib/durationValidators.ts +18 -0
  46. package/src/Subtotal/lib/index.ts +2 -0
  47. package/src/Subtotal/lib/termsValidators.ts +18 -0
  48. package/src/Total/Diviner.ts +67 -0
  49. package/src/Total/index.ts +1 -0
  50. package/src/index.ts +4 -0
  51. package/typedoc.json +5 -0
  52. package/xy.config.ts +11 -0
@@ -0,0 +1,347 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
+ var __decorateClass = (decorators, target, key, kind) => {
5
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
6
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
7
+ if (decorator = decorators[i])
8
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
9
+ if (kind && result) __defProp(target, key, result);
10
+ return result;
11
+ };
12
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
13
+
14
+ // src/Discount/Diviner.ts
15
+ import { assertEx as assertEx2 } from "@xylabs/assert";
16
+ import { exists as exists2 } from "@xylabs/exists";
17
+ import { asArchivistInstance } from "@xyo-network/archivist-model";
18
+ import { AbstractDiviner } from "@xyo-network/diviner-abstract";
19
+ import { BoundWitnessDivinerQuerySchema } from "@xyo-network/diviner-boundwitness-model";
20
+ import {
21
+ isHashLeaseEstimate
22
+ } from "@xyo-network/diviner-hash-lease";
23
+ import {
24
+ asDivinerInstance
25
+ } from "@xyo-network/diviner-model";
26
+ import { creatableModule } from "@xyo-network/module-model";
27
+ import { PayloadBuilder } from "@xyo-network/payload-builder";
28
+ import {
29
+ isCoupon,
30
+ isCouponWithMeta,
31
+ isEscrowTerms,
32
+ NO_DISCOUNT,
33
+ PaymentDiscountDivinerConfigSchema
34
+ } from "@xyo-network/payment-payload-plugins";
35
+
36
+ // src/Discount/lib/applyCoupons.ts
37
+ import { assertEx } from "@xylabs/assert";
38
+ import { exists } from "@xylabs/exists";
39
+ import {
40
+ DiscountSchema,
41
+ isFixedAmountCoupon,
42
+ isFixedPercentageCoupon,
43
+ isStackable
44
+ } from "@xyo-network/payment-payload-plugins";
45
+ var applyCoupons = (appraisals, coupons) => {
46
+ const allAppraisalsAreUSD = appraisals.every((appraisal) => appraisal.currency === "USD");
47
+ assertEx(allAppraisalsAreUSD, "All appraisals must be in USD");
48
+ const allCouponsAreUSD = coupons.map((coupon) => coupon?.currency).filter(exists).every((currency2) => currency2 === "USD");
49
+ assertEx(allCouponsAreUSD, "All coupons must be in USD");
50
+ const total = appraisals.reduce((acc, appraisal) => acc + appraisal.price, 0);
51
+ const singularFixedDiscount = Math.max(...coupons.filter((coupon) => isFixedAmountCoupon(coupon) && !isStackable(coupon)).map((coupon) => coupon.amount), 0);
52
+ const singularPercentageDiscount = Math.max(...coupons.filter((coupon) => isFixedPercentageCoupon(coupon) && !isStackable(coupon)).map((coupon) => coupon.percentage), 0) * total;
53
+ const stackedFixedDiscount = coupons.filter((coupon) => isFixedAmountCoupon(coupon) && isStackable(coupon)).reduce((acc, coupon) => acc + coupon.amount, 0);
54
+ const stackedPercentageDiscount = coupons.filter((coupon) => isFixedPercentageCoupon(coupon) && isStackable(coupon)).reduce((acc, coupon) => acc + coupon.percentage, 0) * (total - stackedFixedDiscount);
55
+ const stackedDiscount = stackedFixedDiscount + stackedPercentageDiscount;
56
+ const maxDiscount = Math.max(
57
+ singularFixedDiscount,
58
+ singularPercentageDiscount,
59
+ stackedDiscount,
60
+ 0
61
+ );
62
+ const amount = Math.min(maxDiscount, total);
63
+ return {
64
+ amount,
65
+ schema: DiscountSchema,
66
+ currency: "USD"
67
+ };
68
+ };
69
+
70
+ // src/Discount/Diviner.ts
71
+ var DEFAULT_BOUND_WITNESS_DIVINER_QUERY_PROPS = {
72
+ limit: 1,
73
+ order: "desc",
74
+ schema: BoundWitnessDivinerQuerySchema
75
+ };
76
+ var PaymentDiscountDiviner = class extends AbstractDiviner {
77
+ get couponAuthorities() {
78
+ return [...this.config.couponAuthorities ?? [], ...this.params.couponAuthorities ?? []];
79
+ }
80
+ async divineHandler(payloads = []) {
81
+ const sources = [];
82
+ const terms = payloads.find(isEscrowTerms);
83
+ if (!terms) return [{ ...NO_DISCOUNT, sources }];
84
+ sources.push(await PayloadBuilder.hash(terms));
85
+ const discountHashes = terms.discounts ?? [];
86
+ if (discountHashes.length === 0) return [{ ...NO_DISCOUNT, sources }];
87
+ const termsAppraisals = terms?.appraisals;
88
+ if (!termsAppraisals || termsAppraisals.length === 0) return [{ ...NO_DISCOUNT, sources }];
89
+ const hashMap = await PayloadBuilder.toAllHashMap(payloads);
90
+ const foundAppraisals = termsAppraisals.filter((hash) => hashMap[hash]);
91
+ sources.push(...foundAppraisals);
92
+ if (foundAppraisals.length !== termsAppraisals.length) {
93
+ return [{ ...NO_DISCOUNT, sources }];
94
+ }
95
+ const appraisals = foundAppraisals.map((hash) => hashMap[hash]).filter(exists2).filter(isHashLeaseEstimate);
96
+ const discounts = discountHashes.map((hash) => hashMap[hash]).filter(exists2).filter(isCoupon);
97
+ if (discounts.length !== discountHashes.length) {
98
+ const discountsArchivist = await this.getDiscountsArchivist();
99
+ const foundDiscounts = await discountsArchivist.get(discountHashes);
100
+ discounts.push(...foundDiscounts.filter(isCouponWithMeta));
101
+ }
102
+ const discountsMap = await PayloadBuilder.toAllHashMap(discounts);
103
+ if (Object.keys(discountsMap).length === 0) return [{ ...NO_DISCOUNT, sources }];
104
+ const foundDiscountsHashes = Object.keys(discountsMap);
105
+ sources.push(...foundDiscountsHashes);
106
+ for (const hash of discountHashes) {
107
+ if (!foundDiscountsHashes.includes(hash)) {
108
+ console.warn(`Discount ${hash} not found for terms ${await PayloadBuilder.hash(terms)}`);
109
+ }
110
+ }
111
+ const coupons = Object.values(discountsMap);
112
+ const validCoupons = await this.filterToSigned(coupons.filter(this.isCouponCurrent));
113
+ if (validCoupons.length === 0) return [{ ...NO_DISCOUNT, sources }];
114
+ const discount = applyCoupons(appraisals, validCoupons);
115
+ return [{ ...discount, sources }];
116
+ }
117
+ /**
118
+ * Filters the supplied list of coupons to only those that are signed by
119
+ * addresses specified in the couponAuthorities
120
+ * @param coupons The list of coupons to filter
121
+ * @returns The filtered list of coupons that are signed by the couponAuthorities
122
+ */
123
+ async filterToSigned(coupons) {
124
+ const signed = [];
125
+ const dataHashMap = await PayloadBuilder.toDataHashMap(coupons);
126
+ const boundWitnessDiviner = await this.getDiscountsBoundWitnessDiviner();
127
+ const hashes = Object.keys(dataHashMap);
128
+ const addresses = this.couponAuthorities;
129
+ await Promise.all(hashes.map((h) => {
130
+ const hash = h;
131
+ return Promise.all(addresses.map(async (address) => {
132
+ const query = {
133
+ ...DEFAULT_BOUND_WITNESS_DIVINER_QUERY_PROPS,
134
+ addresses: [address],
135
+ payload_hashes: [hash]
136
+ };
137
+ const result = await boundWitnessDiviner.divine([query]);
138
+ if (result.length > 0) signed.push(dataHashMap[hash]);
139
+ }));
140
+ }));
141
+ return signed;
142
+ }
143
+ async getDiscountsArchivist() {
144
+ const name = assertEx2(this.config.archivist, () => "Missing archivist in config");
145
+ const mod = assertEx2(await this.resolve(name), () => `Error resolving archivist: ${name}`);
146
+ return assertEx2(asArchivistInstance(mod), () => `Resolved module ${mod.address} not a valid Archivist`);
147
+ }
148
+ async getDiscountsBoundWitnessDiviner() {
149
+ const name = assertEx2(this.config.boundWitnessDiviner, () => "Missing boundWitnessDiviner in config");
150
+ const mod = assertEx2(await this.resolve(name), () => `Error resolving boundWitnessDiviner: ${name}`);
151
+ return assertEx2(asDivinerInstance(mod), () => `Resolved module ${mod.address} not a valid Diviner`);
152
+ }
153
+ isCouponCurrent(coupon) {
154
+ const now = Date.now();
155
+ return coupon.exp > now && coupon.nbf < now;
156
+ }
157
+ };
158
+ __publicField(PaymentDiscountDiviner, "configSchemas", [PaymentDiscountDivinerConfigSchema]);
159
+ __publicField(PaymentDiscountDiviner, "defaultConfigSchema", PaymentDiscountDivinerConfigSchema);
160
+ PaymentDiscountDiviner = __decorateClass([
161
+ creatableModule()
162
+ ], PaymentDiscountDiviner);
163
+
164
+ // src/Invoice/getInvoiceForEscrow.ts
165
+ import { PayloadBuilder as PayloadBuilder2 } from "@xyo-network/payload-builder";
166
+ import {
167
+ isDiscount,
168
+ isSubtotal,
169
+ isTotal,
170
+ PaymentSchema
171
+ } from "@xyo-network/payment-payload-plugins";
172
+ var getInvoiceForEscrow = async (terms, dataHashMap, paymentTotalDiviner) => {
173
+ const payloads = Object.values(dataHashMap);
174
+ const results = await paymentTotalDiviner.divine([terms, ...payloads]);
175
+ const subtotal = results.find(isSubtotal);
176
+ const discount = results.find(isDiscount);
177
+ const total = results.find(isTotal);
178
+ if (!subtotal || !total) return void 0;
179
+ const { amount, currency: currency2 } = total;
180
+ if (currency2 !== "USD") return void 0;
181
+ const sources = await getSources(terms, subtotal, total, discount);
182
+ const payment = {
183
+ amount,
184
+ currency: currency2,
185
+ schema: PaymentSchema,
186
+ sources
187
+ };
188
+ return discount ? [subtotal, total, payment, discount] : [subtotal, total, payment];
189
+ };
190
+ var getSources = async (terms, subtotal, total, discount) => {
191
+ const sources = discount ? [terms, subtotal, total, discount] : [terms, subtotal, total];
192
+ return await Promise.all(sources.map((p) => PayloadBuilder2.dataHash(p)));
193
+ };
194
+
195
+ // src/Subtotal/Diviner.ts
196
+ import { AbstractDiviner as AbstractDiviner2 } from "@xyo-network/diviner-abstract";
197
+ import { isHashLeaseEstimate as isHashLeaseEstimate2 } from "@xyo-network/diviner-hash-lease";
198
+ import { creatableModule as creatableModule2 } from "@xyo-network/module-model";
199
+ import { PayloadBuilder as PayloadBuilder3 } from "@xyo-network/payload-builder";
200
+ import {
201
+ isEscrowTerms as isEscrowTerms2,
202
+ PaymentSubtotalDivinerConfigSchema,
203
+ SubtotalSchema
204
+ } from "@xyo-network/payment-payload-plugins";
205
+
206
+ // src/Subtotal/lib/appraisalValidators.ts
207
+ import { isIso4217CurrencyCode } from "@xyo-network/payment-payload-plugins";
208
+
209
+ // src/Subtotal/lib/durationValidators.ts
210
+ var FIVE_MINUTES = 1e3 * 60 * 5;
211
+ var validateDuration = (value, windowMs = FIVE_MINUTES) => {
212
+ const now = Date.now();
213
+ if (!value.nbf || value.nbf > now) return false;
214
+ if (!value.exp || value.exp - now < windowMs) return false;
215
+ return true;
216
+ };
217
+
218
+ // src/Subtotal/lib/appraisalValidators.ts
219
+ var validateAppraisalAmount = (appraisals) => {
220
+ if (appraisals.some((appraisal) => typeof appraisal.price !== "number")) return false;
221
+ if (appraisals.some((appraisal) => appraisal.price < 0)) return false;
222
+ return true;
223
+ };
224
+ var validateAppraisalCurrency = (appraisals) => {
225
+ if (!appraisals.every((appraisal) => appraisal.currency == "USD")) return false;
226
+ if (!appraisals.every((appraisal) => isIso4217CurrencyCode(appraisal.currency))) return false;
227
+ return true;
228
+ };
229
+ var validateAppraisalConsistentCurrency = (appraisals) => {
230
+ if (appraisals.length <= 1) return true;
231
+ const { currency: currency2 } = appraisals[0];
232
+ if (!currency2) return false;
233
+ if (!appraisals.every((item) => item.currency === currency2)) return false;
234
+ return true;
235
+ };
236
+ var validateAppraisalWindow = (appraisals) => appraisals.every(validateDuration);
237
+ var appraisalValidators = [
238
+ validateAppraisalAmount,
239
+ validateAppraisalCurrency,
240
+ validateAppraisalConsistentCurrency,
241
+ validateAppraisalWindow
242
+ ];
243
+
244
+ // src/Subtotal/lib/termsValidators.ts
245
+ var validateTermsAppraisals = (terms) => {
246
+ if (!terms.appraisals) return false;
247
+ if (terms.appraisals.length === 0) return false;
248
+ return true;
249
+ };
250
+ var validateTermsWindow = (terms) => validateDuration(terms);
251
+ var termsValidators = [
252
+ validateTermsAppraisals,
253
+ validateTermsWindow
254
+ ];
255
+
256
+ // src/Subtotal/Diviner.ts
257
+ var currency = "USD";
258
+ var PaymentSubtotalDiviner = class extends AbstractDiviner2 {
259
+ async divineHandler(payloads = []) {
260
+ const terms = payloads.find(isEscrowTerms2);
261
+ if (!terms) return [];
262
+ if (!termsValidators.every((validator) => validator(terms))) return [];
263
+ const validTerms = terms;
264
+ const hashMap = await PayloadBuilder3.toAllHashMap(payloads);
265
+ const appraisals = validTerms.appraisals.map((appraisal) => hashMap[appraisal]).filter(isHashLeaseEstimate2);
266
+ if (appraisals.length !== validTerms.appraisals.length) return [];
267
+ if (!appraisalValidators.every((validator) => validator(appraisals))) return [];
268
+ const amount = calculateSubtotal(appraisals);
269
+ const sources = [await PayloadBuilder3.dataHash(validTerms), ...validTerms.appraisals];
270
+ return [{
271
+ amount,
272
+ currency,
273
+ schema: SubtotalSchema,
274
+ sources
275
+ }];
276
+ }
277
+ };
278
+ __publicField(PaymentSubtotalDiviner, "configSchemas", [PaymentSubtotalDivinerConfigSchema]);
279
+ __publicField(PaymentSubtotalDiviner, "defaultConfigSchema", PaymentSubtotalDivinerConfigSchema);
280
+ PaymentSubtotalDiviner = __decorateClass([
281
+ creatableModule2()
282
+ ], PaymentSubtotalDiviner);
283
+ var calculateSubtotal = (appraisals) => {
284
+ return appraisals.reduce((sum, appraisal) => sum + appraisal.price, 0);
285
+ };
286
+
287
+ // src/Total/Diviner.ts
288
+ import { assertEx as assertEx3 } from "@xylabs/assert";
289
+ import { AbstractDiviner as AbstractDiviner3 } from "@xyo-network/diviner-abstract";
290
+ import {
291
+ asDivinerInstance as asDivinerInstance2
292
+ } from "@xyo-network/diviner-model";
293
+ import { creatableModule as creatableModule3 } from "@xyo-network/module-model";
294
+ import {
295
+ isDiscountWithMeta,
296
+ isSubtotalWithMeta,
297
+ PaymentTotalDivinerConfigSchema,
298
+ TotalSchema
299
+ } from "@xyo-network/payment-payload-plugins";
300
+ var PaymentTotalDiviner = class extends AbstractDiviner3 {
301
+ async divineHandler(payloads = []) {
302
+ const subtotalDiviner = await this.getPaymentSubtotalDiviner();
303
+ const subtotalResult = await subtotalDiviner.divine(payloads);
304
+ const subtotal = subtotalResult.find(isSubtotalWithMeta);
305
+ if (!subtotal) return [];
306
+ const discountDiviner = await this.getPaymentDiscountsDiviner();
307
+ const discountResult = await discountDiviner.divine(payloads);
308
+ const discount = discountResult.find(isDiscountWithMeta);
309
+ if (!discount) return [];
310
+ const { currency: subtotalCurrency } = subtotal;
311
+ const { currency: discountCurrency } = discount;
312
+ assertEx3(subtotalCurrency === discountCurrency, () => `Subtotal currency ${subtotalCurrency} does not match discount currency ${discountCurrency}`);
313
+ const amount = Math.max(0, subtotal.amount - discount.amount);
314
+ const currency2 = subtotalCurrency;
315
+ const sources = [subtotal.$hash, discount.$hash];
316
+ const total = {
317
+ amount,
318
+ currency: currency2,
319
+ sources,
320
+ schema: TotalSchema
321
+ };
322
+ return [subtotal, discount, total];
323
+ }
324
+ async getPaymentDiscountsDiviner() {
325
+ const name = assertEx3(this.config.paymentDiscountDiviner, () => "Missing paymentDiscountDiviner in config");
326
+ const mod = assertEx3(await this.resolve(name), () => `Error resolving paymentDiscountDiviner: ${name}`);
327
+ return assertEx3(asDivinerInstance2(mod), () => `Resolved module ${mod.address} not a valid Diviner`);
328
+ }
329
+ async getPaymentSubtotalDiviner() {
330
+ const name = assertEx3(this.config.paymentSubtotalDiviner, () => "Missing paymentSubtotalDiviner in config");
331
+ const mod = assertEx3(await this.resolve(name), () => `Error resolving paymentSubtotalDiviner: ${name}`);
332
+ return assertEx3(asDivinerInstance2(mod), () => `Resolved module ${mod.address} not a valid Diviner`);
333
+ }
334
+ };
335
+ __publicField(PaymentTotalDiviner, "configSchemas", [PaymentTotalDivinerConfigSchema]);
336
+ __publicField(PaymentTotalDiviner, "defaultConfigSchema", PaymentTotalDivinerConfigSchema);
337
+ PaymentTotalDiviner = __decorateClass([
338
+ creatableModule3()
339
+ ], PaymentTotalDiviner);
340
+ export {
341
+ PaymentDiscountDiviner,
342
+ PaymentSubtotalDiviner,
343
+ PaymentTotalDiviner,
344
+ applyCoupons,
345
+ getInvoiceForEscrow
346
+ };
347
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/Discount/Diviner.ts","../../src/Discount/lib/applyCoupons.ts","../../src/Invoice/getInvoiceForEscrow.ts","../../src/Subtotal/Diviner.ts","../../src/Subtotal/lib/appraisalValidators.ts","../../src/Subtotal/lib/durationValidators.ts","../../src/Subtotal/lib/termsValidators.ts","../../src/Total/Diviner.ts"],"sourcesContent":["import { assertEx } from '@xylabs/assert'\nimport { exists } from '@xylabs/exists'\nimport { Address, Hash } from '@xylabs/hex'\nimport { ArchivistInstance, asArchivistInstance } from '@xyo-network/archivist-model'\nimport { AbstractDiviner } from '@xyo-network/diviner-abstract'\nimport { BoundWitnessDivinerQueryPayload, BoundWitnessDivinerQuerySchema } from '@xyo-network/diviner-boundwitness-model'\nimport {\n HashLeaseEstimate,\n isHashLeaseEstimate,\n} from '@xyo-network/diviner-hash-lease'\nimport {\n asDivinerInstance, DivinerInstance, DivinerModuleEventData,\n} from '@xyo-network/diviner-model'\nimport { creatableModule } from '@xyo-network/module-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload } from '@xyo-network/payload-model'\nimport {\n Coupon,\n Discount,\n EscrowTerms, isCoupon,\n isCouponWithMeta,\n isEscrowTerms, NO_DISCOUNT, PaymentDiscountDivinerConfigSchema, PaymentDiscountDivinerParams,\n} from '@xyo-network/payment-payload-plugins'\n\nimport { applyCoupons } from './lib/index.ts'\n\nconst DEFAULT_BOUND_WITNESS_DIVINER_QUERY_PROPS: Readonly<BoundWitnessDivinerQueryPayload> = {\n limit: 1,\n order: 'desc',\n schema: BoundWitnessDivinerQuerySchema,\n}\n\nexport type PaymentDiscountDivinerInputType = EscrowTerms | Coupon | HashLeaseEstimate | Payload\n\n@creatableModule()\nexport class PaymentDiscountDiviner<\n TParams extends PaymentDiscountDivinerParams = PaymentDiscountDivinerParams,\n TIn extends PaymentDiscountDivinerInputType = PaymentDiscountDivinerInputType,\n TOut extends Discount = Discount,\n TEventData extends DivinerModuleEventData<DivinerInstance<TParams, TIn, TOut>, TIn, TOut> = DivinerModuleEventData<\n DivinerInstance<TParams, TIn, TOut>,\n TIn,\n TOut\n >,\n> extends AbstractDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [PaymentDiscountDivinerConfigSchema]\n static override defaultConfigSchema = PaymentDiscountDivinerConfigSchema\n\n protected get couponAuthorities(): Address[] {\n return [...(this.config.couponAuthorities ?? []), ...(this.params.couponAuthorities ?? [])]\n }\n\n protected async divineHandler(payloads: TIn[] = []): Promise<TOut[]> {\n const sources: Hash[] = []\n\n // Parse terms\n const terms = payloads.find(isEscrowTerms) as EscrowTerms | undefined\n if (!terms) return [{ ...NO_DISCOUNT, sources }] as TOut[]\n sources.push(await PayloadBuilder.hash(terms))\n\n // Parse discounts\n const discountHashes = terms.discounts ?? []\n if (discountHashes.length === 0) return [{ ...NO_DISCOUNT, sources }] as TOut[]\n\n // TODO: Call paymentSubtotalDiviner to get the subtotal to centralize the logic\n // Parse appraisals\n const termsAppraisals = terms?.appraisals\n if (!termsAppraisals || termsAppraisals.length === 0) return [{ ...NO_DISCOUNT, sources }] as TOut[]\n const hashMap = await PayloadBuilder.toAllHashMap(payloads)\n const foundAppraisals = termsAppraisals.filter(hash => hashMap[hash])\n // Add the appraisals that were found to the sources\n sources.push(...foundAppraisals)\n // If not all appraisals are found, return no discount\n if (foundAppraisals.length !== termsAppraisals.length) {\n return [{ ...NO_DISCOUNT, sources }] as TOut[]\n }\n // TODO: Cast should not be required\n const appraisals = foundAppraisals.map(hash => hashMap[hash]).filter(exists).filter(isHashLeaseEstimate) as unknown as HashLeaseEstimate[]\n\n // Use the supplied payloads to find the discounts\n const discounts = discountHashes.map(hash => hashMap[hash]).filter(exists).filter(isCoupon) as Coupon[]\n // Find any remaining coupons from the archivist\n if (discounts.length !== discountHashes.length) {\n // Find remaining from discounts archivist\n const discountsArchivist = await this.getDiscountsArchivist()\n const foundDiscounts = await discountsArchivist.get(discountHashes)\n discounts.push(...foundDiscounts.filter(isCouponWithMeta))\n }\n const discountsMap = await PayloadBuilder.toAllHashMap(discounts)\n if (Object.keys(discountsMap).length === 0) return [{ ...NO_DISCOUNT, sources }] as TOut[]\n\n // Add the found discounts to the sources\n const foundDiscountsHashes = Object.keys(discountsMap) as Hash[]\n sources.push(...foundDiscountsHashes)\n\n // Log individual discounts that were not found\n for (const hash of discountHashes) {\n if (!foundDiscountsHashes.includes(hash)) {\n console.warn(`Discount ${hash} not found for terms ${await PayloadBuilder.hash(terms)}`)\n }\n }\n\n // Parse coupons\n const coupons = Object.values(discountsMap)\n const validCoupons = await this.filterToSigned(coupons.filter(this.isCouponCurrent))\n if (validCoupons.length === 0) return [{ ...NO_DISCOUNT, sources }] as TOut[]\n\n const discount = applyCoupons(appraisals, validCoupons)\n return [{ ...discount, sources }] as TOut[]\n }\n\n /**\n * Filters the supplied list of coupons to only those that are signed by\n * addresses specified in the couponAuthorities\n * @param coupons The list of coupons to filter\n * @returns The filtered list of coupons that are signed by the couponAuthorities\n */\n protected async filterToSigned(coupons: Coupon[]): Promise<Coupon[]> {\n const signed: Coupon[] = []\n const dataHashMap = await PayloadBuilder.toDataHashMap(coupons)\n const boundWitnessDiviner = await this.getDiscountsBoundWitnessDiviner()\n const hashes = Object.keys(dataHashMap)\n const addresses = this.couponAuthorities\n // TODO: Keep an in memory cache of the hashes queried and their results\n // to avoid querying the same hash multiple times\n await Promise.all(hashes.map((h) => {\n const hash = h as Hash\n return Promise.all(addresses.map(async (address) => {\n const query: BoundWitnessDivinerQueryPayload = {\n ...DEFAULT_BOUND_WITNESS_DIVINER_QUERY_PROPS, addresses: [address], payload_hashes: [hash],\n }\n const result = await boundWitnessDiviner.divine([query])\n if (result.length > 0) signed.push(dataHashMap[hash])\n }))\n }))\n return signed\n }\n\n protected async getDiscountsArchivist(): Promise<ArchivistInstance> {\n const name = assertEx(this.config.archivist, () => 'Missing archivist in config')\n const mod = assertEx(await this.resolve(name), () => `Error resolving archivist: ${name}`)\n return assertEx(asArchivistInstance(mod), () => `Resolved module ${mod.address} not a valid Archivist`)\n }\n\n protected async getDiscountsBoundWitnessDiviner(): Promise<DivinerInstance> {\n const name = assertEx(this.config.boundWitnessDiviner, () => 'Missing boundWitnessDiviner in config')\n const mod = assertEx(await this.resolve(name), () => `Error resolving boundWitnessDiviner: ${name}`)\n return assertEx(asDivinerInstance(mod), () => `Resolved module ${mod.address} not a valid Diviner`)\n }\n\n protected isCouponCurrent(coupon: Coupon): boolean {\n const now = Date.now()\n return coupon.exp > now && coupon.nbf < now\n }\n}\n","import { assertEx } from '@xylabs/assert'\nimport { exists } from '@xylabs/exists'\nimport type { HashLeaseEstimate } from '@xyo-network/diviner-hash-lease'\nimport type {\n AmountFields,\n Coupon, Discount, FixedAmountCoupon,\n FixedPercentageCoupon,\n} from '@xyo-network/payment-payload-plugins'\nimport {\n DiscountSchema, isFixedAmountCoupon, isFixedPercentageCoupon,\n isStackable,\n} from '@xyo-network/payment-payload-plugins'\n\nexport const applyCoupons = (appraisals: HashLeaseEstimate[], coupons: Coupon[]): Discount => {\n // Ensure all appraisals and coupons are in USD\n const allAppraisalsAreUSD = appraisals.every(appraisal => appraisal.currency === 'USD')\n assertEx(allAppraisalsAreUSD, 'All appraisals must be in USD')\n const allCouponsAreUSD = coupons.map(coupon => (coupon as Partial<AmountFields>)?.currency).filter(exists).every(currency => currency === 'USD')\n assertEx(allCouponsAreUSD, 'All coupons must be in USD')\n const total = appraisals.reduce((acc, appraisal) => acc + appraisal.price, 0)\n\n // Calculated non-stackable discount coupons\n const singularFixedDiscount = Math.max(...coupons\n .filter(coupon => isFixedAmountCoupon(coupon) && !isStackable(coupon))\n .map(coupon => (coupon as FixedAmountCoupon).amount), 0)\n const singularPercentageDiscount = (Math.max(...coupons\n .filter(coupon => isFixedPercentageCoupon(coupon) && !isStackable(coupon))\n .map(coupon => (coupon as FixedPercentageCoupon).percentage), 0)) * total\n\n // Calculate stackable discount coupons\n // First calculate the total discount from fixed amount coupons\n const stackedFixedDiscount = coupons\n .filter(coupon => isFixedAmountCoupon(coupon) && isStackable(coupon))\n .reduce((acc, coupon) => acc + (coupon as FixedAmountCoupon).amount, 0)\n // Then calculate the total discount from percentage coupons and apply\n // the percentage discount to the remaining total after fixed discounts\n const stackedPercentageDiscount = coupons\n .filter(coupon => isFixedPercentageCoupon(coupon) && isStackable(coupon))\n .reduce((acc, coupon) => acc + (coupon as FixedPercentageCoupon).percentage, 0) * (total - stackedFixedDiscount)\n // Sum all stackable discounts\n const stackedDiscount = stackedFixedDiscount + stackedPercentageDiscount\n\n // Find the best coupon(s) to apply\n const maxDiscount = Math.max(\n singularFixedDiscount,\n singularPercentageDiscount,\n stackedDiscount,\n 0,\n )\n\n // Ensure discount is not more than the total\n const amount = Math.min(maxDiscount, total)\n\n // Return single discount payload\n return {\n amount, schema: DiscountSchema, currency: 'USD',\n }\n}\n","import type { Hash } from '@xylabs/hex'\nimport type { DivinerInstance } from '@xyo-network/diviner-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport type { Payload } from '@xyo-network/payload-model'\nimport type {\n Discount, EscrowTerms, Invoice, Payment, Subtotal, Total,\n} from '@xyo-network/payment-payload-plugins'\nimport {\n isDiscount, isSubtotal, isTotal, PaymentSchema,\n} from '@xyo-network/payment-payload-plugins'\n\n/**\n * Validates the escrow terms to ensure they are valid for a purchase\n * @returns A payment if the terms are valid for a purchase, undefined otherwise\n */\nexport const getInvoiceForEscrow = async (\n terms: EscrowTerms,\n dataHashMap: Record<Hash, Payload>,\n paymentTotalDiviner: DivinerInstance,\n): Promise<Invoice | undefined> => {\n const payloads = Object.values(dataHashMap)\n const results = await paymentTotalDiviner.divine([terms, ...payloads])\n const subtotal = results.find(isSubtotal) as Subtotal | undefined\n const discount = results.find(isDiscount) as Discount | undefined\n const total = results.find(isTotal) as Total | undefined\n if (!subtotal || !total) return undefined\n const { amount, currency } = total\n if (currency !== 'USD') return undefined\n const sources = await getSources(terms, subtotal, total, discount)\n const payment: Payment = {\n amount, currency, schema: PaymentSchema, sources,\n }\n return discount ? [subtotal, total, payment, discount] : [subtotal, total, payment]\n}\n\nconst getSources = async (terms: EscrowTerms, subtotal: Subtotal, total: Total, discount?: Discount): Promise<Hash[]> => {\n const sources = discount ? [terms, subtotal, total, discount] : [terms, subtotal, total]\n return await Promise.all(sources.map(p => PayloadBuilder.dataHash(p)))\n}\n","import { AbstractDiviner } from '@xyo-network/diviner-abstract'\nimport { HashLeaseEstimate, isHashLeaseEstimate } from '@xyo-network/diviner-hash-lease'\nimport { DivinerInstance, DivinerModuleEventData } from '@xyo-network/diviner-model'\nimport { creatableModule } from '@xyo-network/module-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload } from '@xyo-network/payload-model'\nimport {\n EscrowTerms, isEscrowTerms, PaymentSubtotalDivinerConfigSchema, PaymentSubtotalDivinerParams, Subtotal, SubtotalSchema,\n} from '@xyo-network/payment-payload-plugins'\n\nimport {\n appraisalValidators, termsValidators, ValidEscrowTerms,\n} from './lib/index.ts'\n\nconst currency = 'USD'\n\n/**\n * Escrow terms that contain all the valid fields for calculating a subtotal\n */\nexport type PaymentSubtotalDivinerInputType = EscrowTerms | HashLeaseEstimate | Payload\n\n@creatableModule()\nexport class PaymentSubtotalDiviner<\n TParams extends PaymentSubtotalDivinerParams = PaymentSubtotalDivinerParams,\n TIn extends PaymentSubtotalDivinerInputType = PaymentSubtotalDivinerInputType,\n TOut extends Subtotal = Subtotal,\n TEventData extends DivinerModuleEventData<DivinerInstance<TParams, TIn, TOut>, TIn, TOut> = DivinerModuleEventData<\n DivinerInstance<TParams, TIn, TOut>,\n TIn,\n TOut\n >,\n> extends AbstractDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [PaymentSubtotalDivinerConfigSchema]\n static override defaultConfigSchema = PaymentSubtotalDivinerConfigSchema\n\n protected async divineHandler(payloads: TIn[] = []): Promise<TOut[]> {\n // Find the escrow terms\n const terms = payloads.find(isEscrowTerms) as EscrowTerms | undefined\n if (!terms) return []\n\n // Run all terms validations\n if (!termsValidators.every(validator => validator(terms))) return []\n const validTerms = terms as ValidEscrowTerms\n\n // Retrieve all appraisals from terms\n const hashMap = await PayloadBuilder.toAllHashMap(payloads)\n const appraisals = validTerms.appraisals.map(appraisal => hashMap[appraisal]).filter(isHashLeaseEstimate) as unknown as HashLeaseEstimate[]\n\n // Ensure all appraisals are present\n if (appraisals.length !== validTerms.appraisals.length) return []\n\n // Run all appraisal validations\n if (!appraisalValidators.every(validator => validator(appraisals))) return []\n const amount = calculateSubtotal(appraisals)\n const sources = [await PayloadBuilder.dataHash(validTerms), ...validTerms.appraisals]\n return [{\n amount, currency, schema: SubtotalSchema, sources,\n }] as TOut[]\n }\n}\n\n// TODO: Add support for other currencies\nconst calculateSubtotal = (appraisals: HashLeaseEstimate[]): number => {\n return appraisals.reduce((sum, appraisal) => sum + appraisal.price, 0)\n}\n","import type { HashLeaseEstimate } from '@xyo-network/diviner-hash-lease'\nimport { isIso4217CurrencyCode } from '@xyo-network/payment-payload-plugins'\n\nimport { validateDuration } from './durationValidators.ts'\n\nconst validateAppraisalAmount = (appraisals: HashLeaseEstimate[]): boolean => {\n // Ensure all appraisals are numeric\n if (appraisals.some(appraisal => typeof appraisal.price !== 'number')) return false\n // Ensure all appraisals are positive numbers\n if (appraisals.some(appraisal => appraisal.price < 0)) return false\n return true\n}\n\nconst validateAppraisalCurrency = (appraisals: HashLeaseEstimate[]): boolean => {\n // NOTE: Only supporting USD for now, the remaining checks are for future-proofing.\n if (!appraisals.every(appraisal => appraisal.currency == 'USD')) return false\n\n // Check every object in the array to ensure they all are in a supported currency.\n if (!appraisals.every(appraisal => isIso4217CurrencyCode(appraisal.currency))) return false\n\n return true\n}\n\nconst validateAppraisalConsistentCurrency = (appraisals: HashLeaseEstimate[]): boolean => {\n // Check if the array is empty or contains only one element, no need to compare.\n if (appraisals.length <= 1) return true\n\n // Get the currency of the first element to compare with others.\n const { currency } = appraisals[0]\n if (!currency) return false\n\n // Check every object in the array to ensure they all have the same currency.\n if (!appraisals.every(item => item.currency === currency)) return false\n\n return true\n}\n\nconst validateAppraisalWindow = (appraisals: HashLeaseEstimate[]): boolean => appraisals.every(validateDuration)\n\nexport const appraisalValidators = [\n validateAppraisalAmount,\n validateAppraisalCurrency,\n validateAppraisalConsistentCurrency,\n validateAppraisalWindow,\n]\n","import type { DurationFields } from '@xyo-network/xns-record-payload-plugins'\n\nconst FIVE_MINUTES = 1000 * 60 * 5\n\n/**\n * Validates that the current time is within the duration window, within a configurable a buffer\n * @param value The duration value\n * @param windowMs The window in milliseconds to allow for a buffer\n * @returns True if the duration is valid, false otherwise\n */\nexport const validateDuration = (value: Partial<DurationFields>, windowMs = FIVE_MINUTES): boolean => {\n const now = Date.now()\n if (!value.nbf || value.nbf > now) return false\n // If already expired (include for a 5 minute buffer to allow for a reasonable\n // minimum amount of time for the transaction to be processed)\n if (!value.exp || value.exp - now < windowMs) return false\n return true\n}\n","import type { Hash } from '@xylabs/hex'\nimport type { EscrowTerms } from '@xyo-network/payment-payload-plugins'\n\nimport { validateDuration } from './durationValidators.ts'\n\nexport type ValidEscrowTerms = Required<EscrowTerms>\n\nconst validateTermsAppraisals = (terms: EscrowTerms): terms is Required<EscrowTerms & { appraisals: Hash[] }> => {\n if (!terms.appraisals) return false\n if (terms.appraisals.length === 0) return false\n return true\n}\nconst validateTermsWindow = (terms: EscrowTerms): boolean => validateDuration(terms)\n\nexport const termsValidators = [\n validateTermsAppraisals,\n validateTermsWindow,\n]\n","import { assertEx } from '@xylabs/assert'\nimport { Hash } from '@xylabs/hex'\nimport { AbstractDiviner } from '@xyo-network/diviner-abstract'\nimport {\n asDivinerInstance, DivinerInstance, DivinerModuleEventData,\n} from '@xyo-network/diviner-model'\nimport { creatableModule } from '@xyo-network/module-model'\nimport {\n Discount,\n isDiscountWithMeta,\n isSubtotalWithMeta,\n PaymentTotalDivinerConfigSchema, PaymentTotalDivinerParams, Subtotal, Total, TotalSchema,\n} from '@xyo-network/payment-payload-plugins'\n\nimport { PaymentDiscountDiviner, PaymentDiscountDivinerInputType } from '../Discount/index.ts'\nimport { PaymentSubtotalDiviner, PaymentSubtotalDivinerInputType } from '../Subtotal/index.ts'\n\ntype InputType = PaymentDiscountDivinerInputType | PaymentSubtotalDivinerInputType\ntype OutputType = Subtotal | Discount | Total\n\n@creatableModule()\nexport class PaymentTotalDiviner<\n TParams extends PaymentTotalDivinerParams = PaymentTotalDivinerParams,\n TIn extends InputType = InputType,\n TOut extends OutputType = OutputType,\n TEventData extends DivinerModuleEventData<DivinerInstance<TParams, TIn, TOut>, TIn, TOut> = DivinerModuleEventData<\n DivinerInstance<TParams, TIn, TOut>,\n TIn,\n TOut\n >,\n> extends AbstractDiviner<TParams, TIn, TOut, TEventData> {\n static override configSchemas = [PaymentTotalDivinerConfigSchema]\n static override defaultConfigSchema: PaymentTotalDivinerConfigSchema = PaymentTotalDivinerConfigSchema\n\n protected async divineHandler(payloads: TIn[] = []): Promise<TOut[]> {\n const subtotalDiviner = await this.getPaymentSubtotalDiviner()\n const subtotalResult = await subtotalDiviner.divine(payloads)\n const subtotal = subtotalResult.find(isSubtotalWithMeta)\n if (!subtotal) return []\n const discountDiviner = await this.getPaymentDiscountsDiviner()\n const discountResult = await discountDiviner.divine(payloads)\n const discount = discountResult.find(isDiscountWithMeta)\n if (!discount) return []\n const { currency: subtotalCurrency } = subtotal\n const { currency: discountCurrency } = discount\n assertEx(subtotalCurrency === discountCurrency, () => `Subtotal currency ${subtotalCurrency} does not match discount currency ${discountCurrency}`)\n const amount = Math.max(0, subtotal.amount - discount.amount)\n const currency = subtotalCurrency\n const sources = [subtotal.$hash, discount.$hash] as Hash[]\n const total: Total = {\n amount, currency, sources, schema: TotalSchema,\n }\n return [subtotal, discount, total] as TOut[]\n }\n\n protected async getPaymentDiscountsDiviner(): Promise<PaymentDiscountDiviner> {\n const name = assertEx(this.config.paymentDiscountDiviner, () => 'Missing paymentDiscountDiviner in config')\n const mod = assertEx(await this.resolve(name), () => `Error resolving paymentDiscountDiviner: ${name}`)\n return assertEx(asDivinerInstance(mod), () => `Resolved module ${mod.address} not a valid Diviner`) as PaymentDiscountDiviner\n }\n\n protected async getPaymentSubtotalDiviner(): Promise<PaymentSubtotalDiviner> {\n const name = assertEx(this.config.paymentSubtotalDiviner, () => 'Missing paymentSubtotalDiviner in config')\n const mod = assertEx(await this.resolve(name), () => `Error resolving paymentSubtotalDiviner: ${name}`)\n return assertEx(asDivinerInstance(mod), () => `Resolved module ${mod.address} not a valid Diviner`) as PaymentSubtotalDiviner\n }\n}\n"],"mappings":"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","names":["assertEx","exists","currency","exists","assertEx","PayloadBuilder","currency","AbstractDiviner","isHashLeaseEstimate","creatableModule","PayloadBuilder","isEscrowTerms","currency","AbstractDiviner","isEscrowTerms","PayloadBuilder","isHashLeaseEstimate","creatableModule","assertEx","AbstractDiviner","asDivinerInstance","creatableModule","AbstractDiviner","assertEx","currency","asDivinerInstance","creatableModule"]}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@xyo-network/payment-plugin",
3
+ "version": "3.0.18",
4
+ "description": "Typescript/Javascript Plugins for XYO Platform",
5
+ "homepage": "https://xyo.network",
6
+ "bugs": {
7
+ "url": "git+https://github.com/XYOracleNetwork/plugins/issues",
8
+ "email": "support@xyo.network"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/XYOracleNetwork/plugins.git"
13
+ },
14
+ "license": "LGPL-3.0-only",
15
+ "author": {
16
+ "name": "XYO Development Team",
17
+ "email": "support@xyo.network",
18
+ "url": "https://xyo.network"
19
+ },
20
+ "sideEffects": false,
21
+ "type": "module",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/neutral/index.d.ts",
25
+ "default": "./dist/neutral/index.mjs"
26
+ },
27
+ "./package.json": "./package.json"
28
+ },
29
+ "module": "dist/neutral/index.mjs",
30
+ "types": "dist/neutral/index.d.ts",
31
+ "dependencies": {
32
+ "@xylabs/assert": "^4.0.9",
33
+ "@xylabs/exists": "^4.0.10",
34
+ "@xylabs/hex": "^4.0.10",
35
+ "@xyo-network/archivist-model": "^3.1.9",
36
+ "@xyo-network/diviner-abstract": "^3.1.9",
37
+ "@xyo-network/diviner-boundwitness-model": "^3.1.9",
38
+ "@xyo-network/diviner-hash-lease": "^3.1.9",
39
+ "@xyo-network/diviner-model": "^3.1.9",
40
+ "@xyo-network/module-model": "^3.1.9",
41
+ "@xyo-network/payload-builder": "^3.1.9",
42
+ "@xyo-network/payload-model": "^3.1.9",
43
+ "@xyo-network/payment-payload-plugins": "^3.0.18",
44
+ "@xyo-network/xns-record-payload-plugins": "^3.0.18"
45
+ },
46
+ "devDependencies": {
47
+ "@xylabs/ts-scripts-yarn3": "^4.0.7",
48
+ "@xylabs/tsconfig": "^4.0.7",
49
+ "@xyo-network/account": "^3.1.9",
50
+ "@xyo-network/archivist-memory": "^3.1.9",
51
+ "@xyo-network/boundwitness-builder": "^3.1.9",
52
+ "@xyo-network/diviner-boundwitness-memory": "^3.1.9",
53
+ "@xyo-network/node-memory": "^3.1.9",
54
+ "jest": "^29.7.0",
55
+ "typescript": "^5.5.4",
56
+ "vitest": "^2.0.5"
57
+ },
58
+ "publishConfig": {
59
+ "access": "public"
60
+ }
61
+ }
@@ -0,0 +1,155 @@
1
+ import { assertEx } from '@xylabs/assert'
2
+ import { exists } from '@xylabs/exists'
3
+ import { Address, Hash } from '@xylabs/hex'
4
+ import { ArchivistInstance, asArchivistInstance } from '@xyo-network/archivist-model'
5
+ import { AbstractDiviner } from '@xyo-network/diviner-abstract'
6
+ import { BoundWitnessDivinerQueryPayload, BoundWitnessDivinerQuerySchema } from '@xyo-network/diviner-boundwitness-model'
7
+ import {
8
+ HashLeaseEstimate,
9
+ isHashLeaseEstimate,
10
+ } from '@xyo-network/diviner-hash-lease'
11
+ import {
12
+ asDivinerInstance, DivinerInstance, DivinerModuleEventData,
13
+ } from '@xyo-network/diviner-model'
14
+ import { creatableModule } from '@xyo-network/module-model'
15
+ import { PayloadBuilder } from '@xyo-network/payload-builder'
16
+ import { Payload } from '@xyo-network/payload-model'
17
+ import {
18
+ Coupon,
19
+ Discount,
20
+ EscrowTerms, isCoupon,
21
+ isCouponWithMeta,
22
+ isEscrowTerms, NO_DISCOUNT, PaymentDiscountDivinerConfigSchema, PaymentDiscountDivinerParams,
23
+ } from '@xyo-network/payment-payload-plugins'
24
+
25
+ import { applyCoupons } from './lib/index.ts'
26
+
27
+ const DEFAULT_BOUND_WITNESS_DIVINER_QUERY_PROPS: Readonly<BoundWitnessDivinerQueryPayload> = {
28
+ limit: 1,
29
+ order: 'desc',
30
+ schema: BoundWitnessDivinerQuerySchema,
31
+ }
32
+
33
+ export type PaymentDiscountDivinerInputType = EscrowTerms | Coupon | HashLeaseEstimate | Payload
34
+
35
+ @creatableModule()
36
+ export class PaymentDiscountDiviner<
37
+ TParams extends PaymentDiscountDivinerParams = PaymentDiscountDivinerParams,
38
+ TIn extends PaymentDiscountDivinerInputType = PaymentDiscountDivinerInputType,
39
+ TOut extends Discount = Discount,
40
+ TEventData extends DivinerModuleEventData<DivinerInstance<TParams, TIn, TOut>, TIn, TOut> = DivinerModuleEventData<
41
+ DivinerInstance<TParams, TIn, TOut>,
42
+ TIn,
43
+ TOut
44
+ >,
45
+ > extends AbstractDiviner<TParams, TIn, TOut, TEventData> {
46
+ static override configSchemas = [PaymentDiscountDivinerConfigSchema]
47
+ static override defaultConfigSchema = PaymentDiscountDivinerConfigSchema
48
+
49
+ protected get couponAuthorities(): Address[] {
50
+ return [...(this.config.couponAuthorities ?? []), ...(this.params.couponAuthorities ?? [])]
51
+ }
52
+
53
+ protected async divineHandler(payloads: TIn[] = []): Promise<TOut[]> {
54
+ const sources: Hash[] = []
55
+
56
+ // Parse terms
57
+ const terms = payloads.find(isEscrowTerms) as EscrowTerms | undefined
58
+ if (!terms) return [{ ...NO_DISCOUNT, sources }] as TOut[]
59
+ sources.push(await PayloadBuilder.hash(terms))
60
+
61
+ // Parse discounts
62
+ const discountHashes = terms.discounts ?? []
63
+ if (discountHashes.length === 0) return [{ ...NO_DISCOUNT, sources }] as TOut[]
64
+
65
+ // TODO: Call paymentSubtotalDiviner to get the subtotal to centralize the logic
66
+ // Parse appraisals
67
+ const termsAppraisals = terms?.appraisals
68
+ if (!termsAppraisals || termsAppraisals.length === 0) return [{ ...NO_DISCOUNT, sources }] as TOut[]
69
+ const hashMap = await PayloadBuilder.toAllHashMap(payloads)
70
+ const foundAppraisals = termsAppraisals.filter(hash => hashMap[hash])
71
+ // Add the appraisals that were found to the sources
72
+ sources.push(...foundAppraisals)
73
+ // If not all appraisals are found, return no discount
74
+ if (foundAppraisals.length !== termsAppraisals.length) {
75
+ return [{ ...NO_DISCOUNT, sources }] as TOut[]
76
+ }
77
+ // TODO: Cast should not be required
78
+ const appraisals = foundAppraisals.map(hash => hashMap[hash]).filter(exists).filter(isHashLeaseEstimate) as unknown as HashLeaseEstimate[]
79
+
80
+ // Use the supplied payloads to find the discounts
81
+ const discounts = discountHashes.map(hash => hashMap[hash]).filter(exists).filter(isCoupon) as Coupon[]
82
+ // Find any remaining coupons from the archivist
83
+ if (discounts.length !== discountHashes.length) {
84
+ // Find remaining from discounts archivist
85
+ const discountsArchivist = await this.getDiscountsArchivist()
86
+ const foundDiscounts = await discountsArchivist.get(discountHashes)
87
+ discounts.push(...foundDiscounts.filter(isCouponWithMeta))
88
+ }
89
+ const discountsMap = await PayloadBuilder.toAllHashMap(discounts)
90
+ if (Object.keys(discountsMap).length === 0) return [{ ...NO_DISCOUNT, sources }] as TOut[]
91
+
92
+ // Add the found discounts to the sources
93
+ const foundDiscountsHashes = Object.keys(discountsMap) as Hash[]
94
+ sources.push(...foundDiscountsHashes)
95
+
96
+ // Log individual discounts that were not found
97
+ for (const hash of discountHashes) {
98
+ if (!foundDiscountsHashes.includes(hash)) {
99
+ console.warn(`Discount ${hash} not found for terms ${await PayloadBuilder.hash(terms)}`)
100
+ }
101
+ }
102
+
103
+ // Parse coupons
104
+ const coupons = Object.values(discountsMap)
105
+ const validCoupons = await this.filterToSigned(coupons.filter(this.isCouponCurrent))
106
+ if (validCoupons.length === 0) return [{ ...NO_DISCOUNT, sources }] as TOut[]
107
+
108
+ const discount = applyCoupons(appraisals, validCoupons)
109
+ return [{ ...discount, sources }] as TOut[]
110
+ }
111
+
112
+ /**
113
+ * Filters the supplied list of coupons to only those that are signed by
114
+ * addresses specified in the couponAuthorities
115
+ * @param coupons The list of coupons to filter
116
+ * @returns The filtered list of coupons that are signed by the couponAuthorities
117
+ */
118
+ protected async filterToSigned(coupons: Coupon[]): Promise<Coupon[]> {
119
+ const signed: Coupon[] = []
120
+ const dataHashMap = await PayloadBuilder.toDataHashMap(coupons)
121
+ const boundWitnessDiviner = await this.getDiscountsBoundWitnessDiviner()
122
+ const hashes = Object.keys(dataHashMap)
123
+ const addresses = this.couponAuthorities
124
+ // TODO: Keep an in memory cache of the hashes queried and their results
125
+ // to avoid querying the same hash multiple times
126
+ await Promise.all(hashes.map((h) => {
127
+ const hash = h as Hash
128
+ return Promise.all(addresses.map(async (address) => {
129
+ const query: BoundWitnessDivinerQueryPayload = {
130
+ ...DEFAULT_BOUND_WITNESS_DIVINER_QUERY_PROPS, addresses: [address], payload_hashes: [hash],
131
+ }
132
+ const result = await boundWitnessDiviner.divine([query])
133
+ if (result.length > 0) signed.push(dataHashMap[hash])
134
+ }))
135
+ }))
136
+ return signed
137
+ }
138
+
139
+ protected async getDiscountsArchivist(): Promise<ArchivistInstance> {
140
+ const name = assertEx(this.config.archivist, () => 'Missing archivist in config')
141
+ const mod = assertEx(await this.resolve(name), () => `Error resolving archivist: ${name}`)
142
+ return assertEx(asArchivistInstance(mod), () => `Resolved module ${mod.address} not a valid Archivist`)
143
+ }
144
+
145
+ protected async getDiscountsBoundWitnessDiviner(): Promise<DivinerInstance> {
146
+ const name = assertEx(this.config.boundWitnessDiviner, () => 'Missing boundWitnessDiviner in config')
147
+ const mod = assertEx(await this.resolve(name), () => `Error resolving boundWitnessDiviner: ${name}`)
148
+ return assertEx(asDivinerInstance(mod), () => `Resolved module ${mod.address} not a valid Diviner`)
149
+ }
150
+
151
+ protected isCouponCurrent(coupon: Coupon): boolean {
152
+ const now = Date.now()
153
+ return coupon.exp > now && coupon.nbf < now
154
+ }
155
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Diviner.ts'
2
+ export * from './lib/index.ts'