import { ChainInfo , Keplr , Keplr as IKeplr , KeplrIntereactionOptions , KeplrMode , KeplrSignOptions , Key , } from "@keplr-wallet/types" ; import { Result , JSONUint8Array } from "@keplr-wallet/router" ; import { BroadcastMode , AminoSignResponse , StdSignDoc , StdTx , OfflineSigner , StdSignature , } from "@cosmjs/launchpad" ; import { SecretUtils } from "secretjs/types/enigmautils" ; import { KeplrEnigmaUtils } from "./enigma" ; import { DirectSignResponse , OfflineDirectSigner } from "@cosmjs/proto-signing" ; import { CosmJSOfflineSigner , CosmJSOfflineSignerOnlyAmino } from "./cosmjs" ; import deepmerge from "deepmerge" ; import Long from "long" ; export interface ProxyRequest { type : "proxy-request" ; id : string ; method : keyof Keplr ; args : any [ ] ; } export interface ProxyRequestResponse { type : "proxy-request-response" ; id : string ; result : Result | undefined ; } /**
 * InjectedKeplr would be injected to the webpage.
 * In the webpage, it can't request any messages to the extension because it doesn't have any API related to the extension.
 * So, to request some methods of the extension, this will proxy the request to the content script that is injected to webpage on the extension level.
 * This will use `window.postMessage` to interact with the content script.
 */ export class InjectedKeplr implements IKeplr { static startProxy ( keplr , eventListener = { addMessageListener : ( fn ) => window . addEventListener ( "message" , fn ) , postMessage : ( message ) => window . postMessage ( message , window . location . origin ) , } , parseMessage ? ) { eventListener . addMessageListener ( async ( e ) => { const message = parseMessage ? parseMessage ( e . data ) : e . data ; if ( ! message || message . type !== "proxy-request" ) { return ; } try { if ( ! message . id ) { throw new Error ( "Empty id" ) ; } if ( message . method === "version" ) { throw new Error ( "Version is not function" ) ; } if ( message . method === "mode" ) { throw new Error ( "Mode is not function" ) ; } if ( message . method === "defaultOptions" ) { throw new Error ( "DefaultOptions is not function" ) ; } if ( ! keplr [ message . method ] || typeof keplr [ message . method ] !== "function" ) { throw new Error ( `Invalid method: ${ message . method }` ) ; } if ( message . method === "getOfflineSigner" ) { throw new Error ( "GetOfflineSigner method can't be proxy request" ) ; } if ( message . method === "getOfflineSignerOnlyAmino" ) { throw new Error ( "GetOfflineSignerOnlyAmino method can't be proxy request" ) ; } if ( message . method === "getOfflineSignerAuto" ) { throw new Error ( "GetOfflineSignerAuto method can't be proxy request" ) ; } if ( message . method === "getEnigmaUtils" ) { throw new Error ( "GetEnigmaUtils method can't be proxy request" ) ; } const result = message . method === "signDirect" ? await ( async ( ) => { const receivedSignDoc = message . args [ 2 ] ; const result = await keplr . signDirect ( message . args [ 0 ] , message . args [ 1 ] , { bodyBytes : receivedSignDoc . bodyBytes , authInfoBytes : receivedSignDoc . authInfoBytes , chainId : receivedSignDoc . chainId , accountNumber : receivedSignDoc . accountNumber ? Long . fromString ( receivedSignDoc . accountNumber ) : null , } , message . args [ 3 ] ) ; return { signed : { bodyBytes : result . signed . bodyBytes , authInfoBytes : result . signed . authInfoBytes , chainId : result . signed . chainId , accountNumber : result . signed . accountNumber . toString ( ) , } , signature : result . signature , } ; } ) ( ) : await keplr [ message . method ] ( ... JSONUint8Array . unwrap ( message . args ) ) ; const proxyResponse = { type : "proxy-request-response" , id : message . id , result : { return : JSONUint8Array . wrap ( result ) , } , } ; eventListener . postMessage ( proxyResponse ) ; } catch ( e ) { const proxyResponse = { type : "proxy-request-response" , id : message . id , result : { error : e . message || e . toString ( ) , } , } ; eventListener . postMessage ( proxyResponse ) ; } } ) ; } protected requestMethod ( method , args ) { const bytes = new Uint8Array ( 8 ) ; const id = Array . from ( crypto . getRandomValues ( bytes ) ) . map ( ( value ) => { return value . toString ( 16 ) ; } ) . join ( "" ) ; const proxyMessage = { type : "proxy-request" , id , method , args : JSONUint8Array . wrap ( args ) , } ; return new Promise ( ( resolve , reject ) => { const receiveResponse = ( e ) => { const proxyResponse = this . parseMessage ? this . parseMessage ( e . data ) : e . data ; if ( ! proxyResponse || proxyResponse . type !== "proxy-request-response" ) { return ; } if ( proxyResponse . id !== id ) { return ; } this . eventListener . removeMessageListener ( receiveResponse ) ; const result = JSONUint8Array . unwrap ( proxyResponse . result ) ; if ( ! result ) { reject ( new Error ( "Result is null" ) ) ; return ; } if ( result . error ) { reject ( new Error ( result . error ) ) ; return ; } resolve ( result . return ) ; } ; this . eventListener . addMessageListener ( receiveResponse ) ; this . eventListener . postMessage ( proxyMessage ) ; } ) ; } protected enigmaUtils : Map < string , SecretUtils > = new Map ( ) ; public defaultOptions : KeplrIntereactionOptions = { } ; constructor ( public readonly version , public readonly mode , protected readonly eventListener = { addMessageListener : ( fn ) => window . addEventListener ( "message" , fn ) , removeMessageListener : ( fn ) => window . removeEventListener ( "message" , fn ) , postMessage : ( message ) => window . postMessage ( message , window . location . origin ) , } , protected readonly parseMessage ? ) { } async enable ( chainIds ) { await this . requestMethod ( "enable" , [ chainIds ] ) ; } async experimentalSuggestChain ( chainInfo ) { await this . requestMethod ( "experimentalSuggestChain" , [ chainInfo ] ) ; } async getKey ( chainId ) { return await this . requestMethod ( "getKey" , [ chainId ] ) ; } async sendTx ( chainId , tx , mode ) { return await this . requestMethod ( "sendTx" , [ chainId , tx , mode ] ) ; } async signAmino ( chainId , signer , signDoc , signOptions = { } ) { return await this . requestMethod ( "signAmino" , [ chainId , signer , signDoc , deepmerge ( this . defaultOptions . sign ?? { } , signOptions ) , ] ) ; } async signDirect ( chainId , signer , signDoc , signOptions = { } ) { const result = await this . requestMethod ( "signDirect" , [ chainId , signer , { bodyBytes : signDoc . bodyBytes , authInfoBytes : signDoc . authInfoBytes , chainId : signDoc . chainId , accountNumber : signDoc . accountNumber ? signDoc . accountNumber . toString ( ) : null , } , deepmerge ( this . defaultOptions . sign ?? { } , signOptions ) , ] ) ; const signed = result . signed ; return { signed : { bodyBytes : signed . bodyBytes , authInfoBytes : signed . authInfoBytes , chainId : signed . chainId , accountNumber : Long . fromString ( signed . accountNumber ) , } , signature : result . signature , } ; } async signArbitrary ( chainId , signer , data ) { return await this . requestMethod ( "signArbitrary" , [ chainId , signer , data ] ) ; } async verifyArbitrary ( chainId , signer , data , signature ) { return await this . requestMethod ( "verifyArbitrary" , [ chainId , signer , data , signature , ] ) ; } getOfflineSigner ( chainId ) { return new CosmJSOfflineSigner ( chainId , this ) ; } getOfflineSignerOnlyAmino ( chainId ) { return new CosmJSOfflineSignerOnlyAmino ( chainId , this ) ; } async getOfflineSignerAuto ( chainId ) { const key = await this . getKey ( chainId ) ; if ( key . isNanoLedger ) { return new CosmJSOfflineSignerOnlyAmino ( chainId , this ) ; } return new CosmJSOfflineSigner ( chainId , this ) ; } async suggestToken ( chainId , contractAddress , viewingKey ? ) { return await this . requestMethod ( "suggestToken" , [ chainId , contractAddress , viewingKey , ] ) ; } async getSecret20ViewingKey ( chainId , contractAddress ) { return await this . requestMethod ( "getSecret20ViewingKey" , [ chainId , contractAddress , ] ) ; } async getEnigmaPubKey ( chainId ) { return await this . requestMethod ( "getEnigmaPubKey" , [ chainId ] ) ; } async getEnigmaTxEncryptionKey ( chainId , nonce ) { return await this . requestMethod ( "getEnigmaTxEncryptionKey" , [ chainId , nonce , ] ) ; } async enigmaEncrypt ( chainId , contractCodeHash , msg ) { return await this . requestMethod ( "enigmaEncrypt" , [ chainId , contractCodeHash , msg , ] ) ; } async enigmaDecrypt ( chainId , ciphertext , nonce ) { return await this . requestMethod ( "enigmaDecrypt" , [ chainId , ciphertext , nonce , ] ) ; } getEnigmaUtils ( chainId ) { if ( this . enigmaUtils . has ( chainId ) ) { return this . enigmaUtils . get ( chainId ) ! ; } const enigmaUtils = new KeplrEnigmaUtils ( chainId , this ) ; this . enigmaUtils . set ( chainId , enigmaUtils ) ; return enigmaUtils ; } }