Browse Source

add changekey wizard.

some major refactoring in accordance with the new models created.
Ugochukwu Mmaduekwe 7 years ago
parent
commit
4d96bd6247
29 changed files with 2029 additions and 180 deletions
  1. 1 9
      src/gui/UCTRLWallet.lfm
  2. 10 37
      src/gui/UCTRLWallet.pas
  3. 43 23
      src/gui/wizards/UWIZModels.pas
  4. 476 0
      src/gui/wizards/operations/UWIZChangeKey.pas
  5. 50 0
      src/gui/wizards/operations/UWIZChangeKey_ConfirmAccount.lfm
  6. 144 0
      src/gui/wizards/operations/UWIZChangeKey_ConfirmAccount.pas
  7. 48 0
      src/gui/wizards/operations/UWIZChangeKey_Confirmation.lfm
  8. 178 0
      src/gui/wizards/operations/UWIZChangeKey_Confirmation.pas
  9. 52 0
      src/gui/wizards/operations/UWIZChangeKey_EnterKey.lfm
  10. 97 0
      src/gui/wizards/operations/UWIZChangeKey_EnterKey.pas
  11. 65 0
      src/gui/wizards/operations/UWIZChangeKey_SelectKey.lfm
  12. 163 0
      src/gui/wizards/operations/UWIZChangeKey_SelectKey.pas
  13. 63 0
      src/gui/wizards/operations/UWIZChangeKey_SelectOption.lfm
  14. 63 0
      src/gui/wizards/operations/UWIZChangeKey_SelectOption.pas
  15. 453 0
      src/gui/wizards/operations/UWIZConfirmAccount.pas
  16. 1 1
      src/gui/wizards/operations/UWIZEnlistAccountForSale_Start.lfm
  17. 3 5
      src/gui/wizards/operations/UWIZFeeOverride.pas
  18. 2 3
      src/gui/wizards/operations/UWIZPayloadContentOverride.pas
  19. 1 0
      src/gui/wizards/operations/UWIZPayloadOverride.lfm
  20. 3 5
      src/gui/wizards/operations/UWIZPayloadOverride.pas
  21. 1 1
      src/gui/wizards/operations/UWIZPayloadPasswordOverride.lfm
  22. 2 3
      src/gui/wizards/operations/UWIZPayloadPasswordOverride.pas
  23. 7 9
      src/gui/wizards/operations/UWIZSelectSignerOverride.pas
  24. 1 1
      src/gui/wizards/operations/UWIZSendPASC_ConfirmSender.lfm
  25. 39 23
      src/gui/wizards/operations/UWIZSendPASC_ConfirmSender.pas
  26. 0 1
      src/gui/wizards/operations/UWIZSendPASC_Confirmation.lfm
  27. 0 1
      src/gui/wizards/operations/UWIZSendPASC_EnterQuantity.lfm
  28. 2 0
      src/gui/wizards/operations/UWIZTransferAccount.pas
  29. 61 58
      src/pascalcoin_wallet.lpi

+ 1 - 9
src/gui/UCTRLWallet.lfm

@@ -10,7 +10,6 @@ object CTRLWallet: TCTRLWallet
   ClientWidth = 1151
   ClientWidth = 1151
   OnCreate = FormCreate
   OnCreate = FormCreate
   OnResize = FormResize
   OnResize = FormResize
-  LCLVersion = '1.8.2.0'
   Visible = False
   Visible = False
   object PairSplitter1: TPairSplitter
   object PairSplitter1: TPairSplitter
     Left = 0
     Left = 0
@@ -168,14 +167,7 @@ object CTRLWallet: TCTRLWallet
     end
     end
     object miChangeKey: TMenuItem
     object miChangeKey: TMenuItem
       Caption = 'Change Key'
       Caption = 'Change Key'
-      object miTransferAccounts: TMenuItem
-        Caption = 'Transfer Account(s)'
-        OnClick = miTransferAccountsClick
-      end
-      object miChangeAccountsPrivateKey: TMenuItem
-        Caption = 'Change Account(s) Private Key'
-        OnClick = miChangeAccountsPrivateKeyClick
-      end
+      OnClick = miChangeKeyClick
     end
     end
     object miAccountsMarket: TMenuItem
     object miAccountsMarket: TMenuItem
       Caption = 'Account Market'
       Caption = 'Account Market'

+ 10 - 37
src/gui/UCTRLWallet.pas

@@ -9,8 +9,7 @@ interface
 uses
 uses
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, Menus,
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, Menus,
   ExtCtrls, PairSplitter, Buttons, UVisualGrid, UCommon.UI, Generics.Collections,
   ExtCtrls, PairSplitter, Buttons, UVisualGrid, UCommon.UI, Generics.Collections,
-  UAccounts, UDataSources, UNode, UWIZSendPASC, UWIZTransferAccount,
-  UWIZChangeAccountPrivateKey;
+  UAccounts, UDataSources, UNode, UWIZSendPASC, UWIZChangeKey;
 
 
 type
 type
 
 
@@ -33,8 +32,6 @@ type
     miOperationInfo: TMenuItem;
     miOperationInfo: TMenuItem;
     miSendPASC: TMenuItem;
     miSendPASC: TMenuItem;
     miChangeKey: TMenuItem;
     miChangeKey: TMenuItem;
-    miTransferAccounts: TMenuItem;
-    miChangeAccountsPrivateKey: TMenuItem;
     miAccountsMarket: TMenuItem;
     miAccountsMarket: TMenuItem;
     miEnlistAccountsForSale: TMenuItem;
     miEnlistAccountsForSale: TMenuItem;
     miDelistAccountsFromSale: TMenuItem;
     miDelistAccountsFromSale: TMenuItem;
@@ -55,11 +52,10 @@ type
     procedure FormDestroy(Sender: TObject);
     procedure FormDestroy(Sender: TObject);
     procedure FormResize(Sender: TObject);
     procedure FormResize(Sender: TObject);
     procedure miAccountInfoClick(Sender: TObject);
     procedure miAccountInfoClick(Sender: TObject);
+    procedure miChangeKeyClick(Sender: TObject);
     procedure miCopyOphashClick(Sender: TObject);
     procedure miCopyOphashClick(Sender: TObject);
     procedure miOperationInfoClick(Sender: TObject);
     procedure miOperationInfoClick(Sender: TObject);
     procedure miSendPASCClick(Sender: TObject);
     procedure miSendPASCClick(Sender: TObject);
-    procedure miTransferAccountsClick(Sender: TObject);
-    procedure miChangeAccountsPrivateKeyClick(Sender: TObject);
     procedure miEnlistAccountsForSaleClick(Sender: TObject);
     procedure miEnlistAccountsForSaleClick(Sender: TObject);
     procedure miDelistAccountsFromSaleClick(Sender: TObject);
     procedure miDelistAccountsFromSaleClick(Sender: TObject);
   private
   private
@@ -608,10 +604,8 @@ begin
   miAccountInfo.Visible := ASelection.RowCount = 1;
   miAccountInfo.Visible := ASelection.RowCount = 1;
   miSendPASC.Caption :=
   miSendPASC.Caption :=
     IIF(ASelection.RowCount = 1, 'Send PASC', 'Send All PASC');
     IIF(ASelection.RowCount = 1, 'Send PASC', 'Send All PASC');
-  miTransferAccounts.Caption :=
-    IIF(ASelection.RowCount = 1, 'Transfer Account', 'Transfer All Account');
-  miChangeAccountsPrivateKey.Caption :=
-    IIF(ASelection.RowCount = 1, 'Change Account Private Key', 'Change All Account Private Key');
+   miChangeKey.Caption :=
+    IIF(ASelection.RowCount = 1, 'Change Key', 'Change All Key');
   miEnlistAccountsForSale.Caption :=
   miEnlistAccountsForSale.Caption :=
     IIF(ASelection.RowCount = 1, 'Enlist Account For Sale',
     IIF(ASelection.RowCount = 1, 'Enlist Account For Sale',
     'Enlist All Account For Sale');
     'Enlist All Account For Sale');
@@ -655,45 +649,24 @@ begin
     GetAccNoWithoutChecksum);
     GetAccNoWithoutChecksum);
 
 
   model.SendPASC.SelectedAccounts := GetAccounts(AccountNumbersWithoutChecksum);
   model.SendPASC.SelectedAccounts := GetAccounts(AccountNumbersWithoutChecksum);
-  model.SendPASC.SelectedIndex := 0;
   wiz.Start(model);
   wiz.Start(model);
 end;
 end;
 
 
-procedure TCTRLWallet.miTransferAccountsClick(Sender: TObject);
+procedure TCTRLWallet.miChangeKeyClick(Sender: TObject);
 var
 var
   Scoped: TDisposables;
   Scoped: TDisposables;
-  wiz: TWIZTransferAccountWizard;
+  wiz: TWIZChangeKeyWizard;
   model: TWIZOperationsModel;
   model: TWIZOperationsModel;
   AccountNumbersWithoutChecksum: TArray<cardinal>;
   AccountNumbersWithoutChecksum: TArray<cardinal>;
 begin
 begin
-  wiz := Scoped.AddObject(TWIZTransferAccountWizard.Create(nil)) as
-    TWIZTransferAccountWizard;
-  model := TWIZOperationsModel.Create(wiz, omtTransferAccount);
+   wiz := Scoped.AddObject(TWIZChangeKeyWizard.Create(nil)) as
+    TWIZChangeKeyWizard;
+  model := TWIZOperationsModel.Create(wiz, omtChangeKey);
   AccountNumbersWithoutChecksum :=
   AccountNumbersWithoutChecksum :=
     TListTool<variant, cardinal>.Transform(FAccountsGrid.SelectedRows,
     TListTool<variant, cardinal>.Transform(FAccountsGrid.SelectedRows,
     GetAccNoWithoutChecksum);
     GetAccNoWithoutChecksum);
 
 
-  model.TransferAccount.SelectedAccounts := GetAccounts(AccountNumbersWithoutChecksum);
-  model.TransferAccount.SelectedIndex := 0;
-  wiz.Start(model);
-end;
-
-procedure TCTRLWallet.miChangeAccountsPrivateKeyClick(Sender: TObject);
-var
-  Scoped: TDisposables;
-  wiz: TWIZChangeAccountPrivateKeyWizard;
-  model: TWIZOperationsModel;
-  AccountNumbersWithoutChecksum: TArray<cardinal>;
-begin
-  wiz := Scoped.AddObject(TWIZChangeAccountPrivateKeyWizard.Create(nil)) as TWIZChangeAccountPrivateKeyWizard;
-  model := TWIZOperationsModel.Create(wiz, omtChangeAccountPrivateKey);
-
-  AccountNumbersWithoutChecksum :=
-    TListTool<variant, cardinal>.Transform(FAccountsGrid.SelectedRows,
-    GetAccNoWithoutChecksum);
-
-  model.ChangeAccountPrivateKey.SelectedAccounts := GetAccounts(AccountNumbersWithoutChecksum);
-  model.ChangeAccountPrivateKey.SelectedIndex := 0;
+  model.ChangeKey.SelectedAccounts := GetAccounts(AccountNumbersWithoutChecksum);
   wiz.Start(model);
   wiz.Start(model);
 end;
 end;
 
 

+ 43 - 23
src/gui/wizards/UWIZModels.pas

@@ -30,11 +30,12 @@ type
   { TWIZOperationsModel }
   { TWIZOperationsModel }
 
 
   TWIZOperationsModel = class(TComponent)
   TWIZOperationsModel = class(TComponent)
-  public type
+  public
+    type
 
 
     { TModelType }
     { TModelType }
 
 
-    TModelType = (omtSendPasc, omtTransferAccount, omtChangeAccountPrivateKey, omtAddKey);
+    TModelType = (omtAccount, omtSendPasc, omtChangeKey, omtTransferAccount, omtChangeAccountPrivateKey, omtAddKey);
 
 
     { TPayloadEncryptionMode }
     { TPayloadEncryptionMode }
 
 
@@ -44,35 +45,47 @@ type
 
 
     TOperationSigningMode = (akaPrimary, akaSecondary);
     TOperationSigningMode = (akaPrimary, akaSecondary);
 
 
+    { TChangeKeyMode }
+
+    TChangeKeyMode = (akaTransferAccountOwnership, akaChangeAccountPrivateKey);
+
+    { TAccountModel }
+
+    TAccountModel = class(TComponent)
+    public
+      SelectedAccounts: TArray<TAccount>;
+    end;
+
     { TSendPASCModel }
     { TSendPASCModel }
 
 
     TSendPASCModel = class(TComponent)
     TSendPASCModel = class(TComponent)
     public
     public
