import { AccountSetBase , AccountSetOpts , MsgOpt } from "./base" ; import { HasSecretQueries , QueriesSetBase , QueriesStore } from "../query" ; import { Buffer } from "buffer/" ; import { ChainGetter , CoinPrimitive } from "../common" ; import { StdFee } from "@cosmjs/launchpad" ; import { DenomHelper } from "@keplr-wallet/common" ; import { Dec , DecUtils } from "@keplr-wallet/unit" ; import { AppCurrency , KeplrSignOptions } from "@keplr-wallet/types" ; import { DeepReadonly , Optional } from "utility-types" ; import { cosmos } from "@keplr-wallet/cosmos" ; export interface HasSecretAccount { secret : DeepReadonly < SecretAccount > ; } export interface SecretMsgOpts { readonly send : { readonly secret20 : Pick < MsgOpt , "gas" > ; } ; readonly createSecret20ViewingKey : Pick < MsgOpt , "gas" > ; readonly executeSecretWasm : Pick < MsgOpt , "type" > ; } export class AccountWithSecret extends AccountSetBase < SecretMsgOpts , HasSecretQueries > implements HasSecretAccount { public readonly secret : DeepReadonly < SecretAccount > ; static readonly defaultMsgOpts : SecretMsgOpts = { send : { secret20 : { gas : 250000 , } , } , createSecret20ViewingKey : { gas : 150000 , } , executeSecretWasm : { type : "wasm/MsgExecuteContract" , } , } ; constructor ( protected readonly eventListener , protected readonly chainGetter , protected readonly chainId , protected readonly queriesStore , protected readonly opts ) { super ( eventListener , chainGetter , chainId , queriesStore , opts ) ; this . secret = new SecretAccount ( this , chainGetter , chainId , queriesStore ) ; } } export class SecretAccount { constructor ( protected readonly base , protected readonly chainGetter , protected readonly chainId , protected readonly queriesStore ) { this . base . registerSendTokenFn ( this . processSendToken . bind ( this ) ) ; } protected async processSendToken ( amount , currency , recipient , memo , stdFee , signOptions ? , onTxEvents ? ) { const denomHelper = new DenomHelper ( currency . coinMinimalDenom ) ; switch ( denomHelper . type ) { case "secret20" : const actualAmount = ( ( ) => { let dec = new Dec ( amount ) ; dec = dec . mul ( DecUtils . getPrecisionDec ( currency . coinDecimals ) ) ; return dec . truncate ( ) . toString ( ) ; } ) ( ) ; if ( ! ( "type" in currency ) || currency . type !== "secret20" ) { throw new Error ( "Currency is not secret20" ) ; } await this . sendExecuteSecretContractMsg ( "send" , currency . contractAddress , { transfer : { recipient : recipient , amount : actualAmount , } , } , [ ] , memo , { amount : stdFee . amount ?? [ ] , gas : stdFee . gas ?? this . base . msgOpts . send . secret20 . gas . toString ( ) , } , signOptions , this . txEventsWithPreOnFulfill ( onTxEvents , ( tx ) => { if ( tx . code == null || tx . code === 0 ) { const queryBalance = this . queries . queryBalances . getQueryBech32Address ( this . base . bech32Address ) . balances . find ( ( bal ) => { return ( bal . currency . coinMinimalDenom === currency . coinMinimalDenom ) ; } ) ; if ( queryBalance ) { queryBalance . fetch ( ) ; } } } ) ) ; return true ; } return false ; } async createSecret20ViewingKey ( contractAddress , memo = "" , stdFee = { } , signOptions ? , onFulfill ? ) { const random = new Uint8Array ( 15 ) ; crypto . getRandomValues ( random ) ; const entropy = Buffer . from ( random ) . toString ( "hex" ) ; const encrypted = await this . sendExecuteSecretContractMsg ( "createSecret20ViewingKey" , contractAddress , { create_viewing_key : { entropy } , } , [ ] , memo , { amount : stdFee . amount ?? [ ] , gas : stdFee . gas ?? this . base . msgOpts . createSecret20ViewingKey . gas . toString ( ) , } , signOptions , async ( tx ) => { let viewingKey = "" ; if ( tx && "data" in tx && tx . data ) { const txData = Buffer . from ( tx . data as any , "base64" ) ; const dataFields = cosmos . base . abci . v1beta1 . TxMsgData . decode ( txData ) ; if ( dataFields . data . length !== 1 ) { throw new Error ( "Invalid length of data fields" ) ; } const dataField = dataFields . data [ 0 ] ; if ( ! dataField . data ) { throw new Error ( "Empty data" ) ; } const keplr = await this . base . getKeplr ( ) ; if ( ! keplr ) { throw new Error ( "Can't get the Keplr API" ) ; } const enigmaUtils = keplr . getEnigmaUtils ( this . chainId ) ; const nonce = encrypted . slice ( 0 , 32 ) ; const dataOutput = Buffer . from ( Buffer . from ( await enigmaUtils . decrypt ( dataField . data , nonce ) ) . toString ( ) , "base64" ) . toString ( ) ; const data = JSON . parse ( dataOutput ) ; viewingKey = data [ "create_viewing_key" ] [ "key" ] ; } if ( onFulfill ) { onFulfill ( tx , viewingKey ) ; } } ) ; return ; } async sendExecuteSecretContractMsg ( type = "executeSecretWasm" , contractAddress , obj , sentFunds , memo = "" , stdFee , signOptions ? , onTxEvents ? ) { let encryptedMsg ; await this . base . sendMsgs ( type , async ( ) => { encryptedMsg = await this . encryptSecretContractMsg ( contractAddress , obj ) ; const msg = { type : this . base . msgOpts . executeSecretWasm . type , value : { sender : this . base . bech32Address , contract : contractAddress , msg : Buffer . from ( encryptedMsg ) . toString ( "base64" ) , sent_funds : sentFunds , } , } ; return [ msg ] ; } , memo , { amount : stdFee . amount ?? [ ] , gas : stdFee . gas , } , signOptions , this . txEventsWithPreOnFulfill ( onTxEvents ) ) ; return encryptedMsg ! ; } protected async encryptSecretContractMsg ( contractAddress , obj ) { const queryContractCodeHashResponse = await this . queries . secret . querySecretContractCodeHash . getQueryContract ( contractAddress ) . waitResponse ( ) ; if ( ! queryContractCodeHashResponse ) { throw new Error ( `Can't get the code hash of the contract (${ contractAddress })` ) ; } const contractCodeHash = queryContractCodeHashResponse . data . result ; const keplr = await this . base . getKeplr ( ) ; if ( ! keplr ) { throw new Error ( "Can't get the Keplr API" ) ; } const enigmaUtils = keplr . getEnigmaUtils ( this . chainId ) ; return await enigmaUtils . encrypt ( contractCodeHash , obj ) ; } protected txEventsWithPreOnFulfill ( onTxEvents , preOnFulfill ? ) { if ( ! onTxEvents ) { return ; } const onBroadcasted = typeof onTxEvents === "function" ? undefined : onTxEvents . onBroadcasted ; const onFulfill = typeof onTxEvents === "function" ? onTxEvents : onTxEvents . onFulfill ; return { onBroadcasted , onFulfill : onFulfill ? ( tx ) => { if ( preOnFulfill ) { preOnFulfill ( tx ) ; } onFulfill ( tx ) ; } : undefined , } ; } protected get queries ( ) : DeepReadonly < QueriesSetBase & HasSecretQueries > { return this . queriesStore . get ( this . chainId ) ; } }