Skip to content

Commit

Permalink
fix/ledger-operation-preview (#56)
Browse files Browse the repository at this point in the history
* fix(): execute operation preview from background instead of content script

* chore(): remove unused imports

* feat(): remove chain_id from operationGroup

* chore(sonarcloud): improve code quality

* chore(): fix build

* feat(): allow for direct broadcast upon simulating operation

* feat(): dismiss BeaconRequestPage after dismissing DryRunPreviewPage

* feat(): don't dismiss BeaconRequestPage after dismissing DryRunPreviewPage

Co-authored-by: Mike Godenzi <m.godenzi@papers.ch>
  • Loading branch information
acharl and godenzim authored Sep 29, 2021
1 parent c891d3e commit a963e76
Show file tree
Hide file tree
Showing 14 changed files with 221 additions and 101 deletions.
8 changes: 4 additions & 4 deletions src/app/components/from-to/from-to.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
</ng-container>
</ng-container>

<ng-container *ngIf="operationGroup !== undefined">
<ng-container *ngIf="tezosWrappedOperation !== undefined">
<ion-row class="rawdata--container ion-padding-bottom ion-margin-bottom">
<ion-col class="ion-no-padding">
<ion-item class="ion-no-padding" lines="none">
Expand All @@ -61,11 +61,11 @@
</ion-item>

<ng-container *ngIf="advanced && formGroup !== undefined && operationControls !== undefined">
<ng-container *ngFor="let operationGroup of operationControls.controls; let i = index">
<ng-container *ngFor="let tezosWrappedOperation of operationControls.controls; let i = index">
<h6 *ngIf="operationControls && operationControls.length > 1">
<span class="style__strong color__primary">Operation #{{ i + 1 }}</span>
</h6>
<form [formGroup]="operationGroup" class="ion-padding-bottom">
<form [formGroup]="tezosWrappedOperation" class="ion-padding-bottom">
<ion-row class="padding-top">
<ion-col>
<ion-item mode="md" color="light">
Expand Down Expand Up @@ -94,7 +94,7 @@ <h6 *ngIf="operationControls && operationControls.length > 1">
</ion-button>
</ng-container>

<collapsable-json [json]="operationGroup"></collapsable-json>
<collapsable-json [json]="tezosWrappedOperation"></collapsable-json>
</ion-col>
</ion-row>
</ng-container>
38 changes: 26 additions & 12 deletions src/app/components/from-to/from-to.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Component, Input, Output, EventEmitter } from '@angular/core'
import { getProtocolByIdentifier, IAirGapTransaction, MainProtocolSymbols } from '@airgap/coinlib-core'
import { FullOperationGroup } from 'src/extension/tezos-types'
import {
getProtocolByIdentifier,
IAirGapTransaction,
MainProtocolSymbols,
TezosWrappedOperation
} from '@airgap/coinlib-core'
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'
import { FeeConverterPipe } from 'src/app/pipes/fee-converter/fee-converter.pipe'
import { isInjectableOperation } from 'src/app/types/tezos-operation'
Expand All @@ -17,10 +21,12 @@ export class FromToComponent {
public transactions: IAirGapTransaction[] | undefined

@Input()
public operationGroup: FullOperationGroup | undefined
public tezosWrappedOperation: TezosWrappedOperation | undefined

@Output()
public readonly onOperationGroupUpdate: EventEmitter<FullOperationGroup> = new EventEmitter<FullOperationGroup>()
public readonly onWrappedOperationUpdate: EventEmitter<TezosWrappedOperation> = new EventEmitter<
TezosWrappedOperation
>()

constructor(private readonly formBuilder: FormBuilder, private readonly feeConverter: FeeConverterPipe) {}

Expand All @@ -34,17 +40,21 @@ export class FromToComponent {
}

public async initForms() {
if (this.operationGroup === undefined) {
if (this.tezosWrappedOperation === undefined) {
return
}

const protocol = getProtocolByIdentifier(MainProtocolSymbols.XTZ)
this.formGroup = this.formBuilder.group({
operations: this.formBuilder.array(
this.operationGroup.contents.map(operation => {
this.tezosWrappedOperation.contents.map(operation => {
if (!isInjectableOperation(operation)) {
return this.formBuilder.group({})
}
const feeValue = this.feeConverter.transform(operation.fee, { protocolIdentifier: MainProtocolSymbols.XTZ, appendSymbol: false })
const feeValue = this.feeConverter.transform(operation.fee, {
protocolIdentifier: MainProtocolSymbols.XTZ,
appendSymbol: false
})
const feeControl = this.formBuilder.control(feeValue, [
Validators.required,
Validators.pattern(`^[0-9]+(\.[0-9]{1,${protocol.feeDecimals}})*$`)
Expand All @@ -68,23 +78,27 @@ export class FromToComponent {
}

public updateOperationGroup() {
if (this.operationGroup === undefined) {
if (this.tezosWrappedOperation === undefined) {
return
}
this.operationGroup.contents = this.operationGroup.contents.map((operation, index) => {
this.tezosWrappedOperation.contents = this.tezosWrappedOperation.contents.map((operation, index) => {
if (!isInjectableOperation(operation) || this.formGroup === undefined) {
return operation
}
const group = (this.formGroup.controls.operations as FormArray).controls[index] as FormGroup
const fee = this.feeConverter.transform(group.controls.fee.value, { protocolIdentifier: MainProtocolSymbols.XTZ, reverse: true, appendSymbol: false })
const fee = this.feeConverter.transform(group.controls.fee.value, {
protocolIdentifier: MainProtocolSymbols.XTZ,
reverse: true,
appendSymbol: false
})
return {
...operation,
fee,
gas_limit: String(group.controls.gasLimit.value),
storage_limit: String(group.controls.storageLimit.value),
storage_limit: String(group.controls.storageLimit.value)
}
})
this.onOperationGroupUpdate.emit(this.operationGroup)
this.onWrappedOperationUpdate.emit(this.tezosWrappedOperation)
this.advanced = false
}
}
13 changes: 13 additions & 0 deletions src/app/pages/add-ledger-connection/add-ledger-connection.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ export class AddLedgerConnectionPage implements OnInit {
this.confirmText = 'Confirm Transaction on your ledger.'
}

if (this.targetMethod === Action.DRY_RUN) {
this.title = 'Simulate Operation'
this.showDerivationPath = false
this.confirmText = 'Confirm Simulation on your ledger.'
}

return this.connect()
}

Expand Down Expand Up @@ -102,6 +108,13 @@ export class AddLedgerConnectionPage implements OnInit {
}
await this.walletService.addAndActiveWallet(walletInfo)
}
} else if (this.targetMethod === Action.DRY_RUN) {
const { data }: ExtensionMessageOutputPayload<Action.DRY_RUN> = response as ExtensionMessageOutputPayload<
Action.DRY_RUN
>
setTimeout(() => {
return this.modalController.dismiss(data)
}, 2000)
}
setTimeout(() => {
return this.dismiss(true)
Expand Down
8 changes: 3 additions & 5 deletions src/app/pages/beacon-request/beacon-request.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,7 @@ <h2>
</ng-container>

<ion-row *ngIf="request.type === 'broadcast_request'">
<beacon-from-to
[transactions]="transactionsPromise | async"
></beacon-from-to>
<beacon-from-to [transactions]="transactionsPromise | async"></beacon-from-to>
</ion-row>

<div *ngIf="request.type === 'sign_payload_request'" class="request--container">
Expand All @@ -104,8 +102,8 @@ <h2>
<div *ngIf="request.type === 'operation_request'" class="request--container">
<beacon-from-to
[transactions]="transactionsPromise | async"
[operationGroup]="operationGroupPromise | async"
(onOperationGroupUpdate)="onOperationGroupUpdate($event)"
[tezosWrappedOperation]="wrappedOperationPromise | async"
(onWrappedOperationUpdate)="onWrappedOperationUpdate($event)"
></beacon-from-to>
</div>

Expand Down
70 changes: 46 additions & 24 deletions src/app/pages/beacon-request/beacon-request.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { ModalOptions } from '@ionic/core'
import { Component, OnInit } from '@angular/core'
import { AlertController, ModalController, ToastController } from '@ionic/angular'
import { IAirGapTransaction, TezosProtocol } from '@airgap/coinlib-core'
import { IAirGapTransaction, TezosProtocol, TezosWrappedOperation } from '@airgap/coinlib-core'
import { take } from 'rxjs/operators'
import { ChromeMessagingService } from 'src/app/services/chrome-messaging.service'
import { WalletService } from 'src/app/services/local-wallet.service'
Expand All @@ -24,7 +24,6 @@ import { AddLedgerConnectionPage } from '../add-ledger-connection/add-ledger-con
import { ErrorPage } from '../error/error.page'
import { AirGapOperationProvider } from 'src/extension/AirGapSigner'
import { DryRunPreviewPage } from '../dry-run-preview/dry-run-preview.page'
import { FullOperationGroup } from 'src/extension/tezos-types'

@Component({
selector: 'beacon-request',
Expand All @@ -41,7 +40,7 @@ export class BeaconRequestPage implements OnInit {
public address: string = ''
public inputs?: any
public transactionsPromise: Promise<IAirGapTransaction[]> | undefined
public operationGroupPromise: Promise<FullOperationGroup> | undefined
public wrappedOperationPromise: Promise<TezosWrappedOperation> | undefined

public responseHandler: (() => Promise<void>) | undefined

Expand Down Expand Up @@ -148,11 +147,11 @@ export class BeaconRequestPage implements OnInit {
return modal.present()
}

public async onOperationGroupUpdate(operationGroup: FullOperationGroup) {
public async onWrappedOperationUpdate(tezosWrappedOperation: TezosWrappedOperation) {
if (!isOperationRequestOutput(this.request)) {
return
}
this.request = { ...this.request, operationDetails: operationGroup.contents } as OperationRequestOutput
this.request = { ...this.request, operationDetails: tezosWrappedOperation.contents } as OperationRequestOutput
await this.operationRequest(this.request)
const toast = await this.toastController.create({
message: `Updated Operation Details`,
Expand Down Expand Up @@ -230,7 +229,7 @@ export class BeaconRequestPage implements OnInit {
contents: request.operationDetails
}

this.operationGroupPromise = this.operationProvider.operationGroupFromWrappedOperation(
this.wrappedOperationPromise = this.operationProvider.completeWrappedOperation(
wrappedOperation,
this.requestedNetwork !== undefined ? this.requestedNetwork : { type: NetworkType.MAINNET }
)
Expand Down Expand Up @@ -262,23 +261,45 @@ export class BeaconRequestPage implements OnInit {
const sourceAddress = (this.request as OperationRequestOutput).sourceAddress
const wallets: WalletInfo<WalletType>[] | undefined = await this.walletService.getAllWallets()
const wallet = wallets.find(w => w.address === sourceAddress)
const network = this.requestedNetwork !== undefined ? this.requestedNetwork : { type: NetworkType.MAINNET }

try {
const dryRunPreview = await this.operationProvider.performDryRun(
wrappedOperation,
this.requestedNetwork !== undefined ? this.requestedNetwork : { type: NetworkType.MAINNET },
const request = {
tezosWrappedOperation: wrappedOperation,
network,
wallet
)
}

this.openModal(
{
component: DryRunPreviewPage,
componentProps: {
preapplyResponse: dryRunPreview.preapplyResponse
}
},
false
)
const modalOptions = {
component: AddLedgerConnectionPage,
componentProps: {
request,
targetMethod: Action.DRY_RUN
}
}
const modal = await this.modalController.create(modalOptions)

modal.present()

modal
.onDidDismiss()
.then(async ({ data: dryRunResponse }) => {
const dryRunPreview = await this.operationProvider.performDryRun(dryRunResponse!.body, network)

this.openModal(
{
component: DryRunPreviewPage,
componentProps: {
preapplyResponse: dryRunPreview,
network,
request: this.request,
signedTransaction: dryRunResponse!.signedTransaction
}
},
false
)
})
.catch(error => console.error(error))
} catch (error) {
console.error(error)
this.openModal({
Expand Down Expand Up @@ -386,11 +407,12 @@ export class BeaconRequestPage implements OnInit {
}
}

type RequestOutput = | PermissionRequestOutput
| OperationRequestOutput
| SignPayloadRequestOutput
| BroadcastRequestOutput
| undefined
type RequestOutput =
| PermissionRequestOutput
| OperationRequestOutput
| SignPayloadRequestOutput
| BroadcastRequestOutput
| undefined

function isOperationRequestOutput(request: RequestOutput): request is OperationRequestOutput {
if (request === undefined) {
Expand Down
5 changes: 4 additions & 1 deletion src/app/pages/dry-run-preview/dry-run-preview.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ <h6>
<collapsable-json [json]="preapplyResponse" [displayRawData]="true"></collapsable-json>

<ion-fab vertical="bottom" horizontal="end" slot="fixed">
<ion-button color="primary" (click)="dismiss()">
<ion-button fill="outline" color="primary" (click)="dismiss()">
Close
</ion-button>
<ion-button color="primary" (click)="confirm()">
Confirm
</ion-button>
</ion-fab>
</ion-content>
37 changes: 35 additions & 2 deletions src/app/pages/dry-run-preview/dry-run-preview.page.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { BeaconMessageType, BroadcastRequestOutput, Network, OperationRequestOutput } from '@airgap/beacon-sdk'
import { Component, Input, OnInit } from '@angular/core'
import { ModalController } from '@ionic/angular'
import { ChromeMessagingService } from 'src/app/services/chrome-messaging.service'
import { Action } from 'src/extension/extension-client/Actions'
import { PreapplyResponse, TezosGenericOperationError } from 'src/extension/tezos-types'

@Component({
selector: 'app-dry-run-preview',
templateUrl: './dry-run-preview.page.html',
Expand All @@ -10,10 +14,23 @@ export class DryRunPreviewPage implements OnInit {
public errors: TezosGenericOperationError[] = []

public jsonString: string | undefined

@Input()
public preapplyResponse!: PreapplyResponse[]

@Input()
public preapplyResponse: PreapplyResponse[] | undefined
public signedTransaction!: string

constructor(private readonly modalController: ModalController) {}
@Input()
public network!: Network

@Input()
public request!: OperationRequestOutput

constructor(
private readonly modalController: ModalController,
private readonly chromeMessagingService: ChromeMessagingService
) {}

ngOnInit(): void {
if (this.preapplyResponse) {
Expand All @@ -36,6 +53,22 @@ export class DryRunPreviewPage implements OnInit {
}
}

async confirm() {
const broadcastRequest: BroadcastRequestOutput = {
id: this.request.id,
senderId: this.request.senderId,
appMetadata: this.request.appMetadata,
type: BeaconMessageType.BroadcastRequest,
network: this.network,
signedTransaction: this.signedTransaction
}
this.chromeMessagingService.sendChromeMessage(Action.RESPONSE, {
request: broadcastRequest,
extras: undefined
})
this.dismiss()
}

private flatten<T>(arr: T[][]): T[] {
return Array.prototype.concat.apply([], arr)
}
Expand Down
7 changes: 5 additions & 2 deletions src/app/pipes/fee-converter/fee-converter.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import { BigNumber } from 'bignumber.js'
name: 'feeConverter'
})
export class FeeConverterPipe implements PipeTransform {
public transform(value: BigNumber | string | number, args: { protocolIdentifier: ProtocolSymbols, reverse?: boolean, appendSymbol?: boolean }): string {
public transform(
value: BigNumber | string | number,
args: { protocolIdentifier: ProtocolSymbols; reverse?: boolean; appendSymbol?: boolean }
): string {
const reverse = args.reverse !== undefined && args.reverse
const appendSymbol = args.appendSymbol === undefined || args.appendSymbol
if (BigNumber.isBigNumber(value)) {
Expand All @@ -28,7 +31,7 @@ export class FeeConverterPipe implements PipeTransform {
const amount = new BigNumber(value)
const shiftDirection: number = !reverse ? -1 : 1
const fee = amount.shiftedBy(shiftDirection * protocol.feeDecimals)

return fee.toFixed() + (appendSymbol ? ' ' + protocol.feeSymbol.toUpperCase() : '')
}
}
Loading

0 comments on commit a963e76

Please sign in to comment.