-      SelectedIndex: integer;
       SingleAmountToSend: int64;
       SingleAmountToSend: int64;
       DestinationAccount: TAccount;
       DestinationAccount: TAccount;
-      SelectedAccounts: TArray<TAccount>;
+      // SelectedAccounts: TArray<TAccount>;
+    end;
+
+    { TChangeKeyModel }
+
+    TChangeKeyModel = class(TComponent)
+    public
+      // SelectedAccounts: TArray<TAccount>;
+      ChangeKeyMode: TChangeKeyMode;
     end;
     end;
 
 
     { TTransferAccountModel }
     { TTransferAccountModel }
 
 
     TTransferAccountModel = class(TComponent)
     TTransferAccountModel = class(TComponent)
     public
     public
-      NewPublicKey : string;
-      SelectedIndex: integer;
       AccountKey: TAccountKey;
       AccountKey: TAccountKey;
-      SelectedAccounts: TArray<TAccount>;
     end;
     end;
 
 
     { TChangeAccountPrivateKeyModel }
     { TChangeAccountPrivateKeyModel }
 
 
     TChangeAccountPrivateKeyModel = class(TComponent)
     TChangeAccountPrivateKeyModel = class(TComponent)
     public
     public
-      NewPublicKey : string;
-      SelectedIndex, PrivateKeySelectedIndex: integer;
+      SelectedIndex: integer;
       NewWalletKey: TWalletKey;
       NewWalletKey: TWalletKey;
-      EncodedPayload: TRawBytes;
-      SelectedAccounts: TArray<TAccount>;
     end;
     end;
 
 
     { TFeeModel }
     { TFeeModel }
@@ -88,6 +101,8 @@ type
     public
     public
       OperationSigningMode: TOperationSigningMode;
       OperationSigningMode: TOperationSigningMode;
       SignerAccount: TAccount;
       SignerAccount: TAccount;
+      SignerCandidates: TArray<TAccount>;
+      SelectedIndex: integer;
     end;
     end;
 
 
     { TPayloadModel }
     { TPayloadModel }
@@ -95,28 +110,32 @@ type
     TPayloadModel = class(TComponent)
     TPayloadModel = class(TComponent)
     public
     public
       HasPayload: boolean;
       HasPayload: boolean;
-      Content, Password : string;
+      Content, Password: string;
       Mode: TPayloadEncryptionMode;
       Mode: TPayloadEncryptionMode;
       EncodedBytes: TRawBytes;
       EncodedBytes: TRawBytes;
     end;
     end;
 
 
   private
   private
     FModelType: TModelType;
     FModelType: TModelType;
-    FSendPASC : TSendPASCModel;
-    FTransferAccount : TTransferAccountModel;
-    FChangeAccountPrivateKey : TChangeAccountPrivateKeyModel;
-    FFee : TFeeModel;
-    FSigner : TSignerModel;
-    FPayload : TPayloadModel;
+    FAccount: TAccountModel;
+    FSendPASC: TSendPASCModel;
+    FChangeKey: TChangeKeyModel;
+    FTransferAccount: TTransferAccountModel;
+    FChangeAccountPrivateKey: TChangeAccountPrivateKeyModel;
+    FFee: TFeeModel;
+    FSigner: TSignerModel;
+    FPayload: TPayloadModel;
   public
   public
     constructor Create(AOwner: TComponent; AType: TModelType); overload;
     constructor Create(AOwner: TComponent; AType: TModelType); overload;
     property ModelType: TModelType read FModelType;
     property ModelType: TModelType read FModelType;
+    property Account: TAccountModel read FAccount;
     property SendPASC: TSendPASCModel read FSendPASC;
     property SendPASC: TSendPASCModel read FSendPASC;
+    property ChangeKey: TChangeKeyModel read FChangeKey;
     property TransferAccount: TTransferAccountModel read FTransferAccount;
     property TransferAccount: TTransferAccountModel read FTransferAccount;
-    property ChangeAccountPrivateKey : TChangeAccountPrivateKeyModel read FChangeAccountPrivateKey;
-    property Fee : TFeeModel read FFee;
-    property Signer : TSignerModel read FSigner;
-    property Payload : TPayloadModel read FPayload;
+    property ChangeAccountPrivateKey: TChangeAccountPrivateKeyModel read FChangeAccountPrivateKey;
+    property Fee: TFeeModel read FFee;
+    property Signer: TSignerModel read FSigner;
+    property Payload: TPayloadModel read FPayload;
   end;
   end;
 
 
 implementation
 implementation
