浏览代码

implemented EnlistAccountForSale wizard.

Ugochukwu Mmaduekwe 7 年之前
父节点
当前提交
9c3a6f680c

+ 467 - 0
src/gui/wizards/UWIZEnlistAccountForSale.pas

@@ -0,0 +1,467 @@
+unit UWIZEnlistAccountForSale;
+
+{$mode delphi}
+
+{ Copyright (c) 2018 by Ugochukwu Mmaduekwe
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+
+}
+
+interface
+
+uses
+  Classes, SysUtils, Forms, Dialogs, UCrypto, UCommon, UWizard, UAccounts, LCLType;
+
+type
+
+  { TWIZEnlistAccountForSaleModel }
+  TWIZAccountSaleMode = (akaPublicSale, akaPrivateSale);
+  TWIZPayloadEncryptionMode = (akaEncryptWithOldEC, akaEncryptWithEC,
+    akaEncryptWithPassword, akaNotEncrypt);
+
+  TWIZEnlistAccountForSaleModel = class(TComponent)
+  public
+    DefaultFee: int64;
+    NewPublicKey, Payload, EncryptionPassword: string;
+    SelectedIndex: integer;
+    SalePrice: int64;
+    NewOwnerPublicKey: TAccountKey;
+    LockedUntilBlock: cardinal;
+    EncodedPayload: TRawBytes;
+    SignerAccount, SellerAccount: TAccount;
+    SelectedAccounts: TArray<TAccount>;
+    PayloadEncryptionMode: TWIZPayloadEncryptionMode;
+    AccountSaleMode: TWIZAccountSaleMode;
+  end;
+
+  { TWIZEnlistAccountForSaleWizard }
+
+  TWIZEnlistAccountForSaleWizard = class(TWizard<TWIZEnlistAccountForSaleModel>)
+  private
+    function UpdatePayload(const SenderAccount: TAccount;
+      var errors: string): boolean;
+    function UpdateOperationOptions(var errors: string): boolean;
+    function UpdateOpListForSale(const TargetAccount: TAccount;
+      var SalePrice: int64; var SellerAccount, SignerAccount: TAccount;
+      var NewOwnerPublicKey: TAccountKey; var LockedUntilBlock: cardinal;
+      var errors: ansistring): boolean;
+    procedure EnlistAccountForSale();
+  public
+    constructor Create(AOwner: TComponent); override;
+    function DetermineHasNext: boolean; override;
+    function DetermineHasPrevious: boolean; override;
+    function FinishRequested(out message: ansistring): boolean; override;
+    function CancelRequested(out message: ansistring): boolean; override;
+  end;
+
+implementation
+
+uses
+  UBlockChain,
+  UOpTransaction,
+  UNode,
+  UConst,
+  UWallet,
+  UECIES,
+  UAES,
+  UWIZEnlistAccountForSale_Start,
+  UWIZEnlistAccountForSale_Confirmation;
+
+{ TWIZEnlistAccountForSaleWizard }
+
+function TWIZEnlistAccountForSaleWizard.UpdatePayload(const SenderAccount: TAccount;
+  var errors: string): boolean;
+var
+  valid: boolean;
+  payload_encrypted, payload_u: string;
+  account: TAccount;
+begin
+  valid := False;
+  payload_encrypted := '';
+  Model.EncodedPayload := '';
+  errors := 'Unknown error';
+  payload_u := Model.Payload;
+
+  try
+    if (payload_u = '') then
+    begin
+      valid := True;
+      Exit;
+    end;
+    case Model.PayloadEncryptionMode of
+
+      akaEncryptWithOldEC:
+      begin
+        // Use sender
+        errors := 'Error encrypting';
+        account := SenderAccount;
+        payload_encrypted := ECIESEncrypt(account.accountInfo.accountKey, payload_u);
+        valid := payload_encrypted <> '';
+      end;
+
+      akaEncryptWithEC:
+      begin
+        errors := 'Public key: ' + 'Error encrypting';
+
+        account := Model.SignerAccount;
+        payload_encrypted := ECIESEncrypt(account.accountInfo.accountKey, payload_u);
+        valid := payload_encrypted <> '';
+      end;
+
+      akaEncryptWithPassword:
+      begin
+        payload_encrypted := TAESComp.EVP_Encrypt_AES256(
+          payload_u, Model.EncryptionPassword);
+        valid := payload_encrypted <> '';
+      end;
+
+      akaNotEncrypt:
+      begin
+        payload_encrypted := payload_u;
+        valid := True;
+      end
+
+      else
+      begin
+        raise Exception.Create('Invalid Encryption Selection');
+      end;
+    end;
+
+  finally
+    if valid then
+    begin
+      if length(payload_encrypted) > CT_MaxPayloadSize then
+      begin
+        valid := False;
+        errors := 'Payload size is bigger than ' + IntToStr(CT_MaxPayloadSize) +
+          ' (' + IntToStr(length(payload_encrypted)) + ')';
+      end;
+
+    end;
+    Model.EncodedPayload := payload_encrypted;
+    Result := valid;
+  end;
+
+end;
+
+function TWIZEnlistAccountForSaleWizard.UpdateOperationOptions(
+  var errors: string): boolean;
+var
+  iAcc, iWallet: integer;
+  sender_account, signer_account, seller_account: TAccount;
+  publicKey: TAccountKey;
+  wk: TWalletKey;
+  e: string;
+  salePrice: int64;
+  auxC: cardinal;
+begin
+  Result := False;
+  errors := '';
+  if not Assigned(TWallet.Keys) then
+  begin
+    errors := 'No wallet keys';
+    Exit;
+  end;
+
+  if Length(Model.SelectedAccounts) = 0 then
+  begin
+    errors := 'No sender account';
+    Exit;
+  end
+  else
+  begin
+
+    for iAcc := Low(Model.SelectedAccounts) to High(Model.SelectedAccounts) do
+    begin
+      sender_account := Model.SelectedAccounts[iAcc];
+      iWallet := TWallet.Keys.IndexOfAccountKey(sender_account.accountInfo.accountKey);
+      if (iWallet < 0) then
+      begin
+        errors := 'Private key of account ' +
+          TAccountComp.AccountNumberToAccountTxtNumber(sender_account.account) +
+          ' not found in wallet';
+        Exit;
+      end;
+      wk := TWallet.Keys.Key[iWallet];
+      if not assigned(wk.PrivateKey) then
+      begin
+        if wk.CryptedKey <> '' then
+        begin
+          // TODO: handle unlocking of encrypted wallet here
+          errors := 'Wallet is password protected. Need password';
+        end
+        else
+        begin
+          errors := 'Only public key of account ' +
+            TAccountComp.AccountNumberToAccountTxtNumber(sender_account.account) +
+            ' found in wallet. You cannot operate with this account';
+        end;
+        Exit;
+      end;
+    end;
+  end;
+
+  Result := UpdateOpListForSale(Model.SelectedAccounts[0], salePrice,
+    seller_account, signer_account, publicKey, auxC, errors);
+  UpdatePayload(sender_account, e);
+end;
+
+function TWIZEnlistAccountForSaleWizard.UpdateOpListForSale(
+  const TargetAccount: TAccount; var SalePrice: int64;
+  var SellerAccount, SignerAccount: TAccount; var NewOwnerPublicKey: TAccountKey;
+  var LockedUntilBlock: cardinal; var errors: ansistring): boolean;
+begin
+  Result := False;
+  SalePrice := 0;
+  SellerAccount := CT_Account_NUL;
+  NewOwnerPublicKey := CT_TECDSA_Public_Nul;
+  LockedUntilBlock := 0;
+  errors := '';
+  try
+    if TAccountComp.IsAccountForSale(TargetAccount.accountInfo) then
+    begin
+      errors := 'Account ' + TAccountComp.AccountNumberToAccountTxtNumber(
+        TargetAccount.account) + ' is already enlisted for sale';
+      Exit;
+    end;
+    salePrice := Model.SalePrice;
+
+    SignerAccount := Model.SignerAccount;
+
+    if (Model.SellerAccount.account = TargetAccount.account) then
+    begin
+      errors := 'Seller account cannot be same account';
+      exit;
+    end;
+
+    SellerAccount := Model.SellerAccount;
+    if Model.AccountSaleMode = akaPrivateSale then
+    begin
+
+      NewOwnerPublicKey := Model.NewOwnerPublicKey;
+
+      if TAccountComp.EqualAccountKeys(NewOwnerPublicKey,
+        TargetAccount.accountInfo.accountKey) then
+      begin
+        errors := 'New public key for private sale is the same public key';
+        Exit;
+      end;
+      LockedUntilBlock := Model.LockedUntilBlock;
+      if LockedUntilBlock = 0 then
+      begin
+        errors := 'Insert locking block';
+        exit;
+      end;
+    end;
+    if (TNode.Node.Bank.SafeBox.CurrentProtocol = CT_PROTOCOL_1) then
+    begin
+      errors := 'This operation needs PROTOCOL 2 active';
+      exit;
+    end;
+  finally
+    Result := errors = '';
+  end;
+end;
+
+procedure TWIZEnlistAccountForSaleWizard.EnlistAccountForSale();
+var
+  _V2, dooperation: boolean;
+  iAcc, i: integer;
+  _totalamount, _totalfee, _totalSignerFee, _amount, _fee, _salePrice: int64;
+  _signer_n_ops, _lockedUntil: cardinal;
+  operationstxt, operation_to_string, errors, auxs: string;
+  wk: TWalletKey;
+  ops: TOperationsHashTree;
+  op: TPCOperation;
+  account, signerAccount, destAccount: TAccount;
+  _newOwnerPublicKey: TECDSA_Public;
+label
+  loop_start;
+begin
+  if not Assigned(TWallet.Keys) then
+    raise Exception.Create('No wallet keys');
+  if not UpdateOperationOptions(errors) then
+    raise Exception.Create(errors);
+  ops := TOperationsHashTree.Create;
+
+  try
+    _V2 := TNode.Node.Bank.SafeBox.CurrentProtocol >= CT_PROTOCOL_2;
+    _totalamount := 0;
+    _totalfee := 0;
+    _totalSignerFee := 0;
+    _signer_n_ops := 0;
+    operationstxt := '';
+    operation_to_string := '';
+    for iAcc := Low(Model.SelectedAccounts) to High(Model.SelectedAccounts) do
+    begin
+      loop_start:
+        op := nil;
+      account := Model.SelectedAccounts[iAcc];
+      if not UpdatePayload(account, errors) then
+      begin
+        raise Exception.Create('Error encoding payload of sender account ' +
+          TAccountComp.AccountNumberToAccountTxtNumber(account.account) + ': ' + errors);
+      end;
+      i := TWallet.Keys.IndexOfAccountKey(account.accountInfo.accountKey);
+      if i < 0 then
+      begin
+        raise Exception.Create('Sender account private key not found in Wallet');
+      end;
+
+      wk := TWallet.Keys.Key[i];
+      dooperation := True;
+      // Default fee
+      if account.balance > uint64(Model.DefaultFee) then
+        _fee := Model.DefaultFee
+      else
+        _fee := account.balance;
+
+      if not UpdateOpListForSale(account, _salePrice, destAccount,
+        signerAccount, _newOwnerPublicKey, _lockedUntil, errors) then
+      begin
+        raise Exception.Create(errors);
+      end;
+      // Special fee account:
+      if signerAccount.balance > Model.DefaultFee then
+        _fee := Model.DefaultFee
+      else
+        _fee := signerAccount.balance;
+      if Model.AccountSaleMode = akaPublicSale then
+      begin
+        op := TOpListAccountForSale.CreateListAccountForSale(
+          signerAccount.account, signerAccount.n_operation + 1 + iAcc,
+          account.account, _salePrice, _fee, destAccount.account,
+          CT_TECDSA_Public_Nul, 0, wk.PrivateKey, Model.EncodedPayload);
+      end
+      else if Model.AccountSaleMode = akaPrivateSale then
+      begin
+        op := TOpListAccountForSale.CreateListAccountForSale(
+          signerAccount.account, signerAccount.n_operation + 1 + iAcc,
+          account.account, _salePrice, _fee, destAccount.account,
+          _newOwnerPublicKey, _lockedUntil, wk.PrivateKey, Model.EncodedPayload);
+      end
+      else
+      begin
+        raise Exception.Create('Invalid Sale type');
+      end;
+
+      if Assigned(op) and (dooperation) then
+      begin
+        ops.AddOperationToHashTree(op);
+        if operation_to_string <> '' then
+          operation_to_string := operation_to_string + #10;
+        operation_to_string := operation_to_string + op.ToString;
+      end;
+      FreeAndNil(op);
+    end;
+
+    if (ops.OperationsCount = 0) then
+      raise Exception.Create('No valid operation to execute');
+
+    if (Length(Model.SelectedAccounts) > 1) then
+    begin
+      auxs := '';
+      if Application.MessageBox(
+        PChar('Execute ' + IntToStr(Length(Model.SelectedAccounts)) +
+        ' operations?' + #10 + 'Operation: ' + operationstxt + #10 +
+        auxs + 'Total fee: ' + TAccountComp.FormatMoney(_totalfee) +
+        #10 + #10 + 'Note: This operation will be transmitted to the network!'),
+        PChar(Application.Title), MB_YESNO + MB_ICONINFORMATION + MB_DEFBUTTON2) <>
+        idYes then
+      begin
+        Exit;
+      end;
+    end
+    else
+    begin
+      if Application.MessageBox(PChar('Execute this operation:' +
+        #10 + #10 + operation_to_string + #10 + #10 +
+        'Note: This operation will be transmitted to the network!'),
+        PChar(Application.Title), MB_YESNO + MB_ICONINFORMATION + MB_DEFBUTTON2) <>
+        idYes then
+      begin
+        Exit;
+      end;
+    end;
+    i := TNode.Node.AddOperations(nil, ops, nil, errors);
+    if (i = ops.OperationsCount) then
+    begin
+      operationstxt := 'Successfully executed ' + IntToStr(i) +
+        ' operations!' + #10 + #10 + operation_to_string;
+      if i > 1 then
+      begin
+
+        ShowMessage(operationstxt);
+      end
+      else
+      begin
+        Application.MessageBox(
+          PChar('Successfully executed ' + IntToStr(i) + ' operations!' +
+          #10 + #10 + operation_to_string),
+          PChar(Application.Title), MB_OK + MB_ICONINFORMATION);
+      end;
+
+    end
+    else if (i > 0) then
+    begin
+      operationstxt := 'One or more of your operations has not been executed:' +
+        #10 + 'Errors:' + #10 + errors + #10 + #10 +
+        'Total successfully executed operations: ' + IntToStr(i);
+
+      ShowMessage(operationstxt);
+    end
+    else
+    begin
+      raise Exception.Create(errors);
+    end;
+
+
+  finally
+    ops.Free;
+  end;
+
+end;
+
+constructor TWIZEnlistAccountForSaleWizard.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner, [TWIZEnlistAccountForSale_Start,
+    TWIZEnlistAccountForSale_Confirmation]);
+  TitleText := 'Enlist Account';
+  FinishText := 'Enlist Account';
+end;
+
+function TWIZEnlistAccountForSaleWizard.DetermineHasNext: boolean;
+begin
+  Result := not (CurrentScreen is TWIZEnlistAccountForSale_Confirmation);
+end;
+
+function TWIZEnlistAccountForSaleWizard.DetermineHasPrevious: boolean;
+begin
+  Result := inherited DetermineHasPrevious;
+end;
+
+function TWIZEnlistAccountForSaleWizard.FinishRequested(
+  out message: ansistring): boolean;
+begin
+  // Execute the Enlist Account For Sale Action here
+  try
+    Result := True;
+    EnlistAccountForSale();
+  except
+    On E: Exception do
+    begin
+      Result := False;
+      message := E.ToString;
+    end;
+  end;
+end;
+
+function TWIZEnlistAccountForSaleWizard.CancelRequested(
+  out message: ansistring): boolean;
+begin
+  Result := True;
+end;
+
+end.

+ 63 - 0
src/gui/wizards/UWIZEnlistAccountForSale_Confirmation.lfm

@@ -0,0 +1,63 @@
+object WIZEnlistAccountForSale_Confirmation: TWIZEnlistAccountForSale_Confirmation
+  Left = 0
+  Height = 253
+  Top = 0
+  Width = 429
+  Caption = 'WIZEnlistAccountForSale_Confirmation'
+  ClientHeight = 253
+  ClientWidth = 429
+  Visible = False
+  object GroupBox1: TGroupBox
+    Left = 10
+    Height = 232
+    Top = 8
+    Width = 408
+    Anchors = [akTop, akLeft, akRight, akBottom]
+    Caption = 'Confirm Account Sale Transaction'
+    ClientHeight = 212
+    ClientWidth = 404
+    TabOrder = 0
+    object paGrid: TPanel
+      Left = 8
+      Height = 152
+      Top = 48
+      Width = 382
+      Anchors = [akTop, akLeft, akRight, akBottom]
+      BevelOuter = bvNone
+      Caption = 'GRID PANEL'
+      TabOrder = 0
+    end
+    object Label1: TLabel
+      Left = 8
+      Height = 15
+      Top = 16
+      Width = 84
+      Caption = 'Signer Account:'
+      ParentColor = False
+    end
+    object lblSgnAcc: TLabel
+      Left = 112
+      Height = 15
+      Top = 16
+      Width = 53
+      Caption = 'lblSgnAcc'
+      ParentColor = False
+    end
+    object Label2: TLabel
+      Left = 208
+      Height = 15
+      Top = 16
+      Width = 79
+      Caption = 'Seller Account:'
+      ParentColor = False
+    end
+    object lblSellerAcc: TLabel
+      Left = 304
+      Height = 15
+      Top = 16
+      Width = 61
+      Caption = 'lblSellerAcc'
+      ParentColor = False
+    end
+  end
+end

+ 142 - 0
src/gui/wizards/UWIZEnlistAccountForSale_Confirmation.pas

@@ -0,0 +1,142 @@
+unit UWIZEnlistAccountForSale_Confirmation;
+
+{$mode delphi}
+{$modeswitch nestedprocvars}
+
+{ Copyright (c) 2018 by Ugochukwu Mmaduekwe
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
+  ExtCtrls, UVisualGrid, UCellRenderers, UCommon.Data, UWizard, UWIZEnlistAccountForSale;
+
+type
+
+  { TWIZEnlistAccountForSale_Confirmation }
+
+  TWIZEnlistAccountForSale_Confirmation = class(TWizardForm<TWIZEnlistAccountForSaleModel>)
+    GroupBox1: TGroupBox;
+    Label1: TLabel;
+    Label2: TLabel;
+    lblSgnAcc: TLabel;
+    lblSellerAcc: TLabel;
+    paGrid: TPanel;
+  private
+    FSendersGrid : TVisualGrid;
+  public
+    procedure OnPresent; override;
+  end;
+
+
+implementation
+
+{$R *.lfm}
+
+uses UAccounts, UDataSources, UCommon, UCommon.UI, Generics.Collections;
+
+type
+
+  { TAccountSenderDataSource }
+
+  TAccountSenderDataSource = class(TAccountsDataSourceBase)
+    private
+      FModel : TWIZEnlistAccountForSaleModel;
+    protected
+      function GetColumns : TDataColumns; override;
+    public
+      property Model : TWIZEnlistAccountForSaleModel read FModel write FModel;
+      procedure FetchAll(const AContainer : TList<TAccount>); override;
+      function GetItemField(constref AItem: TAccount; const ABindingName : AnsiString) : Variant; override;
+  end;
+
+{ TWIZEnlistAccountForSale_Confirmation }
+
+procedure TWIZEnlistAccountForSale_Confirmation.OnPresent;
+var
+  data : TAccountSenderDataSource;
+begin
+  FSendersGrid := TVisualGrid.Create(Self);
+  FSendersGrid.CanSearch:= False;
+  FSendersGrid.SortMode := smMultiColumn;
+  FSendersGrid.FetchDataInThread := False;
+  FSendersGrid.AutoPageSize := True;
+  FSendersGrid.SelectionType := stNone;
+  FSendersGrid.Options := [vgoColAutoFill, vgoColSizing, vgoSortDirectionAllowNone, vgoAutoHidePaging];
+  with FSendersGrid.AddColumn('Account') do begin
+    Binding := 'Account';
+    Filters := SORTABLE_NUMERIC_FILTER;
+    Width := 100;
+    HeaderFontStyles := [fsBold];
+    DataFontStyles := [fsBold];
+  end;
+   with FSendersGrid.AddColumn('Sale Price') do begin
+    Binding := 'SalePrice';
+    Filters := SORTABLE_NUMERIC_FILTER;
+    Width := 100;
+    Renderer := TCellRenderers.PASC;
+  end;
+  // with FSendersGrid.AddColumn('New Public Key') do begin
+  //  Binding := 'NewPublicKey';
+  //  Filters := SORTABLE_TEXT_FILTER;
+  //  Width := 100;
+  //end;
+  with FSendersGrid.AddColumn('Fee') do begin
+    Filters := SORTABLE_TEXT_FILTER;
+    Width := 100;
+  end;
+
+   data := TAccountSenderDataSource.Create(FSendersGrid);
+   data.Model := Model;
+   FSendersGrid.DataSource := data;
+   paGrid.AddControlDockCenter(FSendersGrid);
+   lblSgnAcc.Caption := TAccountComp.AccountNumberToAccountTxtNumber(Model.SignerAccount.account);
+   lblSellerAcc.Caption := TAccountComp.AccountNumberToAccountTxtNumber(Model.SellerAccount.account);
+
+end;
+
+{ TAccountSenderDataSource }
+
+function TAccountSenderDataSource.GetColumns : TDataColumns;
+begin
+  Result := TDataColumns.Create(
+    TDataColumn.From('Account'),
+    TDataColumn.From('SalePrice'),
+    //TDataColumn.From('NewPublicKey'),
+    TDataColumn.From('Fee')
+  );
+end;
+
+function TAccountSenderDataSource.GetItemField(constref AItem: TAccount; const ABindingName : AnsiString) : Variant;
+var
+  index : Integer;
+begin
+   if ABindingName = 'Account' then
+     Result := TAccountComp.AccountNumberToAccountTxtNumber(AItem.account)
+   else if ABindingName = 'SalePrice' then
+     Result := TAccountComp.FormatMoney(Model.SalePrice)
+   //else if ABindingName = 'NewPublicKey' then
+   //  Result := Model.NewPublicKey
+     else if ABindingName = 'Fee' then
+     Result := TAccountComp.FormatMoney(Model.DefaultFee)
+   else raise Exception.Create(Format('Field not found [%s]', [ABindingName]));
+end;
+
+
+procedure TAccountSenderDataSource.FetchAll(const AContainer : TList<TAccount>);
+var
+  i: Integer;
+begin
+  for i := Low(Model.SelectedAccounts) to High(Model.SelectedAccounts) do
+  begin
+    AContainer.Add( Model.SelectedAccounts[i] );
+  end;
+end;
+
+
+end.
+

+ 32 - 0
src/gui/wizards/UWIZEnlistAccountForSale_List.lfm

@@ -0,0 +1,32 @@
+object WIZEnlistAccountForSale_List: TWIZEnlistAccountForSale_List
+  Left = 0
+  Height = 253
+  Top = 0
+  Width = 429
+  Caption = 'WIZEnlistAccountForSale_List'
+  ClientHeight = 253
+  ClientWidth = 429
+  LCLVersion = '1.8.1.0'
+  Visible = False
+  object grpTransferAccount: TGroupBox
+    Left = 6
+    Height = 248
+    Top = 0
+    Width = 416
+    Anchors = [akTop, akLeft, akRight, akBottom]
+    Caption = 'Enlisted Accounts For Sale'
+    ClientHeight = 228
+    ClientWidth = 412
+    TabOrder = 0
+    object paGrid: TPanel
+      Left = 6
+      Height = 192
+      Top = 24
+      Width = 402
+      Anchors = [akTop, akLeft, akRight, akBottom]
+      BevelOuter = bvNone
+      Caption = 'GRID PANEL'
+      TabOrder = 0
+    end
+  end
+end

+ 141 - 0
src/gui/wizards/UWIZEnlistAccountForSale_List.pas

@@ -0,0 +1,141 @@
+unit UWIZEnlistAccountForSale_List;
+
+{$mode delphi}
+{$modeswitch nestedprocvars}
+
+{ Copyright (c) 2018 by Ugochukwu Mmaduekwe
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
+  ExtCtrls, UVisualGrid, UCommon.Data, UCellRenderers,
+  UWizard, UWIZEnlistAccountForSale, UWIZEnlistAccountForSale_Transaction, UWIZEnlistAccountForSale_PrivateSaleConfig,
+  UWIZEnlistAccountForSale_Confirmation;
+
+type
+
+  { TWIZEnlistAccountForSale_List }
+
+  TWIZEnlistAccountForSale_List = class(TWizardForm<TWIZEnlistAccountForSaleModel>)
+    grpEnlistAccountForSale: TGroupBox;
+    grpTransferAccount: TGroupBox;
+    paGrid: TPanel;
+  private
+    FSendersGrid: TVisualGrid;
+  public
+    procedure OnPresent; override;
+    procedure OnNext; override;
+    function Validate(out message: ansistring): boolean; override;
+  end;
+
+
+
+implementation
+
+{$R *.lfm}
+
+uses UAccounts, USettings, UDataSources, UCommon, UCommon.UI, Generics.Collections;
+
+type
+
+  { TAccountSenderDataSource }
+
+  TAccountSenderDataSource = class(TAccountsDataSourceBase)
+  private
+    FModel: TWIZEnlistAccountForSaleModel;
+  public
+    property Model: TWIZEnlistAccountForSaleModel read FModel write FModel;
+    procedure FetchAll(const AContainer: TList<TAccount>); override;
+  end;
+
+procedure TAccountSenderDataSource.FetchAll(const AContainer: TList<TAccount>);
+var
+  i: integer;
+begin
+  for i := Low(Model.SelectedAccounts) to High(Model.SelectedAccounts) do
+  begin
+    AContainer.Add(Model.SelectedAccounts[i]);
+  end;
+end;
+
+{ TWIZEnlistAccountForSale_List }
+
+procedure TWIZEnlistAccountForSale_List.OnPresent;
+var
+  Data: TAccountSenderDataSource;
+begin
+  FSendersGrid := TVisualGrid.Create(Self);
+  FSendersGrid.CanSearch := False;
+  FSendersGrid.SortMode := smMultiColumn;
+  FSendersGrid.FetchDataInThread := False;
+  FSendersGrid.AutoPageSize := True;
+  FSendersGrid.SelectionType := stNone;
+  FSendersGrid.Options := [vgoColAutoFill, vgoColSizing, vgoSortDirectionAllowNone,
+    vgoAutoHidePaging];
+  with FSendersGrid.AddColumn('Account') do
+  begin
+    StretchedToFill := True;
+    Binding := 'AccountNumber';
+    SortBinding := 'AccountNumber';
+    DisplayBinding := 'Account';
+    Width := 100;
+    HeaderFontStyles := [fsBold];
+    DataFontStyles := [fsBold];
+    Filters := SORTABLE_NUMERIC_FILTER;
+  end;
+  with FSendersGrid.AddColumn('Balance') do
+  begin
+    Binding := 'BalanceDecimal';
+    SortBinding := 'Balance';
+    DisplayBinding := 'Balance';
+    Width := 100;
+    HeaderAlignment := taRightJustify;
+    DataAlignment := taRightJustify;
+    Renderer := TCellRenderers.PASC;
+    Filters := SORTABLE_NUMERIC_FILTER;
+  end;
+  Data := TAccountSenderDataSource.Create(FSendersGrid);
+  Data.Model := Model;
+  FSendersGrid.DataSource := Data;
+  paGrid.AddControlDockCenter(FSendersGrid);
+end;
+
+procedure TWIZEnlistAccountForSale_List.OnNext;
+begin
+  case Model.AccountSaleMode of
+  akaPublicSale:
+    begin
+      UpdatePath(ptReplaceAllNext, [TWIZEnlistAccountForSale_Transaction, TWIZEnlistAccountForSale_Confirmation]);
+    end;
+
+   akaPrivateSale:
+    begin
+      UpdatePath(ptReplaceAllNext, [TWIZEnlistAccountForSale_PrivateSaleConfig, TWIZEnlistAccountForSale_Confirmation]);
+    end;
+
+  end;
+end;
+
+function TWIZEnlistAccountForSale_List.Validate(out message: ansistring): boolean;
+var
+  i: Integer;
+begin
+  Result := True;
+   for i := Low(model.SelectedAccounts) to High(model.SelectedAccounts) do
+  begin
+    if TAccountComp.IsAccountForSale(model.SelectedAccounts[i].accountInfo) then
+    begin
+      Result := False;
+      message := 'Account ' + TAccountComp.AccountNumberToAccountTxtNumber(
+        model.SelectedAccounts[i].account) + ' is already enlisted for sale';
+      Exit;
+    end;
+  end;
+end;
+
+end.

+ 50 - 0
src/gui/wizards/UWIZEnlistAccountForSale_PrivateSaleConfig.lfm

@@ -0,0 +1,50 @@
+object WIZEnlistAccountForSale_PrivateSaleConfig: TWIZEnlistAccountForSale_PrivateSaleConfig
+  Left = 0
+  Height = 253
+  Top = 40
+  Width = 429
+  Caption = 'WIZEnlistAccountForSale_PrivateSaleConfig'
+  ClientHeight = 253
+  ClientWidth = 429
+  Visible = False
+  object gbTransaction: TGroupBox
+    Left = 24
+    Height = 216
+    Top = 16
+    Width = 384
+    Caption = 'Private Sale Config'
+    ClientHeight = 196
+    ClientWidth = 380
+    TabOrder = 0
+    object lblBlockLock: TLabel
+      Left = 8
+      Height = 15
+      Top = 8
+      Width = 98
+      Caption = 'Locked Until Block'
+      ParentColor = False
+    end
+    object edtBlockLock: TEdit
+      Left = 8
+      Height = 23
+      Top = 40
+      Width = 360
+      TabOrder = 0
+    end
+    object lblPublicKey: TLabel
+      Left = 8
+      Height = 15
+      Top = 104
+      Width = 120
+      Caption = 'New Owner Public Key'
+      ParentColor = False
+    end
+    object edtPublicKey: TEdit
+      Left = 8
+      Height = 23
+      Top = 136
+      Width = 360
+      TabOrder = 1
+    end
+  end
+end

+ 92 - 0
src/gui/wizards/UWIZEnlistAccountForSale_PrivateSaleConfig.pas

@@ -0,0 +1,92 @@
+unit UWIZEnlistAccountForSale_PrivateSaleConfig;
+
+{$mode delphi}
+{$modeswitch nestedprocvars}
+
+{ Copyright (c) 2018 by Ugochukwu Mmaduekwe
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
+  ExtCtrls, Buttons, UWizard, UWIZEnlistAccountForSale,
+  UWIZEnlistAccountForSale_Transaction,
+  UWIZEnlistAccountForSale_Confirmation;
+
+type
+
+  { TWIZEnlistAccountForSale_PrivateSaleConfig }
+
+  TWIZEnlistAccountForSale_PrivateSaleConfig =
+  class(TWizardForm<TWIZEnlistAccountForSaleModel>)
+    edtBlockLock: TEdit;
+    edtPublicKey: TEdit;
+    gbTransaction: TGroupBox;
+    lblBlockLock: TLabel;
+    lblPublicKey: TLabel;
+  public
+    procedure OnNext; override;
+    function Validate(out message: ansistring): boolean; override;
+  end;
+
+
+implementation
+
+{$R *.lfm}
+
+uses
+  UAccounts;
+
+{ TWIZEnlistAccountForSale_PrivateSaleConfig }
+
+procedure TWIZEnlistAccountForSale_PrivateSaleConfig.OnNext;
+begin
+  Model.NewPublicKey := edtPublicKey.Text;
+  UpdatePath(ptReplaceAllNext, [TWIZEnlistAccountForSale_Transaction,
+    TWIZEnlistAccountForSale_Confirmation]);
+end;
+
+function TWIZEnlistAccountForSale_PrivateSaleConfig.Validate(
+  out message: ansistring): boolean;
+var
+  c, LockedUntilBlock: cardinal;
+  i: integer;
+begin
+  Result := True;
+
+  LockedUntilBlock := StrToIntDef(edtBlockLock.Text, 0);
+  if LockedUntilBlock = 0 then
+  begin
+    message := 'Insert locking block';
+    Result := False;
+    exit;
+  end;
+  Model.LockedUntilBlock := LockedUntilBlock;
+
+  if not TAccountComp.AccountKeyFromImport(edtPublicKey.Text,
+    Model.NewOwnerPublicKey, message) then
+  begin
+    message := 'Public key: ' + message;
+    Result := False;
+    exit;
+
+  end;
+
+  for i := Low(Model.SelectedAccounts) to High(Model.SelectedAccounts) do
+  begin
+    if TAccountComp.EqualAccountKeys(Model.NewOwnerPublicKey,
+      Model.SelectedAccounts[i].accountInfo.accountKey) then
+    begin
+      message := 'New public key for private sale is the same public key';
+      Result := False;
+      Exit;
+    end;
+  end;
+
+end;
+
+end.

+ 66 - 0
src/gui/wizards/UWIZEnlistAccountForSale_Start.lfm

@@ -0,0 +1,66 @@
+object WIZEnlistAccountForSale_Start: TWIZEnlistAccountForSale_Start
+  Left = 0
+  Height = 253
+  Top = 0
+  Width = 429
+  Caption = 'WIZEnlistAccountForSale_Start'
+  ClientHeight = 253
+  ClientWidth = 429
+  LCLVersion = '1.8.1.0'
+  Visible = False
+  object Label1: TLabel
+    Left = 24
+    Height = 32
+    Top = 16
+    Width = 370
+    Anchors = [akTop, akLeft, akRight]
+    AutoSize = False
+    Caption = 'What type of account sale would you want to perform?'
+    ParentColor = False
+    WordWrap = True
+  end
+  object rbPublicSale: TRadioButton
+    Left = 24
+    Height = 19
+    Top = 56
+    Width = 79
+    Caption = 'Public Sale'
+    Checked = True
+    Font.Style = [fsBold]
+    ParentFont = False
+    TabOrder = 0
+    TabStop = True
+  end
+  object Label2: TLabel
+    Left = 32
+    Height = 48
+    Top = 80
+    Width = 352
+    Anchors = [akTop, akLeft, akRight]
+    AutoSize = False
+    Caption = 'Account is available to be sold to any willing buyer.'
+    ParentColor = False
+    WordWrap = True
+  end
+  object rbPrivateSale: TRadioButton
+    Left = 24
+    Height = 19
+    Top = 144
+    Width = 86
+    Caption = 'Private Sale'
+    Font.Style = [fsBold]
+    ParentFont = False
+    TabOrder = 1
+  end
+  object Label3: TLabel
+    Left = 32
+    Height = 48
+    Top = 168
+    Width = 350
+    Anchors = [akTop, akLeft, akRight]
+    AutoSize = False
+    Caption = 'This account is only available to the specified public key for sale.'
+    ParentColor = False
+    WordWrap = True
+  end
+end

+ 58 - 0
src/gui/wizards/UWIZEnlistAccountForSale_Start.pas

@@ -0,0 +1,58 @@
+unit UWIZEnlistAccountForSale_Start;
+
+{$mode delphi}
+{$modeswitch nestedprocvars}
+
+{ Copyright (c) 2018 by Ugochukwu Mmaduekwe
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
+  ExtCtrls, UWizard, UWIZEnlistAccountForSale, UWIZEnlistAccountForSale_List,
+  UWIZEnlistAccountForSale_Confirmation;
+
+type
+
+  { TWIZEnlistAccountForSale_Start }
+
+  TWIZEnlistAccountForSale_Start = class(TWizardForm<TWIZEnlistAccountForSaleModel>)
+    grpEnlistAccountForSale: TGroupBox;
+    Label1: TLabel;
+    Label2: TLabel;
+    Label3: TLabel;
+    rbPublicSale: TRadioButton;
+    rbPrivateSale: TRadioButton;
+  public
+    procedure OnNext; override;
+  end;
+
+
+
+implementation
+
+{$R *.lfm}
+
+uses UAccounts, USettings, UDataSources, UCommon, UCommon.UI, Generics.Collections;
+
+{ TWIZEnlistAccountForSale_Start }
+
+procedure TWIZEnlistAccountForSale_Start.OnNext;
+begin
+  if rbPublicSale.Checked = True then
+  begin
+    Model.AccountSaleMode := akaPublicSale;
+  end
+  else
+  begin
+    Model.AccountSaleMode := akaPrivateSale;
+  end;
+  UpdatePath(ptReplaceAllNext, [TWIZEnlistAccountForSale_List,
+    TWIZEnlistAccountForSale_Confirmation]);
+end;
+
+end.

+ 151 - 0
src/gui/wizards/UWIZEnlistAccountForSale_Transaction.lfm

@@ -0,0 +1,151 @@
+object WIZEnlistAccountForSale_Transaction: TWIZEnlistAccountForSale_Transaction
+  Left = 0
+  Height = 253
+  Top = 40
+  Width = 429
+  ActiveControl = cbSignerAccount
+  Caption = 'WIZEnlistAccountForSale_Transaction'
+  ClientHeight = 253
+  ClientWidth = 429
+  LCLVersion = '1.8.1.0'
+  Visible = False
+  object gbTransaction: TGroupBox
+    Left = 24
+    Height = 192
+    Top = 40
+    Width = 384
+    Caption = 'Sale Transaction'
+    ClientHeight = 172
+    ClientWidth = 380
+    TabOrder = 0
+    object cbSignerAccount: TComboBox
+      Left = 8
+      Height = 23
+      Top = 8
+      Width = 168
+      ItemHeight = 15
+      Items.Strings = (
+        ''
+      )
+      OnChange = cbSignerAccountChange
+      TabOrder = 0
+      Text = 'Select Signer Account'
+    end
+    object lblBalance: TLabel
+      Left = 200
+      Height = 15
+      Top = 12
+      Width = 151
+      Caption = 'Please Select Signer Account'
+      Font.Color = clRed
+      ParentColor = False
+      ParentFont = False
+    end
+    object lblSalePrice: TLabel
+      Left = 8
+      Height = 15
+      Top = 48
+      Width = 50
+      Caption = 'Sale Price'
+      ParentColor = False
+    end
+    object edtSalePrice: TEdit
+      Left = 8
+      Height = 23
+      Top = 74
+      Width = 136
+      TabOrder = 1
+    end
+    object btnSearch: TSpeedButton
+      Left = 345
+      Height = 22
+      Top = 75
+      Width = 23
+      Glyph.Data = {
+        36040000424D3604000000000000360000002800000010000000100000000100
+        2000000000000004000064000000640000000000000000000000000000000000
+        0000000000000000000F00000010000000100000001000000010000000100000
+        001000000010000000100000000F000000000000000000000000000000003509
+        033190370DEBA6410FFFA6410FFFA6410FFFA6410FFFA6410FFFA6410FFFA641
+        0FFFA6410FFFA6410FFFA6410FFF90370DEB350903310000000000000000953A
+        0EE6B14B10FFB04B10FFB04B10FFB04B10FFB04B10FFB04B10FFB04B10FFB04B
+        10FFB14B11FFAE460CFFB14B11FFB14B10FF953A0EE60000000000000001AA47
+        12FFB24D11FFB24D11FFB24D11FFB24D11FFB34E12FFB34E12FFB24D11FFB34E
+        12FFAB440DFFE1C19FFFAC4914FFB34E12FFAA4712FF0000000100000001AD4A
+        13FFB65113FFB65113FFB55113FFB14A0EFFAA3F05FFAA3F05FFB14A0EFFAE48
+        0EFFEEDCBCFFFFFFE2FFE6C6A1FFB44E0FFFAD4A13FF0000000100000001AF4D
+        14FFB85414FFB85414FFB75A25FFE5C8A7FFFFFFEBFFFFFFEAFFE3C3A1FFECD7
+        B8FFFFFFE4FFF1DFBEFFB55011FFB85414FFAF4D14FF0000000100000001B251
+        15FFBB5816FFBB6027FFFFFFEAFFEBCFABFFCC814BFFCC824CFFECD1AEFFFFFF
+        E8FFEFDAB9FFB95412FFBB5816FFBB5816FFB25115FF0000000100000001B555
+        16FFBC5712FFEACDAAFFEBCEAAFFBB540EFFBF5E19FFBF5E19FFBB550FFFEDD2
+        B0FFE7C7A5FFBC5712FFBE5C16FFBE5C16FFB55516FF0000000100000001B859
+        19FFBD5915FFFFFFFCFFD08857FFC56B29FFC46B29FFC46A29FFC56B29FFD18B
+        5BFFFFFFF9FFBF5D1BFFC46A27FFC36623FFB85919FF0000000100000001C170
+        35FFC66B2BFFFFFFFFFFD38F64FFCB7839FFCA7739FFCA7739FFCB7839FFD492
+        67FFFFFFFFFFC66C2CFFCA7739FFCB7739FFC17035FF0000000100000001C77F
+        46FFCF8248FFF1DBCCFFEED3C5FFCC7A40FFD1864CFFD1864CFFCC7A40FFEFD5
+        C8FFF1D9C9FFCF8348FFD0864CFFD0864CFFC77F46FF0000000100000001CD8D
+        59FFD79561FFDA9D6EFFFFFFFFFFF0D7C8FFDA9F79FFDAA079FFF0D8CAFFFFFF
+        FFFFD99C6DFFD79561FFD69460FFD69460FFCD8D59FF0000000100000000D29A
+        6CFFDCA373FFDCA373FFDEA97FFFF5E5D8FFFFFFFFFFFFFFFFFFF5E5D7FFDEA9
+        7EFFDCA373FFDCA373FFDCA373FFDCA373FFD29A6CFF0000000000000000C99C
+        75E0E1AE83FFE1AE84FFE1AE84FFE0AC80FFDFA87AFFDEA87AFFE0AC80FFE1AE
+        84FFE1AE84FFE1AE84FFE1AE84FFE1AE83FFC99C75E000000000FFFFFE007960
+        4721D0AA87DCDCB58FFFDDB590FFDDB590FFDDB590FFDDB590FFDDB590FFDDB5
+        90FFDDB590FFDDB590FFDCB58FFFD0AA87DC79604821FFFFFA0000000000FFFF
+        FF00000000000000000000000000000000000000000000000000000000000000
+        000000000000000000000000000000000000FFFFFF0000000000
+      }
+      OnClick = btnSearchClick
+    end
+    object lblOpFee: TLabel
+      Left = 8
+      Height = 15
+      Top = 112
+      Width = 74
+      Caption = 'Operation Fee'
+      ParentColor = False
+    end
+    object edtOpFee: TEdit
+      Left = 8
+      Height = 23
+      Top = 136
+      Width = 360
+      TabOrder = 2
+    end
+    object edtSellerAccount: TEdit
+      Left = 200
+      Height = 23
+      Top = 74
+      Width = 136
+      TabOrder = 3
+    end
+    object lblSellerAccount: TLabel
+      Left = 200
+      Height = 15
+      Top = 48
+      Width = 76
+      Caption = 'Seller Account'
+      ParentColor = False
+    end
+  end
+  object lblTotalBalances: TLabel
+    Left = 24
+    Height = 15
+    Top = 16
+    Width = 176
+    Caption = 'Selected Accounts Total Balance: '
+    ParentColor = False
+  end
+  object lblTotalBalanceValue: TLabel
+    Left = 208
+    Height = 15
+    Top = 16
+    Width = 70
+    Caption = 'Total Balance'
+    Font.Color = clGreen
+    ParentColor = False
+    ParentFont = False
+  end
+end

+ 199 - 0
src/gui/wizards/UWIZEnlistAccountForSale_Transaction.pas

@@ -0,0 +1,199 @@
+unit UWIZEnlistAccountForSale_Transaction;
+
+{$mode delphi}
+{$modeswitch nestedprocvars}
+
+{ Copyright (c) 2018 by Ugochukwu Mmaduekwe
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
+  ExtCtrls, Buttons, UCommon, UCommon.Collections, UWallet,
+  UFRMAccountSelect, UNode, UWizard, UWIZEnlistAccountForSale,
+  UWIZEnlistAccountForSale_TransactionPayload,
+  UWIZEnlistAccountForSale_Confirmation;
+
+type
+
+  { TWIZEnlistAccountForSale_Transaction }
+
+  TWIZEnlistAccountForSale_Transaction =
+    class(TWizardForm<TWIZEnlistAccountForSaleModel>)
+    btnSearch: TSpeedButton;
+    cbSignerAccount: TComboBox;
+    edtSalePrice: TEdit;
+    edtSellerAccount: TEdit;
+    edtOpFee: TEdit;
+    gbTransaction: TGroupBox;
+    lblBalance: TLabel;
+    lblSalePrice: TLabel;
+    lblSellerAccount: TLabel;
+    lblOpFee: TLabel;
+    lblTotalBalances: TLabel;
+    lblTotalBalanceValue: TLabel;
+    procedure btnSearchClick(Sender: TObject);
+    procedure cbSignerAccountChange(Sender: TObject);
+
+  public
+    procedure OnPresent; override;
+    procedure OnNext; override;
+    function Validate(out message: ansistring): boolean; override;
+  end;
+
+
+implementation
+
+{$R *.lfm}
+
+uses
+  UAccounts, UUserInterface, USettings;
+
+{ TWIZEnlistAccountForSale_Transaction }
+
+procedure TWIZEnlistAccountForSale_Transaction.cbSignerAccountChange(Sender: TObject);
+begin
+  if cbSignerAccount.ItemIndex < 1 then
+  begin
+    lblBalance.Font.Color := clRed;
+    lblBalance.Caption := 'Please Select Signer Account';
+  end
+  else
+  begin
+    lblBalance.Font.Color := clGreen;
+    lblBalance.Caption := Format('%s PASC',
+      [TAccountComp.FormatMoney(Model.SelectedAccounts[PtrInt(
+      cbSignerAccount.Items.Objects[cbSignerAccount.ItemIndex])].Balance)]);
+  end;
+end;
+
+procedure TWIZEnlistAccountForSale_Transaction.btnSearchClick(Sender: TObject);
+var
+  F: TFRMAccountSelect;
+  c: cardinal;
+begin
+  F := TFRMAccountSelect.Create(Self);
+  F.Position := poMainFormCenter;
+  try
+    F.Node := TNode.Node;
+    F.WalletKeys := TWallet.Keys;
+    F.Filters := edtSellerAccount.Tag;
+    if TAccountComp.AccountTxtNumberToAccountNumber(edtSellerAccount.Text, c) then
+      F.DefaultAccount := c;
+    F.AllowSelect := True;
+    if F.ShowModal = mrOk then
+    begin
+      edtSellerAccount.Text := TAccountComp.AccountNumberToAccountTxtNumber(F.GetSelected);
+    end;
+  finally
+    F.Free;
+  end;
+end;
+
+procedure TWIZEnlistAccountForSale_Transaction.OnPresent;
+
+  function GetAccNoWithChecksum(AAccountNumber: cardinal): string;
+  begin
+    Result := TAccountComp.AccountNumberToAccountTxtNumber(AAccountNumber);
+  end;
+
+var
+  acc: TAccount;
+  accNumberwithChecksum: string;
+  totalBalance: int64;
+  i: integer;
+begin
+  cbSignerAccount.Items.BeginUpdate;
+  totalBalance := 0;
+  try
+    cbSignerAccount.Items.Clear;
+    cbSignerAccount.Items.Add('Select Signer Account');
+    for i := Low(Model.SelectedAccounts) to High(Model.SelectedAccounts) do
+    begin
+      acc := Model.SelectedAccounts[i];
+      accNumberwithChecksum := GetAccNoWithChecksum(acc.account);
+      totalBalance := totalBalance + acc.balance;
+      cbSignerAccount.Items.AddObject(accNumberwithChecksum, TObject(i));
+    end;
+  finally
+    cbSignerAccount.Items.EndUpdate;
+  end;
+  cbSignerAccount.ItemIndex := Model.SelectedIndex;
+  cbSignerAccountChange(Self);
+  lblTotalBalanceValue.Caption :=
+    Format('%s PASC', [TAccountComp.FormatMoney(totalBalance)]);
+  edtOpFee.Text := TAccountComp.FormatMoney(TSettings.DefaultFee);
+  edtSalePrice.Text := TAccountComp.FormatMoney(0);
+end;
+
+procedure TWIZEnlistAccountForSale_Transaction.OnNext;
+begin
+  Model.SelectedIndex := cbSignerAccount.ItemIndex;
+  Model.SignerAccount := Model.SelectedAccounts[PtrInt(
+    cbSignerAccount.Items.Objects[cbSignerAccount.ItemIndex])];
+
+  UpdatePath(ptReplaceAllNext, [TWIZEnlistAccountForSale_TransactionPayload,
+    TWIZEnlistAccountForSale_Confirmation]);
+end;
+
+function TWIZEnlistAccountForSale_Transaction.Validate(out message: ansistring): boolean;
+var
+  c: cardinal;
+  i: integer;
+begin
+  Result := True;
+  if cbSignerAccount.ItemIndex < 1 then
+  begin
+    message := 'A signer account must be selected';
+    Result := False;
+    Exit;
+  end;
+
+  if not TAccountComp.TxtToMoney(edtSalePrice.Text, Model.SalePrice) then
+  begin
+    message := 'Invalid price (' + edtSalePrice.Text + ')';
+    Result := False;
+    Exit;
+  end;
+
+  if not (TAccountComp.AccountTxtNumberToAccountNumber(edtSellerAccount.Text, c)) then
+  begin
+    message := 'Invalid seller account (' + edtSellerAccount.Text + ')';
+    Result := False;
+    Exit;
+  end;
+
+  if (c < 0) or (c >= TNode.Node.Bank.AccountsCount) then
+  begin
+    message := 'Invalid seller account (' +
+      TAccountComp.AccountNumberToAccountTxtNumber(c) + ')';
+    Result := False;
+    Exit;
+  end;
+
+  for i := Low(Model.SelectedAccounts) to High(Model.SelectedAccounts) do
+  begin
+    if (Model.SelectedAccounts[i].Account = c) then
+    begin
+      message := 'Seller account cannot be same account';
+      Result := False;
+      Exit;
+    end;
+  end;
+
+  Model.SellerAccount := TNode.Node.Operations.SafeBoxTransaction.account(c);
+
+  if not TAccountComp.TxtToMoney(Trim(edtOpFee.Text), Model.DefaultFee) then
+  begin
+    message := 'Invalid fee value "' + edtOpFee.Text + '"';
+    Result := False;
+    Exit;
+  end;
+
+end;
+
+end.

+ 98 - 0
src/gui/wizards/UWIZEnlistAccountForSale_TransactionPayload.lfm

@@ -0,0 +1,98 @@
+object WIZEnlistAccountForSale_TransactionPayload: TWIZEnlistAccountForSale_TransactionPayload
+  Left = 0
+  Height = 253
+  Top = 0
+  Width = 429
+  ActiveControl = mmoPayload
+  Caption = 'Form1'
+  ClientHeight = 253
+  ClientWidth = 429
+  LCLVersion = '1.8.1.0'
+  Visible = False
+  object grpPayload: TGroupBox
+    Left = 8
+    Height = 244
+    Top = 2
+    Width = 416
+    Anchors = [akTop, akLeft, akRight, akBottom]
+    Caption = 'Enlist Account Transaction Payload'
+    ClientHeight = 224
+    ClientWidth = 412
+    TabOrder = 0
+    object paPayload: TPanel
+      Left = 8
+      Height = 214
+      Top = 2
+      Width = 396
+      Anchors = [akTop, akLeft, akRight, akBottom]
+      BevelOuter = bvNone
+      ClientHeight = 214
+      ClientWidth = 396
+      TabOrder = 0
+      object mmoPayload: TMemo
+        Left = 0
+        Height = 70
+        Top = 136
+        Width = 386
+        TabOrder = 0
+      end
+      object Label1: TLabel
+        Left = 0
+        Height = 15
+        Top = 118
+        Width = 69
+        Caption = 'Payload Data'
+        ParentColor = False
+      end
+      object rbEncryptedWithOldEC: TRadioButton
+        Left = 3
+        Height = 19
+        Top = 0
+        Width = 176
+        Caption = 'Encrypted with old public key'
+        TabOrder = 1
+      end
+      object rbEncryptedWithEC: TRadioButton
+        Left = 3
+        Height = 19
+        Top = 24
+        Width = 181
+        Caption = 'Encrypted with new public key'
+        Checked = True
+        TabOrder = 2
+        TabStop = True
+      end
+      object rbEncryptedWithPassword: TRadioButton
+        Left = 3
+        Height = 19
+        Top = 48
+        Width = 152
+        Caption = 'Encrypted with password'
+        TabOrder = 3
+      end
+      object lblPassword: TLabel
+        Left = 3
+        Height = 15
+        Top = 73
+        Width = 53
+        Caption = 'Password:'
+        ParentColor = False
+      end
+      object rbNotEncrypted: TRadioButton
+        Left = 3
+        Height = 19
+        Top = 94
+        Width = 181
+        Caption = 'Don''t encrypt (public payload)'
+        TabOrder = 4
+      end
+      object edtPassword: TEdit
+        Left = 64
+        Height = 23
+        Top = 70
+        Width = 160
+        TabOrder = 5
+      end
+    end
+  end
+end

+ 82 - 0
src/gui/wizards/UWIZEnlistAccountForSale_TransactionPayload.pas

@@ -0,0 +1,82 @@
+unit UWIZEnlistAccountForSale_TransactionPayload;
+
+{$mode delphi}
+{$modeswitch nestedprocvars}
+
+{ Copyright (c) 2018 by Ugochukwu Mmaduekwe
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
+  ExtCtrls, Buttons, UWizard, UWIZEnlistAccountForSale;
+
+type
+
+  { TWIZEnlistAccountForSale_TransactionPayload }
+
+  TWIZEnlistAccountForSale_TransactionPayload =
+    class(TWizardForm<TWIZEnlistAccountForSaleModel>)
+    edtPassword: TEdit;
+    grpPayload: TGroupBox;
+    Label1: TLabel;
+    lblPassword: TLabel;
+    mmoPayload: TMemo;
+    paPayload: TPanel;
+    rbEncryptedWithOldEC: TRadioButton;
+    rbEncryptedWithEC: TRadioButton;
+    rbEncryptedWithPassword: TRadioButton;
+    rbNotEncrypted: TRadioButton;
+  public
+    procedure OnNext; override;
+    function Validate(out message: ansistring): boolean; override;
+  end;
+
+
+implementation
+
+{$R *.lfm}
+
+{ TWIZEnlistAccountForSale_TransactionPayload }
+
+procedure TWIZEnlistAccountForSale_TransactionPayload.OnNext;
+begin
+  Model.Payload := mmoPayload.Lines.Text;
+  if rbEncryptedWithOldEC.Checked then
+  begin
+    Model.PayloadEncryptionMode := akaEncryptWithOldEC;
+  end
+  else
+  if rbEncryptedWithEC.Checked then
+  begin
+    Model.PayloadEncryptionMode := akaEncryptWithEC;
+  end
+  else
+  if rbEncryptedWithPassword.Checked then
+  begin
+    Model.PayloadEncryptionMode := akaEncryptWithPassword;
+  end
+  else
+  if rbNotEncrypted.Checked then
+  begin
+    Model.PayloadEncryptionMode := akaNotEncrypt;
+  end;
+end;
+
+function TWIZEnlistAccountForSale_TransactionPayload.Validate(
+  out message: ansistring): boolean;
+begin
+  if (not rbNotEncrypted.Checked) and (not rbEncryptedWithEC.Checked) and
+    (not rbEncryptedWithOldEC.Checked) and (not rbEncryptedWithPassword.Checked) then
+  begin
+    message := 'you must select an encryption option for payload';
+    Result := False;
+    Exit;
+  end;
+end;
+
+end.

+ 2 - 0
src/gui/wizards/UWIZTransferAccount_Transaction.pas

@@ -118,6 +118,7 @@ function TWIZTransferAccount_Transaction.Validate(out message: ansistring): bool
 var
   i: integer;
 begin
+  Result := True;
   if cbSignerAccount.ItemIndex < 1 then
   begin
     message := 'A signer account must be selected';
@@ -141,6 +142,7 @@ begin
     begin
       Result := False;
       message := 'new public key is same as selected account public key';
+      Exit;
     end;
   end;
 end;

+ 47 - 1
src/pascalcoin_wallet.lpi

@@ -33,7 +33,7 @@
         <PackageName Value="LCL"/>
       </Item1>
     </RequiredPackages>
-    <Units Count="88">
+    <Units Count="95">
       <Unit0>
         <Filename Value="pascalcoin_wallet.dpr"/>
         <IsPartOfProject Value="True"/>
@@ -499,6 +499,52 @@
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
       </Unit87>
+      <Unit88>
+        <Filename Value="gui\wizards\UWIZEnlistAccountForSale.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit88>
+      <Unit89>
+        <Filename Value="gui\wizards\UWIZEnlistAccountForSale_Confirmation.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZEnlistAccountForSale_Confirmation"/>
+        <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
+      </Unit89>
+      <Unit90>
+        <Filename Value="gui\wizards\UWIZEnlistAccountForSale_Start.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZEnlistAccountForSale_Start"/>
+        <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
+      </Unit90>
+      <Unit91>
+        <Filename Value="gui\wizards\UWIZEnlistAccountForSale_Transaction.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZEnlistAccountForSale_Transaction"/>
+        <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
+      </Unit91>
+      <Unit92>
+        <Filename Value="gui\wizards\UWIZEnlistAccountForSale_TransactionPayload.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZEnlistAccountForSale_TransactionPayload"/>
+        <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
+      </Unit92>
+      <Unit93>
+        <Filename Value="gui\wizards\UWIZEnlistAccountForSale_List.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZEnlistAccountForSale_List"/>
+        <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
+      </Unit93>
+      <Unit94>
+        <Filename Value="gui\wizards\UWIZEnlistAccountForSale_PrivateSaleConfig.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZEnlistAccountForSale_PrivateSaleConfig"/>
+        <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
+      </Unit94>
     </Units>
   </ProjectOptions>
   <CompilerOptions>