Skip to content

Commit

Permalink
mahmoud/eng-3688-detecting-the-wallet-api-bitcoin-provider (#63)
Browse files Browse the repository at this point in the history
* inject webbtc providersArray and export utils

* init listen util and update getProvider to return the provider object

* init request method

* fix provider imports

* export request method types

* Update Stacks request types

* Update types

* Add more stx request types

* Remove placeholder code

* Update Stacks types

* init btc methods

* Finalize btc request types

* Simplify types

* re-arrange request types

* Add stx getAddress and getAccounts types

* Prepend Stx types to avoid naming collisions

* fix error type

* Set default type for Request

* update signPsbt types and added success response type

* Fix type inferrence

* update rpc success response

* updated btc methods types and added jsdocs

* Added JSDocs for error types

* Construct Request type from request types for each network

* Add type for method names

* Set return and params to never for unknown methods

* Update contract call & deploy method names

* Start adding zod schemas

* Remove zod

* code review fixes

* Add exports

* remove listen request and update rpcid type

* added response type check util

* Proposed typing for the result (#75)

* Proposed typing for the result

* Remove cancel state

---------

Co-authored-by: Eduard Bardají Puig <[email protected]>
Co-authored-by: Victor Kirov <[email protected]>
  • Loading branch information
3 people authored Mar 8, 2024
1 parent 13fdd83 commit a6f0ed7
Show file tree
Hide file tree
Showing 12 changed files with 597 additions and 44 deletions.
25 changes: 0 additions & 25 deletions src/call/index.ts

This file was deleted.

10 changes: 0 additions & 10 deletions src/call/types.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/capabilities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const extractOrValidateCapabilities = (
};

const capabilityMap: CapabilityMap = {
call: validateCapability('call'),
request: validateCapability('request'),
connect: validateCapability('connect'),
signMessage: validateCapability('signMessage'),
signTransaction: validateCapability('signTransaction'),
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './addresses';
export * from './call';
export * from './request';
export * from './capabilities';
export * from './inscriptions';
export * from './messages';
Expand Down
20 changes: 18 additions & 2 deletions src/provider/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { BitcoinProvider } from './types';
import { type BitcoinProvider, type WebbtcProvider } from './types';

export async function getProviderOrThrow(
getProvider?: () => Promise<BitcoinProvider | undefined>
): Promise<BitcoinProvider> {
const provider = (await getProvider?.()) || window.XverseProviders?.BitcoinProvider || window.BitcoinProvider;
const provider =
(await getProvider?.()) || window.XverseProviders?.BitcoinProvider || window.BitcoinProvider;

if (!provider) {
throw new Error('No Bitcoin wallet installed');
Expand All @@ -12,4 +13,19 @@ export async function getProviderOrThrow(
return provider;
}

export function getProviders(): WebbtcProvider[] {
if (!window.webbtc_providers) window.webbtc_providers = [];
return window.webbtc_providers;
}

export function getProviderById(providerId: string) {
if (Array.isArray(window.webbtc_providers)) {
const provider = window.webbtc_providers.find((provider) => provider.id === providerId);
return provider?.id?.split('.').reduce((acc: any, part) => acc?.[part], window);
} else {
console.log('window.webbtc_providers is not defined or not an array');
return undefined;
}
}

export * from './types';
25 changes: 21 additions & 4 deletions src/provider/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Params, Requests } from '../request';
import type { GetAddressResponse } from '../addresses';
import type { CallWalletResponse } from '../call';
import type { GetCapabilitiesResponse } from '../capabilities';
import type { CreateInscriptionResponse, CreateRepeatInscriptionsResponse } from '../inscriptions';
import type { SignMessageResponse } from '../messages';
Expand All @@ -8,9 +8,14 @@ import type {
SignMultipleTransactionsResponse,
SignTransactionResponse,
} from '../transactions';
import { RpcResponse } from '../types';

interface BaseBitcoinProvider {
call: (request: string) => Promise<CallWalletResponse>;
request: <Method extends keyof Requests>(
method: Method,
options: Params<Method>,
providerId?: string
) => Promise<RpcResponse<Method>>;
connect: (request: string) => Promise<GetAddressResponse>;
signMessage: (request: string) => Promise<SignMessageResponse>;
signTransaction: (request: string) => Promise<SignTransactionResponse>;
Expand All @@ -26,13 +31,25 @@ export interface BitcoinProvider extends BaseBitcoinProvider {
getCapabilities?: (request: string) => Promise<GetCapabilitiesResponse>;
}

export interface WebbtcProvider {
id: string;
name: string;
icon: string;
webUrl?: string;
chromeWebStoreUrl?: string;
mozillaAddOnsUrl?: string;
googlePlayStoreUrl?: string;
iOSAppStoreUrl?: string;
methods?: string[];
}

declare global {
interface XverseProviders {
BitcoinProvider?: BitcoinProvider;
}

interface Window {
BitcoinProvider?: BitcoinProvider;
XverseProviders?: XverseProviders
XverseProviders?: XverseProviders;
webbtc_providers?: WebbtcProvider[];
}
}
42 changes: 42 additions & 0 deletions src/request/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { getProviderById } from '../provider';
import { RpcBase, RpcResult, RpcSuccessResponse } from '../types';
import { Params, Requests } from './types';

export const request = async <Method extends keyof Requests>(
method: Method,
params: Params<Method>,
providerId?: string
): Promise<RpcResult<Method>> => {
let provider = window.XverseProviders?.BitcoinProvider || window.BitcoinProvider;
if (providerId) {
provider = await getProviderById(providerId);
}
if (!provider) {
throw new Error('no wallet provider was found');
}
if (!method) {
throw new Error('A wallet method is required');
}

const response = await provider.request(method, params);

if (isRpcSuccessResponse<Method>(response)) {
return {
status: 'success',
result: response.result,
};
}

return {
status: 'error',
error: response.error,
};
};

const isRpcSuccessResponse = <Method extends keyof Requests>(
response: RpcBase
): response is RpcSuccessResponse<Method> => {
return Object.hasOwn(response, 'result') && !!(response as RpcSuccessResponse<Method>).result;
};

export * from './types';
125 changes: 125 additions & 0 deletions src/request/types/btcMethods.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* Represents the types and interfaces related to BTC methods.
*/

import { Address, AddressPurpose } from '../../addresses';
import { MethodParamsAndResult } from '../../types';

type GetInfoResult = {
version: number | string;
methods?: Array<string>;
supports?: Array<string>;
};

export type GetInfo = MethodParamsAndResult<null, GetInfoResult>;

type GetAddressesParams = {
/**
* The purposes for which to generate addresses.
* possible values are "payment", "ordinals", ...
*/
purposes: Array<AddressPurpose>;
/**
* a message to be displayed to the user in the request prompt.
*/
message?: string;
};

/**
* The addresses generated for the given purposes.
*/
type GetAddressesResult = {
addresses: Array<Address>;
};

export type GetAddresses = MethodParamsAndResult<GetAddressesParams, GetAddressesResult>;

type SignMessageParams = {
/**
* The address used for signing.
**/
address: string;
/**
* The message to sign.
**/
message: string;
};

type SignMessageResult = {
/**
* The signature of the message.
*/
signature: string;
/**
* hash of the message.
*/
messageHash: string;
/**
* The address used for signing.
*/
address: string;
};

export type SignMessage = MethodParamsAndResult<SignMessageParams, SignMessageResult>;

type Recipient = {
/**
* The recipient's address.
**/
address: string;
/**
* The amount to send to the recipient in satoshis.
*/
amount: number;
};

type SendTransferParams = {
/**
* Array of recipients to send to.
* The amount to send to each recipient is in satoshis.
*/
recipients: Array<Recipient>;
};
type SendTransferResult = {
/**
* The transaction id as a hex-encoded string.
*/
txid: string;
};

export type SendTransfer = MethodParamsAndResult<SendTransferParams, SendTransferResult>;

export type SignPsbtParams = {
/**
* The base64 encoded PSBT to sign.
*/
psbt: string;
/**
* The inputs to sign.
* The key is the address and the value is an array of indexes of the inputs to sign.
*/
signInputs: Record<string, number[]>;
/**
* the sigHash type to use for signing.
* will default to the sighash type of the input if not provided.
**/
allowedSignHash?: number;
/**
* Whether to broadcast the transaction after signing.
**/
broadcast?: boolean;
};

export type SignPsbtResult = {
/**
* The base64 encoded PSBT after signing.
*/
psbt: string;
/**
* The transaction id as a hex-encoded string.
* This is only returned if the transaction was broadcast.
**/
txid?: string;
};

export type SignPsbt = MethodParamsAndResult<SignPsbtParams, SignPsbtResult>;
43 changes: 43 additions & 0 deletions src/request/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { RpcSuccessResponse } from 'src/types';
import { GetAddresses, GetInfo, SendTransfer, SignMessage, SignPsbt } from './btcMethods';
import {
StxCallContract,
StxDeployContract,
StxGetAccounts,
StxGetAddresses,
StxSignStructuredMessage,
StxSignStxMessage,
StxSignTransaction,
StxTransferStx,
} from './stxMethods';

export interface StxRequests {
stx_callContract: StxCallContract;
stx_deployContract: StxDeployContract;
stx_getAccounts: StxGetAccounts;
stx_getAddresses: StxGetAddresses;
stx_signMessage: StxSignStxMessage;
stx_signStructuredMessage: StxSignStructuredMessage;
stx_signTransaction: StxSignTransaction;
stx_transferStx: StxTransferStx;
}

export type StxRequestMethod = keyof StxRequests;

export interface BtcRequests {
getInfo: GetInfo;
getAddresses: GetAddresses;
signMessage: SignMessage;
sendTransfer: SendTransfer;
signPsbt: SignPsbt;
}

export type BtcRequestMethod = keyof BtcRequests;

export type Requests = BtcRequests & StxRequests;

export type Return<Method> = Method extends keyof Requests ? Requests[Method]['result'] : never;
export type Params<Method> = Method extends keyof Requests ? Requests[Method]['params'] : never;

export * from './stxMethods';
export * from './btcMethods';
Loading

0 comments on commit a6f0ed7

Please sign in to comment.