1.7.3 Add automatic ack on confirming Trades accordingly to new update

This commit is contained in:
achiez
2025-07-17 22:49:22 +03:00
parent 04e2188437
commit 1ad30ba49d
18 changed files with 212 additions and 64 deletions
+1
View File
@@ -21,6 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "changelog", "changelog", "{
changelog\1.5.6.html = changelog\1.5.6.html changelog\1.5.6.html = changelog\1.5.6.html
changelog\1.7.1.html = changelog\1.7.1.html changelog\1.7.1.html = changelog\1.7.1.html
changelog\1.7.2.html = changelog\1.7.2.html changelog\1.7.2.html = changelog\1.7.2.html
changelog\1.7.3.html = changelog\1.7.3.html
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamLibForked", "src\SteamLibForked\SteamLibForked.csproj", "{224F9DB0-3D20-A614-BA2A-12F22B13A2C6}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamLibForked", "src\SteamLibForked\SteamLibForked.csproj", "{224F9DB0-3D20-A614-BA2A-12F22B13A2C6}"
+3 -3
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<item> <item>
<version>1.7.2.0</version> <version>1.7.3.0</version>
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.7.2/NebulaAuth.1.7.2.zip</url> <url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.7.3/NebulaAuth.1.7.3.zip</url>
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.7.2.html</changelog> <changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.7.3.html</changelog>
<mandatory>false</mandatory> <mandatory>false</mandatory>
</item> </item>
+4 -4
View File
@@ -76,10 +76,10 @@
<a href="https://t.me/nebulaauth">t.me/nebulaauth</a> <a href="https://t.me/nebulaauth">t.me/nebulaauth</a>
</b> </b>
</li> </li>
<li><b>UPDATE:</b> Added support for a new confirmation type — Purchase.</li> <li><b>UPDATE:</b> Added support for a new confirmation type — Purchase.</li>
<li><b>UPDATE:</b> Merged Global 1.7.0 updates into the release version (<a href="https://t.me/nebulaauth/32">patch notes</a>).</li> <li><b>UPDATE:</b> Merged Global 1.7.0 updates into the release version (<a href="https://t.me/nebulaauth/32">patch notes</a>).</li>
<li><b>FIX:</b> Fixed a crash occurring during confirmation loading.</li> <li><b>FIX:</b> Fixed a crash occurring during confirmation loading.</li>
<li><b>ENHANCEMENT:</b> Improved localization and system messages.</li> <li><b>ENHANCEMENT:</b> Improved localization and system messages.</li>
</ul> </ul>
</div> </div>
</div> </div>
+1 -1
View File
@@ -76,7 +76,7 @@
<a href="https://t.me/nebulaauth">t.me/nebulaauth</a> <a href="https://t.me/nebulaauth">t.me/nebulaauth</a>
</b> </b>
</li> </li>
<li><b>FIX:</b> Crash on loading trade confirmations</li> <li><b>FIX:</b> Crash on loading trade confirmations</li>
</ul> </ul>
</div> </div>
</div> </div>
+87
View File
@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #eeeeee;
color: #333;
line-height: 1.6;
}
.changelog-container {
background-color: #fff;
border-radius: 10px;
padding: 25px;
margin: 25px auto;
max-width: 800px;
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.change {
margin-bottom: 20px;
padding: 0;
border-left: 4px solid #a50ec7;
background-color: #f9f9f9;
}
.version {
font-weight: 600;
font-size: 1.5em;
color: #a50ec7;
margin-left: 10px;
}
.date {
font-style: italic;
color: #888;
margin-bottom: 10px;
margin-left: 15px;
}
.description {
font-size: 1em;
padding: 0 15px;
}
.description ul {
list-style: inside square;
padding: 0;
}
@media only screen and (max-width: 600px) {
.changelog-container {
width: 90%;
margin: 25px auto;
padding: 25px;
}
}
</style>
</head>
<body>
<div class="changelog-container">
<!-- Changelog entry -->
<div class="change">
<div class="version">Version 1.7.3</div>
<div class="date">17.07.2025</div>
<div class="description">
<ul>
<li>
<b>NEWS:</b> Official Telegram group now available! Join us at
<b>
<a href="https://t.me/nebulaauth">t.me/nebulaauth</a>
</b>
</li>
<li><b>UPDATE:</b> Added automatic acknowledgment request for sending trade confirmation</li>
<li><b>FIX:</b> Fixed notifications text in auto-confirm timer</li>
</ul>
</div>
</div>
</div>
</body>
</html>
+2 -6
View File
@@ -1,17 +1,13 @@
<Application x:Class="NebulaAuth.App" <Application x:Class="NebulaAuth.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:NebulaAuth.Converters"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:background="clr-namespace:NebulaAuth.Converters.Background">
<Application.Resources> <Application.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Converters/Converters.xaml"/> <ResourceDictionary Source="/Converters/Converters.xaml" />
<ResourceDictionary Source="Theme/Themes/DefaultTheme.xaml" /> <ResourceDictionary Source="Theme/Themes/DefaultTheme.xaml" />
<ResourceDictionary <ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Defaults.xaml" /> Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Defaults.xaml" />
+16 -2
View File
@@ -13,6 +13,7 @@ using NebulaAuth.Core;
using NebulaAuth.Model.Entities; using NebulaAuth.Model.Entities;
using NebulaAuth.Utility; using NebulaAuth.Utility;
using SteamLib.Api.Mobile; using SteamLib.Api.Mobile;
using SteamLib.Api.Trade;
using SteamLib.Authentication; using SteamLib.Authentication;
using SteamLib.Exceptions.Authorization; using SteamLib.Exceptions.Authorization;
using SteamLib.SteamMobile.Confirmations; using SteamLib.SteamMobile.Confirmations;
@@ -90,7 +91,7 @@ public partial class PortableMaClient : ObservableObject, IDisposable
var toConfirm = new List<Confirmation>(); var toConfirm = new List<Confirmation>();
if (AutoConfirmMarket) if (AutoConfirmMarket)
{ {
var market = conf.Where(c => var market = conf.Where(c =>
c.ConfType is ConfirmationType.MarketSellTransaction or ConfirmationType.Purchase); c.ConfType is ConfirmationType.MarketSellTransaction or ConfirmationType.Purchase);
toConfirm.AddRange(market); toConfirm.AddRange(market);
} }
@@ -107,6 +108,7 @@ public partial class PortableMaClient : ObservableObject, IDisposable
Shell.Logger.Debug("Timer {accountName}: Sending confirmations. Count: {count}", Mafile.AccountName, Shell.Logger.Debug("Timer {accountName}: Sending confirmations. Count: {count}", Mafile.AccountName,
toConfirm.Count); toConfirm.Count);
var success = await HandleTimerRequest(() => SendConfirmations(toConfirm)); var success = await HandleTimerRequest(() => SendConfirmations(toConfirm));
//TODO: handle success == false
Shell.Logger.Debug("Timer {accountName}: Confirmation sent: {confirmResult}", Mafile.AccountName, success); Shell.Logger.Debug("Timer {accountName}: Confirmation sent: {confirmResult}", Mafile.AccountName, success);
return toConfirm.Count; return toConfirm.Count;
} }
@@ -126,7 +128,19 @@ public partial class PortableMaClient : ObservableObject, IDisposable
private async Task<bool> SendConfirmations(IEnumerable<Confirmation> confirmations) private async Task<bool> SendConfirmations(IEnumerable<Confirmation> confirmations)
{ {
return await SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, confirmations, var conf = confirmations.ToList();
var res = await SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, conf,
Mafile.SessionData!.SteamId, Mafile, true, _cts.Token);
if (!res && conf.Any(c => c.ConfType == ConfirmationType.Trade))
{
Shell.Logger.Warn("Timer {accountName}: Failed to send trade confirmations. Sending ack",
Mafile.AccountName);
await SteamTradeApi.Acknowledge(Client, Mafile.SessionData!.SessionId, _cts.Token);
await Task.Delay(10, _cts.Token);
}
return await SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, conf,
Mafile.SessionData!.SteamId, Mafile, true, _cts.Token); Mafile.SessionData!.SteamId, Mafile, true, _cts.Token);
} }
+29 -5
View File
@@ -8,6 +8,7 @@ using AchiesUtilities.Web.Proxy;
using NebulaAuth.Model.Entities; using NebulaAuth.Model.Entities;
using SteamLib.Api.Mobile; using SteamLib.Api.Mobile;
using SteamLib.Api.Services; using SteamLib.Api.Services;
using SteamLib.Api.Trade;
using SteamLib.Authentication; using SteamLib.Authentication;
using SteamLib.Exceptions.Authorization; using SteamLib.Exceptions.Authorization;
using SteamLib.ProtoCore.Services; using SteamLib.ProtoCore.Services;
@@ -78,26 +79,49 @@ public static class MaClient
return SessionHandler.RefreshMobileToken(new HttpClientHandlerPair(Client, ClientHandler), mafile); return SessionHandler.RefreshMobileToken(new HttpClientHandlerPair(Client, ClientHandler), mafile);
} }
public static Task<bool> SendConfirmation(Mafile mafile, Confirmation confirmation, bool confirm) public static async Task<bool> SendConfirmation(Mafile mafile, Confirmation confirmation, bool confirm)
{ {
ValidateMafile(mafile); ValidateMafile(mafile);
SetProxy(mafile); SetProxy(mafile);
return SteamMobileConfirmationsApi.SendConfirmation(Client, confirmation, mafile.SessionData!.SteamId, mafile, var res = await SteamMobileConfirmationsApi.SendConfirmation(Client, confirmation, mafile.SessionData!.SteamId,
mafile,
confirm);
if (!res && confirmation.ConfType == ConfirmationType.Trade)
{
Shell.Logger.Warn("Failed to send trade confirmation for {accountName}. Sending ack", mafile.AccountName);
await SteamTradeApi.Acknowledge(Client, mafile.SessionData.SessionId);
await Task.Delay(10);
}
return await SteamMobileConfirmationsApi.SendConfirmation(Client, confirmation, mafile.SessionData!.SteamId,
mafile,
confirm); confirm);
} }
public static Task<bool> SendMultipleConfirmation(Mafile mafile, IEnumerable<Confirmation> confirmations, public static async Task<bool> SendMultipleConfirmation(Mafile mafile, IEnumerable<Confirmation> confirmations,
bool confirm) bool confirm)
{ {
var enumerable = confirmations.ToList(); var enumerable = confirmations.ToList();
if (enumerable.Count == 0) if (enumerable.Count == 0)
{ {
return Task.FromResult(false); return false;
} }
ValidateMafile(mafile); ValidateMafile(mafile);
SetProxy(mafile); SetProxy(mafile);
return SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, enumerable, mafile.SessionData!.SteamId,
var res = await SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, enumerable,
mafile.SessionData!.SteamId,
mafile, confirm);
if (!res && enumerable.Any(c => c.ConfType == ConfirmationType.Trade))
{
Shell.Logger.Warn("Failed to send trade confirmations for {accountName}. Sending ack", mafile.AccountName);
await SteamTradeApi.Acknowledge(Client, mafile.SessionData.SessionId);
await Task.Delay(10);
}
return await SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, enumerable,
mafile.SessionData!.SteamId,
mafile, confirm); mafile, confirm);
} }
+2 -2
View File
@@ -10,7 +10,7 @@
<SatelliteResourceLanguages>en;ru;ua</SatelliteResourceLanguages> <SatelliteResourceLanguages>en;ru;ua</SatelliteResourceLanguages>
<ApplicationIcon>Theme\lock.ico</ApplicationIcon> <ApplicationIcon>Theme\lock.ico</ApplicationIcon>
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion> <SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
<AssemblyVersion>1.7.2</AssemblyVersion> <AssemblyVersion>1.7.3</AssemblyVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
@@ -36,7 +36,7 @@
<ItemGroup> <ItemGroup>
<Resource Include="Theme\Background.png"> <Resource Include="Theme\Background.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory> <CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Resource> </Resource>
<Resource Include="Theme\Background_Old.jpg"> <Resource Include="Theme\Background_Old.jpg">
<CopyToOutputDirectory>Never</CopyToOutputDirectory> <CopyToOutputDirectory>Never</CopyToOutputDirectory>
+2 -5
View File
@@ -1,10 +1,6 @@
Есть люди которым нужна функция просмотра и завершения сессий аккаунта. Скорее всего в следующей версии это будет реализовано Сейчас, при наличии новой версии, отображается стандартное окно обновлений из библиотеки. Планируется сделать кастомный дизайн, для соответствия общему стилю.
• Сейчас, при наличии новой версии, отображается стандартное окно обновлений из библиотеки. Планируется сделать кастомный дизайн, для соответствия общему стилю.
• В приложении реализованы "совмещённые" подтверждения для торговой площадки. Т.е. при наличии более одного предмета, ожидающего подтверждение, можно либо отменить, либо подтвердить все. В планах добавить расширенный функционал, с возможностью точечного управления. • В приложении реализованы "совмещённые" подтверждения для торговой площадки. Т.е. при наличии более одного предмета, ожидающего подтверждение, можно либо отменить, либо подтвердить все. В планах добавить расширенный функционал, с возможностью точечного управления.
• Добавить кнопку "открыть мафайл" в меню "Файл" по аналогии с открытием папки • Добавить кнопку "открыть мафайл" в меню "Файл" по аналогии с открытием папки
• Ускорить появление подсказок в интерфейсе
• Добавить запоминание пароля при привязке мафайла
• Функция переноса гуарда с мобильного устройства на ПК с созданием мафайла
• Добавить полное шифрование мафайлов по аналогии с SDA • Добавить полное шифрование мафайлов по аналогии с SDA
• Сделать автоматический билд и загрузку обновления на Github при изменении в мастер ветке • Сделать автоматический билд и загрузку обновления на Github при изменении в мастер ветке
• Антик-окно как в MarketApp, возможность сразу открыть браузер напр. на CefSharp с залогиненым аккаунтом • Антик-окно как в MarketApp, возможность сразу открыть браузер напр. на CefSharp с залогиненым аккаунтом
@@ -12,3 +8,4 @@
• Безопасное сохранение мафайлов через .tmp / .bak • Безопасное сохранение мафайлов через .tmp / .bak
• Создание механизма накопления ошибок, чтобы исправить Patch Tuesday, условно аккаунт может игнорировать ошибки 1-2 часа (настриавемо) • Создание механизма накопления ошибок, чтобы исправить Patch Tuesday, условно аккаунт может игнорировать ошибки 1-2 часа (настриавемо)
• Исправить "Перенос гуарда". Иногда не хочет принимать код / подтверждение
@@ -7,7 +7,7 @@
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities" xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
xmlns:vm="clr-namespace:NebulaAuth.ViewModel"> xmlns:vm="clr-namespace:NebulaAuth.ViewModel">
<DataTemplate DataType="{x:Type confirmations:Confirmation}"> <DataTemplate DataType="{x:Type confirmations:Confirmation}">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
+30 -30
View File
@@ -260,7 +260,7 @@
"en": "Purchase", "en": "Purchase",
"ru": "Покупка", "ru": "Покупка",
"ua": "Покупка" "ua": "Покупка"
} }
}, },
"CodeCopied": { "CodeCopied": {
"ru": "Скопировано", "ru": "Скопировано",
@@ -1286,36 +1286,36 @@
"ru": "Ошибка SMS-кода, попробуйте еще раз", "ru": "Ошибка SMS-кода, попробуйте еще раз",
"ua": "Помилка SMS-коду, спробуйте ще раз" "ua": "Помилка SMS-коду, спробуйте ще раз"
} }
}
},
"MAAC": {
"TimerConfirmed": {
"ru": "Авто: подтверждено ",
"en": "Auto: confirmed ",
"ua": "Авто: підтверджено "
}, },
"TimerNotConfirmed": { "MAAC": {
"ru": "Авто: подтверждение не сработало", "TimerConfirmed": {
"en": "Auto: confirmation was unsuccessful", "ru": "Авто: подтверждено ",
"ua": "Авто: підтвердження не спрацювало" "en": "Auto: confirmed ",
}, "ua": "Авто: підтверджено "
"TimerSessionError": { },
"ru": "необходимо обновить сессию или перелогиниться", "TimerNotConfirmed": {
"en": "need to refresh session or relogin", "ru": "Авто: подтверждение не сработало",
"ua": "необхідно оновити сесію або перелогінитися" "en": "Auto: confirmation was unsuccessful",
}, "ua": "Авто: підтвердження не спрацювало"
"TimerPrefix": { },
"ru": "Авто ", "TimerSessionError": {
"en": "Auto ", "ru": "необходимо обновить сессию или перелогиниться",
"ua": "Авто " "en": "need to refresh session or relogin",
}, "ua": "необхідно оновити сесію або перелогінитися"
"TimerPreventedOverlap": { },
"ru": "TimerPrefix": {
"Авто: таймер подтверждений пытался начать новый цикл, когда предыдущий еще не завершился. Советуем увеличить задержку", "ru": "Авто ",
"en": "en": "Auto ",
"Auto: confirmation timer tried to start new cycle when previous one was not finished yet. We recommend to increase delay", "ua": "Авто "
"ua": },
"Авто: таймер підтверджень намагався запустити новий цикл, коли попередній ще не закінчився. Рекомендуємо збільшити затримку" "TimerPreventedOverlap": {
"ru":
"Авто: таймер подтверждений пытался начать новый цикл, когда предыдущий еще не завершился. Советуем увеличить задержку",
"en":
"Auto: confirmation timer tried to start new cycle when previous one was not finished yet. We recommend to increase delay",
"ua":
"Авто: таймер підтверджень намагався запустити новий цикл, коли попередній ще не закінчився. Рекомендуємо збільшити затримку"
}
} }
}, },
"CantAlignTimeError": { "CantAlignTimeError": {
@@ -72,8 +72,8 @@ public static class SteamMobileConfirmationsApi
var op = confirm ? "allow" : "cancel"; var op = confirm ? "allow" : "cancel";
var query = GetConfirmationKvp(steamId, data.DeviceId, data.IdentitySecret, op).ToList(); var query = GetConfirmationKvp(steamId, data.DeviceId, data.IdentitySecret, op).ToList();
query.Insert(0, new KeyValuePair<string, string>("op", op)); query.Insert(0, new KeyValuePair<string, string>("op", op));
var list = confirmations.ToList();
foreach (var confirmation in confirmations) foreach (var confirmation in list)
{ {
query.Add(new KeyValuePair<string, string>("cid[]", confirmation.Id.ToString())); query.Add(new KeyValuePair<string, string>("cid[]", confirmation.Id.ToString()));
query.Add(new KeyValuePair<string, string>("ck[]", confirmation.Nonce.ToString())); query.Add(new KeyValuePair<string, string>("ck[]", confirmation.Nonce.ToString()));
@@ -0,0 +1,27 @@
using SteamLib.Core;
namespace SteamLib.Api.Trade;
public static class SteamTradeApi
{
public static async Task<bool> Acknowledge(HttpClient client, string sessionId,
CancellationToken cancellationToken = default)
{
var data = new Dictionary<string, string>
{
{"sessionid", sessionId},
{"message", "1"}
};
var req = new HttpRequestMessage(HttpMethod.Post, Routes.ACKNOWLEDGE);
req.Content = new FormUrlEncodedContent(data);
var resp = await client.SendAsync(req, cancellationToken);
var cont = await resp.EnsureSuccessStatusCode().Content.ReadAsStringAsync(cancellationToken);
return cont == "[]";
//{"success":false} - unauthenticated
}
public static class Routes
{
public const string ACKNOWLEDGE = SteamConstants.STEAM_COMMUNITY + "//trade/new/acknowledge"; //Not a typo
}
}
@@ -21,7 +21,7 @@ public class SessionData : ISessionData
public SteamAuthToken RefreshToken { get; } public SteamAuthToken RefreshToken { get; }
public ConcurrentDictionary<SteamDomain, SteamAuthToken> Tokens { get; } public ConcurrentDictionary<SteamDomain, SteamAuthToken> Tokens { get; }
[JsonConstructor] [JsonConstructor]
public SessionData(string sessionId, SteamId steamId, SteamAuthToken refreshToken, public SessionData(string sessionId, SteamId steamId, SteamAuthToken refreshToken,
IDictionary<SteamDomain, SteamAuthToken>? tokens) IDictionary<SteamDomain, SteamAuthToken>? tokens)
@@ -2,7 +2,8 @@
public class PurchaseConfirmation : Confirmation public class PurchaseConfirmation : Confirmation
{ {
public PurchaseConfirmation(long id, ulong nonce, int intType, ulong creatorId, string typeName) : base(id, nonce, intType, creatorId, ConfirmationType.Purchase, typeName) public PurchaseConfirmation(long id, ulong nonce, ulong creatorId, string typeName) : base(id, nonce, 12, creatorId,
ConfirmationType.Purchase, typeName)
{ {
} }
} }
+1
View File
@@ -33,6 +33,7 @@ public static class ClientBuilder
{ {
container.SetSteamMobileCookiesWithMobileToken(sessionData); container.SetSteamMobileCookiesWithMobileToken(sessionData);
} }
//Nebula tweak: //Nebula tweak:
handler.CookieContainer.InjectWebTradeEligibilityCookie(); handler.CookieContainer.InjectWebTradeEligibilityCookie();
ConfigureCommon(handler, client); ConfigureCommon(handler, client);
@@ -170,7 +170,7 @@ public static class MobileConfirmationScrapper
private static PurchaseConfirmation GetPurchaseConfirmation(ConfirmationJson confirmation) private static PurchaseConfirmation GetPurchaseConfirmation(ConfirmationJson confirmation)
{ {
return new PurchaseConfirmation(confirmation.Id, confirmation.Nonce, (int) confirmation.Type, return new PurchaseConfirmation(confirmation.Id, confirmation.Nonce,
confirmation.CreatorId, confirmation.TypeName) confirmation.CreatorId, confirmation.TypeName)
{ {
Time = confirmation.CreationTime.ToLocalDateTime() Time = confirmation.CreationTime.ToLocalDateTime()