import { AppConfig , APP_CONFIG , BaseIACService , ClipboardService , DeeplinkService , IACMessageTransport , IACMessageWrapper , ProtocolService , RelayMessage , UiEventElementsService , UiEventService } from '@airgap/angular-core' ; import { AirGapWallet , AirGapWalletStatus , IACMessageDefinitionObjectV3 , IACMessageType , MainProtocolSymbols , MessageSignRequest , RawBitcoinSegwitTransaction , UnsignedTransaction } from '@airgap/coinlib-core' ; import { Inject , Injectable } from '@angular/core' ; import { SignTransactionInfo } from '../../models/sign-transaction-info' ; import { ErrorCategory , handleErrorLocal } from '../error-handler/error-handler.service' ; import { InteractionOperationType , InteractionService } from '../interaction/interaction.service' ; import { NavigationService } from '../navigation/navigation.service' ; import { SecretsService } from '../secrets/secrets.service' ; import * as bitcoinJS from 'bitcoinjs-lib' ; import { ModalController } from '@ionic/angular' ; import { SelectAccountPage } from 'src/app/pages/select-account/select-account.page' ; import { RawTypedEthereumTransaction } from '@airgap/coinlib-core/serializer/types' ; @ Injectable ( { providedIn : 'root' } ) export class IACService extends BaseIACService { constructor ( public readonly uiEventService , public readonly uiEventElementsService , public readonly deeplinkService , private readonly protocolService , protected readonly clipboard , private readonly navigationService , private readonly secretsService , private readonly interactionService , private readonly modalController , @ Inject ( APP_CONFIG ) appConfig ) { super ( uiEventElementsService , clipboard , secretsService . isReady ( ) , [ ] , deeplinkService , appConfig ) ; this . serializerMessageHandlers [ IACMessageType . TransactionSignRequest ] = this . handleUnsignedTransactions . bind ( this ) ; this . serializerMessageHandlers [ IACMessageType . MessageSignRequest ] = this . handleMessageSignRequest . bind ( this ) ; } public async relay ( data ) { this . interactionService . startInteraction ( { operationType : InteractionOperationType . WALLET_SYNC , iacMessage : ( data as any ) . messages ?? ( data as any ) . rawString } ) ; } private async handleUnsignedTransactions ( messageWrapper , _transport , scanAgainCallback ) { const signTransactionRequests = messageWrapper . result ; const transactionInfos = ( await Promise . all ( signTransactionRequests . map ( async ( signTransactionRequest ) : Promise < SignTransactionInfo > => { return this . findMatchingWallet ( signTransactionRequest , messageWrapper . context ) ; } ) ) ) . filter ( ( signTransactionDetails ) => signTransactionDetails . wallet !== undefined ) ; if ( transactionInfos . length > 0 ) { if ( transactionInfos . length !== signTransactionRequests . length ) { } this . navigationService . routeWithState ( 'deserialized-detail' , { transactionInfos : transactionInfos , iacContext : messageWrapper . context , type : IACMessageType . TransactionSignRequest } ) . catch ( handleErrorLocal ( ErrorCategory . IONIC_NAVIGATION ) ) ; return true ; } else { const cancelButton = { text : 'tab-wallets.no-secret_alert.okay_label' , role : 'cancel' , handler : ( ) => { scanAgainCallback ( ) ; } } ; this . uiEventService . showTranslatedAlert ( { header : 'tab-wallets.no-secret_alert.title' , message : 'tab-wallets.no-secret_alert.text' , buttons : [ cancelButton ] } ) ; return false ; } } private async handleMessageSignRequest ( messageWrapper , _transport , _scanAgainCallback ) { const messageDefinitionObjects = messageWrapper . result ; const transactionInfos = await Promise . all ( messageDefinitionObjects . map ( async ( messageDefinitionObject ) : Promise < SignTransactionInfo > => { return this . findMatchingSignWallet ( messageDefinitionObject , messageWrapper . context ) ; } ) ) ; this . navigationService . routeWithState ( 'deserialized-detail' , { transactionInfos : transactionInfos , iacContext : messageWrapper . context , type : IACMessageType . MessageSignRequest } ) . catch ( handleErrorLocal ( ErrorCategory . IONIC_NAVIGATION ) ) ; return true ; } private async activateWallet ( wallet ) { if ( wallet . status === AirGapWalletStatus . ACTIVE ) { return ; } wallet . status = AirGapWalletStatus . ACTIVE ; await this . secretsService . updateWallet ( wallet ) ; } private async findMatchingWallet ( signTransactionRequest , metadata ) { const unsignedTransaction = signTransactionRequest . payload as UnsignedTransaction ; let correctWallet = this . secretsService . findWalletByPublicKeyAndProtocolIdentifier ( unsignedTransaction . publicKey , signTransactionRequest . protocol ) ; if ( ! correctWallet && signTransactionRequest . protocol === MainProtocolSymbols . BTC_SEGWIT ) { const transaction = unsignedTransaction . transaction ; const decodedPSBT = bitcoinJS . Psbt . fromHex ( transaction . psbt ) ; for ( const input of decodedPSBT . data . inputs ) { for ( const derivation of input . bip32Derivation ) { const masterFingerprint = derivation . masterFingerprint . toString ( 'hex' ) ; correctWallet = this . secretsService . findWalletByFingerprintDerivationPathAndProtocolIdentifier ( masterFingerprint , signTransactionRequest . protocol , derivation . path , derivation . pubkey ) ; if ( correctWallet ) { break ; } } if ( correctWallet ) { break ; } } if ( ! correctWallet ) { await new Promise ( async ( resolve ) => { const modal = await this . modalController . create ( { component : SelectAccountPage , componentProps : { type : 'psbt' , symbolFilter : MainProtocolSymbols . BTC_SEGWIT } } ) ; modal . onDidDismiss ( ) . then ( ( result ) => { console . log ( 'modal closed' , result ) ; correctWallet = result . data ; resolve ( undefined ) ; } ) . catch ( handleErrorLocal ( ErrorCategory . IONIC_MODAL ) ) ; modal . present ( ) . catch ( handleErrorLocal ( ErrorCategory . IONIC_MODAL ) ) ; } ) ; } if ( correctWallet && ! unsignedTransaction . publicKey ) { unsignedTransaction . publicKey = correctWallet . publicKey ; } } if ( ! correctWallet && signTransactionRequest . protocol === MainProtocolSymbols . ETH ) { const transaction = unsignedTransaction . transaction ; const fingerprint = transaction . masterFingerprint ?? metadata . sourceFingerprint ; const derivationPath = transaction . derivationPath ?? metadata . derivationPath ; correctWallet = await this . findMetaMaskWallet ( fingerprint , signTransactionRequest . protocol , derivationPath ) ; if ( correctWallet && ! derivationPath . startsWith ( correctWallet . derivationPath . slice ( 2 ) ) ) { throw new Error ( 'Derivation path of the request does not match the one of the selected wallet' ) ; } if ( correctWallet && ! unsignedTransaction . publicKey ) { unsignedTransaction . publicKey = correctWallet . publicKey ; } } if ( correctWallet ) { await this . activateWallet ( correctWallet ) ; } if ( ! correctWallet ) { correctWallet = await this . findBaseWallet ( unsignedTransaction . publicKey , signTransactionRequest ) ; } return { wallet : correctWallet , signTransactionRequest } ; } private async findMatchingSignWallet ( messageDefinitionObject , metadata ) { const messageSignRequest = messageDefinitionObject . payload as MessageSignRequest ; let correctWallet = this . secretsService . findWalletByPublicKeyAndProtocolIdentifier ( messageSignRequest . publicKey , messageDefinitionObject . protocol ) ; if ( ! correctWallet && messageDefinitionObject . protocol === MainProtocolSymbols . ETH ) { correctWallet = await this . findMetaMaskWallet ( metadata . sourceFingerprint , messageDefinitionObject . protocol , metadata . derivationPath ) ; if ( correctWallet && ! metadata . derivationPath . startsWith ( correctWallet . derivationPath . slice ( 2 ) ) ) { throw new Error ( 'Derivation path of the request does not match the one of the selected wallet' ) ; } } if ( correctWallet ) { await this . activateWallet ( correctWallet ) ; } if ( ! correctWallet ) { correctWallet = await this . findBaseWallet ( messageSignRequest . publicKey , messageDefinitionObject ) ; } return { wallet : correctWallet , signTransactionRequest : { ... messageDefinitionObject , payload : { ... messageSignRequest , publicKey : correctWallet ?. publicKey ?? '' } } } ; } private async findBaseWallet ( publicKey , messageDefinitionObject ) { let correctWallet ; const baseWallet = this . secretsService . findBaseWalletByPublicKeyAndProtocolIdentifier ( publicKey , messageDefinitionObject . protocol ) ; if ( baseWallet ) { await this . activateWallet ( baseWallet ) ; const protocol = await this . protocolService . getProtocol ( messageDefinitionObject . protocol ) ; try { correctWallet = new AirGapWallet ( protocol , baseWallet . publicKey , baseWallet . isExtendedPublicKey , baseWallet . derivationPath , baseWallet . masterFingerprint , baseWallet . status ) ; correctWallet . addresses = baseWallet . addresses ; } catch ( e ) { if ( e . message === 'PROTOCOL_NOT_SUPPORTED' ) { correctWallet = baseWallet ; } } } return correctWallet ; } private async findMetaMaskWallet ( fingerprint , protocol , derivationPath ) { let correctWallet ; correctWallet = this . secretsService . findWalletByXPubFingerprintDerivationPathAndProtocolIdentifier ( fingerprint , protocol , derivationPath ) ; return correctWallet ; } }