import { stringify } from 'querystring' ; import { parse } from 'url' ; export const DAPPKIT_BASE_HOST = 'celo://wallet/dappkit' ; export enum DappKitRequestTypes { ACCOUNT_ADDRESS = 'account_address' , SIGN_TX = 'sign_tx' } export enum DappKitResponseStatus { SUCCESS = '200' , UNAUTHORIZED = '401' } export interface DappKitRequestBase { type : DappKitRequestTypes ; callback : string ; requestId : string ; dappName : string ; } export interface DappKitRequestMeta { callback : string ; requestId : string ; dappName : string ; } export interface AccountAuthRequest extends DappKitRequestBase { type : DappKitRequestTypes . ACCOUNT_ADDRESS ; } export const AccountAuthRequest = ( meta ) : AccountAuthRequest => ( { type : DappKitRequestTypes . ACCOUNT_ADDRESS , ... meta } ) ; export interface AccountAuthResponseSuccess { type : DappKitRequestTypes . ACCOUNT_ADDRESS ; status : DappKitResponseStatus . SUCCESS ; address : string ; phoneNumber : string ; pepper : string | undefined ; } export const AccountAuthResponseSuccess = ( address , phoneNumber , pepper ) : AccountAuthResponseSuccess => ( { type : DappKitRequestTypes . ACCOUNT_ADDRESS , status : DappKitResponseStatus . SUCCESS , address , phoneNumber , pepper } ) ; export interface AccountAuthResponseFailure { type : DappKitRequestTypes . ACCOUNT_ADDRESS ; status : DappKitResponseStatus . UNAUTHORIZED ; } export type AccountAuthResponse = AccountAuthResponseSuccess | AccountAuthResponseFailure ; export interface SignTxResponseSuccess { type : DappKitRequestTypes . SIGN_TX ; status : DappKitResponseStatus . SUCCESS ; rawTxs : string [ ] ; } export const SignTxResponseSuccess = ( rawTxs ) : SignTxResponseSuccess => ( { type : DappKitRequestTypes . SIGN_TX , status : DappKitResponseStatus . SUCCESS , rawTxs } ) ; export interface SignTxResponseFailure { type : DappKitRequestTypes . SIGN_TX ; status : DappKitResponseStatus . UNAUTHORIZED ; } export type SignTxResponse = SignTxResponseSuccess | SignTxResponseFailure ; export type DappKitResponse = AccountAuthResponse | SignTxResponse ; export function produceResponseDeeplink ( request , response ) { const params = { type : response . type , status : response . status , requestId : request . requestId } ; switch ( response . type ) { case DappKitRequestTypes . ACCOUNT_ADDRESS : if ( response . status === DappKitResponseStatus . SUCCESS ) { params . account = response . address ; params . phoneNumber = response . phoneNumber ; } break ; case DappKitRequestTypes . SIGN_TX : if ( response . status === DappKitResponseStatus . SUCCESS ) { params . rawTxs = response . rawTxs ; } default : break ; } return request . callback + '?' + stringify ( params ) ; } export interface TxToSignParam { txData : string ; estimatedGas : number ; from : string ; to ? : string ; nonce : number ; feeCurrencyAddress : string ; value : string ; } export interface SignTxRequest extends DappKitRequestBase { type : DappKitRequestTypes . SIGN_TX ; txs : TxToSignParam [ ] ; } export const SignTxRequest = ( txs , meta ) : SignTxRequest => ( { type : DappKitRequestTypes . SIGN_TX , txs : txs . map ( ( tx ) => ( { txData : tx . txData , estimatedGas : tx . estimatedGas , from : tx . from , to : tx . to , nonce : tx . nonce , feeCurrencyAddress : tx . feeCurrencyAddress , value : tx . value } ) ) , ... meta } ) ; export type DappKitRequest = AccountAuthRequest | SignTxRequest ; function assertString ( objectName , key , value ) { if ( value === undefined ) { throw new Error ( `Expected ${ objectName } to contain ${ key }` ) ; } if ( typeof value !== 'string' ) { throw new Error ( `Expected ${ objectName }[${ key }] to be a string, but is ${ typeof value }` ) ; } return ; } export function serializeDappKitRequestDeeplink ( request ) { assertString ( 'request' , 'type' , request . type ) ; assertString ( 'request' , 'requestId' , request . requestId ) ; assertString ( 'request' , 'callback' , request . callback ) ; assertString ( 'request' , 'dappName' , request . dappName ) ; let params = { type : request . type , requestId : request . requestId , callback : request . callback , dappName : request . dappName } ; switch ( request . type ) { case DappKitRequestTypes . SIGN_TX : params = { ... params , txs : Buffer . from ( JSON . stringify ( request . txs ) , 'utf8' ) . toString ( 'base64' ) } ; break ; case DappKitRequestTypes . ACCOUNT_ADDRESS : break ; default : throw new Error ( `Invalid DappKitRequest type: ${ JSON . stringify ( request ) }` ) ; } return DAPPKIT_BASE_HOST + '?' + stringify ( params ) ; } export function parseDappkitResponseDeeplink ( url ) { const rawParams = parse ( url , true ) ; if ( rawParams . query . type === undefined ) { throw new Error ( 'Invalid Deeplink: does not contain type:' + url ) ; } if ( rawParams . query . requestId === undefined ) { throw new Error ( 'Invalid Deeplink: does not contain requestId' ) ; } const requestId = rawParams . query . requestId as string ; const address = rawParams . query . account as string ; const phoneNumber = rawParams . query . phoneNumber as string ; switch ( rawParams . query . type ) { case DappKitRequestTypes . ACCOUNT_ADDRESS : if ( rawParams . query . status === DappKitResponseStatus . SUCCESS ) { return { type : DappKitRequestTypes . ACCOUNT_ADDRESS , status : DappKitResponseStatus . SUCCESS , address , phoneNumber , requestId } ; } else { return { type : DappKitRequestTypes . ACCOUNT_ADDRESS , status : DappKitResponseStatus . UNAUTHORIZED , requestId } ; } case DappKitRequestTypes . SIGN_TX : if ( rawParams . query . status === DappKitResponseStatus . SUCCESS ) { let rawTxs = rawParams . query . rawTxs ! ; if ( typeof rawTxs === 'string' ) { rawTxs = [ rawTxs ] ; } return { type : DappKitRequestTypes . SIGN_TX , status : DappKitResponseStatus . SUCCESS , rawTxs : rawTxs ! , requestId } ; } else { return { type : DappKitRequestTypes . SIGN_TX , status : DappKitResponseStatus . UNAUTHORIZED , requestId } ; } default : throw new Error ( 'Invalid Deeplink: does not match defined requests' ) ; } } export function parseDappKitRequestDeeplink ( url ) { const rawParams = parse ( url , true ) ; if ( rawParams . query . type === undefined ) { throw new Error ( 'Invalid Deeplink: does not contain type:' + url ) ; } if ( ! rawParams . query . dappName || ! rawParams . query . callback || ! rawParams . query . requestId ) { throw new Error ( `Invalid Deeplink: Does not contain meta parameters: ` + url ) ; } const requestMeta = { callback : rawParams . query . callback , requestId : rawParams . query . requestId , dappName : rawParams . query . dappName } ; switch ( rawParams . query . type ) { case DappKitRequestTypes . ACCOUNT_ADDRESS : return AccountAuthRequest ( requestMeta ) ; break ; case DappKitRequestTypes . SIGN_TX : return { type : DappKitRequestTypes . SIGN_TX , txs : JSON . parse ( Buffer . from ( rawParams . query . txs , 'base64' ) . toString ( 'utf8' ) ) , ... requestMeta } ; default : throw new Error ( 'Invalid Deeplink: does not match defined requests' ) ; } }