Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
MarketplaceProcessor.cs
1using System;
3using System.Text;
4using System.Threading.Tasks;
5using System.Xml;
7using Waher.Events;
17using Waher.Script;
27
29{
33 public static class MarketplaceProcessor
34 {
35 private static Scheduler scheduler;
36
40 public const string MarketplaceNamespace = "https://paiwise.tagroot.io/Schema/Marketplace.xsd";
41
45 public const string AuctioneerRole = "Auctioneer";
46
50 public const string SellerRole = "Seller";
51
55 public const string BuyerRole = "Buyer";
56
57 private static readonly Dictionary<CaseInsensitiveString, AuctionItem> activeItems = new Dictionary<CaseInsensitiveString, AuctionItem>();
58 private static decimal? minCommission = null;
59
71 internal static async Task ContractSigned(Contract Contract, bool ContractIsLocked,
72 Dictionary<CaseInsensitiveString, Parameter> TransientParameters,
74 {
75 if (Contract?.ForMachines is null || Contract.ForMachinesNamespace != MarketplaceNamespace)
76 return;
77
78 if ((TransientParameters?.Count ?? 0) > 0)
79 {
80 await NeuroFeaturesProcessor.RejectContract(Contract,ContractIsLocked,
81 "Transient parameters not permitted in auctions.", true, Legal, EDaler);
82 return;
83 }
84
85 switch (Contract.ForMachinesLocalName)
86 {
87 case "ForSale":
88 if (!OnlyMissingAuctioneer(Contract, SellerRole, AuctioneerRole, out string PaymentLegalId, out string PaymentJid))
89 return;
90
91 await AddItemForSale(Contract.ForMachinesParsed.DocumentElement,
92 Contract, ContractIsLocked, PaymentLegalId, PaymentJid, Legal,
93 EDaler, PubSub);
94 break;
95
96 case "ToBuy":
97 if (!OnlyMissingAuctioneer(Contract, BuyerRole, AuctioneerRole, out PaymentLegalId, out PaymentJid))
98 return;
99
100 await AddItemToBuy(Contract.ForMachinesParsed.DocumentElement, Contract,
101 ContractIsLocked, PaymentLegalId, PaymentJid, Legal, EDaler, PubSub);
102 break;
103
104 case "Offer":
105 if (!OnlyMissingAuctioneer(Contract, null, AuctioneerRole, out _, out _))
106 return;
107
108 await AddOffer(Contract.ForMachinesParsed.DocumentElement, Contract,
109 ContractIsLocked, Legal, EDaler, PubSub);
110 break;
111
112 default:
113 Log.Error("Rejected marketplace contract: Unrecognized element: " + Contract.ForMachinesLocalName, Contract.ContractId);
114 break;
115 }
116 }
117
118 internal static bool OnlyMissingAuctioneer(Contract Contract, string PaymentPartyRole,
119 string TrustProviderRole, out string PaymentLegalId, out string PaymentJid)
120 {
121 PaymentLegalId = null;
122 PaymentJid = null;
123
124 if (Contract.State != ContractState.BeingSigned || Contract.ClientSignatures is null)
125 return false;
126
127 if ((Contract.Roles?.Length ?? 0) < 2)
128 return false;
129
130 bool HasAuctioneer = false;
131 bool HasPaymentParty = false;
132 Dictionary<string, SignatureStat> Signatures = new Dictionary<string, SignatureStat>();
133
134 foreach (Role Role in Contract.Roles)
135 {
136 if (Role.CanRevoke)
137 return false;
138
139 if (Role.Name == TrustProviderRole)
140 {
141 if (Role.MinCount != 1 || Role.MaxCount != 1)
142 return false;
143
144 HasAuctioneer = true;
145 }
146 else
147 {
148 if (!(PaymentPartyRole is null) && Role.Name == PaymentPartyRole)
149 {
150 if (Role.MinCount != 1 || Role.MaxCount != 1)
151 return false;
152
153 HasPaymentParty = true;
154 }
155
156 Signatures[Role.Name] = new SignatureStat()
157 {
158 Min = Role.MinCount,
159 Max = Role.MaxCount,
160 Count = 0
161 };
162 }
163 }
164
165 if (!HasAuctioneer || (!HasPaymentParty && !(PaymentPartyRole is null)))
166 return false;
167
168 foreach (ClientSignature Signature in Contract.ClientSignatures)
169 {
170 if (Signatures.TryGetValue(Signature.Role, out SignatureStat Stat))
171 Stat.Count++;
172 else
173 return false;
174
175 if (!(PaymentPartyRole is null) && Signature.Role == PaymentPartyRole)
176 {
177 PaymentLegalId = Signature.LegalId;
178 PaymentJid = Signature.BareJid;
179 }
180 }
181
182 foreach (SignatureStat Stat in Signatures.Values)
183 {
184 if (Stat.Count < Stat.Min || Stat.Count > Stat.Max)
185 return false;
186 }
187
188 if (!(Contract.Parts is null))
189 {
190 foreach (Part Part in Contract.Parts)
191 {
192 if (Part.Role == TrustProviderRole && !LegalIdentityConfiguration.IsMeApproved(Part.LegalId))
193 {
194 // TODO: Forward contract in message to intended auctioneer
195 return false;
196 }
197 }
198 }
199
200 return true;
201 }
202
203 private class SignatureStat
204 {
205 public int Min;
206 public int Max;
207 public int Count;
208 }
209
210 private static async Task<bool> AddItemForSale(XmlElement ForSale, Contract Contract, bool ContractIsLocked, string PaymentLegalId,
211 string PaymentJid, LegalComponent Legal, EDalerComponent EDaler, PubSubComponent PubSub)
212 {
213 AuctionItem Item = await ParseAuctionItem(ForSale, PaymentLegalId, PaymentJid, Contract);
214 if (Item is null)
215 return false;
216
217 await AddItem(Item, Contract, ContractIsLocked, OptimizeDirection.Up, Legal, EDaler, PubSub);
218
219 return true;
220 }
221
222 private static async Task<bool> AddItemToBuy(XmlElement ToBuy, Contract Contract, bool ContractIsLocked, string PaymentLegalId,
223 string PaymentJid, LegalComponent Legal, EDalerComponent EDaler, PubSubComponent PubSub)
224 {
225 AuctionItem Item = await ParseAuctionItem(ToBuy, PaymentLegalId, PaymentJid, Contract);
226 if (Item is null)
227 return false;
228
229 await AddItem(Item, Contract, ContractIsLocked, OptimizeDirection.Down, Legal, EDaler, PubSub);
230
231 return true;
232 }
233
234 private static async Task AddItem(AuctionItem Item, Contract Contract, bool ContractIsLocked, OptimizeDirection Direction, LegalComponent Legal,
236 {
237 Item.Direction = Direction;
238 await Database.Insert(Item);
239
240 Item.ScheduledExpiry = AddItem(Item, EDaler);
241
242 SignatureReference Ref = await SignContract(Contract, ContractIsLocked, Legal, PubSub, Item.Node, AuctioneerRole);
243 if (Ref is null)
244 {
245 lock (activeItems)
246 {
247 activeItems.Remove(Item.ContractId);
248 }
249
250 Scheduler.Remove(Item.ScheduledExpiry);
251 Item.ScheduledExpiry = DateTime.MinValue;
252
253 await Database.Delete(Item);
254 return;
255 }
256
257 Item.TrustProviderJid = Ref.BareJid;
258 Item.TrustProviderLegalId = Ref.LegalId;
259
260 await Database.Update(Item);
261 }
262
263 internal class SignatureReference
264 {
265 public string LegalId;
266 public string BareJid;
267 }
268
269 internal static Task<SignatureReference> SignContract(Contract Contract, bool ContractIsLocked, LegalComponent Legal, string Role)
270 {
271 return SignContract(Contract, ContractIsLocked, Legal, null, null, Role);
272 }
273
274 private static async Task<SignatureReference> SignContract(Contract Contract, bool ContractIsLocked, LegalComponent Legal,
275 PubSubComponent PubSub, string Node, string Role)
276 {
277 try
278 {
279 if (Gateway.ContractsClient is null)
280 return null;
281
282 StringBuilder sb = new StringBuilder();
283
284 if (Legal.IsComponentDomain(Networking.XMPP.XmppClient.GetDomain(Contract.ContractId), true))
285 {
287
288 if (ContractIsLocked)
289 Semaphore = null;
290 else
292
293 try
294 {
295 await Contract.Serialize(sb, false, false, false, false, false, false, false, null, Legal);
296 byte[] Data = Encoding.UTF8.GetBytes(sb.ToString());
297 byte[] Signature = await Gateway.ContractsClient.SignAsync(Data, Networking.XMPP.Contracts.SignWith.LatestApprovedId);
298
299 Contract = await Legal.SignContract(Contract, true, Role, false, Signature, new XmppAddress(Gateway.ContractsClient.Client.BareJID));
300
301 if (!string.IsNullOrEmpty(Node))
302 {
303 string ContractXml = null;
304
305 try
306 {
307 sb.Clear();
308 await Contract.Serialize(sb, true, true, false, false, false, false, false, null, Legal);
309 ContractXml = sb.ToString();
310
311 XmlDocument Xml = XML.ParseXml(ContractXml, true);
312
314 new XmppAddress(Contract.Account + "@" + Legal.MainDomain.Address),
316 }
317 catch (XmlException ex)
318 {
320 }
321 catch (Exception ex)
322 {
324 }
325 }
326
327 SignatureReference Result = new SignatureReference();
328
329 if (!(Contract.ClientSignatures is null))
330 {
331 foreach (ClientSignature Signature2 in Contract.ClientSignatures)
332 {
333 if (Signature2.Role == Role)
334 {
335 Result.LegalId = Signature2.Role;
336 Result.BareJid = Signature2.BareJid;
337 }
338 }
339 }
340
341 return Result;
342 }
343 finally
344 {
345 if (!(Semaphore is null))
346 await Semaphore.DisposeAsync();
347 }
348 }
349 else
350 {
351 await Contract.Serialize(sb, true, true, false, false, false, false, false, null, Legal);
352
353 string ContractXml = sb.ToString();
354 XmlDocument Xml = XML.ParseXml(ContractXml, true);
355
356 Networking.XMPP.Contracts.ParsedContract Parsed = await Networking.XMPP.Contracts.Contract.Parse(Xml, Gateway.ContractsClient);
358
359 Contract2 = await Gateway.ContractsClient.SignContractAsync(Contract2, Role, false);
360
361 if (!string.IsNullOrEmpty(Node))
362 {
363 ContractXml = null;
364
365 try
366 {
367 sb.Clear();
368 Contract2.Serialize(sb, true, true, false, false, false, false, false);
369 ContractXml = sb.ToString();
370 Xml = XML.ParseXml(ContractXml, true);
371
373 new XmppAddress(Contract.Account + "@" + Legal.MainDomain.Address),
374 PubSub.Subdomain, Contract.ContractId, Xml, Contract2.DefaultLanguage);
375 }
376 catch (XmlException ex)
377 {
379 }
380 catch (Exception ex)
381 {
383 }
384 }
385
386 SignatureReference Result = new SignatureReference();
387
388 if (!(Contract2.ClientSignatures is null))
389 {
390 foreach (Networking.XMPP.Contracts.ClientSignature Signature in
391 Contract2.ClientSignatures)
392 {
393 if (Signature.Role == Role)
394 {
395 Result.LegalId = Signature.Role;
396 Result.BareJid = Signature.BareJid;
397 }
398 }
399 }
400
401 return Result;
402 }
403 }
404 catch (XmlException ex)
405 {
406 ex = XML.AnnotateException(ex);
407 Log.Error("Unable to sign contract: " + ex.Message, Contract.ContractId);
408 return null;
409 }
410 catch (Exception ex)
411 {
412 Log.Error("Unable to sign contract: " + ex.Message, Contract.ContractId);
413 return null;
414 }
415 }
416
417 private static DateTime AddItem(AuctionItem Item, EDalerComponent EDaler)
418 {
419 if (Item.ContractId is null)
420 return DateTime.MaxValue;
421
422 lock (activeItems)
423 {
424 activeItems[Item.ContractId] = Item;
425 }
426
427 Item.ScheduledExpiry = Scheduler.Add(Item.Expires.ToLocalTime(), ItemExpires, new ExpiryRecord()
428 {
429 Item = Item,
430 EDaler = EDaler
431 });
432
433 return Item.ScheduledExpiry;
434 }
435
436 private class ExpiryRecord
437 {
438 public AuctionItem Item;
439 public EDalerComponent EDaler;
440 }
441
442 private static Scheduler Scheduler
443 {
444 get
445 {
446 if (scheduler is null)
447 {
448 if (Types.TryGetModuleParameter("Scheduler", out Scheduler Scheduler))
449 scheduler = Scheduler;
450 else
451 {
452 scheduler = new Scheduler();
453 Log.Terminating += Log_Terminating;
454 }
455 }
456
457 return scheduler;
458 }
459 }
460
461 private static Task Log_Terminating(object Sender, EventArgs e)
462 {
463 scheduler?.Dispose();
464 scheduler = null;
465
466 Log.Terminating -= Log_Terminating;
467
468 return Task.CompletedTask;
469 }
470
471 internal static async Task LoadActiveItems(EDalerComponent EDaler)
472 {
473 foreach (AuctionItem Item in await Database.Find<AuctionItem>(new FilterFieldEqualTo("Processed", null), "Created"))
474 AddItem(Item, EDaler);
475 }
476
477 private static async Task<AuctionItem> ParseAuctionItem(XmlElement Item, string PaymentLegalId,
478 string PaymentJid, Contract Contract)
479 {
480 List<AuctionItemTag> Tags = new List<AuctionItemTag>();
481 List<ExpectedTag> ExpectedTags = null;
482 string Type = null;
483 string Class = null;
484 string Node = null;
485 string Currency = null;
486 string ScoreFunction = null;
487 string TokenId = null;
488 decimal? AskingPrice = null;
489 decimal? RejectPrice = null;
490 decimal? AcceptPrice = null;
491 decimal? CommissionPercent = null;
492 decimal? RejectScore = null;
493 decimal? AcceptScore = null;
494 decimal? AvailableDays = null;
495
496 foreach (XmlNode N in Item.ChildNodes)
497 {
498 if (!(N is XmlElement E) || E.NamespaceURI != MarketplaceNamespace)
499 continue;
500
501 switch (E.LocalName)
502 {
503 case "Description":
504 foreach (XmlNode N2 in E.ChildNodes)
505 {
506 if (!(N2 is XmlElement E2))
507 continue;
508
509 switch (E2.LocalName)
510 {
511 case "Type":
512 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is string s))
513 {
514 Log.Error("Rejected marketplace contract: Invalid type.", Contract.ContractId);
515 return null;
516 }
517
518 Type = s;
519 break;
520
521 case "Class":
522 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is string s2))
523 {
524 Log.Error("Rejected marketplace contract: Invalid class.", Contract.ContractId);
525 return null;
526 }
527
528 Class = s2;
529 break;
530
531 case "Node":
532 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is string s3))
533 {
534 Log.Error("Rejected marketplace contract: Invalid node.", Contract.ContractId);
535 return null;
536 }
537
538 Node = s3;
539 break;
540
541 case "Tag":
542 AuctionItemTag Tag = new AuctionItemTag()
543 {
544 Name = XML.Attribute(E2, "name"),
545 Value = await PaiwiseProcessor.GetParameterValue(E2, Contract)
546 };
547
548 Tags.Add(Tag);
549
550 if (Tag.Name == "TokenID")
551 TokenId = Tag.Value as string;
552 break;
553 }
554 }
555 break;
556
557 case "Price":
558 foreach (XmlNode N2 in E.ChildNodes)
559 {
560 if (!(N2 is XmlElement E2))
561 continue;
562
563 switch (E2.LocalName)
564 {
565 case "AskingPrice":
566 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is decimal d))
567 {
568 Log.Error("Rejected marketplace contract: Invalid asking price.", Contract.ContractId);
569 return null;
570 }
571
572 AskingPrice = d;
573 break;
574
575 case "RejectPrice":
576 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is decimal d2))
577 {
578 Log.Error("Rejected marketplace contract: Invalid rejection price.", Contract.ContractId);
579 return null;
580 }
581
582 RejectPrice = d2;
583 break;
584
585 case "AcceptPrice":
586 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is decimal d3))
587 {
588 Log.Error("Rejected marketplace contract: Invalid accept price.", Contract.ContractId);
589 return null;
590 }
591
592 AcceptPrice = d3;
593 break;
594
595 case "CommissionPercent":
596 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is decimal d4) || d4 < 0 || d4 > 100)
597 {
598 Log.Error("Rejected marketplace contract: Invalid commission (%).", Contract.ContractId);
599 return null;
600 }
601
602 CommissionPercent = d4;
603 break;
604
605 case "Currency":
606 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is string s))
607 {
608 Log.Error("Rejected marketplace contract: Invalid currency.", Contract.ContractId);
609 return null;
610 }
611
612 Currency = s;
613 break;
614 }
615 }
616 break;
617
618 case "Score":
619 foreach (XmlNode N2 in E.ChildNodes)
620 {
621 if (!(N2 is XmlElement E2))
622 continue;
623
624 switch (E2.LocalName)
625 {
626 case "ExpectsTag":
627 ExpectedTags ??= new List<ExpectedTag>();
628
629 string Name = E2.InnerText;
630 string Where = null;
631
632 if (E2.HasAttribute("where"))
633 {
634 Where = E2.GetAttribute("where");
635 if (!IsValidExpression(Where, out _))
636 {
637 Log.Error("Rejected marketplace contract: Invalid where expression.", Contract.ContractId);
638 return null;
639 }
640 }
641
642 ExpectedTags.Add(new ExpectedTag()
643 {
644 Name = Name,
645 Where = Where
646 });
647 break;
648
649 case "ScoreFunction":
650 ScoreFunction = E2.InnerText;
651 if (!IsValidExpression(ScoreFunction, out _))
652 {
653 Log.Error("Rejected marketplace contract: Invalid scoring function.", Contract.ContractId);
654 return null;
655 }
656 break;
657
658 case "RejectScore":
659 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is decimal d))
660 {
661 Log.Error("Rejected marketplace contract: Invalid rejection score.", Contract.ContractId);
662 return null;
663 }
664
665 RejectScore = d;
666 break;
667
668 case "AcceptScore":
669 if (!(await PaiwiseProcessor.GetParameterValue(E2, Contract) is decimal d2))
670 {
671 Log.Error("Rejected marketplace contract: Invalid accept score.", Contract.ContractId);
672 return null;
673 }
674
675 AcceptScore = d2;
676 break;
677 }
678 }
679 break;
680
681 case "AvailableDays":
682 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is decimal d5) || d5 <= 0)
683 {
684 Log.Error("Rejected marketplace contract: Invalid number of available days.", Contract.ContractId);
685 return null;
686 }
687
688 AvailableDays = d5;
689 break;
690 }
691 }
692
693 if (Type is null || Class is null || Currency is null || !AskingPrice.HasValue || !CommissionPercent.HasValue || !AvailableDays.HasValue)
694 {
695 Log.Error("Rejected marketplace contract: Incomplete.", Contract.ContractId);
696 return null;
697 }
698
699 if (CommissionPercent.Value < await GetMinCommission())
700 {
701 Log.Error("Rejected marketplace contract: Commission too low.", Contract.ContractId);
702 return null;
703 }
704
706 {
707 if (string.IsNullOrEmpty(TokenId))
708 {
709 Log.Error("Rejected marketplace contract: TokenID Tag expected.", Contract.ContractId);
710 return null;
711 }
712
713 Token Token = await NeuroFeaturesProcessor.GetToken(TokenId, true);
714 if (Token is null)
715 {
716 // TODO: Allow remote tokens in marketplace.
717
718 Log.Error("Rejected marketplace contract: Token not hosted by Trust Provider.", Contract.ContractId);
719 return null;
720 }
721
722 if (Token.OwnerJid != PaymentJid)
723 {
724 Log.Error("Rejected marketplace contract: Not the current owner of the token.", Contract.ContractId);
725 return null;
726 }
727
728 if (Token.Expires.ToUniversalTime() < DateTime.UtcNow)
729 {
730 Log.Error("Rejected marketplace contract: Token has expired.", Contract.ContractId);
731 return null;
732 }
733
734 if (Token.Expires.ToUniversalTime() < DateTime.UtcNow.AddDays((double)AvailableDays.Value))
735 {
736 Log.Error("Rejected marketplace contract: Token will expire during the auction.", Contract.ContractId);
737 return null;
738 }
739
740 AuctionItem Item2 = await Database.FindFirstIgnoreRest<AuctionItem>(new FilterAnd(
741 new FilterFieldEqualTo("TokenId", TokenId),
742 new FilterFieldEqualTo("Processed", null)));
743
744 if (!(Item2 is null))
745 {
746 Log.Error("Rejected marketplace contract: Another auction for the token is already in process.", Contract.ContractId);
747 return null;
748 }
749 }
750
751 DateTime Created = DateTime.UtcNow;
752
753 return new AuctionItem()
754 {
755 ContractId = Contract.ContractId,
756 InitiatorLegalId = PaymentLegalId,
757 InitiatorJid = PaymentJid,
759 Expires = Created.AddDays((double)AvailableDays.Value),
760 Class = Class,
761 Type = Type,
762 TokenId = TokenId,
763 Node = Node,
764 Tags = Tags.ToArray(),
765 ExpectedTags = ExpectedTags?.ToArray(),
766 AskingPrice = AskingPrice.Value,
767 AcceptPrice = AcceptPrice.Value,
768 RejectPrice = RejectPrice.Value,
769 CommissionPercent = CommissionPercent.Value,
770 Currency = Currency,
771 ScoreFunction = ScoreFunction,
772 AcceptScore = AcceptScore,
773 RejectScore = RejectScore,
774 };
775 }
776
777 internal static async Task<decimal> GetMinCommission()
778 {
779 if (!minCommission.HasValue)
780 minCommission = (decimal)await RuntimeSettings.GetAsync("Commission.Min", 10.0);
781
782 return (decimal)minCommission.Value;
783 }
784
785 private static bool IsValidExpression(string Script, out ScriptNode Prohibited)
786 {
787 if (string.IsNullOrEmpty(Script))
788 {
789 Prohibited = null;
790 return false;
791 }
792
793 try
794 {
795 Expression Exp = new Expression(Script);
796 return XmppServer.CheckExpressionSafe(Exp, true, true, false, out Prohibited);
797 }
798 catch (Exception)
799 {
800 Prohibited = null;
801 return false;
802 }
803 }
804
805 private static async Task<bool> AddOffer(XmlElement Offer, Contract Contract, bool ContractIsLocked, LegalComponent Legal, EDalerComponent EDaler,
806 PubSubComponent PubSub)
807 {
808 Variables v = new Variables();
809 List<AuctionItemTag> Tags = new List<AuctionItemTag>();
810 CaseInsensitiveString ItemReference = null;
811 string Currency = null;
812 decimal? Price = null;
813
814 foreach (XmlNode N in Offer.ChildNodes)
815 {
816 if (!(N is XmlElement E) || E.NamespaceURI != MarketplaceNamespace)
817 continue;
818
819 switch (E.LocalName)
820 {
821 case "Tag":
822 AuctionItemTag Tag = new AuctionItemTag()
823 {
824 Name = XML.Attribute(E, "name"),
825 Value = await PaiwiseProcessor.GetParameterValue(E, Contract)
826 };
827
828 Tags.Add(Tag);
829 v[Tag.Name] = Tag.Value;
830 break;
831
832 case "Price":
833 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is decimal d))
834 {
835 Log.Error("Rejected marketplace offer: Invalid price.", Contract.ContractId);
836 return false;
837 }
838
839 Price = d;
840 break;
841
842 case "Currency":
843 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is string s))
844 {
845 Log.Error("Rejected marketplace offer: Invalid currency.", Contract.ContractId);
846 return false;
847 }
848
849 Currency = s;
850 break;
851
852 case "ItemReference":
853 if (!(await PaiwiseProcessor.GetParameterValue(E, Contract) is string s2))
854 {
855 Log.Error("Rejected marketplace offer: Invalid item reference.", Contract.ContractId);
856 return false;
857 }
858
859 int i = s2.IndexOf('@');
860 if (i < 0 || !Guid.TryParse(s2[..i], out Guid _))
861 {
862 Log.Error("Rejected marketplace offer: Invalid item reference.", Contract.ContractId);
863 return false;
864 }
865
866 ItemReference = s2;
867
868 s2 = s2[(i + 1)..];
869 if (!Legal.IsComponentDomain(s2, true))
870 {
871 // TODO: If contract reference on other domain, send proposal message to that neuron
872 return false;
873 }
874 break;
875 }
876 }
877
878 if (ItemReference is null || Currency is null || !Price.HasValue)
879 {
880 Log.Error("Rejected marketplace offer: Incomplete.", Contract.ContractId);
881 return false;
882 }
883
884 AuctionItem Item;
885
886 lock (activeItems)
887 {
888 if (!activeItems.TryGetValue(ItemReference, out Item))
889 Item = null;
890 }
891
892 if (Item is null)
893 {
894 Log.Error("Rejected marketplace offer: Not active.", ItemReference, Contract.ContractId);
895 return false;
896 }
897
898 if (!(Item.Tags is null))
899 {
900 foreach (AuctionItemTag Tag in Item.Tags)
901 {
902 if (!v.ContainsVariable(Tag.Name))
903 v[Tag.Name] = Tag.Value;
904 }
905 }
906
907 v["Price"] = Price.Value;
908
909 if (!(Item.ExpectedTags is null))
910 {
911 foreach (ExpectedTag ExpectedTag in Item.ExpectedTags)
912 {
913 if (!v.ContainsVariable(ExpectedTag.Name))
914 {
915 Log.Error("Rejected marketplace offer: Tag '" + ExpectedTag
916 + "' used for scoring offers is missing.", ItemReference, Contract.ContractId);
917 return false;
918 }
919
920 if (!string.IsNullOrEmpty(ExpectedTag.Where))
921 {
922 try
923 {
924 object Result = await ExpectedTag.Parsed.EvaluateAsync(v);
925
926 if (Result is bool b)
927 {
928 if (!b)
929 {
930 Log.Error("Rejected marketplace offer: Tag '" + ExpectedTag
931 + "' not valid in accordance with where expression.", ItemReference, Contract.ContractId);
932 return false;
933 }
934 }
935 else
936 {
937 Log.Error("Rejected marketplace offer: Where expression for '" + ExpectedTag
938 + "' did not return a boolean value.", ItemReference, Contract.ContractId);
939 return false;
940 }
941 }
942 catch (Exception ex)
943 {
944 Log.Error("Rejected marketplace offer: Where expression for '" + ExpectedTag
945 + "' returned an exception: " + ex.Message, ItemReference, Contract.ContractId);
946 return false;
947 }
948 }
949 }
950 }
951
952 bool Accept = false;
953 bool NewBest = false;
954 decimal? Score = null;
955 string BestBidContractIdBak = Item.BestBidContractId;
956 string BestBidLegalIdBak = Item.BestBidLegalId;
957 string BestBidLegalJidBak = Item.BestBidJid;
958 decimal? BestBidPriceBak = Item.BestBidPrice;
959 decimal? BestBidScoreBak = Item.BestBidScore;
960 string PaymentLegalId;
961 string PaymentJid;
962
963 switch (Item.Direction)
964 {
965 case OptimizeDirection.Up: // Initiator sells. Offerers buy.
966 if (!OnlyMissingAuctioneer(Contract, BuyerRole, AuctioneerRole, out PaymentLegalId, out PaymentJid))
967 {
968 Log.Error("Rejected marketplace offer: No buyer defined.", ItemReference, Contract.ContractId);
969 return false;
970 }
971 break;
972
973 case OptimizeDirection.Down: // Initiator buys. Offerers sell.
974 if (!OnlyMissingAuctioneer(Contract, SellerRole, AuctioneerRole, out PaymentLegalId, out PaymentJid))
975 {
976 Log.Error("Rejected marketplace offer: No seller defined.", ItemReference, Contract.ContractId);
977 return false;
978 }
979 break;
980
981 default:
982 return false;
983 }
984
985 using (Semaphore Lock = await Semaphores.BeginWrite(Item.ObjectId.ToString()))
986 {
987 if (Item.Processed.HasValue)
988 {
989 Log.Error("Rejected marketplace offer: Not active.", ItemReference, Contract.ContractId);
990 return false;
991 }
992
993 if (Currency != Item.Currency)
994 {
995 Log.Error("Rejected marketplace offer: Currency mismatch.", ItemReference, Contract.ContractId);
996 return false;
997 }
998
999 if (Item.RejectPrice.HasValue)
1000 {
1001 switch (Item.Direction)
1002 {
1003 case OptimizeDirection.Up:
1004 if (Price.Value < Item.RejectPrice.Value)
1005 {
1006 Log.Notice("Rejected marketplace offer: Below rejection price threshold.", ItemReference, Contract.ContractId);
1007 return false;
1008 }
1009 break;
1010
1011 case OptimizeDirection.Down:
1012 if (Price.Value > Item.RejectPrice.Value)
1013 {
1014 Log.Notice("Rejected marketplace offer: Above rejection price threshold.", ItemReference, Contract.ContractId);
1015 return false;
1016 }
1017 break;
1018 }
1019 }
1020
1021 if (!(Item.ScoreFunction is null))
1022 {
1023 try
1024 {
1025 if (Item.ScoreExpression is null)
1026 {
1027 Expression Exp = new Expression(Item.ScoreFunction);
1028
1029 if (!XmppServer.CheckExpressionSafe(Exp, true, true, false, out ScriptNode Prohibited))
1030 throw new UnauthorizedAccessException("Expression not permitted: " + Prohibited?.SubExpression);
1031
1032 Item.ScoreExpression = Exp;
1033 }
1034
1035 object Result = await Item.ScoreExpression.EvaluateAsync(v);
1036 Score = Expression.ToDecimal(Result);
1037 }
1038 catch (Exception ex)
1039 {
1040 Log.Notice("Rejected marketplace offer: Scoring error: " + ex.Message, ItemReference, Contract.ContractId);
1041 return false;
1042 }
1043
1044 if (Item.RejectScore.HasValue)
1045 {
1046 switch (Item.Direction)
1047 {
1048 case OptimizeDirection.Up:
1049 if (Score.Value < Item.RejectScore.Value)
1050 {
1051 Log.Notice("Rejected marketplace offer: Below rejection score threshold.", ItemReference, Contract.ContractId);
1052 return false;
1053 }
1054 break;
1055
1056 case OptimizeDirection.Down:
1057 if (Score.Value > Item.RejectScore.Value)
1058 {
1059 Log.Notice("Rejected marketplace offer: Above rejection score threshold.", ItemReference, Contract.ContractId);
1060 return false;
1061 }
1062 break;
1063 }
1064 }
1065
1066 if (Item.AcceptScore.HasValue)
1067 {
1068 switch (Item.Direction)
1069 {
1070 case OptimizeDirection.Up:
1071 if (Score.Value >= Item.AcceptScore.Value)
1072 {
1073 Accept = NewBest = true;
1074 Item.Processed = DateTime.UtcNow;
1075 }
1076 break;
1077
1078 case OptimizeDirection.Down:
1079 if (Score.Value <= Item.AcceptScore.Value)
1080 {
1081 Accept = NewBest = true;
1082 Item.Processed = DateTime.UtcNow;
1083 }
1084 break;
1085 }
1086 }
1087 }
1088
1089 if (Item.AcceptPrice.HasValue && !Accept)
1090 {
1091 switch (Item.Direction)
1092 {
1093 case OptimizeDirection.Up:
1094 if (Price.Value >= Item.AcceptPrice.Value)
1095 {
1096 Accept = NewBest = true;
1097 Item.Processed = DateTime.UtcNow;
1098 }
1099 break;
1100
1101 case OptimizeDirection.Down:
1102 if (Price.Value <= Item.AcceptPrice.Value)
1103 {
1104 Accept = NewBest = true;
1105 Item.Processed = DateTime.UtcNow;
1106 }
1107 break;
1108 }
1109 }
1110
1111 if (!NewBest)
1112 {
1113 if (Score.HasValue)
1114 {
1115 switch (Item.Direction)
1116 {
1117 case OptimizeDirection.Up:
1118 if (!Item.BestBidScore.HasValue || Score.Value > Item.BestBidScore.Value)
1119 NewBest = true;
1120 break;
1121
1122 case OptimizeDirection.Down:
1123 if (!Item.BestBidScore.HasValue || Score.Value < Item.BestBidScore.Value)
1124 NewBest = true;
1125 break;
1126 }
1127 }
1128 else
1129 {
1130 switch (Item.Direction)
1131 {
1132 case OptimizeDirection.Up:
1133 if (!Item.BestBidPrice.HasValue || Price.Value > Item.BestBidPrice.Value)
1134 NewBest = true;
1135 break;
1136
1137 case OptimizeDirection.Down:
1138 if (!Item.BestBidPrice.HasValue || Price.Value < Item.BestBidPrice.Value)
1139 NewBest = true;
1140 break;
1141 }
1142 }
1143 }
1144
1145 if (NewBest)
1146 {
1147 Item.BestBidContractId = Contract.ContractId;
1148 Item.BestBidLegalId = PaymentLegalId;
1149 Item.BestBidJid = PaymentJid;
1150 Item.BestBidPrice = Price;
1151 Item.BestBidScore = Score;
1152 }
1153 }
1154
1155 if (await SignContract(Contract, ContractIsLocked, Legal, PubSub, Item.Node, AuctioneerRole) is null)
1156 {
1157 if (Accept)
1158 Item.Processed = null;
1159
1160 if (NewBest)
1161 {
1162 Item.BestBidContractId = BestBidContractIdBak;
1163 Item.BestBidLegalId = BestBidLegalIdBak;
1164 Item.BestBidJid = BestBidLegalJidBak;
1165 Item.BestBidPrice = BestBidPriceBak;
1166 Item.BestBidScore = BestBidScoreBak;
1167 }
1168
1169 return false;
1170 }
1171
1172 if (!Accept && NewBest)
1173 await Database.Update(Item);
1174
1175 await Database.Insert(new AuctionOffer()
1176 {
1177 OfferContractId = Contract.ContractId,
1178 ContractId = ItemReference,
1179 OfferLegalId = PaymentLegalId,
1180 OfferJid = PaymentJid,
1181 Created = DateTime.UtcNow,
1182 Price = Price.Value,
1183 Currency = Currency,
1184 Score = Score ?? Price,
1185 Tags = Tags.ToArray()
1186 });
1187
1188 if (Accept)
1189 {
1190 if (await ResolvePayments(Item, EDaler, false)) // Will update Item in the database
1191 {
1192 lock (activeItems)
1193 {
1194 activeItems.Remove(Item.ContractId);
1195 }
1196
1197 Scheduler.Remove(Item.ScheduledExpiry);
1198 Item.ScheduledExpiry = DateTime.MinValue;
1199 }
1200 else
1201 {
1202 Item.Processed = null;
1203 Item.BestBidContractId = BestBidContractIdBak;
1204 Item.BestBidLegalId = BestBidLegalIdBak;
1205 Item.BestBidJid = BestBidLegalJidBak;
1206 Item.BestBidPrice = BestBidPriceBak;
1207 Item.BestBidScore = BestBidScoreBak;
1208
1209 await Database.Update(Item);
1210 }
1211 }
1212
1213 return true;
1214 }
1215
1216 private static async Task ItemExpires(object P)
1217 {
1218 ExpiryRecord Rec = (ExpiryRecord)P;
1219 AuctionItem Item = Rec.Item;
1220 if (Item.Processed.HasValue)
1221 return;
1222
1223 lock (activeItems)
1224 {
1225 activeItems.Remove(Item.ContractId);
1226 }
1227
1228 Item.Processed = DateTime.UtcNow;
1229
1230 if (!await ResolvePayments(Item, Rec.EDaler, true)) // Will update Item in the database
1231 Log.Error("Auctioned item expired, but payments offered were not possible to process.", Item.ContractId);
1232 }
1233
1234 private static async Task<bool> ResolvePayments(AuctionItem Item, EDalerComponent EDaler,
1235 bool BackTrackIfPaymentFails)
1236 {
1237 bool Ok = true;
1238
1239 if (Item.BestBidPrice.HasValue)
1240 {
1241 CaseInsensitiveString BidderId = Item.BestBidLegalId;
1242 CaseInsensitiveString BidderJid = Item.BestBidJid;
1243 CaseInsensitiveString BidId = Item.BestBidContractId;
1244 decimal Amount = Item.BestBidPrice.Value;
1245
1246 if (!(Ok = await ResolvePayments(BidderId, BidderJid, Amount, BidId, Item, EDaler)))
1247 {
1248 if (BackTrackIfPaymentFails)
1249 {
1250 string Order = Item.Direction == OptimizeDirection.Up ? "-Score" : "Score";
1251 IEnumerable<AuctionOffer> Bids = await Database.Find<AuctionOffer>(
1252 new FilterAnd(
1253 new FilterFieldEqualTo("ContractId", Item.ContractId),
1254 new FilterFieldNotEqualTo("OfferContractId", Item.BestBidContractId)),
1255 Order);
1256
1257 foreach (AuctionOffer Bid in Bids)
1258 {
1259 BidderId = Bid.OfferLegalId;
1260 BidderJid = Bid.OfferJid;
1261 BidId = Bid.OfferContractId;
1262 Amount = Bid.Price;
1263
1264 if (Ok = await ResolvePayments(BidderId, BidderJid, Amount, BidId, Item, EDaler))
1265 break;
1266 }
1267 }
1268 }
1269 }
1270
1271 await Database.Update(Item);
1272
1273 return Ok;
1274 }
1275
1276 private static async Task<bool> ResolvePayments(CaseInsensitiveString Bidder,
1277 CaseInsensitiveString BidderJid, decimal Amount, CaseInsensitiveString BidId,
1278 AuctionItem Item, EDalerComponent EDaler)
1279 {
1280 decimal Commission = Amount * Item.CommissionPercent * 0.01m;
1281 string Ref = "iotsc:" + Item.ContractId;
1282 Token Token;
1283 int i = Item.ContractId.IndexOf('@');
1284
1285 if (i <= 0 || !Guid.TryParse(Item.ContractId.Substring(0, i), out Guid TransactionId))
1286 TransactionId = Guid.NewGuid();
1287
1289 {
1290 string TokenId = Item.TokenId;
1291 if (string.IsNullOrEmpty(TokenId))
1292 return false;
1293
1294 Token = await NeuroFeaturesProcessor.GetToken(TokenId, true);
1295 if (Token is null)
1296 return false;
1297
1298 // TODO: Allow remote tokens in marketplace.
1299 }
1300 else
1301 Token = null;
1302
1303 switch (Item.Direction)
1304 {
1305 case OptimizeDirection.Up: // Creator of item sells item to generator of best offer
1306
1307 if (!(Token is null) && Item.InitiatorJid != Token.OwnerJid)
1308 return false;
1309
1310 Item.SalePaymentUri = PaiwiseProcessor.GenerateContractualPaymentUri(TransactionId, Bidder, true,
1311 Gateway.ContractsClient.Client.BareJID, false, Item.Currency, Amount, null, Ref,
1312 BidId, null, 1, out _, out _);
1313
1314 Item.CommissionPaymentUri = PaiwiseProcessor.GenerateContractualPaymentUri(Guid.NewGuid(),
1315 Gateway.ContractsClient.Client.BareJID, false, Item.InitiatorLegalId, true, Item.Currency,
1316 Amount - Commission, null, Ref, Item.ContractId, null, 1, out _, out _);
1317
1318 break;
1319
1320 case OptimizeDirection.Down: // Generator of best offer sells item to generator of item
1321
1322 if (!(Token is null) && BidderJid != Token.OwnerJid)
1323 return false;
1324
1325 Item.SalePaymentUri = PaiwiseProcessor.GenerateContractualPaymentUri(TransactionId, Item.InitiatorLegalId, true,
1326 Gateway.ContractsClient.Client.BareJID, false, Item.Currency, Amount + Commission,
1327 null, Ref, Item.ContractId, null, 1, out _, out _);
1328
1329 Item.CommissionPaymentUri = PaiwiseProcessor.GenerateContractualPaymentUri(Guid.NewGuid(),
1330 Gateway.ContractsClient.Client.BareJID, false, Bidder, true, Item.Currency, Amount, null,
1331 Ref, Item.ContractId, null, 1, out _, out _);
1332
1333 break;
1334
1335 default:
1336 return false;
1337 }
1338
1339 if (!string.IsNullOrEmpty(await PaiwiseProcessor.ProcessPayment(Item.SalePaymentUri, EDaler)))
1340 {
1341 Log.Error("Unable to process payment for consigned auction item.",
1342 Item.ContractId, string.Empty, "AuctionPayment",
1343 new KeyValuePair<string, object>("Bidder", Bidder),
1344 new KeyValuePair<string, object>("BidId", BidId),
1345 new KeyValuePair<string, object>("Amount", Amount),
1346 new KeyValuePair<string, object>("Currency", Item.Currency));
1347
1348 return false;
1349 }
1350
1351 if (Commission > 0)
1352 {
1353 string Msg = await PaiwiseProcessor.ProcessPayment(Item.CommissionPaymentUri, EDaler); // Funds covered by first transaction
1354 if (!string.IsNullOrEmpty(Msg))
1355 {
1356 Log.Error("Auction payment error: Sales payment went through to Auctioneer, but secondary payment failed: " + Msg,
1357 Item.ContractId, string.Empty, "AuctionPayment",
1358 new KeyValuePair<string, object>("Bidder", Bidder),
1359 new KeyValuePair<string, object>("BidId", BidId),
1360 new KeyValuePair<string, object>("Amount", Amount),
1361 new KeyValuePair<string, object>("Currency", Item.Currency),
1362 new KeyValuePair<string, object>("URI", Item.CommissionPaymentUri));
1363 }
1364 }
1365
1366 if (!(Token is null))
1367 {
1368 Transferred TransferToAuctioneer;
1369 Transferred TransferToBuyer;
1370 string SellerJid;
1371 string BuyerJid;
1372
1373 switch (Item.Direction)
1374 {
1375 case OptimizeDirection.Up: // Creator of item sells item to generator of best offer
1376
1377 SellerJid = Item.InitiatorJid;
1378 BuyerJid = BidderJid;
1379
1380 Token.Value = Amount;
1381 Token.Currency = Item.Currency;
1382 Token.Owner = Bidder;
1383 Token.OwnerJid = BidderJid;
1384 Token.OwnershipContract = BidId;
1385 await Token.Sign();
1386
1387 TransferToAuctioneer = new Transferred()
1388 {
1389 ArchiveOptional = Token.ArchiveOptional,
1390 ArchiveRequired = Token.ArchiveRequired,
1391 Expires = Token.Expires,
1392 Seller = Item.InitiatorLegalId,
1393 Owner = Item.TrustProviderLegalId,
1394 OwnershipContract = Item.ContractId,
1395 Value = Amount,
1396 Commission = 0,
1397 Currency = Item.Currency,
1398 TokenId = Token.TokenId,
1399 Timestamp = DateTime.UtcNow.AddSeconds(-1),
1400 Personal = false
1401 };
1402
1403 TransferToBuyer = new Transferred()
1404 {
1405 ArchiveOptional = Token.ArchiveOptional,
1406 ArchiveRequired = Token.ArchiveRequired,
1407 Expires = Token.Expires,
1408 Seller = Item.TrustProviderLegalId,
1409 Owner = Bidder,
1410 OwnershipContract = BidId,
1411 Value = Amount,
1412 Commission = Commission,
1413 Currency = Item.Currency,
1414 TokenId = Token.TokenId,
1415 Timestamp = DateTime.UtcNow,
1416 Personal = false
1417 };
1418 break;
1419
1420 case OptimizeDirection.Down: // Generator of best offer sells item to generator of item
1421
1422 SellerJid = BidderJid;
1423 BuyerJid = Item.InitiatorJid;
1424
1425 Token.Value = Amount;
1426 Token.Currency = Item.Currency;
1427 Token.Owner = Item.InitiatorLegalId;
1428 Token.OwnerJid = Item.InitiatorJid;
1429 Token.OwnershipContract = Item.ContractId;
1430 await Token.Sign();
1431
1432 TransferToAuctioneer = new Transferred()
1433 {
1434 ArchiveOptional = Token.ArchiveOptional,
1435 ArchiveRequired = Token.ArchiveRequired,
1436 Expires = Token.Expires,
1437 Seller = Bidder,
1438 Owner = Item.TrustProviderLegalId,
1439 OwnershipContract = BidId,
1440 Value = Amount,
1441 Commission = 0,
1442 Currency = Item.Currency,
1443 TokenId = Token.TokenId,
1444 Timestamp = DateTime.UtcNow.AddSeconds(-1),
1445 Personal = false
1446 };
1447
1448 TransferToBuyer = new Transferred()
1449 {
1450 ArchiveOptional = Token.ArchiveOptional,
1451 ArchiveRequired = Token.ArchiveRequired,
1452 Expires = Token.Expires,
1453 Seller = Item.TrustProviderLegalId,
1454 Owner = Item.InitiatorLegalId,
1455 OwnershipContract = Item.ContractId,
1456 Value = Amount,
1457 Commission = Commission,
1458 Currency = Item.Currency,
1459 TokenId = Token.TokenId,
1460 Timestamp = DateTime.UtcNow,
1461 Personal = false
1462 };
1463
1464 break;
1465
1466 default:
1467 return false;
1468 }
1469
1470 await Database.StartBulk();
1471 try
1472 {
1473 await NeuroFeaturesProcessor.DeletePersonalEvents(new string[] { Token.TokenId });
1474 await Database.Update(Token);
1475
1476 try
1477 {
1478 await Database.Insert(TransferToAuctioneer, TransferToBuyer);
1479 }
1480 catch (Exception ex)
1481 {
1482 Log.Exception(ex);
1483 }
1484
1485 // TODO: Protect integrity with transaction, commit, rollback, etc.
1486 }
1487 finally
1488 {
1489 await Database.EndBulk();
1490 }
1491
1492 try
1493 {
1494 await StateMachineProcessor.EventGenerated(Token, TransferToAuctioneer);
1495 }
1496 catch (Exception ex)
1497 {
1499 }
1500
1501 try
1502 {
1503 await StateMachineProcessor.EventGenerated(Token, TransferToBuyer);
1504 }
1505 catch (Exception ex)
1506 {
1508 }
1509
1510 StringBuilder Xml = new StringBuilder();
1511
1512 Xml.Append("<tokenRemoved xmlns='");
1514 Xml.Append("'>");
1515 await Token.Serialize(Xml, false, true);
1516 Xml.Append("</tokenRemoved>");
1517
1518 if (await NeuroFeaturesProcessor.SendTokenMessage(EDaler, Xml.ToString(), SellerJid, true, true))
1519 await NeuroFeaturesProcessor.TokenRemoved(Token, SellerJid);
1520
1521 Xml.Clear();
1522
1523 Xml.Append("<tokenAdded xmlns='");
1525 Xml.Append("'>");
1526 await Token.Serialize(Xml, false, true);
1527 Xml.Append("</tokenAdded>");
1528
1529 if (await NeuroFeaturesProcessor.SendTokenMessage(EDaler, Xml.ToString(), BuyerJid, true, true))
1530 await NeuroFeaturesProcessor.TokenAdded(Token, BuyerJid);
1531 }
1532
1533 return true;
1534 }
1535
1536 }
1537}
Helps with common XML-related tasks.
Definition: XML.cs:21
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:1062
static XmlException AnnotateException(XmlException ex)
Creates a new XML Exception object, with reference to the source XML file, for information.
Definition: XML.cs:1762
static XmlDocument ParseXml(string Xml)
Parses an XML Document from its string representation.
Definition: XML.cs:713
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
static void Error(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an error event.
Definition: Log.cs:692
static void Notice(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a notice event.
Definition: Log.cs:460
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:147
static ContractsClient ContractsClient
XMPP Contracts Client, if such a compoent is available on the XMPP broker.
Definition: Gateway.cs:5299
Contains the definition of a contract
Definition: Contract.cs:22
static Task< ParsedContract > Parse(XmlDocument Xml)
Validates a contract XML Document, and returns the contract definition in it.
Definition: Contract.cs:441
ClientSignature[] ClientSignatures
Client signatures of the contract.
Definition: Contract.cs:309
void Serialize(StringBuilder Xml, bool IncludeNamespace, bool IncludeIdAttribute, bool IncludeClientSignatures, bool IncludeAttachments, bool IncludeStatus, bool IncludeServerSignature, bool IncludeAttachmentReferences)
Serializes the Contract, in normalized form.
Definition: Contract.cs:1621
string DefaultLanguage
Default language for contract.
Definition: Contract.cs:1992
Contains information about a parsed contract.
CaseInsensitiveString Subdomain
Subdomain name.
Definition: Component.cs:77
XmppAddress MainDomain
Main/principal domain address
Definition: Component.cs:87
bool IsComponentDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the component domain, or optionally, an alternative component domain.
Definition: Component.cs:124
Contains information about one XMPP address.
Definition: XmppAddress.cs:9
CaseInsensitiveString Address
XMPP Address
Definition: XmppAddress.cs:37
static bool CheckExpressionSafe(Expression Expression, out ScriptNode Prohibited)
Checks if an expression is safe to execute (if it comes from an external source).
Definition: XmppServer.cs:7184
Represents a case-insensitive string.
string Value
String-representation of the case-insensitive string. (Representation is case sensitive....
static readonly CaseInsensitiveString Empty
Empty case-insensitive string
string LowerCase
Lower-case representation of the case-insensitive string.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static Task EndBulk()
Ends bulk-processing of data. Must be called once for every call to StartBulk.
Definition: Database.cs:2259
static Task StartBulk()
Starts bulk-proccessing of data. Must be followed by a call to EndBulk.
Definition: Database.cs:2251
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:1211
static async Task Delete(object Object)
Deletes an object in the database.
Definition: Database.cs:1291
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:238
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:97
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
This filter selects objects that have a named field equal to a given value.
This filter selects objects that have a named field not equal to a given value.
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
static bool TryGetModuleParameter(string Name, out object Value)
Tries to get a module parameter value.
Definition: Types.cs:607
Static class managing persistent settings.
static async Task< string > GetAsync(string Key, string DefaultValue)
Gets a string-valued setting.
Represents a named semaphore, i.e. an object, identified by a name, that allows single concurrent wri...
Definition: Semaphore.cs:19
async Task DisposeAsync()
Disposes of the object, asynchronously.
Definition: Semaphore.cs:199
Static class of application-wide semaphores that can be used to order access to editable objects.
Definition: Semaphores.cs:17
static async Task< Semaphore > BeginWrite(string Key)
Waits until the semaphore identified by Key is ready for writing. Each call to BeginWrite must be fo...
Definition: Semaphores.cs:91
Class that can be used to schedule events in time. It uses a timer to execute tasks at the appointed ...
Definition: Scheduler.cs:14
bool Remove(DateTime When)
Removes an event scheduled for a given point in time.
Definition: Scheduler.cs:186
void Dispose()
IDisposable.Dispose
Definition: Scheduler.cs:34
DateTime Add(DateTime When, Action< object > Callback, object State)
Adds an event.
Definition: Scheduler.cs:54
Class managing a script expression.
Definition: Expression.cs:41
async Task< object > EvaluateAsync(Variables Variables)
Evaluates the expression, using the variables provided in the Variables collection....
Definition: Expression.cs:4461
static decimal ToDecimal(object Object)
Converts an object to a double value.
Definition: Expression.cs:5169
Base class for all nodes in a parsed script tree.
Definition: ScriptNode.cs:69
Collection of variables.
Definition: Variables.cs:25
virtual bool ContainsVariable(string Name)
If the collection contains a variable with a given name.
Definition: Variables.cs:80
Manages eDaler on accounts connected to the broker.
Marketplace processor, brokering sales of items via tenders and offers defined in smart contracts.
const string MarketplaceNamespace
https://paiwise.tagroot.io/Schema/Marketplace.xsd
Event raised when a token has been created.
Definition: Created.cs:10
Event raised when a token has been transferred.
Definition: Transferred.cs:11
Marketplace processor, brokering sales of items via tenders and offers defined in smart contracts.
const string NeuroFeaturesNamespace
https://paiwise.tagroot.io/Schema/NeuroFeatures.xsd
async Task Serialize(StringBuilder Xml, bool IncludeNamespace, bool IncludeServerSignature)
Serializes the Token, in normalized form.
Definition: Token.cs:652
Duration? ArchiveOptional
Duration after which token expires, and the required archiving time, the token can optionally be arch...
Definition: Token.cs:422
CaseInsensitiveString OwnerJid
JID of Current owner of token
Definition: Token.cs:216
Duration? ArchiveRequired
Duration after which token expires, the token is required to be archived.
Definition: Token.cs:412
DateTime Expires
Expiry date of token.
Definition: Token.cs:402
CaseInsensitiveString TokenId
Token ID
Definition: Token.cs:145
Paiwise processor, processing payment instructions defined in smart contracts.
PubSub component, as defined in XEP-0060. https://xmpp.org/extensions/xep-0060.html
Task< string > PublishItem(string Service, string NodeName, string AutoCreateAccess, string From, string Domain, string ItemId, string XmlContent, string Language)
Call this method for automated publishing of pubsub items from hosted services.
Definition: ImplTypes.g.cs:58
OptimizeDirection
Direction in which to optimize prices and/or scores.
Definition: AuctionItem.cs:12
NodeAccessModel
Node access model.
Definition: Enumerations.cs:9