Browse Source

implemented change account private key wizard.

Ugochukwu Mmaduekwe 7 years ago
parent
commit
7fe7d9f5b0

+ 21 - 3
src/gui/UCTRLWallet.pas

@@ -9,7 +9,8 @@ interface
 uses
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, Menus,
   ExtCtrls, PairSplitter, Buttons, UVisualGrid, UCommon.UI, Generics.Collections,
-  UAccounts, UDataSources, UNode, UWIZSendPASC, UWIZTransferAccount;
+  UAccounts, UDataSources, UNode, UWIZSendPASC, UWIZTransferAccount,
+  UWIZChangeAccountPrivateKey;
 
 type
 
@@ -383,7 +384,8 @@ end;
 //  Result := ARow.Account;
 //end;
 
-function TCTRLWallet.GetAccounts(const AccountNumbers: TArray<cardinal>): TArray<TAccount>;
+function TCTRLWallet.GetAccounts(
+  const AccountNumbers: TArray<cardinal>): TArray<TAccount>;
 var
   acc: TAccount;
   safeBox: TPCSafeBox;
@@ -660,8 +662,24 @@ begin
 end;
 
 procedure TCTRLWallet.miChangeAccountsPrivateKeyClick(Sender: TObject);
+var
+  Scoped: TDisposables;
+  wiz: TWIZChangeAccountPrivateKeyWizard;
+  model: TWIZChangeAccountPrivateKeyModel;
+  AccountNumbersWithoutChecksum: TArray<cardinal>;
 begin
