Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
WalletModel.cs
1using EDaler;
2using EDaler.Events;
3using EDaler.Uris;
10using NeuroFeatures;
12using System;
13using System.Collections.Generic;
14using System.Diagnostics;
15using System.Threading.Tasks;
16using System.Windows.Controls;
17using System.Windows.Input;
23
25{
29 [Singleton]
30 public class WalletModel : PersistedModel, IDisposable
31 {
32 private readonly Property<decimal> amount;
33 private readonly Property<decimal> reserved;
34 private readonly Property<decimal> pending;
35 private readonly Property<decimal> available;
36 private readonly Property<string> currency;
37 private readonly Property<DateTime> timestamp;
38 private readonly Property<string> uri;
39
40 private readonly List<AccountEventWrapper> events = new();
41
42 private readonly Command sendUri;
43 private readonly Command transferEDaler;
44 private readonly Command buyEDaler;
45 private readonly Command sellEDaler;
46
47 private readonly ContractsClient contractsClient;
48 private readonly EDalerClient eDalerClient;
49 private readonly NetworkModel networkModel;
50 private Balance balance = null;
51 private AccountEventWrapper selectedItem = null;
52 private string optionsTransactionId = null;
53 private string optionsContractId = null;
54
62 public WalletModel(XmppClient Client, ContractsClient Contracts, string ComponentJid, NetworkModel Network)
63 : base()
64 {
65 this.amount = new Property<decimal>(nameof(this.Amount), 0, this);
66 this.pending = new Property<decimal>(nameof(this.Pending), 0, this);
67 this.available = new Property<decimal>(nameof(this.Available), 0, this);
68 this.reserved = new Property<decimal>(nameof(this.Reserved), 0, this);
69 this.currency = new Property<string>(nameof(this.Currency), string.Empty, this);
70 this.timestamp = new Property<DateTime>(nameof(this.Timestamp), DateTime.MinValue, this);
71 this.uri = new Property<string>(nameof(this.Uri), string.Empty, this);
72
73 this.sendUri = new Command(this.CanExecuteSendUri, this.ExecuteSendUri);
74 this.transferEDaler = new Command(this.CanTransferEDaler, this.ExecuteTransferEDaler);
75 this.buyEDaler = new Command(this.CanBuyEDaler, this.ExecuteBuyEDaler);
76 this.sellEDaler = new Command(this.CanSellEDaler, this.ExecuteSellEDaler);
77
78 this.contractsClient = Contracts;
79 this.networkModel = Network;
80
81 this.eDalerClient = new EDalerClient(Client, Contracts, ComponentJid);
82 this.eDalerClient.BalanceUpdated += this.EDalerClient_BalanceUpdated;
83 this.eDalerClient.BuyEDalerClientUrlReceived += this.EDalerClient_BuyEDalerClientUrlReceived;
84 this.eDalerClient.BuyEDalerCompleted += this.EDalerClient_BuyEDalerCompleted;
85 this.eDalerClient.BuyEDalerError += this.EDalerClient_BuyEDalerError;
86 this.eDalerClient.BuyEDalerOptionsClientUrlReceived += this.EDalerClient_BuyEDalerOptionsClientUrlReceived;
87 this.eDalerClient.BuyEDalerOptionsCompleted += this.EDalerClient_BuyEDalerOptionsCompleted;
88 this.eDalerClient.BuyEDalerOptionsError += this.EDalerClient_BuyEDalerOptionsError;
89 this.eDalerClient.SellEDalerClientUrlReceived += this.EDalerClient_SellEDalerClientUrlReceived;
90 this.eDalerClient.SellEDalerCompleted += this.EDalerClient_SellEDalerCompleted;
91 this.eDalerClient.SellEDalerError += this.EDalerClient_SellEDalerError;
92 this.eDalerClient.SellEDalerOptionsClientUrlReceived += this.EDalerClient_SellEDalerOptionsClientUrlReceived;
93 this.eDalerClient.SellEDalerOptionsCompleted += this.EDalerClient_SellEDalerOptionsCompleted;
94 this.eDalerClient.SellEDalerOptionsError += this.EDalerClient_SellEDalerOptionsError;
95 }
96
97 private async Task EDalerClient_BalanceUpdated(object Sender, BalanceEventArgs e)
98 {
99 await this.SetBalance(e.Balance);
100
101 AccountEventWrapper Item = new(e.Balance.Event);
102 Item.Selected += this.Item_Selected;
103 Item.Deselected += this.Item_Deselected;
104
105 lock (this.events)
106 {
107 this.events.Insert(0, Item);
108 }
109
110 this.RaisePropertyChanged(nameof(this.Events));
111 }
112
113 private void Item_Deselected(object sender, EventArgs e)
114 {
115 if (this.selectedItem == sender)
116 {
117 this.selectedItem = null;
118 this.RaisePropertyChanged(nameof(this.SelectedItem));
119 }
120 }
121
122 private void Item_Selected(object sender, EventArgs e)
123 {
124 this.selectedItem = sender as AccountEventWrapper;
125 this.RaisePropertyChanged(nameof(this.SelectedItem));
126 }
127
129 public void Dispose()
130 {
131 this.eDalerClient.Dispose();
132 }
133
137 public EDalerClient EDaler => this.eDalerClient;
138
142 public decimal Amount
143 {
144 get => this.amount.Value;
145 set => this.amount.Value = value;
146 }
147
151 public decimal Reserved
152 {
153 get => this.reserved.Value;
154 set => this.reserved.Value = value;
155 }
156
160 public decimal Pending
161 {
162 get => this.pending.Value;
163 set => this.pending.Value = value;
164 }
165
169 public decimal Available
170 {
171 get => this.available.Value;
172 set => this.available.Value = value;
173 }
174
178 public string Currency
179 {
180 get => this.currency.Value;
181 set => this.currency.Value = value;
182 }
183
187 public DateTime Timestamp
188 {
189 get => this.timestamp.Value;
190 set => this.timestamp.Value = value;
191 }
192
196 public string Uri
197 {
198 get => this.uri.Value;
199 set
200 {
201 this.uri.Value = value;
202 this.sendUri.RaiseCanExecuteChanged();
203 }
204 }
205
209 public Balance Balance => this.balance;
210
216 {
217 this.balance = Balance;
218 return this.UpdateAmounts();
219 }
220
221 private async Task UpdateAmounts()
222 {
223 this.Amount = this.balance.Amount;
224 this.Reserved = this.balance.Reserved;
225 this.Currency = this.balance.Currency;
226 this.Timestamp = this.balance.Timestamp;
227
228 (decimal Pending, _, _) = await this.eDalerClient.GetPendingPayments();
229
230 this.Pending = Pending;
231 this.Available = this.Amount - this.Reserved - this.Pending;
232 }
233
237 public IEnumerable<AccountEventWrapper> Events
238 {
239 get
240 {
241 lock (this.events)
242 {
243 return this.events.ToArray();
244 }
245 }
246 }
247
252 {
253 get => this.selectedItem;
254 set => this.selectedItem = value;
255 }
256
260 public ICommand SendUri => this.sendUri;
261
262 private bool CanExecuteSendUri()
263 {
264 return this.eDalerClient.Client.State == XmppState.Connected && !string.IsNullOrEmpty(this.Uri);
265 }
266
267 private async Task ExecuteSendUri()
268 {
269 try
270 {
271 if (!EDalerUri.TryParse(this.Uri, out EDalerUri Uri))
272 throw new Exception("Invalid eDaler® URI.");
273
274 if (Uri is EDalerIncompletePaymentUri IncompleteUri)
275 {
276 this.Uri = await this.eDalerClient.CreateFullPaymentUri(
277 IncompleteUri.To,
278 IncompleteUri.Amount,
279 IncompleteUri.AmountExtra,
280 IncompleteUri.Currency,
281 (int)IncompleteUri.Expires.Subtract(DateTime.Today.AddDays(-1)).TotalDays);
282 }
283
284 await this.eDalerClient.SendEDalerUriAsync(this.Uri);
285 this.Uri = string.Empty;
286 }
287 catch (Exception ex)
288 {
289 MainWindow.ErrorBox(ex.Message);
290 }
291 }
292
296 public ICommand TransferEDaler => this.transferEDaler;
297
298 private bool CanTransferEDaler()
299 {
300 return this.eDalerClient.Client.State == XmppState.Connected;
301 }
302
303 private async Task ExecuteTransferEDaler()
304 {
305 try
306 {
308
309 CreationAttributesEventArgs DefaultArgs = await this.networkModel.Tokens.NeuroFeaturesClient.GetCreationAttributesAsync();
310
312
313 TransferEDalerDialog Dialog = new();
314 TransferEDalerModel Model = new(Dialog, string.IsNullOrEmpty(this.currency.Value) ? DefaultArgs.Currency : this.currency.Value);
315
316 bool? Result = Dialog.ShowDialog();
317 if (!Result.HasValue || !Result.Value)
318 return;
319
320 this.Uri = await this.eDalerClient.CreateFullPaymentUri(Model.Recipient, Model.Amount,
321 Model.AmountExtra > 0 ? Model.AmountExtra : (decimal?)null, Model.Currency, Model.ValidNrDays, Model.Message);
322
323 await this.UpdateAmounts();
324 }
325 catch (Exception ex)
326 {
327 MainWindow.ErrorBox(ex.Message);
328 }
329 }
330
334 public ICommand BuyEDaler => this.buyEDaler;
335
336 private bool CanBuyEDaler()
337 {
338 return this.eDalerClient.Client.State == XmppState.Connected;
339 }
340
341 private async Task ExecuteBuyEDaler()
342 {
343 try
344 {
346
347 IBuyEDalerServiceProvider[] Providers = await this.eDalerClient.GetServiceProvidersForBuyingEDalerAsync();
348 if (Providers.Length == 0)
349 throw new Exception("No providers available for buying eDaler®.");
350
351 CreationAttributesEventArgs DefaultArgs = await this.networkModel.Tokens.NeuroFeaturesClient.GetCreationAttributesAsync();
352
354
355 BuyEDalerDialog Dialog = new();
356 BuyEDalerModel Model = new(Dialog, Providers, string.IsNullOrEmpty(this.currency.Value) ? DefaultArgs.Currency : this.currency.Value);
357
358 bool? Result = Dialog.ShowDialog();
359 if (!Result.HasValue || !Result.Value)
360 return;
361
362 if (Dialog.ServiceProvider.SelectedItem is not ServiceProviderModel ServiceProviderModel ||
364 {
365 throw new Exception("Cannot buy eDaler® using that service provider.");
366 }
367
368 if (string.IsNullOrEmpty(ServiceProvider.BuyEDalerTemplateContractId))
369 {
370 await this.eDalerClient.InitiateBuyEDalerAsync(ServiceProvider.Id, ServiceProvider.Type,
371 Model.Amount, Model.Currency);
372
373 // Server will ask client to open web URL via event.
374 }
375 else
376 {
378
379 string TemplateName = "Buy eDaler® using " + ServiceProvider.Name;
380 string Key = "Contract.Template." + TemplateName;
381 string StoredId = await RuntimeSettings.GetAsync(Key, string.Empty);
382
383 if (StoredId != ServiceProvider.BuyEDalerTemplateContractId)
384 {
385 Contract Contract = await this.contractsClient.GetContractAsync(ServiceProvider.BuyEDalerTemplateContractId);
387 throw new Exception("Contract referenced by service provider is not a template.");
388
390 this.networkModel.Legal.ContractTemplateAdded(TemplateName, Contract);
391 }
392
393 Dictionary<CaseInsensitiveString, object> PresetValues = new()
394 {
395 { "Amount", Model.Amount },
396 { "Currency", Model.Currency }
397 };
398
399 await this.networkModel.Legal.SetContractTemplateName(TemplateName, PresetValues);
400
401 foreach (TabItem Item in MainWindow.currentInstance.TabControl.Items)
402 {
403 if (Item.Content == MainWindow.currentInstance.ContractsTab)
404 {
405 MainWindow.currentInstance.TabControl.SelectedItem = Item;
406 break;
407 }
408 }
409
411
412 this.optionsTransactionId = Guid.NewGuid().ToString();
413 this.optionsContractId = ServiceProvider.BuyEDalerTemplateContractId;
414
415 await this.eDalerClient.InitiateGetOptionsBuyEDalerAsync(ServiceProvider.Id, ServiceProvider.Type,
416 this.optionsTransactionId, null, null, null);
417 }
418 }
419 catch (Exception ex)
420 {
421 MainWindow.ErrorBox(ex.Message);
422 }
423 }
424
425 private Task EDalerClient_BuyEDalerClientUrlReceived(object Sender, BuyEDalerClientUrlEventArgs e)
426 {
427 OpenUrl(e.ClientUrl);
428 return Task.CompletedTask;
429 }
430
431 private static void OpenUrl(string Url)
432 {
433 ProcessStartInfo StartInfo = new()
434 {
435 FileName = Url,
436 UseShellExecute = true
437 };
438
439 Process.Start(StartInfo);
440 }
441
442 private Task EDalerClient_BuyEDalerCompleted(object Sender, PaymentCompletedEventArgs e)
443 {
444 MainWindow.SuccessBox("Successfully bought " + MoneyToString.ToString(e.Amount) + " " + e.Currency + " eDaler®.");
445 return Task.CompletedTask;
446 }
447
448 private Task EDalerClient_BuyEDalerError(object Sender, PaymentErrorEventArgs e)
449 {
450 MainWindow.ErrorBox("Unable to buy eDaler®: " + e.Message);
451 return Task.CompletedTask;
452 }
453
454 private Task EDalerClient_BuyEDalerOptionsClientUrlReceived(object Sender, BuyEDalerClientUrlEventArgs e)
455 {
456 OpenUrl(e.ClientUrl);
457 return Task.CompletedTask;
458 }
459
460 private Task EDalerClient_BuyEDalerOptionsCompleted(object Sender, PaymentOptionsEventArgs e)
461 {
462 this.ContractOptionsReceived(e.TransactionId, e.Options);
463 return Task.CompletedTask;
464 }
465
466 private Task EDalerClient_BuyEDalerOptionsError(object Sender, PaymentErrorEventArgs e)
467 {
468 MainWindow.ErrorBox("Unable to get payment options for buying eDaler®: " + e.Message);
469 return Task.CompletedTask;
470 }
471
475 public ICommand SellEDaler => this.sellEDaler;
476
477 private bool CanSellEDaler()
478 {
479 return this.eDalerClient.Client.State == XmppState.Connected;
480 }
481
482 private async Task ExecuteSellEDaler()
483 {
484 try
485 {
487
488 ISellEDalerServiceProvider[] Providers = await this.eDalerClient.GetServiceProvidersForSellingEDalerAsync();
489 if (Providers.Length == 0)
490 throw new Exception("No providers available for selling eDaler®.");
491
492 CreationAttributesEventArgs DefaultArgs = await this.networkModel.Tokens.NeuroFeaturesClient.GetCreationAttributesAsync();
493
495
496 SellEDalerDialog Dialog = new();
497 SellEDalerModel Model = new(Dialog, Providers, string.IsNullOrEmpty(this.currency.Value) ? DefaultArgs.Currency : this.currency.Value);
498
499 bool? Result = Dialog.ShowDialog();
500 if (!Result.HasValue || !Result.Value)
501 return;
502
503 if (Dialog.ServiceProvider.SelectedItem is not ServiceProviderModel ServiceProviderModel ||
505 {
506 throw new Exception("Cannot sell eDaler® using that service provider.");
507 }
508
509 if (string.IsNullOrEmpty(ServiceProvider.SellEDalerTemplateContractId))
510 {
511 await this.eDalerClient.InitiateSellEDalerAsync(ServiceProvider.Id, ServiceProvider.Type,
512 Model.Amount, Model.Currency);
513
514 // Server will ask client to open web URL via event.
515 }
516 else
517 {
519
520 string TemplateName = "Sell eDaler® using " + ServiceProvider.Name;
521 string Key = "Contract.Template." + TemplateName;
522 string StoredId = await RuntimeSettings.GetAsync(Key, string.Empty);
523
524 if (StoredId != ServiceProvider.SellEDalerTemplateContractId)
525 {
526 Contract Contract = await this.contractsClient.GetContractAsync(ServiceProvider.SellEDalerTemplateContractId);
528 throw new Exception("Contract referenced by service provider is not a template.");
529
531 this.networkModel.Legal.ContractTemplateAdded(TemplateName, Contract);
532 }
533
534 Dictionary<CaseInsensitiveString, object> PresetValues = new()
535 {
536 { "Amount", Model.Amount },
537 { "Currency", Model.Currency }
538 };
539
540 await this.networkModel.Legal.SetContractTemplateName(TemplateName, PresetValues);
541
542 foreach (TabItem Item in MainWindow.currentInstance.TabControl.Items)
543 {
544 if (Item.Content == MainWindow.currentInstance.ContractsTab)
545 {
546 MainWindow.currentInstance.TabControl.SelectedItem = Item;
547 break;
548 }
549 }
550
552
553 this.optionsTransactionId = Guid.NewGuid().ToString();
554 this.optionsContractId = ServiceProvider.SellEDalerTemplateContractId;
555
556 await this.eDalerClient.InitiateGetOptionsSellEDalerAsync(ServiceProvider.Id, ServiceProvider.Type,
557 this.optionsTransactionId, null, null, null);
558 }
559 }
560 catch (Exception ex)
561 {
562 MainWindow.ErrorBox(ex.Message);
563 }
564 }
565
566 private Task EDalerClient_SellEDalerClientUrlReceived(object Sender, SellEDalerClientUrlEventArgs e)
567 {
568 OpenUrl(e.ClientUrl);
569 return Task.CompletedTask;
570 }
571
572 private Task EDalerClient_SellEDalerCompleted(object Sender, PaymentCompletedEventArgs e)
573 {
574 MainWindow.SuccessBox("Successfully sold " + MoneyToString.ToString(e.Amount) + " " + e.Currency + " eDaler®.");
575 return Task.CompletedTask;
576 }
577
578 private Task EDalerClient_SellEDalerError(object Sender, PaymentErrorEventArgs e)
579 {
580 MainWindow.ErrorBox("Unable to sell eDaler®: " + e.Message);
581 return Task.CompletedTask;
582 }
583
584 private Task EDalerClient_SellEDalerOptionsClientUrlReceived(object Sender, SellEDalerClientUrlEventArgs e)
585 {
586 OpenUrl(e.ClientUrl);
587 return Task.CompletedTask;
588 }
589
590 private Task EDalerClient_SellEDalerOptionsCompleted(object Sender, PaymentOptionsEventArgs e)
591 {
592 this.ContractOptionsReceived(e.TransactionId, e.Options);
593 return Task.CompletedTask;
594 }
595
596 private Task EDalerClient_SellEDalerOptionsError(object Sender, PaymentErrorEventArgs e)
597 {
598 MainWindow.ErrorBox("Unable to get payment options for selling eDaler®: " + e.Message);
599 return Task.CompletedTask;
600 }
601
603 public override async Task Start()
604 {
606 {
607 MainWindow.currentInstance.WalletTab.DataContext = this;
608 return Task.CompletedTask;
609 });
610
611 await this.SetBalance(await this.eDalerClient.GetBalanceAsync());
612
613 (AccountEvent[] Events, bool More) = await this.eDalerClient.GetAccountEventsAsync(50);
614
615 lock (this.events)
616 {
617 foreach (AccountEvent Event in Events)
618 {
619 AccountEventWrapper Item = new(Event);
620 Item.Selected += this.Item_Selected;
621 Item.Deselected += this.Item_Deselected;
622
623 this.events.Add(Item);
624 }
625 }
626
627 this.RaisePropertyChanged(nameof(this.Events));
628
629 await base.Start();
630 }
631
632 private void ContractOptionsReceived(string TransactionId, IDictionary<CaseInsensitiveString, object>[] Options)
633 {
634 if (this.optionsTransactionId != TransactionId ||
635 this.networkModel.Legal.CurrentContract.ContractId != this.optionsContractId)
636 {
637 return;
638 }
639
640 MainWindow.UpdateGui(async () => await this.networkModel.Legal.CurrentContract.ShowContractOptions(Options));
641 }
642 }
643}
Account event
Definition: AccountEvent.cs:16
Contains information about a balance.
Definition: Balance.cs:11
CaseInsensitiveString Currency
Currency of amount.
Definition: Balance.cs:54
AccountEvent Event
Any account event associated to the balance message.
Definition: Balance.cs:59
decimal Amount
Amount at given point in time.
Definition: Balance.cs:44
decimal Reserved
Reserved amount, that the user cannot use directly.
Definition: Balance.cs:49
DateTime Timestamp
Timestamp of balance.
Definition: Balance.cs:39
eDaler XMPP client.
Definition: EDalerClient.cs:24
Wallet balance event arguments.
Event arguments for events where a client URL needs to be displayed when buying eDaler.
string ClientUrl
URL client needs to open to complete the transaction of buying eDaler.
Event arguments for event signalling the completion of a payment operation.
Event arguments for event signalling an error of a payment operation.
Event arguments for operations returning payment options.
IDictionary< CaseInsensitiveString, object >[] Options
Payment options.
string TransactionId
Transaction ID, if available in the response.
Event arguments for events where a client URL needs to be displayed when selling eDaler.
string ClientUrl
URL client needs to open to complete the transaction of selling eDaler.
Abstract base class for eDaler URIs
Definition: EDalerUri.cs:14
static bool TryParse(string Uri, out EDalerUri Result)
Tries to parse an eDaler URI
Definition: EDalerUri.cs:192
Incomplete eDaler URI for simplifying payments to a predefined recipient.
Event arguments for callback methods to token creation attributes queries.
Contains the definition of a contract
Definition: Contract.cs:22
bool CanActAsTemplate
If the contract can act as a template for other contracts.
Definition: Contract.cs:336
string ContractId
Contract identity
Definition: Contract.cs:65
Adds support for legal identities, smart contracts and signatures to an XMPP client.
Contains information about a service provider.
string Name
Displayable name of service provider.
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
Definition: XmppClient.cs:59
Static class managing persistent settings.
static async Task< string > GetAsync(string Key, string DefaultValue)
Gets a string-valued setting.
static async Task< bool > SetAsync(string Key, string Value)
Sets a string-valued setting.
Interface for information about a service provider that users can use to buy eDaler.
Interface for information about a service provider that users can use to sell eDaler.
XmppState
State of XMPP connection.
Definition: XmppState.cs:7