@@ -125,10 +144,11 @@ constructor TWIZOperationsModel.Create(AOwner: TComponent; AType: TWIZOperations
 begin
 begin
   inherited Create(AOwner);
   inherited Create(AOwner);
   FModelType := AType;
   FModelType := AType;
+  FAccount := TAccountModel.Create(Self);
   FSendPASC := TSendPASCModel.Create(Self);
   FSendPASC := TSendPASCModel.Create(Self);
+  FChangeKey := TChangeKeyModel.Create(Self);
   FTransferAccount := TTransferAccountModel.Create(Self);
   FTransferAccount := TTransferAccountModel.Create(Self);
   FChangeAccountPrivateKey := TChangeAccountPrivateKeyModel.Create(Self);
   FChangeAccountPrivateKey := TChangeAccountPrivateKeyModel.Create(Self);
-  FChangeAccountPrivateKey := TChangeAccountPrivateKeyModel.Create(Self);
   FFee := TFeeModel.Create(Self);
   FFee := TFeeModel.Create(Self);
   FSigner := TSignerModel.Create(Self);
   FSigner := TSignerModel.Create(Self);
   FPayload := TPayloadModel.Create(Self);
   FPayload := TPayloadModel.Create(Self);

+ 476 - 0
src/gui/wizards/operations/UWIZChangeKey.pas

@@ -0,0 +1,476 @@
+unit UWIZChangeKey;
+
+{$mode delphi}
+
+{ Copyright (c) 2018 Sphere 10 Software (http://www.sphere10.com/)
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+
+  Acknowledgements:
+  Ugochukwu Mmaduekwe - main developer
+  Herman Schoenfeld - designer
+}
+
+interface
+
+uses
+  Classes, SysUtils, Forms, Dialogs, UCrypto, UCommon, UWizard, UAccounts, LCLType, UWIZModels;
+
+type
+
+  { TWIZChangeKeyWizard }
+
+  TWIZChangeKeyWizard = class(TWizard<TWIZOperationsModel>)
+  private
+    function UpdatePayload(const SenderAccount: TAccount; var errors: string): boolean;
+    function UpdateOperationOptions(var errors: string): boolean;
+    function UpdateOpChangeKey(const TargetAccount: TAccount; var SignerAccount: TAccount; var NewPublicKey: TAccountKey; var errors: ansistring): boolean;
+    procedure WIZChangeKeyOwnership();
+  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,
+  UWIZChangeKey_ConfirmAccount,
+  UWIZChangeKey_Confirmation;
+
+{ TWIZChangeKeyWizard }
+
+function TWIZChangeKeyWizard.UpdatePayload(const SenderAccount: TAccount; var errors: string): boolean;
+var
+  valid: boolean;
+  payload_encrypted, payload_u: string;
+  account: TAccount;
+  public_key: TAccountKey;
+begin
+  valid := False;
+  payload_encrypted := '';
+  Model.Payload.EncodedBytes := '';
+  errors := 'Unknown error';
+  payload_u := Model.Payload.Content;
+
+  try
+    if (payload_u = '') then
+    begin
+      valid := True;
+      Exit;
+    end;
+    case Model.Payload.Mode of
+
+      akaEncryptWithSender:
+      begin
+        // Use sender
+        errors := 'Error encrypting';
+        account := SenderAccount;
+        payload_encrypted := ECIESEncrypt(account.accountInfo.accountKey, payload_u);
+        valid := payload_encrypted <> '';
+      end;
+
+      akaEncryptWithReceiver:
+      begin
+
+        case Model.ChangeKey.ChangeKeyMode of
+          akaTransferAccountOwnership:
+          begin
+            public_key := Model.TransferAccount.AccountKey;
+          end;
+
+          akaChangeAccountPrivateKey:
+          begin
+            public_key := Model.ChangeAccountPrivateKey.NewWalletKey.AccountKey;
+          end;
+
+        end;
+
+        errors := 'Public key: ' + 'Error encrypting';
+
+        if Model.TransferAccount.AccountKey.EC_OpenSSL_NID <>
+          CT_Account_NUL.accountInfo.accountKey.EC_OpenSSL_NID then
+        begin
+          payload_encrypted := ECIESEncrypt(public_key, payload_u);
+          valid := payload_encrypted <> '';
+        end
+        else
+        begin
+          valid := False;
+          errors := 'Selected private key is not valid to encode';
+          exit;
+        end;
+      end;
+
+      akaEncryptWithPassword:
+      begin
+        payload_encrypted := TAESComp.EVP_Encrypt_AES256(
+          payload_u, Model.Payload.Password);
+        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.Payload.EncodedBytes := payload_encrypted;
+    Result := valid;
+  end;
+
+end;
+
+function TWIZChangeKeyWizard.UpdateOperationOptions(var errors: string): boolean;
+var
+  iAcc, iWallet: integer;
+  sender_account, signer_account: TAccount;
+  publicKey: TAccountKey;
+  wk: TWalletKey;
+  e: string;
+  amount: int64;
+begin
+  Result := False;
+  errors := '';
+  if not Assigned(TWallet.Keys) then
+  begin
+    errors := 'No wallet keys';
+    Exit;
+  end;
+
+  if Length(Model.ChangeKey.SelectedAccounts) = 0 then
+  begin
+    errors := 'No sender account';
+    Exit;
+  end
+  else
+  begin
+
+    for iAcc := Low(Model.ChangeKey.SelectedAccounts) to High(Model.ChangeKey.SelectedAccounts) do
+    begin
+      sender_account := Model.ChangeKey.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 := UpdateOpChangeKey(Model.ChangeKey.SelectedAccounts[0], signer_account,
+    publicKey, errors);
+  UpdatePayload(sender_account, e);
+end;
+
+function TWIZChangeKeyWizard.UpdateOpChangeKey(const TargetAccount: TAccount; var SignerAccount: TAccount; var NewPublicKey: TAccountKey; var errors: ansistring): boolean;
+
+begin
+  Result := False;
+  errors := '';
+  try
+
+    case Model.ChangeKey.ChangeKeyMode of
+      akaTransferAccountOwnership:
+      begin
+        NewPublicKey := Model.TransferAccount.AccountKey;
+      end;
+
+      akaChangeAccountPrivateKey:
+      begin
+        NewPublicKey := Model.ChangeAccountPrivateKey.NewWalletKey.AccountKey;
+      end;
+
+    end;
+
+    if TNode.Node.Bank.SafeBox.CurrentProtocol >= 1 then
+    begin
+      // Signer:
+      SignerAccount := Model.Signer.SignerAccount;
+      if (TAccountComp.IsAccountLocked(SignerAccount.accountInfo,
+        TNode.Node.Bank.BlocksCount)) then
+      begin
+        errors := 'Signer account ' + TAccountComp.AccountNumberToAccountTxtNumber(
+          SignerAccount.account) + ' is locked until block ' + IntToStr(
+          SignerAccount.accountInfo.locked_until_block);
+        exit;
+      end;
+      if (not TAccountComp.EqualAccountKeys(
+        SignerAccount.accountInfo.accountKey, TargetAccount.accountInfo.accountKey)) then
+      begin
+        errors := 'Signer account ' + TAccountComp.AccountNumberToAccountTxtNumber(
+          SignerAccount.account) + ' is not owner of account ' +
+          TAccountComp.AccountNumberToAccountTxtNumber(TargetAccount.account);
+        exit;
+      end;
+    end
+    else
+    begin
+      SignerAccount := TargetAccount;
+    end;
+
+    if (TAccountComp.EqualAccountKeys(TargetAccount.accountInfo.accountKey,
+      NewPublicKey)) then
+    begin
+      errors := 'New key is same as current key';
+      exit;
+    end;
+
+  finally
+    Result := errors = '';
+  end;
+end;
+
+procedure TWIZChangeKeyWizard.WIZChangeKeyOwnership();
+var
+  _V2, dooperation: boolean;
+  iAcc, i: integer;
+  _totalamount, _totalfee, _totalSignerFee, _amount, _fee: int64;
+  _signer_n_ops: cardinal;
+  operationstxt, operation_to_string, errors, auxs: string;
+  wk: TWalletKey;
+  ops: TOperationsHashTree;
+  op: TPCOperation;
+  account, signerAccount: 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.ChangeKey.SelectedAccounts) to High(Model.ChangeKey.SelectedAccounts) do
+    begin
+      loop_start:
+        op := nil;
+      account := Model.ChangeKey.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.Fee.DefaultFee) then
+        _fee := Model.Fee.DefaultFee
+      else
+        _fee := account.balance;
+
+      if not UpdateOpChangeKey(account, signerAccount, _newOwnerPublicKey, errors) then
+      begin
+        raise Exception.Create(errors);
+      end;
+      if _V2 then
+      begin
+        // must ensure is Signer account last if included in sender accounts (not necessarily ordered enumeration)
+        if (iAcc < Length(Model.ChangeKey.SelectedAccounts) - 1) and
+          (account.account = signerAccount.account) then
+        begin
+          TArrayTool<TAccount>.Swap(Model.ChangeKey.SelectedAccounts, iAcc,
+            Length(Model.ChangeKey.SelectedAccounts) - 1); // ensure signer account processed last
+          // TArrayTool_internal<Cardinal>.Swap(_senderAccounts, iAcc, Length(_senderAccounts) - 1);
+          goto loop_start; // TODO: remove ugly hack with refactoring!
+        end;
+
+        // Maintain correct signer fee distribution
+        if uint64(_totalSignerFee) >= signerAccount.balance then
+          _fee := 0
+        else if signerAccount.balance - uint64(_totalSignerFee) >
+          uint64(Model.Fee.DefaultFee) then
+          _fee := Model.Fee.DefaultFee
+        else
+          _fee := signerAccount.balance - uint64(_totalSignerFee);
+        op := TOpChangeKeySigned.Create(signerAccount.account,
+          signerAccount.n_operation + _signer_n_ops + 1, account.account,
+          wk.PrivateKey, _newOwnerPublicKey, _fee, Model.Payload.EncodedBytes);
+        Inc(_signer_n_ops);
+        Inc(_totalSignerFee, _fee);
+      end
+      else
+      begin
+        op := TOpChangeKey.Create(account.account, account.n_operation +
+          1, account.account, wk.PrivateKey, _newOwnerPublicKey, _fee, Model.Payload.EncodedBytes);
+      end;
+      Inc(_totalfee, _fee);
+      operationstxt :=
+        'Change private key to ' + TAccountComp.GetECInfoTxt(
+        _newOwnerPublicKey.EC_OpenSSL_NID);
+
+      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.ChangeKey.SelectedAccounts) > 1) then
+    begin
+      auxs := '';
+      if Application.MessageBox(
+        PChar('Execute ' + IntToStr(Length(Model.ChangeKey.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 TWIZChangeKeyWizard.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner, [TWIZChangeKey_ConfirmAccount,
+    TWIZChangeKey_Confirmation]);
+  TitleText := 'Change Key';
+  FinishText := 'Change Key';
+end;
+
+function TWIZChangeKeyWizard.DetermineHasNext: boolean;
+begin
+  Result := not (CurrentScreen is TWIZChangeKey_Confirmation);
+end;
+
+function TWIZChangeKeyWizard.DetermineHasPrevious: boolean;
+begin
+  Result := inherited DetermineHasPrevious;
+end;
+
+function TWIZChangeKeyWizard.FinishRequested(out message: ansistring): boolean;
+begin
+  // Execute the Change Key Action here
+  try
+    Result := True;
+    WIZChangeKeyOwnership();
+  except
+    On E: Exception do
+    begin
+      Result := False;
+      message := E.ToString;
+    end;
+  end;
+end;
+
+function TWIZChangeKeyWizard.CancelRequested(out message: ansistring): boolean;
+begin
+  Result := True;
+end;
+
+end.

+ 50 - 0
src/gui/wizards/operations/UWIZChangeKey_ConfirmAccount.lfm

@@ -0,0 +1,50 @@
+object WIZChangeKey_ConfirmAccount: TWIZChangeKey_ConfirmAccount
+  Left = 0
+  Height = 261
+  Top = 0
+  Width = 429
+  Caption = 'ChangeKey_ConfirmAccount'
+  ClientHeight = 261
+  ClientWidth = 429
+  LCLVersion = '1.8.2.0'
+  Visible = False
+  object gpChangeKey: TGroupBox
+    Left = 4
+    Height = 256
+    Top = 0
+    Width = 420
+    Anchors = [akTop, akLeft, akRight, akBottom]
+    Caption = 'Change Key Accounts'
+    ClientHeight = 236
+    ClientWidth = 416
+    TabOrder = 0
+    object paGrid: TPanel
+      Left = 8
+      Height = 192
+      Top = 24
+      Width = 400
+      Anchors = [akTop, akLeft, akRight, akBottom]
+      BevelOuter = bvNone
+      Caption = 'GRID PANEL'
+      TabOrder = 0
+    end
+    object lblTotalBalances: TLabel
+      Left = 8
+      Height = 15
+      Top = 0
+      Width = 176
+      Caption = 'Selected Accounts Total Balance: '
+      ParentColor = False
+    end
+    object lblTotalBalanceValue: TLabel
+      Left = 192
+      Height = 15
+      Top = 0
+      Width = 70
+      Caption = 'Total Balance'
+      Font.Color = clGreen
+      ParentColor = False
+      ParentFont = False
+    end
+  end
+end

+ 144 - 0
src/gui/wizards/operations/UWIZChangeKey_ConfirmAccount.pas

@@ -0,0 +1,144 @@
+unit UWIZChangeKey_ConfirmAccount;
+
+{$mode delphi}
+
+{ Copyright (c) 2018 Sphere 10 Software (http://www.sphere10.com/)
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+
+  Acknowledgements:
+  Ugochukwu Mmaduekwe - main developer
+  Herman Schoenfeld - designer
+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
+  ExtCtrls, UVisualGrid, UCommon.Data, UCellRenderers,
+  UWizard, UWIZSendPASC, UWIZChangeKey_SelectOption, UWIZChangeKey_Confirmation, UWIZModels;
+
+type
+
+  { TWIZChangeKey_ConfirmAccount }
+
+  TWIZChangeKey_ConfirmAccount = class(TWizardForm<TWIZOperationsModel>)
+    gpChangeKey: TGroupBox;
+    lblTotalBalances: TLabel;
+    lblTotalBalanceValue: TLabel;
+    paGrid: TPanel;
+  private
+    FChangeKeyGrid: TVisualGrid;
+  public
+    procedure OnPresent; override;
+    procedure OnNext; override;
+    function Validate(out message: ansistring): boolean; override;
+  end;
+
+
+
+implementation
+
+{$R *.lfm}
+
+uses UAccounts, UCoreUtils, UDataSources, UCommon, UCommon.UI, Generics.Collections;
+
+type
+
+  { TAccountChangeKeyDataSource }
+
+  TAccountChangeKeyDataSource = class(TAccountsDataSourceBase)
+  private
+    FModel: TWIZOperationsModel.TChangeKeyModel;
+  public
+    property Model: TWIZOperationsModel.TChangeKeyModel read FModel write FModel;
+    procedure FetchAll(const AContainer: TList<TAccount>); override;
+  end;
+
+{ TWIZChangeKey_ConfirmAccount }
+
+procedure TWIZChangeKey_ConfirmAccount.OnPresent;
+var
+  Data: TAccountChangeKeyDataSource;
+  i: integer;
+  acc: TAccount;
+  totalBalance: int64;
+begin
+  FChangeKeyGrid := TVisualGrid.Create(Self);
+  FChangeKeyGrid.CanSearch := False;
+  FChangeKeyGrid.SortMode := smMultiColumn;
+  FChangeKeyGrid.FetchDataInThread := False;
+  FChangeKeyGrid.AutoPageSize := True;
+  FChangeKeyGrid.SelectionType := stNone;
+  FChangeKeyGrid.Options := [vgoColAutoFill, vgoColSizing, vgoSortDirectionAllowNone, vgoAutoHidePaging];
+  with FChangeKeyGrid.AddColumn('Account') do
+  begin
+    StretchedToFill := True;
+    Binding := 'AccountNumber';
+    SortBinding := 'AccountNumber';
+    DisplayBinding := 'Account';
+    Width := 100;
+    HeaderFontStyles := [fsBold];
+    DataFontStyles := [fsBold];
+    Filters := SORTABLE_NUMERIC_FILTER;
+  end;
+  with FChangeKeyGrid.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 := TAccountChangeKeyDataSource.Create(FChangeKeyGrid);
+  Data.Model := Model.ChangeKey;
+  FChangeKeyGrid.DataSource := Data;
+  paGrid.AddControlDockCenter(FChangeKeyGrid);
+
+  totalBalance := 0;
+  for i := Low(Model.ChangeKey.SelectedAccounts) to High(Model.ChangeKey.SelectedAccounts) do
+  begin
+    acc := Model.ChangeKey.SelectedAccounts[i];
+    totalBalance := totalBalance + acc.balance;
+  end;
+
+  lblTotalBalanceValue.Caption :=
+    Format('%s PASC', [TAccountComp.FormatMoney(totalBalance)]);
+end;
+
+procedure TWIZChangeKey_ConfirmAccount.OnNext;
+begin
+  UpdatePath(ptReplaceAllNext, [TWIZChangeKey_SelectOption, TWIZChangeKey_Confirmation]);
+end;
+
+function TWIZChangeKey_ConfirmAccount.Validate(out message: ansistring): boolean;
+begin
+  Result := True;
+  // get signer accounts from selected accounts
+  Model.Signer.SignerCandidates := TCoreTool.GetSignerCandidates(Length(Model.ChangeKey.SelectedAccounts), Model.ChangeKey.SelectedAccounts);
+
+  if Length(Model.Signer.SignerCandidates) < 1 then
+  begin
+    Result := False;
+    message := 'no valid signer account was found.';
+  end;
+
+end;
+
+{ TAccountChangeKeyDataSource }
+
+procedure TAccountChangeKeyDataSource.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.

+ 48 - 0
src/gui/wizards/operations/UWIZChangeKey_Confirmation.lfm

@@ -0,0 +1,48 @@
+object WIZChangeKey_Confirmation: TWIZChangeKey_Confirmation
+  Left = 0
+  Height = 253
+  Top = 0
+  Width = 429
+  Caption = 'WIZChangeKey_Confirmation'
+  ClientHeight = 253
+  ClientWidth = 429
+  LCLVersion = '1.8.2.0'
+  Visible = False
+  object GroupBox1: TGroupBox
+    Left = 10
+    Height = 232
+    Top = 8
+    Width = 408
+    Anchors = [akTop, akLeft, akRight, akBottom]
+    Caption = 'Confirm Change Key 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 lblSgnAcc: TLabel
+      Left = 112
+      Height = 15
+      Top = 10
+      Width = 53
+      Caption = 'lblSgnAcc'
+      ParentColor = False
+    end
+    object Label1: TLabel
+      Left = 8
+      Height = 15
+      Top = 10
+      Width = 84
+      Caption = 'Signer Account:'
+      ParentColor = False
+    end
+  end
+end

+ 178 - 0
src/gui/wizards/operations/UWIZChangeKey_Confirmation.pas

@@ -0,0 +1,178 @@
+unit UWIZChangeKey_Confirmation;
+
+{$mode delphi}
+{$modeswitch nestedprocvars}
+
+{ Copyright (c) 2018 Sphere 10 Software (http://www.sphere10.com/)
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+
+  Acknowledgements:
+  Ugochukwu Mmaduekwe - main developer
+  Herman Schoenfeld - designer
+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
+  ExtCtrls, UVisualGrid, UCellRenderers, UCommon.Data, UWizard, UWIZChangeKey, UWIZModels;
+
+type
+
+  { TWIZChangeKey_Confirmation }
+
+  TWIZChangeKey_Confirmation = class(TWizardForm<TWIZOperationsModel>)
+    GroupBox1: TGroupBox;
+    Label1: TLabel;
+    lblSgnAcc: TLabel;
+    paGrid: TPanel;
+  private
+    FChangeKeyGrid: TVisualGrid;
+  public
+    procedure OnPresent; override;
+  end;
+
+
+implementation
+
+{$R *.lfm}
+
+uses UAccounts, UCrypto, UDataSources, UCommon, UCommon.UI, Generics.Collections;
+
+type
+
+  { TAccountChangeKeyDataSource }
+
+  TAccountChangeKeyDataSource = class(TAccountsDataSourceBase)
+  private
+    FModel: TWIZOperationsModel;
+  protected
+    function GetColumns: TDataColumns; override;
+  public
+    property Model: TWIZOperationsModel read FModel write FModel;
+    procedure FetchAll(const AContainer: TList<TAccount>); override;
+    function GetItemField(constref AItem: TAccount; const ABindingName: ansistring): variant; override;
+  end;
+
+{ TWIZChangeKey_Confirmation }
+
+procedure TWIZChangeKey_Confirmation.OnPresent;
+var
+  Data: TAccountChangeKeyDataSource;
+begin
+  FChangeKeyGrid := TVisualGrid.Create(Self);
+  FChangeKeyGrid.CanSearch := False;
+  FChangeKeyGrid.SortMode := smMultiColumn;
+  FChangeKeyGrid.FetchDataInThread := False;
+  FChangeKeyGrid.AutoPageSize := True;
+  FChangeKeyGrid.SelectionType := stNone;
+  FChangeKeyGrid.Options := [vgoColAutoFill, vgoColSizing, vgoSortDirectionAllowNone, vgoAutoHidePaging];
+
+  with FChangeKeyGrid.AddColumn('Account') do
+  begin
+    Binding := 'Account';
+    Filters := SORTABLE_NUMERIC_FILTER;
+    Width := 100;
+    HeaderFontStyles := [fsBold];
+    DataFontStyles := [fsBold];
+  end;
+
+  with FChangeKeyGrid.AddColumn('Current Key') do
+  begin
+    Binding := 'CurrentKey';
+    Filters := SORTABLE_TEXT_FILTER;
+    Width := 100;
+  end;
+
+  with FChangeKeyGrid.AddColumn('New Key') do
+  begin
+    Binding := 'NewKey';
+    Filters := SORTABLE_TEXT_FILTER;
+    Width := 100;
+  end;
+
+  with FChangeKeyGrid.AddColumn('Fee') do
+  begin
+    Filters := SORTABLE_TEXT_FILTER;
+    Width := 100;
+  end;
+
+  Data := TAccountChangeKeyDataSource.Create(FChangeKeyGrid);
+  Data.Model := Model;
+  FChangeKeyGrid.DataSource := Data;
+  paGrid.AddControlDockCenter(FChangeKeyGrid);
+  lblSgnAcc.Caption := TAccountComp.AccountNumberToAccountTxtNumber(Model.Signer.SignerAccount.account);
+end;
+
+{ TAccountChangeKeyDataSource }
+
+function TAccountChangeKeyDataSource.GetColumns: TDataColumns;
+begin
+  Result := TDataColumns.Create(
+    TDataColumn.From('Account'),
+    TDataColumn.From('CurrentKey'),
+    TDataColumn.From('NewKey'),
+    TDataColumn.From('Fee')
+    );
+end;
+
+function TAccountChangeKeyDataSource.GetItemField(constref AItem: TAccount; const ABindingName: ansistring): variant;
+var
+  index: integer;
+begin
+  if ABindingName = 'Account' then
+    Result := TAccountComp.AccountNumberToAccountTxtNumber(AItem.account)
+  else if ABindingName = 'Fee' then
+    Result := TAccountComp.FormatMoney(Model.Fee.SingleOperationFee)
+  else
+  begin
+    case Model.ChangeKey.ChangeKeyMode of
+      akaTransferAccountOwnership:
+      begin
+        if ABindingName = 'CurrentKey' then
+          Result := TAccountComp.AccountPublicKeyExport(AItem.accountInfo.accountKey)
+        else if ABindingName = 'NewKey' then
+          Result := TAccountComp.AccountPublicKeyExport(Model.TransferAccount.AccountKey)
+        else
+          raise Exception.Create(Format('Field not found [%s]', [ABindingName]));
+      end;
+
+      akaChangeAccountPrivateKey:
+      begin
+        if ABindingName = 'CurrentKey' then
+          { TODO : Check how to get the wallet name an account is in }
+          Result := '??? unknown'
+        else if ABindingName = 'NewKey' then
+        begin
+          Result := IIF(Model.ChangeAccountPrivateKey.NewWalletKey.Name = '',
+            TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(
+            Model.ChangeAccountPrivateKey.NewWalletKey.AccountKey)), Model.ChangeAccountPrivateKey.NewWalletKey.Name);
+          if not Assigned(Model.ChangeAccountPrivateKey.NewWalletKey.PrivateKey) then
+          begin
+            Result := Result + '(*)';
+          end;
+        end
+        else
+          raise Exception.Create(Format('Field not found [%s]', [ABindingName]));
+      end;
+
+    end;
+  end;
+
+end;
+
+
+procedure TAccountChangeKeyDataSource.FetchAll(const AContainer: TList<TAccount>);
+var
+  i: integer;
+begin
+  for i := Low(Model.ChangeKey.SelectedAccounts) to High(Model.ChangeKey.SelectedAccounts) do
+  begin
+    AContainer.Add(Model.ChangeKey.SelectedAccounts[i]);
+  end;
+end;
+
+
+end.

+ 52 - 0
src/gui/wizards/operations/UWIZChangeKey_EnterKey.lfm

@@ -0,0 +1,52 @@
+object WIZChangeKey_EnterKey: TWIZChangeKey_EnterKey
+  Left = 0
+  Height = 253
+  Top = 0
+  Width = 429
+  ActiveControl = chkChooseFee
+  Caption = 'ChangeKey_EnterKey'
+  ClientHeight = 253
+  ClientWidth = 429
+  Visible = False
+  object gbNewPublicKey: TGroupBox
+    Left = 16
+    Height = 232
+    Top = 8
+    Width = 400
+    Caption = 'New Public Key'
+    ClientHeight = 212
+    ClientWidth = 396
+    TabOrder = 0
+    object lblPublicKeyNotice: TLabel
+      Left = 8
+      Height = 30
+      Top = 7
+      Width = 348
+      Caption = 'Please enter the public key of the new owner who will control this '#13#10'account'
+      ParentColor = False
+    end
+    object chkChooseFee: TCheckBox
+      Left = 8
+      Height = 19
+      Top = 152
+      Width = 226
+      Caption = 'Let me choose fee''s for this transaction'
+      TabOrder = 0
+    end
+    object chkAttachPayload: TCheckBox
+      Left = 8
+      Height = 19
+      Top = 184
+      Width = 303
+      Caption = 'I want to attach a message payload to this transaction'
+      TabOrder = 1
+    end
+    object mmoNewPrivateKey: TMemo
+      Left = 8
+      Height = 102
+      Top = 42
+      Width = 376
+      TabOrder = 2
+    end
+  end
+end

+ 97 - 0
src/gui/wizards/operations/UWIZChangeKey_EnterKey.pas

@@ -0,0 +1,97 @@
+unit UWIZChangeKey_EnterKey;
+
+{$mode delphi}
+{$modeswitch nestedprocvars}
+
+{ Copyright (c) 2018 Sphere 10 Software (http://www.sphere10.com/)
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+
+  Acknowledgements:
+  Ugochukwu Mmaduekwe - main developer
+  Herman Schoenfeld - designer
+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
+  ExtCtrls, Buttons, UCommon, UCommon.Collections, UWallet,
+  UFRMAccountSelect, UNode, UWizard, UWIZFeeOverride, UWIZPayloadOverride, UWIZSelectSignerOverride,
+  UWIZChangeKey_Confirmation, UWIZModels;
+
+type
+
+  { TWIZChangeKey_EnterKey }
+
+  TWIZChangeKey_EnterKey = class(TWizardForm<TWIZOperationsModel>)
+    chkChooseFee: TCheckBox;
+    chkAttachPayload: TCheckBox;
+    gbNewPublicKey: TGroupBox;
+    lblPublicKeyNotice: TLabel;
+    mmoNewPrivateKey: TMemo;
+
+  public
+    procedure OnNext; override;
+    function Validate(out message: ansistring): boolean; override;
+  end;
+
+
+implementation
+
+{$R *.lfm}
+
+uses
+  UAccounts, UUserInterface, USettings;
+
+{ TWIZChangeKey_EnterKey }
+
+procedure TWIZChangeKey_EnterKey.OnNext;
+begin
+  Model.Payload.HasPayload := chkAttachPayload.Checked;
+
+  if chkChooseFee.Checked then
+  begin
+    UpdatePath(ptReplaceAllNext, [TWIZFeeOverride, TWIZChangeKey_Confirmation]);
+  end
+  else
+  begin
+    Model.Fee.SingleOperationFee := TSettings.DefaultFee;
+    if Model.Payload.HasPayload then
+    begin
+      UpdatePath(ptReplaceAllNext, [TWIZPayloadOverride, TWIZChangeKey_Confirmation]);
+    end
+    else
+    begin
+      UpdatePath(ptReplaceAllNext, [TWIZSelectSignerOverride, TWIZChangeKey_Confirmation]);
+    end;
+  end;
+end;
+
+function TWIZChangeKey_EnterKey.Validate(out message: ansistring): boolean;
+var
+  tempAccountKey: TAccountKey;
+  i: Integer;
+begin
+  Result := True;
+  if not TAccountComp.AccountKeyFromImport(mmoNewPrivateKey.Lines.Text,
+    tempAccountKey, message) then
+  begin
+    Result := False;
+    Exit;
+  end;
+  for i := Low(Model.ChangeKey.SelectedAccounts) to High(Model.ChangeKey.SelectedAccounts) do
+  begin
+    if TAccountComp.EqualAccountKeys(Model.ChangeKey.SelectedAccounts[i].accountInfo.accountKey,
+      tempAccountKey) then
+    begin
+      Result := False;
+      message := 'New key is same as current key';
+      Exit;
+    end;
+  end;
+  Model.TransferAccount.AccountKey := tempAccountKey;
+end;
+
+end.

+ 65 - 0
src/gui/wizards/operations/UWIZChangeKey_SelectKey.lfm

@@ -0,0 +1,65 @@
+object WIZChangeKey_SelectKey: TWIZChangeKey_SelectKey
+  Left = 0
+  Height = 253
+  Top = 0
+  Width = 429
+  Caption = 'WIZChangeKey_SelectKey'
+  ClientHeight = 253
+  ClientWidth = 429
+  LCLVersion = '1.8.2.0'
+  Visible = False
+  object gbNewPrivateKey: TGroupBox
+    Left = 16
+    Height = 232
+    Top = 8
+    Width = 400
+    Caption = 'New Private Key'
+    ClientHeight = 212
+    ClientWidth = 396
+    TabOrder = 0
+    object lblPrivateKeyNote: TLabel
+      Left = 8
+      Height = 15
+      Top = 8
+      Width = 329
+      Caption = 'Please select the new private key to update your accounts with'
+      ParentColor = False
+    end
+    object cbNewPrivateKey: TComboBox
+      Left = 8
+      Height = 23
+      Top = 40
+      Width = 168
+      ItemHeight = 15
+      OnChange = cbNewPrivateKeyChange
+      TabOrder = 0
+      Text = 'Select Private Key'
+    end
+    object lblKeyName: TLabel
+      Left = 192
+      Height = 15
+      Top = 45
+      Width = 128
+      Caption = 'Please Select Private Key'
+      Font.Color = clRed
+      ParentColor = False
+      ParentFont = False
+    end
+    object chkChooseFee: TCheckBox
+      Left = 8
+      Height = 19
+      Top = 80
+      Width = 226
+      Caption = 'Let me choose fee''s for this transaction'
+      TabOrder = 1
+    end
+    object chkAttachPayload: TCheckBox
+      Left = 8
+      Height = 19
+      Top = 112
+      Width = 303
+      Caption = 'I want to attach a message payload to this transaction'
+      TabOrder = 2
+    end
+  end
+end

+ 163 - 0
src/gui/wizards/operations/UWIZChangeKey_SelectKey.pas

@@ -0,0 +1,163 @@
+unit UWIZChangeKey_SelectKey;
+
+{$mode delphi}
+{$modeswitch nestedprocvars}
+
+{ Copyright (c) 2018 Sphere 10 Software (http://www.sphere10.com/)
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+
+  Acknowledgements:
+  Ugochukwu Mmaduekwe - main developer
+  Herman Schoenfeld - designer
+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
+  ExtCtrls, Buttons, UCommon, UCommon.Collections, UWallet,
+  UFRMAccountSelect, UNode, UWizard,
+  UWIZChangeKey_Confirmation, UWIZFeeOverride, UWIZSelectSignerOverride, UWIZPayloadOverride, UWIZModels;
+
+type
+
+  { TWIZChangeKey_SelectKey }
+
+  TWIZChangeKey_SelectKey = class(TWizardForm<TWIZOperationsModel>)
+    cbNewPrivateKey: TComboBox;
+    chkAttachPayload: TCheckBox;
+    chkChooseFee: TCheckBox;
+    gbNewPrivateKey: TGroupBox;
+    lblKeyName: TLabel;
+    lblPrivateKeyNote: TLabel;
+    procedure cbNewPrivateKeyChange(Sender: TObject);
+  private
+    procedure UpdateWalletKeys();
+  public
+    procedure OnPresent; override;
+    procedure OnNext; override;
+    function Validate(out message: ansistring): boolean; override;
+  end;
+
+
+implementation
+
+{$R *.lfm}
+
+uses
+  UAccounts, UCrypto, UUserInterface, USettings;
+
+{ TWIZChangeKey_SelectKey }
+
+procedure TWIZChangeKey_SelectKey.cbNewPrivateKeyChange(Sender: TObject);
+var
+  i: integer;
+  wk: TWalletKey;
+begin
+  if cbNewPrivateKey.ItemIndex < 1 then
+  begin
+    lblKeyName.Font.Color := clRed;
+    lblKeyName.Caption := 'Please Select Private Key';
+  end
+  else
+  begin
+    lblKeyName.Font.Color := clGreen;
+    i := PtrInt(cbNewPrivateKey.Items.Objects[cbNewPrivateKey.ItemIndex]);
+    wk := TWallet.Keys.Key[i];
+    lblKeyName.Caption := Format('%s ',
+      [IIF(wk.Name = '', TCrypto.ToHexaString(
+      TAccountComp.AccountKey2RawString(wk.AccountKey)), wk.Name)]);
+  end;
+end;
+
+procedure TWIZChangeKey_SelectKey.UpdateWalletKeys();
+var
+  i: integer;
+  wk: TWalletKey;
+  s: string;
+begin
+  cbNewPrivateKey.items.BeginUpdate;
+  try
+    cbNewPrivateKey.Items.Clear;
+    cbNewPrivateKey.Items.Add('Select Private Key');
+    if not Assigned(TWallet.Keys) then
+    begin
+      Exit;
+    end;
+    for i := 0 to TWallet.Keys.Count - 1 do
+    begin
+      wk := TWallet.Keys.Key[i];
+      s := IIF(wk.Name = '', TCrypto.ToHexaString(TAccountComp.AccountKey2RawString(wk.AccountKey)), wk.Name);
+      if not Assigned(wk.PrivateKey) then
+      begin
+        s := s + '(*)';
+      end;
+      cbNewPrivateKey.Items.AddObject(s, TObject(i));
+    end;
+    cbNewPrivateKey.Sorted := True;
+  finally
+    cbNewPrivateKey.Items.EndUpdate;
+  end;
+end;
+
+procedure TWIZChangeKey_SelectKey.OnPresent;
+begin
+  UpdateWalletKeys();
+  cbNewPrivateKey.ItemIndex := Model.ChangeAccountPrivateKey.SelectedIndex;
+  cbNewPrivateKeyChange(Self);
+end;
+
+procedure TWIZChangeKey_SelectKey.OnNext;
+begin
+  Model.ChangeAccountPrivateKey.SelectedIndex := cbNewPrivateKey.ItemIndex;
+  Model.ChangeAccountPrivateKey.NewWalletKey := TWallet.Keys.Key[PtrInt(cbNewPrivateKey.Items.Objects[cbNewPrivateKey.ItemIndex])];
+  Model.Payload.HasPayload := chkAttachPayload.Checked;
+
+  if chkChooseFee.Checked then
+  begin
+    UpdatePath(ptReplaceAllNext, [TWIZFeeOverride, TWIZChangeKey_Confirmation]);
+  end
+  else
+  begin
+    Model.Fee.SingleOperationFee := TSettings.DefaultFee;
+    if Model.Payload.HasPayload then
+    begin
+      UpdatePath(ptReplaceAllNext, [TWIZPayloadOverride, TWIZChangeKey_Confirmation]);
+    end
+    else
+    begin
+      UpdatePath(ptReplaceAllNext, [TWIZSelectSignerOverride, TWIZChangeKey_Confirmation]);
+    end;
+  end;
+end;
+
+function TWIZChangeKey_SelectKey.Validate(out message: ansistring): boolean;
+var
+  i: integer;
+  tempAccountKey: TAccountKey;
+begin
+  Result := True;
+  if cbNewPrivateKey.ItemIndex < 1 then
+  begin
+    message := 'A key must be selected';
+    Result := False;
+    Exit;
+  end;
+
+  tempAccountKey := TWallet.Keys.Key[PtrInt(cbNewPrivateKey.Items.Objects[cbNewPrivateKey.ItemIndex])].AccountKey;
+
+   for i := Low(Model.ChangeKey.SelectedAccounts) to High(Model.ChangeKey.SelectedAccounts) do
+  begin
+    if TAccountComp.EqualAccountKeys(Model.ChangeKey.SelectedAccounts[i].accountInfo.accountKey,
+      tempAccountKey) then
+    begin
+      Result := False;
+      message := 'New key is same as current key';
+      Exit;
+    end;
+  end;
+end;
+
+end.

+ 63 - 0
src/gui/wizards/operations/UWIZChangeKey_SelectOption.lfm

@@ -0,0 +1,63 @@
+object WIZChangeKey_SelectOption: TWIZChangeKey_SelectOption
+  Left = 0
+  Height = 253
+  Top = 0
+  Width = 429
+  Caption = 'WIZChangeKey_SelectOption'
+  ClientHeight = 253
+  ClientWidth = 429
+  LCLVersion = '1.8.2.0'
+  Visible = False
+  object gbChangeKeyOptions: TGroupBox
+    Left = 8
+    Height = 245
+    Top = 2
+    Width = 410
+    Caption = 'Change Key Options'
+    ClientHeight = 225
+    ClientWidth = 406
+    TabOrder = 0
+    object lblNote: TLabel
+      Left = 8
+      Height = 15
+      Top = 8
+      Width = 310
+      Caption = 'How would you like to change the keys of your account(s).'
+      ParentColor = False
+    end
+    object rbTransferAccountOwnership: TRadioButton
+      Left = 8
+      Height = 19
+      Top = 40
+      Width = 192
+      Caption = 'Transfer account to another user'
+      Checked = True
+      TabOrder = 1
+      TabStop = True
+    end
+    object lblTransferAccountOwnership: TLabel
+      Left = 8
+      Height = 15
+      Top = 72
+      Width = 351
+      Caption = 'Use this to transfer ownership of the account to another user''s key.'
+      ParentColor = False
+    end
+    object rbChangeAccountPrivateKey: TRadioButton
+      Left = 8
+      Height = 19
+      Top = 120
+      Width = 207
+      Caption = 'Change to another key in my wallet'
+      TabOrder = 0
+    end
+    object lblChangeAccountPrivateKey: TLabel
+      Left = 8
+      Height = 15
+      Top = 152
+      Width = 383
+      Caption = 'This will change the key of your account to another key that you control.'
+      ParentColor = False
+    end
+  end
+end

+ 63 - 0
src/gui/wizards/operations/UWIZChangeKey_SelectOption.pas

@@ -0,0 +1,63 @@
+unit UWIZChangeKey_SelectOption;
+
+{$mode delphi}
+{$modeswitch nestedprocvars}
+
+{ Copyright (c) 2018 Sphere 10 Software (http://www.sphere10.com/)
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+
+  Acknowledgements:
+  Ugochukwu Mmaduekwe - main developer
+  Herman Schoenfeld - designer
+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
+  ExtCtrls, UWizard, UWIZChangeKey, UWIZModels, UWIZChangeKey_EnterKey, UWIZChangeKey_SelectKey, UWIZChangeKey_Confirmation;
+
+type
+
+  { TWIZChangeKey_SelectOption }
+
+  TWIZChangeKey_SelectOption = class(TWizardForm<TWIZOperationsModel>)
+    gbChangeKeyOptions: TGroupBox;
+    lblTransferAccountOwnership: TLabel;
+    lblNote: TLabel;
+    lblChangeAccountPrivateKey: TLabel;
+    rbTransferAccountOwnership: TRadioButton;
+    rbChangeAccountPrivateKey: TRadioButton;
+  public
+    procedure OnNext; override;
+  end;
+
+
+
+implementation
+
+{$R *.lfm}
+
+uses UAccounts, USettings, UDataSources, UCommon, UCommon.UI, Generics.Collections;
+
+{ TWIZChangeKey_SelectOption }
+
+procedure TWIZChangeKey_SelectOption.OnNext;
+begin
+  if rbTransferAccountOwnership.Checked then
+  begin
+    Model.ChangeKey.ChangeKeyMode := akaTransferAccountOwnership;
+    UpdatePath(ptReplaceAllNext, [TWIZChangeKey_EnterKey,
+      TWIZChangeKey_Confirmation]);
+  end
+  else if rbChangeAccountPrivateKey.Checked then
+  begin
+    Model.ChangeKey.ChangeKeyMode := akaChangeAccountPrivateKey;
+    UpdatePath(ptReplaceAllNext, [TWIZChangeKey_SelectKey,
+      TWIZChangeKey_Confirmation]);
+  end;
+end;
+
+end.

+ 453 - 0
src/gui/wizards/operations/UWIZConfirmAccount.pas

@@ -0,0 +1,453 @@
+unit UWIZChangeKey;
+
+{$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.
+
+  Acknowledgements:
+    Herman Schoenfeld <[email protected]>: added grid-based layout
+}
+
+interface
+
+uses
+  Classes, SysUtils, Forms, Dialogs, UCrypto, UCommon, UWizard, UAccounts, LCLType, UWIZModels;
+
+type
+
+  { TWIZChangeKeyWizard }
+
+  TWIZChangeKeyWizard = class(TWizard<TWIZOperationsModel>)
+  private
+    function UpdatePayload(const SenderAccount: TAccount; var errors: string): boolean;
+    function UpdateOperationOptions(var errors: string): boolean;
+    function UpdateOpChangeKey(const TargetAccount: TAccount; var SignerAccount: TAccount; var NewPublicKey: TAccountKey; var errors: ansistring): boolean;
+    procedure WIZChangeKeyOwnership();
+  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,
+  UWIZChangeKey_Start,
+  UWIZChangeKey_Confirmation;
+
+{ TWIZChangeKeyWizard }
+
+function TWIZChangeKeyWizard.UpdatePayload(const SenderAccount: TAccount; var errors: string): boolean;
+var
+  valid: boolean;
+  payload_encrypted, payload_u: string;
+  account: TAccount;
+begin
+  valid := False;
+  payload_encrypted := '';
+  Model.Payload.EncodedBytes := '';
+  errors := 'Unknown error';
+  payload_u := Model.Payload.Content;
+
+  try
+    if (payload_u = '') then
+    begin
+      valid := True;
+      Exit;
+    end;
+    case Model.Payload.Mode of
+
+      akaEncryptWithSender:
+      begin
+        // Use sender
+        errors := 'Error encrypting';
+        account := SenderAccount;
+        payload_encrypted := ECIESEncrypt(account.accountInfo.accountKey, payload_u);
+        valid := payload_encrypted <> '';
+      end;
+
+      akaEncryptWithReceiver:
+      begin
+        errors := 'Public key: ' + 'Error encrypting';
+
+        if Model.WIZChangeKey.AccountKey.EC_OpenSSL_NID <>
+          CT_Account_NUL.accountInfo.accountKey.EC_OpenSSL_NID then
+        begin
+          payload_encrypted := ECIESEncrypt(Model.WIZChangeKey.AccountKey, payload_u);
+          valid := payload_encrypted <> '';
+        end
+        else
+        begin
+          valid := False;
+          errors := 'Selected private key is not valid to encode';
+          exit;
+        end;
+      end;
+
+      akaEncryptWithPassword:
+      begin
+        payload_encrypted := TAESComp.EVP_Encrypt_AES256(
+          payload_u, Model.Payload.Password);
+        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.Payload.EncodedBytes := payload_encrypted;
+    Result := valid;
+  end;
+
+end;
+
+function TWIZChangeKeyWizard.UpdateOperationOptions(var errors: string): boolean;
+var
+  iAcc, iWallet: integer;
+  sender_account, signer_account: TAccount;
+  publicKey: TAccountKey;
+  wk: TWalletKey;
+  e: string;
+  amount: int64;
+begin
+  Result := False;
+  errors := '';
+  if not Assigned(TWallet.Keys) then
+  begin
+    errors := 'No wallet keys';
+    Exit;
+  end;
+
+  if Length(Model.WIZChangeKey.SelectedAccounts) = 0 then
+  begin
+    errors := 'No sender account';
+    Exit;
+  end
+  else
+  begin
+
+    for iAcc := Low(Model.WIZChangeKey.SelectedAccounts) to High(Model.WIZChangeKey.SelectedAccounts) do
+    begin
+      sender_account := Model.WIZChangeKey.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 := UpdateOpChangeKey(Model.WIZChangeKey.SelectedAccounts[0], signer_account,
+    publicKey, errors);
+  UpdatePayload(sender_account, e);
+end;
+
+function TWIZChangeKeyWizard.UpdateOpChangeKey(const TargetAccount: TAccount;
+  var SignerAccount: TAccount; var NewPublicKey: TAccountKey;
+  var errors: ansistring): boolean;
+begin
+  Result := False;
+  errors := '';
+  try
+    if not TAccountComp.AccountKeyFromImport(Model.WIZChangeKey.NewPublicKey,
+      NewPublicKey, errors) then
+    begin
+      Exit;
+    end;
+
+    if TNode.Node.Bank.SafeBox.CurrentProtocol >= 1 then
+    begin
+      // Signer:
+      SignerAccount := Model.Signer.SignerAccount;
+      if (TAccountComp.IsAccountLocked(SignerAccount.accountInfo,
+        TNode.Node.Bank.BlocksCount)) then
+      begin
+        errors := 'Signer account ' + TAccountComp.AccountNumberToAccountTxtNumber(
+          SignerAccount.account) + ' is locked until block ' + IntToStr(
+          SignerAccount.accountInfo.locked_until_block);
+        exit;
+      end;
+      if (not TAccountComp.EqualAccountKeys(
+        SignerAccount.accountInfo.accountKey, TargetAccount.accountInfo.accountKey)) then
+      begin
+        errors := 'Signer account ' + TAccountComp.AccountNumberToAccountTxtNumber(
+          SignerAccount.account) + ' is not owner of account ' +
+          TAccountComp.AccountNumberToAccountTxtNumber(TargetAccount.account);
+        exit;
+      end;
+    end
+    else
+    begin
+      SignerAccount := TargetAccount;
+    end;
+
+    if (TAccountComp.EqualAccountKeys(TargetAccount.accountInfo.accountKey,
+      NewPublicKey)) then
+    begin
+      errors := 'New public key is the same public key';
+      exit;
+    end;
+
+  finally
+    Result := errors = '';
+  end;
+end;
+
+procedure TWIZChangeKeyWizard.WIZChangeKeyOwnership();
+var
+  _V2, dooperation: boolean;
+  iAcc, i: integer;
+  _totalamount, _totalfee, _totalSignerFee, _amount, _fee: int64;
+  _signer_n_ops: cardinal;
+  operationstxt, operation_to_string, errors, auxs: string;
+  wk: TWalletKey;
+  ops: TOperationsHashTree;
+  op: TPCOperation;
+  account, signerAccount: 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.WIZChangeKey.SelectedAccounts) to High(Model.WIZChangeKey.SelectedAccounts) do
+    begin
+      loop_start:
+        op := nil;
+      account := Model.WIZChangeKey.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.Fee.DefaultFee) then
+        _fee := Model.Fee.DefaultFee
+      else
+        _fee := account.balance;
+
+      if not UpdateOpChangeKey(account, signerAccount, _newOwnerPublicKey, errors) then
+      begin
+        raise Exception.Create(errors);
+      end;
+      if _V2 then
+      begin
+        // must ensure is Signer account last if included in sender accounts (not necessarily ordered enumeration)
+        if (iAcc < Length(Model.WIZChangeKey.SelectedAccounts) - 1) and
+          (account.account = signerAccount.account) then
+        begin
+          TArrayTool<TAccount>.Swap(Model.WIZChangeKey.SelectedAccounts, iAcc,
+            Length(Model.WIZChangeKey.SelectedAccounts) - 1); // ensure signer account processed last
+          // TArrayTool_internal<Cardinal>.Swap(_senderAccounts, iAcc, Length(_senderAccounts) - 1);
+          goto loop_start; // TODO: remove ugly hack with refactoring!
+        end;
+
+        // Maintain correct signer fee distribution
+        if uint64(_totalSignerFee) >= signerAccount.balance then
+          _fee := 0
+        else if signerAccount.balance - uint64(_totalSignerFee) >
+          uint64(Model.Fee.DefaultFee) then
+          _fee := Model.Fee.DefaultFee
+        else
+          _fee := signerAccount.balance - uint64(_totalSignerFee);
+        op := TOpChangeKeySigned.Create(signerAccount.account,
+          signerAccount.n_operation + _signer_n_ops + 1, account.account,
+          wk.PrivateKey, _newOwnerPublicKey, _fee, Model.Payload.EncodedBytes);
+        Inc(_signer_n_ops);
+        Inc(_totalSignerFee, _fee);
+      end
+      else
+      begin
+        op := TOpChangeKey.Create(account.account, account.n_operation +
+          1, account.account, wk.PrivateKey, _newOwnerPublicKey, _fee, Model.Payload.EncodedBytes);
+      end;
+      Inc(_totalfee, _fee);
+      operationstxt :=
+        'Change private key to ' + TAccountComp.GetECInfoTxt(
+        _newOwnerPublicKey.EC_OpenSSL_NID);
+
+      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.WIZChangeKey.SelectedAccounts) > 1) then
+    begin
+      auxs := '';
+      if Application.MessageBox(
+        PChar('Execute ' + IntToStr(Length(Model.WIZChangeKey.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 TWIZChangeKeyWizard.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner, [TWIZChangeKey_Start,
+    TWIZChangeKey_Confirmation]);
+  TitleText := 'Transfer Account';
+  FinishText := 'Transfer Account';
+end;
+
+function TWIZChangeKeyWizard.DetermineHasNext: boolean;
+begin
+  Result := not (CurrentScreen is TWIZChangeKey_Confirmation);
+end;
+
+function TWIZChangeKeyWizard.DetermineHasPrevious: boolean;
+begin
+  Result := inherited DetermineHasPrevious;
+end;
+
+function TWIZChangeKeyWizard.FinishRequested(out message: ansistring): boolean;
+begin
+  // Execute the Transfer Account Action here
+  try
+    Result := True;
+    WIZChangeKeyOwnership();
+  except
+    On E: Exception do
+    begin
+      Result := False;
+      message := E.ToString;
+    end;
+  end;
+end;
+
+function TWIZChangeKeyWizard.CancelRequested(out message: ansistring): boolean;
+begin
+  Result := True;
+end;
+
+end.

+ 1 - 1
src/gui/wizards/operations/UWIZEnlistAccountForSale_Start.lfm

@@ -3,10 +3,10 @@ object WIZEnlistAccountForSale_Start: TWIZEnlistAccountForSale_Start
   Height = 253
   Height = 253
   Top = 0
   Top = 0
   Width = 429
   Width = 429
+  ActiveControl = rbPublicSale
   Caption = 'WIZEnlistAccountForSale_Start'
   Caption = 'WIZEnlistAccountForSale_Start'
   ClientHeight = 253
   ClientHeight = 253
   ClientWidth = 429
   ClientWidth = 429
-  LCLVersion = '1.8.1.0'
   Visible = False
   Visible = False
   object Label1: TLabel
   object Label1: TLabel
     Left = 24
     Left = 24

+ 3 - 5
src/gui/wizards/operations/UWIZFeeOverride.pas

@@ -18,7 +18,7 @@ interface
 uses
 uses
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
   ExtCtrls, Buttons, Spin, UCommon, UCommon.Collections, UWallet,
   ExtCtrls, Buttons, Spin, UCommon, UCommon.Collections, UWallet,
-  UFRMAccountSelect, UNode, UWizard, UWIZSendPASC, UWIZPayloadOverride, UWIZSelectSignerOverride, UWIZSendPASC_Confirmation, UWIZModels;
+  UFRMAccountSelect, UNode, UWizard, UWIZSendPASC, UWIZPayloadOverride, UWIZSelectSignerOverride, UWIZModels;
 
 
 type
 type
 
 
@@ -77,13 +77,11 @@ begin
     Model.Fee.SingleOperationFee);
     Model.Fee.SingleOperationFee);
   if Model.Payload.HasPayload then
   if Model.Payload.HasPayload then
   begin
   begin
-    UpdatePath(ptReplaceAllNext, [TWIZPayloadOverride,
-      TWIZSendPASC_Confirmation]);
+    UpdatePath(ptInject, [TWIZPayloadOverride]);
   end
   end
   else
   else
   begin
   begin
-    UpdatePath(ptReplaceAllNext, [TWIZSelectSignerOverride,
-      TWIZSendPASC_Confirmation]);
+  UpdatePath(ptInject, [TWIZSelectSignerOverride]);
   end;
   end;
 end;
 end;
 
 

+ 2 - 3
src/gui/wizards/operations/UWIZPayloadContentOverride.pas

@@ -18,7 +18,7 @@ interface
 uses
 uses
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
   ExtCtrls, Buttons, UCommon, UCommon.Collections,
   ExtCtrls, Buttons, UCommon, UCommon.Collections,
-  UWizard, UWIZSendPASC, UWIZSelectSignerOverride, UWIZSendPASC_Confirmation, UWIZModels;
+  UWizard, UWIZSelectSignerOverride, UWIZModels;
 
 
 type
 type
 
 
@@ -46,8 +46,7 @@ uses
 procedure TWIZPayloadContentOverride.OnNext;
 procedure TWIZPayloadContentOverride.OnNext;
 begin
 begin
   Model.Payload.Content := mmoPayload.Lines.Text;
   Model.Payload.Content := mmoPayload.Lines.Text;
-   UpdatePath(ptReplaceAllNext, [TWIZSelectSignerOverride,
-      TWIZSendPASC_Confirmation]);
+   UpdatePath(ptInject, [TWIZSelectSignerOverride]);
 end;
 end;
 
 
 end.
 end.

+ 1 - 0
src/gui/wizards/operations/UWIZPayloadOverride.lfm

@@ -7,6 +7,7 @@ object WIZPayloadOverride: TWIZPayloadOverride
   Caption = 'Form1'
   Caption = 'Form1'
   ClientHeight = 253
   ClientHeight = 253
   ClientWidth = 429
   ClientWidth = 429
+  LCLVersion = '1.8.2.0'
   Visible = False
   Visible = False
   object grpPayload: TGroupBox
   object grpPayload: TGroupBox
     Left = 8
     Left = 8

+ 3 - 5
src/gui/wizards/operations/UWIZPayloadOverride.pas

@@ -18,7 +18,7 @@ interface
 uses
 uses
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
   ExtCtrls, Buttons, UCommon, UCommon.Collections,
   ExtCtrls, Buttons, UCommon, UCommon.Collections,
-  UWizard, UWIZSendPASC, UWIZPayloadContentOverride, UWIZPayloadPasswordOverride, UWIZSendPASC_Confirmation, UWIZModels;
+  UWizard, UWIZPayloadContentOverride, UWIZPayloadPasswordOverride, UWIZModels;
 
 
 type
 type
 
 
@@ -75,13 +75,11 @@ begin
   case Model.Payload.Mode of
   case Model.Payload.Mode of
     akaEncryptWithPassword:
     akaEncryptWithPassword:
     begin
     begin
-      UpdatePath(ptReplaceAllNext, [TWIZPayloadPasswordOverride,
-        TWIZSendPASC_Confirmation]);
+      UpdatePath(ptInject, [TWIZPayloadPasswordOverride]);
     end
     end
     else
     else
     begin
     begin
-      UpdatePath(ptReplaceAllNext, [TWIZPayloadContentOverride,
-        TWIZSendPASC_Confirmation]);
+      UpdatePath(ptInject, [TWIZPayloadContentOverride]);
     end;
     end;
   end;
   end;
 end;
 end;

+ 1 - 1
src/gui/wizards/operations/UWIZPayloadPasswordOverride.lfm

@@ -6,7 +6,7 @@ object WIZPayloadPasswordOverride: TWIZPayloadPasswordOverride
   Caption = 'Form1'
   Caption = 'Form1'
   ClientHeight = 253
   ClientHeight = 253
   ClientWidth = 429
   ClientWidth = 429
-  LCLVersion = '1.8.1.0'
+  LCLVersion = '1.8.2.0'
   Visible = False
   Visible = False
   object grpPayload: TGroupBox
   object grpPayload: TGroupBox
     Left = 8
     Left = 8

+ 2 - 3
src/gui/wizards/operations/UWIZPayloadPasswordOverride.pas

@@ -18,7 +18,7 @@ interface
 uses
 uses
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
   ExtCtrls, Buttons, UCommon, UCommon.Collections,
   ExtCtrls, Buttons, UCommon, UCommon.Collections,
-  UWizard, UWIZSendPASC, UWIZSelectSignerOverride, UWIZSendPASC_Confirmation, UWIZModels;
+  UWizard, UWIZSelectSignerOverride, UWIZModels;
 
 
 type
 type
 
 
@@ -48,8 +48,7 @@ uses
 procedure TWIZPayloadPasswordOverride.OnNext;
 procedure TWIZPayloadPasswordOverride.OnNext;
 begin
 begin
   Model.Payload.Password := edtPassword.Text;
   Model.Payload.Password := edtPassword.Text;
-  UpdatePath(ptReplaceAllNext, [TWIZSelectSignerOverride,
-    TWIZSendPASC_Confirmation]);
+   UpdatePath(ptInject, [TWIZSelectSignerOverride]);
 end;
 end;
 
 
 function TWIZPayloadPasswordOverride.Validate(out message: ansistring): boolean;
 function TWIZPayloadPasswordOverride.Validate(out message: ansistring): boolean;

+ 7 - 9
src/gui/wizards/operations/UWIZSelectSignerOverride.pas

@@ -18,7 +18,7 @@ interface
 uses
 uses
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
   ExtCtrls, Buttons, UCommon, UCommon.Collections, UWallet,
   ExtCtrls, Buttons, UCommon, UCommon.Collections, UWallet,
-  UFRMAccountSelect, UNode, UWizard, UWIZSendPASC, UWIZSendPASC_Confirmation, UWIZModels;
+  UFRMAccountSelect, UNode, UWizard, UWIZModels;
 
 
 type
 type
 
 
@@ -33,8 +33,6 @@ type
     rbSecondary: TRadioButton;
     rbSecondary: TRadioButton;
     procedure cbSignerAccountChange(Sender: TObject);
     procedure cbSignerAccountChange(Sender: TObject);
 
 
-
-
   public
   public
     procedure OnPresent; override;
     procedure OnPresent; override;
     procedure OnNext; override;
     procedure OnNext; override;
@@ -62,7 +60,7 @@ begin
   begin
   begin
     lblBalance.Font.Color := clGreen;
     lblBalance.Font.Color := clGreen;
     lblBalance.Caption := Format('%s PASC',
     lblBalance.Caption := Format('%s PASC',
-      [TAccountComp.FormatMoney(Model.SendPASC.SelectedAccounts[PtrInt(
+      [TAccountComp.FormatMoney(Model.Signer.SignerCandidates[PtrInt(
       cbSignerAccount.Items.Objects[cbSignerAccount.ItemIndex])].Balance)]);
       cbSignerAccount.Items.Objects[cbSignerAccount.ItemIndex])].Balance)]);
   end;
   end;
 end;
 end;
@@ -83,23 +81,23 @@ begin
   try
   try
     cbSignerAccount.Items.Clear;
     cbSignerAccount.Items.Clear;
     cbSignerAccount.Items.Add('Select Signer Account');
     cbSignerAccount.Items.Add('Select Signer Account');
-    for i := Low(Model.SendPASC.SelectedAccounts) to High(Model.SendPASC.SelectedAccounts) do
+    for i := Low(Model.Signer.SignerCandidates) to High(Model.Signer.SignerCandidates) do
     begin
     begin
-      acc := Model.SendPASC.SelectedAccounts[i];
+      acc := Model.Signer.SignerCandidates[i];
       accNumberwithChecksum := GetAccNoWithChecksum(acc.account);
       accNumberwithChecksum := GetAccNoWithChecksum(acc.account);
       cbSignerAccount.Items.AddObject(accNumberwithChecksum, TObject(i));
       cbSignerAccount.Items.AddObject(accNumberwithChecksum, TObject(i));
     end;
     end;
   finally
   finally
     cbSignerAccount.Items.EndUpdate;
     cbSignerAccount.Items.EndUpdate;
   end;
   end;
-  cbSignerAccount.ItemIndex := Model.SendPASC.SelectedIndex;
+  cbSignerAccount.ItemIndex := Model.Signer.SelectedIndex;
   cbSignerAccountChange(Self);
   cbSignerAccountChange(Self);
 end;
 end;
 
 
 procedure TWIZSelectSignerOverride.OnNext;
 procedure TWIZSelectSignerOverride.OnNext;
 begin
 begin
-  Model.SendPASC.SelectedIndex := cbSignerAccount.ItemIndex;
-  Model.Signer.SignerAccount := Model.SendPASC.SelectedAccounts[PtrInt(
+  Model.Signer.SelectedIndex := cbSignerAccount.ItemIndex;
+  Model.Signer.SignerAccount := Model.Signer.SignerCandidates[PtrInt(
     cbSignerAccount.Items.Objects[cbSignerAccount.ItemIndex])];
     cbSignerAccount.Items.Objects[cbSignerAccount.ItemIndex])];
   if rbPrimary.Checked then
   if rbPrimary.Checked then
   begin
   begin

+ 1 - 1
src/gui/wizards/operations/UWIZSendPASC_ConfirmSender.lfm

@@ -6,7 +6,7 @@ object WIZSendPASC_ConfirmSender: TWIZSendPASC_ConfirmSender
   Caption = 'WIZSendPASC_ConfirmSender'
   Caption = 'WIZSendPASC_ConfirmSender'
   ClientHeight = 261
   ClientHeight = 261
   ClientWidth = 429
   ClientWidth = 429
-  LCLVersion = '1.8.1.0'
+  LCLVersion = '1.8.2.0'
   Visible = False
   Visible = False
   object gpSender: TGroupBox
   object gpSender: TGroupBox
     Left = 4
     Left = 4

+ 39 - 23
src/gui/wizards/operations/UWIZSendPASC_ConfirmSender.pas

@@ -27,10 +27,11 @@ type
     gpSender: TGroupBox;
     gpSender: TGroupBox;
     paGrid: TPanel;
     paGrid: TPanel;
   private
   private
-    FSendersGrid : TVisualGrid;
+    FSendersGrid: TVisualGrid;
   public
   public
     procedure OnPresent; override;
     procedure OnPresent; override;
     procedure OnNext; override;
     procedure OnNext; override;
+    function Validate(out message: ansistring): boolean; override;
   end;
   end;
 
 
 
 
@@ -39,56 +40,58 @@ implementation
 
 
 {$R *.lfm}
 {$R *.lfm}
 
 
-uses UAccounts, UDataSources, UCommon, UCommon.UI, Generics.Collections;
+uses UAccounts, UCoreUtils, UDataSources, UCommon, UCommon.UI, Generics.Collections;
 
 
 type
 type
 
 
   { TAccountSenderDataSource }
   { TAccountSenderDataSource }
 
 
   TAccountSenderDataSource = class(TAccountsDataSourceBase)
   TAccountSenderDataSource = class(TAccountsDataSourceBase)
-    private
-      FModel : TWIZOperationsModel.TSendPASCModel;
-    public
-      property Model : TWIZOperationsModel.TSendPASCModel read FModel write FModel;
-      procedure FetchAll(const AContainer : TList<TAccount>); override;
+  private
+    FModel: TWIZOperationsModel.TSendPASCModel;
+  public
+    property Model: TWIZOperationsModel.TSendPASCModel read FModel write FModel;
+    procedure FetchAll(const AContainer: TList<TAccount>); override;
   end;
   end;
 
 
 { TWIZSendPASC_ConfirmSender }
 { TWIZSendPASC_ConfirmSender }
 
 
 procedure TWIZSendPASC_ConfirmSender.OnPresent;
 procedure TWIZSendPASC_ConfirmSender.OnPresent;
 var
 var
-  data : TAccountSenderDataSource;
+  Data: TAccountSenderDataSource;
 begin
 begin
   FSendersGrid := TVisualGrid.Create(Self);
   FSendersGrid := TVisualGrid.Create(Self);
-  FSendersGrid.CanSearch:= False;
+  FSendersGrid.CanSearch := False;
   FSendersGrid.SortMode := smMultiColumn;
   FSendersGrid.SortMode := smMultiColumn;
   FSendersGrid.FetchDataInThread := False;
   FSendersGrid.FetchDataInThread := False;
   FSendersGrid.AutoPageSize := True;
   FSendersGrid.AutoPageSize := True;
   FSendersGrid.SelectionType := stNone;
   FSendersGrid.SelectionType := stNone;
   FSendersGrid.Options := [vgoColAutoFill, vgoColSizing, vgoSortDirectionAllowNone, vgoAutoHidePaging];
   FSendersGrid.Options := [vgoColAutoFill, vgoColSizing, vgoSortDirectionAllowNone, vgoAutoHidePaging];
-  with FSendersGrid.AddColumn('Account') do begin
-    StretchedToFill := true;
+  with FSendersGrid.AddColumn('Account') do
+  begin
+    StretchedToFill := True;
     Binding := 'AccountNumber';
     Binding := 'AccountNumber';
     SortBinding := 'AccountNumber';
     SortBinding := 'AccountNumber';
     DisplayBinding := 'Account';
     DisplayBinding := 'Account';
     Width := 100;
     Width := 100;
     HeaderFontStyles := [fsBold];
     HeaderFontStyles := [fsBold];
     DataFontStyles := [fsBold];
     DataFontStyles := [fsBold];
-    Filters:=SORTABLE_NUMERIC_FILTER;
+    Filters := SORTABLE_NUMERIC_FILTER;
   end;
   end;
-  with FSendersGrid.AddColumn('Balance') do begin
+  with FSendersGrid.AddColumn('Balance') do
+  begin
     Binding := 'BalanceDecimal';
     Binding := 'BalanceDecimal';
     SortBinding := 'Balance';
     SortBinding := 'Balance';
     DisplayBinding := 'Balance';
     DisplayBinding := 'Balance';
     Width := 100;
     Width := 100;
-    HeaderAlignment:=taRightJustify;
-    DataAlignment:=taRightJustify;
+    HeaderAlignment := taRightJustify;
+    DataAlignment := taRightJustify;
     Renderer := TCellRenderers.PASC;
     Renderer := TCellRenderers.PASC;
-    Filters:=SORTABLE_NUMERIC_FILTER;
+    Filters := SORTABLE_NUMERIC_FILTER;
   end;
   end;
-  data := TAccountSenderDataSource.Create(FSendersGrid);
-  data.Model := Model.SendPASC;
-  FSendersGrid.DataSource := data;
+  Data := TAccountSenderDataSource.Create(FSendersGrid);
+  Data.Model := Model.SendPASC;
+  FSendersGrid.DataSource := Data;
   paGrid.AddControlDockCenter(FSendersGrid);
   paGrid.AddControlDockCenter(FSendersGrid);
 end;
 end;
 
 
@@ -97,17 +100,30 @@ begin
   UpdatePath(ptReplaceAllNext, [TWIZSendPASC_EnterRecipient, TWIZSendPASC_Confirmation]);
   UpdatePath(ptReplaceAllNext, [TWIZSendPASC_EnterRecipient, TWIZSendPASC_Confirmation]);
 end;
 end;
 
 
+function TWIZSendPASC_ConfirmSender.Validate(out message: ansistring): boolean;
+begin
+  Result := True;
+  // get signer accounts from selected accounts
+  Model.Signer.SignerCandidates := TCoreTool.GetSignerCandidates(Length(Model.SendPASC.SelectedAccounts), Model.SendPASC.SelectedAccounts);
+
+  if Length(Model.Signer.SignerCandidates) < 1 then
+  begin
+    Result := False;
+    message := 'no valid signer account was found.';
+  end;
+
+end;
+
 { TAccountSenderDataSource }
 { TAccountSenderDataSource }
 
 
-procedure TAccountSenderDataSource.FetchAll(const AContainer : TList<TAccount>);
+procedure TAccountSenderDataSource.FetchAll(const AContainer: TList<TAccount>);
 var
 var
-  i: Integer;
+  i: integer;
 begin
 begin
   for i := Low(Model.SelectedAccounts) to High(Model.SelectedAccounts) do
   for i := Low(Model.SelectedAccounts) to High(Model.SelectedAccounts) do
   begin
   begin
-    AContainer.Add( Model.SelectedAccounts[i] );
+    AContainer.Add(Model.SelectedAccounts[i]);
   end;
   end;
 end;
 end;
 
 
 end.
 end.
-

+ 0 - 1
src/gui/wizards/operations/UWIZSendPASC_Confirmation.lfm

@@ -6,7 +6,6 @@ object WIZSendPASC_Confirmation: TWIZSendPASC_Confirmation
   Caption = 'WIZSendPASC_Confirmation'
   Caption = 'WIZSendPASC_Confirmation'
   ClientHeight = 253
   ClientHeight = 253
   ClientWidth = 429
   ClientWidth = 429
-  LCLVersion = '1.8.2.0'
   Visible = False
   Visible = False
   object GroupBox1: TGroupBox
   object GroupBox1: TGroupBox
     Left = 10
     Left = 10

+ 0 - 1
src/gui/wizards/operations/UWIZSendPASC_EnterQuantity.lfm

@@ -7,7 +7,6 @@ object WIZSendPASC_EnterQuantity: TWIZSendPASC_EnterQuantity
   Caption = 'WIZSendPASC_EnterQuantity'
   Caption = 'WIZSendPASC_EnterQuantity'
   ClientHeight = 253
   ClientHeight = 253
   ClientWidth = 429
   ClientWidth = 429
-  LCLVersion = '1.8.2.0'
   Visible = False
   Visible = False
   object gbQuantity: TGroupBox
   object gbQuantity: TGroupBox
     Left = 16
     Left = 16

+ 2 - 0
src/gui/wizards/operations/UWIZTransferAccount.pas

@@ -7,6 +7,8 @@ unit UWIZTransferAccount;
   Distributed under the MIT software license, see the accompanying file LICENSE
   Distributed under the MIT software license, see the accompanying file LICENSE
   or visit http://www.opensource.org/licenses/mit-license.php.
   or visit http://www.opensource.org/licenses/mit-license.php.
 
 
+  Acknowledgements:
+    Herman Schoenfeld <[email protected]>: added grid-based layout
 }
 }
 
 
 interface
 interface

+ 61 - 58
src/pascalcoin_wallet.lpi

@@ -33,7 +33,7 @@
         <PackageName Value="LCL"/>
         <PackageName Value="LCL"/>
       </Item1>
       </Item1>
     </RequiredPackages>
     </RequiredPackages>
-    <Units Count="101">
+    <Units Count="97">
       <Unit0>
       <Unit0>
         <Filename Value="pascalcoin_wallet.dpr"/>
         <Filename Value="pascalcoin_wallet.dpr"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
@@ -368,182 +368,185 @@
       <Unit68>
       <Unit68>
         <Filename Value="gui\wizards\wallet\UWIZAddKey_EnterName.pas"/>
         <Filename Value="gui\wizards\wallet\UWIZAddKey_EnterName.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZAddKey_EnterName"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
       </Unit68>
       </Unit68>
       <Unit69>
       <Unit69>
         <Filename Value="gui\wizards\wallet\UWIZAddKey_GenerateOrImport.pas"/>
         <Filename Value="gui\wizards\wallet\UWIZAddKey_GenerateOrImport.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZAddKey_GenerateOrImport"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
       </Unit69>
       </Unit69>
       <Unit70>
       <Unit70>
         <Filename Value="gui\wizards\wallet\UWIZAddKey_ImportPrivKey.pas"/>
         <Filename Value="gui\wizards\wallet\UWIZAddKey_ImportPrivKey.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZAddKey_ImportPrivKey"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
       </Unit70>
       </Unit70>
       <Unit71>
       <Unit71>
         <Filename Value="gui\wizards\wallet\UWIZAddKey_ImportPubKey.pas"/>
         <Filename Value="gui\wizards\wallet\UWIZAddKey_ImportPubKey.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZAddKey_ImportPubKey"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
       </Unit71>
       </Unit71>
       <Unit72>
       <Unit72>
         <Filename Value="gui\wizards\wallet\UWIZAddKey_SelectEncryption.pas"/>
         <Filename Value="gui\wizards\wallet\UWIZAddKey_SelectEncryption.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZAddKey_SelectEncryption"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
       </Unit72>
       </Unit72>
       <Unit73>
       <Unit73>
         <Filename Value="gui\wizards\wallet\UWIZAddKey_Start.pas"/>
         <Filename Value="gui\wizards\wallet\UWIZAddKey_Start.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZAddKey_Start"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
       </Unit73>
       </Unit73>
       <Unit74>
       <Unit74>
-        <Filename Value="gui\wizards\operations\UWIZChangeAccountPrivateKey.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZEnlistAccountForSale.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit74>
       </Unit74>
       <Unit75>
       <Unit75>
-        <Filename Value="gui\wizards\operations\UWIZChangeAccountPrivateKey_Confirmation.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZEnlistAccountForSale_Confirmation.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
-        <ComponentName Value="WIZChangeAccountPrivateKey_Confirmation"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
-        <ResourceBaseClass Value="Form"/>
       </Unit75>
       </Unit75>
       <Unit76>
       <Unit76>
-        <Filename Value="gui\wizards\operations\UWIZChangeAccountPrivateKey_Start.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZEnlistAccountForSale_List.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
       </Unit76>
       </Unit76>
       <Unit77>
       <Unit77>
-        <Filename Value="gui\wizards\operations\UWIZChangeAccountPrivateKey_Transaction.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZEnlistAccountForSale_PrivateSaleConfig.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
-        <ComponentName Value="WIZChangeAccountPrivateKey_Transaction"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
-        <ResourceBaseClass Value="Form"/>
       </Unit77>
       </Unit77>
       <Unit78>
       <Unit78>
-        <Filename Value="gui\wizards\operations\UWIZChangeAccountPrivateKey_TransactionPayload.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZEnlistAccountForSale_Start.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
       </Unit78>
       </Unit78>
       <Unit79>
       <Unit79>
-        <Filename Value="gui\wizards\operations\UWIZEnlistAccountForSale.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZEnlistAccountForSale_Transaction.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZEnlistAccountForSale_Transaction"/>
+        <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
       </Unit79>
       </Unit79>
       <Unit80>
       <Unit80>
-        <Filename Value="gui\wizards\operations\UWIZEnlistAccountForSale_Confirmation.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZEnlistAccountForSale_TransactionPayload.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
       </Unit80>
       </Unit80>
       <Unit81>
       <Unit81>
-        <Filename Value="gui\wizards\operations\UWIZEnlistAccountForSale_List.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZFeeOverride.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZFeeOverride"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
       </Unit81>
       </Unit81>
       <Unit82>
       <Unit82>
-        <Filename Value="gui\wizards\operations\UWIZEnlistAccountForSale_PrivateSaleConfig.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZPayloadContentOverride.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZPayloadContentOverride"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
       </Unit82>
       </Unit82>
       <Unit83>
       <Unit83>
-        <Filename Value="gui\wizards\operations\UWIZEnlistAccountForSale_Start.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZPayloadOverride.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZPayloadOverride"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
       </Unit83>
       </Unit83>
       <Unit84>
       <Unit84>
-        <Filename Value="gui\wizards\operations\UWIZEnlistAccountForSale_Transaction.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZPayloadPasswordOverride.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
-        <ComponentName Value="WIZEnlistAccountForSale_Transaction"/>
+        <ComponentName Value="WIZPayloadPasswordOverride"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
         <ResourceBaseClass Value="Form"/>
       </Unit84>
       </Unit84>
       <Unit85>
       <Unit85>
-        <Filename Value="gui\wizards\operations\UWIZEnlistAccountForSale_TransactionPayload.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZSelectSignerOverride.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZSelectSignerOverride"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
       </Unit85>
       </Unit85>
       <Unit86>
       <Unit86>
-        <Filename Value="gui\wizards\operations\UWIZFeeOverride.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZSendPASC.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
-        <HasResources Value="True"/>
       </Unit86>
       </Unit86>
       <Unit87>
       <Unit87>
-        <Filename Value="gui\wizards\operations\UWIZPayloadContentOverride.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZSendPASC_Confirmation.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
-        <ComponentName Value="WIZPayloadContentOverride"/>
+        <ComponentName Value="WIZSendPASC_Confirmation"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
         <ResourceBaseClass Value="Form"/>
       </Unit87>
       </Unit87>
       <Unit88>
       <Unit88>
-        <Filename Value="gui\wizards\operations\UWIZPayloadOverride.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZSendPASC_ConfirmSender.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZSendPASC_ConfirmSender"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
       </Unit88>
       </Unit88>
       <Unit89>
       <Unit89>
-        <Filename Value="gui\wizards\operations\UWIZPayloadPasswordOverride.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZSendPASC_EnterQuantity.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZSendPASC_EnterQuantity"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
       </Unit89>
       </Unit89>
       <Unit90>
       <Unit90>
-        <Filename Value="gui\wizards\operations\UWIZSelectSignerOverride.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZSendPASC_EnterRecipient.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
-        <ComponentName Value="WIZSelectSignerOverride"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
-        <ResourceBaseClass Value="Form"/>
       </Unit90>
       </Unit90>
       <Unit91>
       <Unit91>
-        <Filename Value="gui\wizards\operations\UWIZSendPASC.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZChangeKey.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit91>
       </Unit91>
       <Unit92>
       <Unit92>
-        <Filename Value="gui\wizards\operations\UWIZSendPASC_Confirmation.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZChangeKey_SelectOption.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
-        <ComponentName Value="WIZSendPASC_Confirmation"/>
+        <ComponentName Value="WIZChangeKey_SelectOption"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
         <ResourceBaseClass Value="Form"/>
       </Unit92>
       </Unit92>
       <Unit93>
       <Unit93>
-        <Filename Value="gui\wizards\operations\UWIZSendPASC_ConfirmSender.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZChangeKey_EnterKey.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZChangeKey_EnterKey"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
       </Unit93>
       </Unit93>
       <Unit94>
       <Unit94>
-        <Filename Value="gui\wizards\operations\UWIZSendPASC_EnterQuantity.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZChangeKey_SelectKey.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
-        <ComponentName Value="WIZSendPASC_EnterQuantity"/>
+        <ComponentName Value="WIZChangeKey_SelectKey"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
         <ResourceBaseClass Value="Form"/>
       </Unit94>
       </Unit94>
       <Unit95>
       <Unit95>
-        <Filename Value="gui\wizards\operations\UWIZSendPASC_EnterRecipient.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZChangeKey_ConfirmAccount.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZChangeKey_ConfirmAccount"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
       </Unit95>
       </Unit95>
       <Unit96>
       <Unit96>
-        <Filename Value="gui\wizards\operations\UWIZTransferAccount.pas"/>
-        <IsPartOfProject Value="True"/>
-      </Unit96>
-      <Unit97>
-        <Filename Value="gui\wizards\operations\UWIZTransferAccount_Confirmation.pas"/>
+        <Filename Value="gui\wizards\operations\UWIZChangeKey_Confirmation.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
-        <ComponentName Value="WIZTransferAccount_Confirmation"/>
+        <ComponentName Value="WIZChangeKey_Confirmation"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
         <ResourceBaseClass Value="Form"/>
-      </Unit97>
-      <Unit98>
-        <Filename Value="gui\wizards\operations\UWIZTransferAccount_Start.pas"/>
-        <IsPartOfProject Value="True"/>
-        <HasResources Value="True"/>
-      </Unit98>
-      <Unit99>
-        <Filename Value="gui\wizards\operations\UWIZTransferAccount_Transaction.pas"/>
-        <IsPartOfProject Value="True"/>
-        <ComponentName Value="WIZTransferAccount_Transaction"/>
-        <HasResources Value="True"/>
-        <ResourceBaseClass Value="Form"/>
-      </Unit99>
-      <Unit100>
-        <Filename Value="gui\wizards\operations\UWIZTransferAccount_TransactionPayload.pas"/>
-        <IsPartOfProject Value="True"/>
-        <HasResources Value="True"/>
-      </Unit100>
+      </Unit96>
     </Units>
     </Units>
   </ProjectOptions>
   </ProjectOptions>
   <CompilerOptions>
   <CompilerOptions>