-  raise ENotImplemented.Create('Not Implemented');
+  wiz := Scoped.AddObject(TWIZChangeAccountPrivateKeyWizard.Create(nil)) as
+    TWIZChangeAccountPrivateKeyWizard;
+  model := Scoped.AddObject(TWIZChangeAccountPrivateKeyModel.Create(nil)) as
+    TWIZChangeAccountPrivateKeyModel;
+
+  AccountNumbersWithoutChecksum :=
+    TListTool<variant, cardinal>.Transform(FAccountsGrid.SelectedRows,
+    GetAccNoWithoutChecksum);
+
+  model.SelectedAccounts := GetAccounts(AccountNumbersWithoutChecksum);
+  model.SelectedIndex := 0;
+  wiz.Start(model);
 end;
 
 procedure TCTRLWallet.OnPrepareOperationsPopupMenu(Sender: TObject;

+ 474 - 0
src/gui/wizards/UWIZChangeAccountPrivateKey.pas

@@ -0,0 +1,474 @@
+unit UWIZChangeAccountPrivateKey;
+
+{$mode delphi}
+
+{ Copyright (c) 2018 by Ugochukwu Mmaduekwe
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+
+}
+
+interface
+
+uses
+  Classes, SysUtils, Forms, Dialogs, UCrypto, UWallet, UCommon, UWizard,
+  UAccounts, LCLType;
+
+type
+
+  { TWIZChangeAccountPrivateKeyModel }
+  TWIZPayloadEncryptionMode = (akaEncryptWithOldEC, akaEncryptWithEC,
+    akaEncryptWithPassword, akaNotEncrypt);
+
+  TWIZChangeAccountPrivateKeyModel = class(TComponent)
+  public
+    DefaultFee: int64;
+    NewPublicKey, Payload, EncryptionPassword: string;
+    SelectedIndex, PrivateKeySelectedIndex: integer;
+    NewWalletKey: TWalletKey;
+    EncodedPayload: TRawBytes;
+    SignerAccount: TAccount;
+    SelectedAccounts: TArray<TAccount>;
+    PayloadEncryptionMode: TWIZPayloadEncryptionMode;
+  end;
+
+  { TWIZChangeAccountPrivateKeyWizard }
+
+  TWIZChangeAccountPrivateKeyWizard = class(TWizard<TWIZChangeAccountPrivateKeyModel>)
+  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 ChangeAccountPrivateKey();
+  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,
+  UECIES,
+  UAES,
+  UWIZChangeAccountPrivateKey_Start,
+  UWIZChangeAccountPrivateKey_Confirmation;
+
+{ TWIZChangeAccountPrivateKeyWizard }
+
+function TWIZChangeAccountPrivateKeyWizard.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.EncodedPayload := '';
+  errors := 'Unknown error';
+  payload_u := Model.Payload;
+
+  try
+    if (payload_u = '') then
+    begin
+      valid := True;
+      Exit;
+    end;
+    case Model.PayloadEncryptionMode of
+
+      akaEncryptWithOldEC:
+      begin
+        // Use sender
+        errors := 'Error encrypting';
+        account := SenderAccount;
+        payload_encrypted := ECIESEncrypt(account.accountInfo.accountKey, payload_u);
+        valid := payload_encrypted <> '';
+      end;
+
+      akaEncryptWithEC:
+      begin
+        errors := 'Error encrypting';
+
+        public_key := Model.NewWalletKey.AccountKey;
+        if public_key.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.EncryptionPassword);
+        valid := payload_encrypted <> '';
+      end;
+
+      akaNotEncrypt:
+      begin
+        payload_encrypted := payload_u;
+        valid := True;
+      end
+
+      else
+      begin
+        raise Exception.Create('Invalid Encryption Selection');
+      end;
+    end;
+
+  finally
+    if valid then
+    begin
+      if length(payload_encrypted) > CT_MaxPayloadSize then
+      begin
+        valid := False;
+        errors := 'Payload size is bigger than ' + IntToStr(CT_MaxPayloadSize) +
+          ' (' + IntToStr(length(payload_encrypted)) + ')';
+      end;
+
+    end;
+    Model.EncodedPayload := payload_encrypted;
+    Result := valid;
+  end;
+
+end;
+
+function TWIZChangeAccountPrivateKeyWizard.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.SelectedAccounts) = 0 then
+  begin
+    errors := 'No sender account';
+    Exit;
+  end
+  else
+  begin
+
+    for iAcc := Low(Model.SelectedAccounts) to High(Model.SelectedAccounts) do
+    begin
+      sender_account := Model.SelectedAccounts[iAcc];
+      iWallet := TWallet.Keys.IndexOfAccountKey(sender_account.accountInfo.accountKey);
+      if (iWallet < 0) then
+      begin
+        errors := 'Private key of account ' +
+          TAccountComp.AccountNumberToAccountTxtNumber(sender_account.account) +
+          ' not found in wallet';
+        Exit;
+      end;
+      wk := TWallet.Keys.Key[iWallet];
+      if not assigned(wk.PrivateKey) then
+      begin
+        if wk.CryptedKey <> '' then
+        begin
+          // TODO: handle unlocking of encrypted wallet here
+          errors := 'Wallet is password protected. Need password';
+        end
+        else
+        begin
+          errors := 'Only public key of account ' +
+            TAccountComp.AccountNumberToAccountTxtNumber(sender_account.account) +
+            ' found in wallet. You cannot operate with this account';
+        end;
+        Exit;
+      end;
+    end;
+  end;
+
+  Result := UpdateOpChangeKey(Model.SelectedAccounts[0], signer_account,
+    publicKey, errors);
+  UpdatePayload(sender_account, e);
+end;
+
+function TWIZChangeAccountPrivateKeyWizard.UpdateOpChangeKey(
+  const TargetAccount: TAccount; var SignerAccount: TAccount;
+  var NewPublicKey: TAccountKey; var errors: ansistring): boolean;
+begin
+  Result := False;
+  errors := '';
+  try
+    NewPublicKey := Model.NewWalletKey.AccountKey;
+
+    if TNode.Node.Bank.SafeBox.CurrentProtocol >= 1 then
+    begin
+      // Signer:
+      SignerAccount := Model.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 TWIZChangeAccountPrivateKeyWizard.ChangeAccountPrivateKey();
+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.SelectedAccounts) to High(Model.SelectedAccounts) do
+    begin
+      loop_start:
+        op := nil;
+      account := Model.SelectedAccounts[iAcc];
+      if not UpdatePayload(account, errors) then
+      begin
+        raise Exception.Create('Error encoding payload of sender account ' +
+          TAccountComp.AccountNumberToAccountTxtNumber(account.account) + ': ' + errors);
+      end;
+      i := TWallet.Keys.IndexOfAccountKey(account.accountInfo.accountKey);
+      if i < 0 then
+      begin
+        raise Exception.Create('Sender account private key not found in Wallet');
+      end;
+
+      wk := TWallet.Keys.Key[i];
+      dooperation := True;
+      // Default fee
+      if account.balance > uint64(Model.DefaultFee) then
+        _fee := Model.DefaultFee
+      else
+        _fee := account.balance;
+
+      if not 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.SelectedAccounts) - 1) and
+          (account.account = signerAccount.account) then
+        begin
+          TArrayTool<TAccount>.Swap(Model.SelectedAccounts, iAcc,
+            Length(Model.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.DefaultFee) then
+          _fee := Model.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.EncodedPayload);
+        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.EncodedPayload);
+      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.SelectedAccounts) > 1) then
+    begin
+      auxs := '';
+      if Application.MessageBox(
+        PChar('Execute ' + IntToStr(Length(Model.SelectedAccounts)) +
+        ' operations?' + #10 + 'Operation: ' + operationstxt + #10 +
+        auxs + 'Total fee: ' + TAccountComp.FormatMoney(_totalfee) +
+        #10 + #10 + 'Note: This operation will be transmitted to the network!'),
+        PChar(Application.Title), MB_YESNO + MB_ICONINFORMATION + MB_DEFBUTTON2) <>
+        idYes then
+      begin
+        Exit;
+      end;
+    end
+    else
+    begin
+      if Application.MessageBox(PChar('Execute this operation:' +
+        #10 + #10 + operation_to_string + #10 + #10 +
+        'Note: This operation will be transmitted to the network!'),
+        PChar(Application.Title), MB_YESNO + MB_ICONINFORMATION + MB_DEFBUTTON2) <>
+        idYes then
+      begin
+        Exit;
+      end;
+    end;
+    i := TNode.Node.AddOperations(nil, ops, nil, errors);
+    if (i = ops.OperationsCount) then
+    begin
+      operationstxt := 'Successfully executed ' + IntToStr(i) +
+        ' operations!' + #10 + #10 + operation_to_string;
+      if i > 1 then
+      begin
+
+        ShowMessage(operationstxt);
+      end
+      else
+      begin
+        Application.MessageBox(
+          PChar('Successfully executed ' + IntToStr(i) + ' operations!' +
+          #10 + #10 + operation_to_string),
+          PChar(Application.Title), MB_OK + MB_ICONINFORMATION);
+      end;
+
+    end
+    else if (i > 0) then
+    begin
+      operationstxt := 'One or more of your operations has not been executed:' +
+        #10 + 'Errors:' + #10 + errors + #10 + #10 +
+        'Total successfully executed operations: ' + IntToStr(i);
+
+      ShowMessage(operationstxt);
+    end
+    else
+    begin
+      raise Exception.Create(errors);
+    end;
+
+
+  finally
+    ops.Free;
+  end;
+
+end;
+
+constructor TWIZChangeAccountPrivateKeyWizard.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner, [TWIZChangeAccountPrivateKey_Start,
+    TWIZChangeAccountPrivateKey_Confirmation]);
+  TitleText := 'Change Key';
+  FinishText := 'Change Key';
+end;
+
+function TWIZChangeAccountPrivateKeyWizard.DetermineHasNext: boolean;
+begin
+  Result := not (CurrentScreen is TWIZChangeAccountPrivateKey_Confirmation);
+end;
+
+function TWIZChangeAccountPrivateKeyWizard.DetermineHasPrevious: boolean;
+begin
+  Result := inherited DetermineHasPrevious;
+end;
+
+function TWIZChangeAccountPrivateKeyWizard.FinishRequested(
+  out message: ansistring): boolean;
+begin
+  // Execute the Change Account Private Key Action here
+  try
+    Result := True;
+    ChangeAccountPrivateKey();
+  except
+    On E: Exception do
+    begin
+      Result := False;
+      message := E.ToString;
+    end;
+  end;
+end;
+
+function TWIZChangeAccountPrivateKeyWizard.CancelRequested(
+  out message: ansistring): boolean;
+begin
+  Result := True;
+end;
+
+end.

+ 48 - 0
src/gui/wizards/UWIZChangeAccountPrivateKey_Confirmation.lfm

@@ -0,0 +1,48 @@
+object WIZChangeAccountPrivateKey_Confirmation: TWIZChangeAccountPrivateKey_Confirmation
+  Left = 0
+  Height = 253
+  Top = 0
+  Width = 429
+  Caption = 'WIZChangeAccountPrivateKey_Confirmation'
+  ClientHeight = 253
+  ClientWidth = 429
+  LCLVersion = '1.8.1.0'
+  Visible = False
+  object GroupBox1: TGroupBox
+    Left = 10
+    Height = 232
+    Top = 8
+    Width = 408
+    Anchors = [akTop, akLeft, akRight, akBottom]
+    Caption = 'Confirm Transfer Transaction'
+    ClientHeight = 212
+    ClientWidth = 404
+    TabOrder = 0
+    object paGrid: TPanel
+      Left = 8
+      Height = 152
+      Top = 48
+      Width = 382
+      Anchors = [akTop, akLeft, akRight, akBottom]
+      BevelOuter = bvNone
+      Caption = 'GRID PANEL'
+      TabOrder = 0
+    end
+    object Label1: TLabel
+      Left = 8
+      Height = 15
+      Top = 16
+      Width = 84
+      Caption = 'Signer Account:'
+      ParentColor = False
+    end
+    object lblSgnAcc: TLabel
+      Left = 112
+      Height = 15
+      Top = 16
+      Width = 53
+      Caption = 'lblSgnAcc'
+      ParentColor = False
+    end
+  end
+end

+ 146 - 0
src/gui/wizards/UWIZChangeAccountPrivateKey_Confirmation.pas

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

+ 31 - 0
src/gui/wizards/UWIZChangeAccountPrivateKey_Start.lfm

@@ -0,0 +1,31 @@
+object WIZChangeAccountPrivateKey_Start: TWIZChangeAccountPrivateKey_Start
+  Left = 0
+  Height = 253
+  Top = 0
+  Width = 429
+  Caption = 'WIZChangeAccountPrivateKey_Start'
+  ClientHeight = 253
+  ClientWidth = 429
+  Visible = False
+  object grpSelectedAccount: TGroupBox
+    Left = 6
+    Height = 248
+    Top = 0
+    Width = 416
+    Anchors = [akTop, akLeft, akRight, akBottom]
+    Caption = 'Selected Accounts'
+    ClientHeight = 228
+    ClientWidth = 412
+    TabOrder = 0
+    object paGrid: TPanel
+      Left = 6
+      Height = 200
+      Top = 24
+      Width = 402
+      Anchors = [akTop, akLeft, akRight, akBottom]
+      BevelOuter = bvNone
+      Caption = 'GRID PANEL'
+      TabOrder = 0
+    end
+  end
+end

+ 110 - 0
src/gui/wizards/UWIZChangeAccountPrivateKey_Start.pas

@@ -0,0 +1,110 @@
+unit UWIZChangeAccountPrivateKey_Start;
+
+{$mode delphi}
+{$modeswitch nestedprocvars}
+
+{ Copyright (c) 2018 by Ugochukwu Mmaduekwe
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
+  ExtCtrls, UVisualGrid, UCommon.Data, UCellRenderers,
+  UWizard, UWIZChangeAccountPrivateKey, UWIZChangeAccountPrivateKey_Transaction, UWIZChangeAccountPrivateKey_Confirmation;
+
+type
+
+  { TWIZChangeAccountPrivateKey_Start }
+
+  TWIZChangeAccountPrivateKey_Start = class(TWizardForm<TWIZChangeAccountPrivateKeyModel>)
+    grpSelectedAccount: TGroupBox;
+    paGrid: TPanel;
+  private
+    FSendersGrid: TVisualGrid;
+  public
+    procedure OnPresent; override;
+    procedure OnNext; override;
+  end;
+
+
+
+implementation
+
+{$R *.lfm}
+
+uses UAccounts, USettings, UDataSources, UCommon, UCommon.UI, Generics.Collections;
+
+type
+
+  { TAccountSenderDataSource }
+
+  TAccountSenderDataSource = class(TAccountsDataSourceBase)
+  private
+    FModel: TWIZChangeAccountPrivateKeyModel;
+  public
+    property Model: TWIZChangeAccountPrivateKeyModel read FModel write FModel;
+    procedure FetchAll(const AContainer: TList<TAccount>); override;
+  end;
+
+procedure TAccountSenderDataSource.FetchAll(const AContainer: TList<TAccount>);
+var
+  i: integer;
+begin
+  for i := Low(Model.SelectedAccounts) to High(Model.SelectedAccounts) do
+  begin
+    AContainer.Add(Model.SelectedAccounts[i]);
+  end;
+end;
+
+{ TWIZChangeAccountPrivateKey_Start }
+
+procedure TWIZChangeAccountPrivateKey_Start.OnPresent;
+var
+  Data: TAccountSenderDataSource;
+begin
+  FSendersGrid := TVisualGrid.Create(Self);
+  FSendersGrid.CanSearch := False;
+  FSendersGrid.SortMode := smMultiColumn;
+  FSendersGrid.FetchDataInThread := False;
+  FSendersGrid.AutoPageSize := True;
+  FSendersGrid.SelectionType := stNone;
+  FSendersGrid.Options := [vgoColAutoFill, vgoColSizing, vgoSortDirectionAllowNone,
+    vgoAutoHidePaging];
+  with FSendersGrid.AddColumn('Account') do
+  begin
+    StretchedToFill := True;
+    Binding := 'AccountNumber';
+    SortBinding := 'AccountNumber';
+    DisplayBinding := 'Account';
+    Width := 100;
+    HeaderFontStyles := [fsBold];
+    DataFontStyles := [fsBold];
+    Filters := SORTABLE_NUMERIC_FILTER;
+  end;
+  with FSendersGrid.AddColumn('Balance') do
+  begin
+    Binding := 'BalanceDecimal';
+    SortBinding := 'Balance';
+    DisplayBinding := 'Balance';
+    Width := 100;
+    HeaderAlignment := taRightJustify;
+    DataAlignment := taRightJustify;
+    Renderer := TCellRenderers.PASC;
+    Filters := SORTABLE_NUMERIC_FILTER;
+  end;
+  Data := TAccountSenderDataSource.Create(FSendersGrid);
+  Data.Model := Model;
+  FSendersGrid.DataSource := Data;
+  paGrid.AddControlDockCenter(FSendersGrid);
+end;
+
+procedure TWIZChangeAccountPrivateKey_Start.OnNext;
+begin
+   UpdatePath(ptReplaceAllNext, [TWIZChangeAccountPrivateKey_Transaction, TWIZChangeAccountPrivateKey_Confirmation]);
+end;
+
+end.

+ 105 - 0
src/gui/wizards/UWIZChangeAccountPrivateKey_Transaction.lfm

@@ -0,0 +1,105 @@
+object WIZChangeAccountPrivateKey_Transaction: TWIZChangeAccountPrivateKey_Transaction
+  Left = 0
+  Height = 253
+  Top = 0
+  Width = 429
+  ActiveControl = cbSignerAccount
+  Caption = 'WIZChangeAccountPrivateKey_Transaction'
+  ClientHeight = 253
+  ClientWidth = 429
+  Visible = False
+  object gbTransaction: TGroupBox
+    Left = 24
+    Height = 192
+    Top = 40
+    Width = 384
+    Caption = 'Transaction Details'
+    ClientHeight = 172
+    ClientWidth = 380
+    TabOrder = 0
+    object cbSignerAccount: TComboBox
+      Left = 8
+      Height = 23
+      Top = 8
+      Width = 168
+      ItemHeight = 15
+      Items.Strings = (
+        ''
+      )
+      OnChange = cbSignerAccountChange
+      TabOrder = 0
+      Text = 'Select Signer Account'
+    end
+    object lblBalance: TLabel
+      Left = 200
+      Height = 15
+      Top = 12
+      Width = 151
+      Caption = 'Please Select Signer Account'
+      Font.Color = clRed
+      ParentColor = False
+      ParentFont = False
+    end
+    object lblprivatekey: TLabel
+      Left = 8
+      Height = 15
+      Top = 48
+      Width = 85
+      Caption = 'New Private Key'
+      ParentColor = False
+    end
+    object edtOpFee: TEdit
+      Left = 8
+      Height = 23
+      Top = 144
+      Width = 360
+      TabOrder = 1
+    end
+    object lblOpFee: TLabel
+      Left = 8
+      Height = 15
+      Top = 112
+      Width = 74
+      Caption = 'Operation Fee'
+      ParentColor = False
+    end
+    object cbNewPrivateKey: TComboBox
+      Left = 8
+      Height = 23
+      Top = 74
+      Width = 168
+      ItemHeight = 15
+      OnChange = cbNewPrivateKeyChange
+      TabOrder = 2
+      Text = 'Select Private Key'
+    end
+    object lblKeyName: TLabel
+      Left = 200
+      Height = 15
+      Top = 78
+      Width = 128
+      Caption = 'Please Select Private Key'
+      Font.Color = clRed
+      ParentColor = False
+      ParentFont = False
+    end
+  end
+  object lblTotalBalances: TLabel
+    Left = 24
+    Height = 15
+    Top = 16
+    Width = 176
+    Caption = 'Selected Accounts Total Balance: '
+    ParentColor = False
+  end
+  object lblTotalBalanceValue: TLabel
+    Left = 208
+    Height = 15
+    Top = 16
+    Width = 70
+    Caption = 'Total Balance'
+    Font.Color = clGreen
+    ParentColor = False
+    ParentFont = False
+  end
+end

+ 216 - 0
src/gui/wizards/UWIZChangeAccountPrivateKey_Transaction.pas

@@ -0,0 +1,216 @@
+unit UWIZChangeAccountPrivateKey_Transaction;
+
+{$mode delphi}
+{$modeswitch nestedprocvars}
+
+{ Copyright (c) 2018 by Ugochukwu Mmaduekwe
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+}
+
+interface
+
+uses
+  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
+  ExtCtrls, Buttons, UCommon, UCommon.Collections, UWallet,
+  UFRMAccountSelect, UNode, UWizard, UWIZChangeAccountPrivateKey,
+  UWIZChangeAccountPrivateKey_TransactionPayload,
+  UWIZChangeAccountPrivateKey_Confirmation;
+
+type
+
+  { TWIZChangeAccountPrivateKey_Transaction }
+
+  TWIZChangeAccountPrivateKey_Transaction =
+  class(TWizardForm<TWIZChangeAccountPrivateKeyModel>)
+    cbSignerAccount: TComboBox;
+    cbNewPrivateKey: TComboBox;
+    edtOpFee: TEdit;
+    gbTransaction: TGroupBox;
+    lblKeyName: TLabel;
+    lblOpFee: TLabel;
+    lblprivatekey: TLabel;
+    lblTotalBalanceValue: TLabel;
+    lblTotalBalances: TLabel;
+    lblBalance: TLabel;
+    procedure cbNewPrivateKeyChange(Sender: TObject);
+    procedure cbSignerAccountChange(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;
+
+{ TWIZChangeAccountPrivateKey_Transaction }
+
+procedure TWIZChangeAccountPrivateKey_Transaction.cbSignerAccountChange(Sender: TObject);
+begin
+  if cbSignerAccount.ItemIndex < 1 then
+  begin
+    lblBalance.Font.Color := clRed;
+    lblBalance.Caption := 'Please Select Signer Account';
+  end
+  else
+  begin
+    lblBalance.Font.Color := clGreen;
+    lblBalance.Caption := Format('%s PASC',
+      [TAccountComp.FormatMoney(Model.SelectedAccounts[PtrInt(
+      cbSignerAccount.Items.Objects[cbSignerAccount.ItemIndex])].Balance)]);
+  end;
+end;
+
+procedure TWIZChangeAccountPrivateKey_Transaction.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 TWIZChangeAccountPrivateKey_Transaction.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 TWIZChangeAccountPrivateKey_Transaction.OnPresent;
+
+  function GetAccNoWithChecksum(AAccountNumber: cardinal): string;
+  begin
+    Result := TAccountComp.AccountNumberToAccountTxtNumber(AAccountNumber);
+  end;
+
+var
+  acc: TAccount;
+  accNumberwithChecksum: string;
+  totalBalance: int64;
+  i: integer;
+begin
+  cbSignerAccount.Items.BeginUpdate;
+  totalBalance := 0;
+  try
+    cbSignerAccount.Items.Clear;
+    cbSignerAccount.Items.Add('Select Signer Account');
+    for i := Low(Model.SelectedAccounts) to High(Model.SelectedAccounts) do
+    begin
+      acc := Model.SelectedAccounts[i];
+      accNumberwithChecksum := GetAccNoWithChecksum(acc.account);
+      totalBalance := totalBalance + acc.balance;
+      cbSignerAccount.Items.AddObject(accNumberwithChecksum, TObject(i));
+    end;
+  finally
+    cbSignerAccount.Items.EndUpdate;
+  end;
+  UpdateWalletKeys();
+  cbSignerAccount.ItemIndex := Model.SelectedIndex;
+  cbSignerAccountChange(Self);
+  cbNewPrivateKey.ItemIndex := Model.PrivateKeySelectedIndex;
+  cbNewPrivateKeyChange(Self);
+  lblTotalBalanceValue.Caption :=
+    Format('%s PASC', [TAccountComp.FormatMoney(totalBalance)]);
+
+  edtOpFee.Text := TAccountComp.FormatMoney(TSettings.DefaultFee);
+end;
+
+procedure TWIZChangeAccountPrivateKey_Transaction.OnNext;
+begin
+  Model.SelectedIndex := cbSignerAccount.ItemIndex;
+  Model.PrivateKeySelectedIndex := cbNewPrivateKey.ItemIndex;
+  Model.SignerAccount := Model.SelectedAccounts[PtrInt(
+    cbSignerAccount.Items.Objects[cbSignerAccount.ItemIndex])];
+  Model.NewWalletKey := TWallet.Keys.Key[PtrInt(
+    cbNewPrivateKey.Items.Objects[cbNewPrivateKey.ItemIndex])];
+
+  UpdatePath(ptReplaceAllNext, [TWIZChangeAccountPrivateKey_TransactionPayload,
+    TWIZChangeAccountPrivateKey_Confirmation]);
+end;
+
+function TWIZChangeAccountPrivateKey_Transaction.Validate(
+  out message: ansistring): boolean;
+var
+  i: integer;
+begin
+  if cbSignerAccount.ItemIndex < 1 then
+  begin
+    message := 'A signer account must be selected';
+    Result := False;
+    Exit;
+  end;
+
+  if cbNewPrivateKey.ItemIndex < 1 then
+  begin
+    message := 'A private key must be selected';
+    Result := False;
+    Exit;
+  end;
+
+  if not TAccountComp.TxtToMoney(Trim(edtOpFee.Text), Model.DefaultFee) then
+  begin
+    message := 'Invalid fee value "' + edtOpFee.Text + '"';
+    Result := False;
+    Exit;
+  end;
+
+  //Result := TAccountComp.AccountKeyFromImport(edtPublicKey.Text,
+  //  Model.AccountKey, message);
+  //for i := Low(Model.SelectedAccounts) to High(Model.SelectedAccounts) do
+  //begin
+  //  if TAccountComp.EqualAccountKeys(Model.SelectedAccounts[i].accountInfo.accountKey,
+  //    Model.AccountKey) then
+  //  begin
+  //    Result := False;
+  //    message := 'new public key is same as selected account public key';
+  //  end;
+  //end;
+end;
+
+end.

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

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

+ 84 - 0
src/gui/wizards/UWIZChangeAccountPrivateKey_TransactionPayload.pas

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

+ 33 - 1
src/pascalcoin_wallet.lpi

@@ -33,7 +33,7 @@
         <PackageName Value="LCL"/>
       </Item1>
     </RequiredPackages>
-    <Units Count="83">
+    <Units Count="88">
       <Unit0>
         <Filename Value="pascalcoin_wallet.dpr"/>
         <IsPartOfProject Value="True"/>
@@ -467,6 +467,38 @@
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
       </Unit82>
+      <Unit83>
+        <Filename Value="gui\wizards\UWIZChangeAccountPrivateKey.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit83>
+      <Unit84>
+        <Filename Value="gui\wizards\UWIZChangeAccountPrivateKey_Confirmation.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZChangeAccountPrivateKey_Confirmation"/>
+        <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
+      </Unit84>
+      <Unit85>
+        <Filename Value="gui\wizards\UWIZChangeAccountPrivateKey_Start.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZChangeAccountPrivateKey_Start"/>
+        <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
+      </Unit85>
+      <Unit86>
+        <Filename Value="gui\wizards\UWIZChangeAccountPrivateKey_Transaction.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZChangeAccountPrivateKey_Transaction"/>
+        <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
+      </Unit86>
+      <Unit87>
+        <Filename Value="gui\wizards\UWIZChangeAccountPrivateKey_TransactionPayload.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="WIZChangeAccountPrivateKey_TransactionPayload"/>
+        <HasResources Value="True"/>
+        <ResourceBaseClass Value="Form"/>
+      </Unit87>
     </Units>
   </ProjectOptions>
   <CompilerOptions>