Socket
Socket
Sign inDemoInstall

@0xsequence/provider

Package Overview
Dependencies
Maintainers
4
Versions
498
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@0xsequence/provider - npm Package Compare versions

Comparing version 0.18.0 to 0.19.0

17

CHANGELOG.md
# @0xsequence/provider
## 0.19.0
### Minor Changes
- - provider, improve dapp / wallet transport io
### Patch Changes
- Updated dependencies [undefined]
- @0xsequence/abi@0.19.0
- @0xsequence/auth@0.19.0
- @0xsequence/config@0.19.0
- @0xsequence/network@0.19.0
- @0xsequence/transactions@0.19.0
- @0xsequence/utils@0.19.0
- @0xsequence/wallet@0.19.0
## 0.18.0

@@ -4,0 +21,0 @@

3

dist/declarations/src/provider.d.ts

@@ -12,3 +12,5 @@ import { ethers } from 'ethers';

export declare class Web3Provider extends EthersWeb3Provider implements JsonRpcHandler {
static isSequenceProvider(cand: any): cand is Web3Provider;
readonly _sender: JsonRpcSender;
readonly _isSequenceProvider: boolean;
readonly _defaultChainId?: number;

@@ -21,2 +23,3 @@ constructor(provider: JsonRpcProvider | JsonRpcHandler | JsonRpcFetchFunc, defaultChainId?: ChainId);

}
export declare function isSequenceProvider(provider: any): provider is Web3Provider;
export declare class LocalWeb3Provider extends Web3Provider {

@@ -23,0 +26,0 @@ constructor(signer: Signer, networks?: Networks);

24

dist/declarations/src/transports/base-provider-transport.d.ts
import EventEmitter from 'eventemitter3';
import { ProviderTransport, ProviderMessage, ProviderMessageRequest, ProviderMessageEvent, ProviderMessageResponse, ProviderMessageResponseCallback, WalletSession, OpenState, OpenWalletIntent } from '../types';
import { ProviderTransport, ProviderMessage, ProviderMessageRequest, ProviderEventTypes, ProviderMessageResponse, ProviderMessageResponseCallback, OpenState, OpenWalletIntent, ConnectDetails, WalletSession, InitState } from '../types';
import { NetworkConfig, WalletContext, JsonRpcRequest, JsonRpcResponseCallback } from '@0xsequence/network';

@@ -11,7 +11,13 @@ export declare const PROVIDER_OPEN_TIMEOUT = 30000;

protected confirmationOnly: boolean;
protected events: EventEmitter<ProviderMessageEvent, any>;
protected accountPayload: string | undefined;
protected events: EventEmitter<ProviderEventTypes, any>;
protected openPayload: {
sessionId?: string;
session?: WalletSession;
} | undefined;
protected connectPayload: ConnectDetails | undefined;
protected accountsChangedPayload: string | undefined;
protected networksPayload: NetworkConfig[] | undefined;
protected walletContextPayload: WalletContext | undefined;
protected _sessionId?: string;
protected _init: InitState;
protected _registered: boolean;

@@ -22,3 +28,3 @@ constructor();

unregister(): void;
openWallet(path?: string, intent?: OpenWalletIntent, defaultNetworkId?: string | number): void;
openWallet(path?: string, intent?: OpenWalletIntent, networkId?: string | number): void;
closeWallet(): void;

@@ -31,8 +37,8 @@ isOpened(): boolean;

sendMessage(message: ProviderMessage<any>): void;
on(event: ProviderMessageEvent, fn: (...args: any[]) => void): void;
once(event: ProviderMessageEvent, fn: (...args: any[]) => void): void;
waitUntilOpened: (openTimeout?: number) => Promise<boolean>;
waitUntilConnected: () => Promise<WalletSession>;
protected open: () => Promise<boolean>;
on<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void;
once<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void;
emit<K extends keyof ProviderEventTypes>(event: K, ...args: Parameters<ProviderEventTypes[K]>): boolean;
waitUntilOpened: (openTimeout?: number) => Promise<WalletSession | undefined>;
waitUntilConnected: () => Promise<ConnectDetails>;
protected close(): void;
}

@@ -1,2 +0,2 @@

import { WalletTransport, ProviderMessage, ProviderMessageRequest, ProviderMessageResponse, ProviderConnectInfo, ProviderRpcError, InitState } from '../types';
import { WalletTransport, ProviderMessage, ProviderMessageRequest, ProviderMessageResponse, ProviderRpcError, InitState, ConnectDetails, OpenWalletIntent, WalletSession } from '../types';
import { WalletRequestHandler } from './wallet-request-handler';

@@ -9,2 +9,5 @@ import { NetworkConfig, WalletContext, JsonRpcRequest, JsonRpcResponseCallback } from '@0xsequence/network';

protected _init: InitState;
protected _initNonce: string;
protected _initCallback?: (error?: string) => void;
protected appOrigin?: string;
constructor(walletRequestHandler: WalletRequestHandler);

@@ -21,14 +24,15 @@ get registered(): boolean;

sessionId?: string;
session?: WalletSession;
error?: string;
}): void;
notifyClose(): void;
notifyConnect(connectInfo: ProviderConnectInfo & {
error?: string;
}): void;
notifyClose(error?: ProviderRpcError): void;
notifyConnect(connectDetails: ConnectDetails): void;
notifyDisconnect(error?: ProviderRpcError): void;
notifyAccountsChanged(accounts: string[]): void;
notifyChainChanged(hexChainId: string): void;
notifyChainChanged(chainIdHex: string): void;
notifyNetworks(networks: NetworkConfig[]): void;
notifyWalletContext(walletContext: WalletContext): void;
protected open: (defaultNetworkId?: string | number | undefined) => Promise<boolean>;
protected isValidInitAck(message: ProviderMessage<any>): boolean;
private init;
protected open: (intent?: OpenWalletIntent | undefined, networkId?: string | number | undefined) => Promise<boolean>;
}

@@ -1,2 +0,2 @@

import { ProviderMessage, ProviderTransport, ProviderMessageEvent, ProviderMessageRequest, ProviderMessageResponse, WalletSession, OpenWalletIntent } from '../../types';
import { ProviderMessage, ProviderTransport, ProviderEventTypes, ProviderMessageRequest, ProviderMessageResponse, WalletSession, OpenWalletIntent, ConnectDetails } from '../../types';
import { JsonRpcRequest, JsonRpcResponseCallback } from '@0xsequence/network';

@@ -10,8 +10,9 @@ export declare class MuxMessageProvider implements ProviderTransport {

unregister: () => void;
openWallet: (path?: string | undefined, intent?: OpenWalletIntent | undefined, defaultNetworkId?: string | number | undefined) => void;
openWallet: (path?: string | undefined, intent?: OpenWalletIntent | undefined, networkId?: string | number | undefined) => void;
closeWallet(): void;
isOpened(): boolean;
isConnected(): boolean;
on(event: ProviderMessageEvent, fn: (...args: any[]) => void): void;
once(event: ProviderMessageEvent, fn: (...args: any[]) => void): void;
on<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void;
once<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void;
emit<K extends keyof ProviderEventTypes>(event: K, ...args: Parameters<ProviderEventTypes[K]>): boolean;
sendAsync: (request: JsonRpcRequest, callback: JsonRpcResponseCallback, chainId?: number | undefined) => Promise<void>;

@@ -21,4 +22,4 @@ sendMessage(message: ProviderMessage<any>): void;

handleMessage(message: ProviderMessage<any>): void;
waitUntilOpened: () => Promise<boolean>;
waitUntilConnected: () => Promise<WalletSession>;
waitUntilOpened: () => Promise<WalletSession | undefined>;
waitUntilConnected: () => Promise<ConnectDetails>;
}
import EventEmitter from 'eventemitter3';
import { ProviderMessage, ProviderMessageTransport } from '../../types';
import { ProviderMessage, ProviderMessageTransport, ProviderEventTypes } from '../../types';
export declare class ProxyMessageChannel {

@@ -10,9 +10,8 @@ app: ProxyMessageChannelPort;

conn: ProviderMessageTransport;
events: EventEmitter<ProxyMessageEvent, any>;
events: EventEmitter<ProxyEventTypes, any>;
handleMessage: (message: ProviderMessage<any>) => void;
sendMessage: (message: ProviderMessage<any>) => void;
on(event: ProxyMessageEvent, fn: (...args: any[]) => void): void;
once(event: ProxyMessageEvent, fn: (...args: any[]) => void): void;
on<K extends keyof ProxyEventTypes>(event: K, fn: ProxyEventTypes[K]): void;
once<K extends keyof ProxyEventTypes>(event: K, fn: ProxyEventTypes[K]): void;
}
declare type ProxyMessageEvent = 'open' | 'close' | 'connect' | 'disconnect';
export {};
export declare type ProxyEventTypes = Pick<ProviderEventTypes, 'open' | 'close' | 'connect' | 'disconnect'>;

@@ -9,5 +9,5 @@ import { BaseProviderTransport } from '../base-provider-transport';

unregister: () => void;
openWallet: (path?: string | undefined, intent?: OpenWalletIntent | undefined, defaultNetworkId?: string | number | undefined) => void;
openWallet: (path?: string | undefined, intent?: OpenWalletIntent | undefined, networkId?: string | number | undefined) => void;
closeWallet(): void;
sendMessage(message: ProviderMessage<any>): void;
}

@@ -1,2 +0,2 @@

import { ProviderMessageRequest, ProviderMessageResponse, WalletMessageEvent, ProviderMessageRequestHandler, MessageToSign } from '../types';
import { ProviderMessageRequest, ProviderMessageResponse, ProviderMessageRequestHandler, MessageToSign, ProviderRpcError, ConnectOptions, ConnectDetails, PromptConnectDetails, WalletSession, ProviderEventTypes } from '../types';
import { ExternalProvider } from '@ethersproject/providers';

@@ -6,2 +6,8 @@ import { Networks, NetworkConfig, JsonRpcHandler, JsonRpcRequest, JsonRpcResponseCallback } from '@0xsequence/network';

import { TransactionRequest } from '@0xsequence/transactions';
export interface WalletSignInOptions {
connect?: boolean;
mainnetNetworks?: Networks;
testnetNetworks?: Networks;
defaultNetworkId?: string | number;
}
export declare class WalletRequestHandler implements ExternalProvider, JsonRpcHandler, ProviderMessageRequestHandler {

@@ -12,2 +18,3 @@ private signer;

private testnetNetworks;
private _connectOptions?;
private _defaultNetworkId?;

@@ -17,16 +24,23 @@ private _chainId?;

constructor(signer: Signer | null, prompter: WalletUserPrompter | null, mainnetNetworks: Networks, testnetNetworks?: Networks);
connect(signer: Signer | null, mainnetNetworks?: Networks, testnetNetworks?: Networks): Promise<void>;
signIn(signer: Signer | null, options?: WalletSignInOptions): Promise<void>;
connect(options?: ConnectOptions): Promise<ConnectDetails>;
promptConnect: (options?: ConnectOptions | undefined) => Promise<ConnectDetails>;
sendMessageRequest(message: ProviderMessageRequest): Promise<ProviderMessageResponse>;
sendAsync: (request: JsonRpcRequest, callback: JsonRpcResponseCallback, chainId?: number | undefined) => Promise<void>;
on: (event: WalletMessageEvent, fn: (...args: any[]) => void) => void;
once: (event: WalletMessageEvent, fn: (...args: any[]) => void) => void;
on<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void;
once<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void;
getAddress(): Promise<string>;
getChainId(): Promise<number>;
get connectOptions(): ConnectOptions | undefined;
setConnectOptions(options: ConnectOptions | undefined): void;
get defaultNetworkId(): string | number | undefined;
setDefaultNetwork(chainId: string | number, notifyNetworks?: boolean): Promise<number | undefined>;
get defaultNetworkId(): string | number | undefined;
getNetworks(jsonRpcResponse?: boolean): Promise<NetworkConfig[]>;
notifyConnect(accountAddress: string, chainId: number): void;
walletSession(): Promise<WalletSession | undefined>;
notifyConnect(connectDetails: ConnectDetails): void;
notifyDisconnect(): void;
notifyNetworks(networks?: NetworkConfig[]): Promise<void>;
notifyWalletContext(): Promise<void>;
notifyClose(error?: ProviderRpcError): void;
isSignedIn(): boolean;
getSigner(): Signer | null;

@@ -36,2 +50,3 @@ setSigner(signer: Signer | null): void;

export interface WalletUserPrompter {
promptConnect(options?: ConnectOptions): Promise<PromptConnectDetails>;
promptSignMessage(message: MessageToSign): Promise<string>;

@@ -38,0 +53,0 @@ promptSignTransaction(txn: TransactionRequest, chaindId?: number): Promise<string>;

import { ProviderMessage } from '../../types';
import { WalletRequestHandler } from '../wallet-request-handler';
import { BaseWalletTransport } from '../base-wallet-transport';
export interface RegisterOptions {
loadingPath: string;
}
export declare class WindowMessageHandler extends BaseWalletTransport {
protected parentWindow: Window;
protected parentOrigin?: string;
private _isPopup;
private _initNonce;
private _postMessageQueue;
constructor(walletRequestHandler: WalletRequestHandler);
register(options?: RegisterOptions): void;
register(): void;
unregister(): void;

@@ -19,4 +13,3 @@ private onWindowEvent;

get isPopup(): boolean;
private flushPostMessageQueue;
private postMessage;
}

@@ -6,10 +6,9 @@ import { OpenWalletIntent, ProviderMessage } from '../../types';

private walletWindow;
private _init;
constructor(walletAppURL: string);
register: () => void;
unregister: () => void;
openWallet: (path?: string | undefined, intent?: OpenWalletIntent | undefined, defaultNetworkId?: string | number | undefined) => void;
openWallet: (path?: string | undefined, intent?: OpenWalletIntent | undefined, networkId?: string | number | undefined) => void;
closeWallet(): void;
private onWindowEvent;
sendMessage(message: ProviderMessage<any>, skipIdx?: boolean): void;
sendMessage(message: ProviderMessage<any>): void;
}
import { NetworkConfig, WalletContext, JsonRpcRequest, JsonRpcResponse, JsonRpcHandler } from '@0xsequence/network';
import { TypedData } from '@0xsequence/utils';
export interface WalletSession {
walletContext?: WalletContext;
accountAddress?: string;
networks?: NetworkConfig[];
providerCache?: {
[key: string]: any;
};
}
export interface ProviderTransport extends JsonRpcHandler, ProviderMessageTransport, ProviderMessageRequestHandler {
register(): void;
unregister(): void;
openWallet(path?: string, intent?: OpenWalletIntent, defaultNetworkId?: string | number): void;
openWallet(path?: string, intent?: OpenWalletIntent, networkId?: string | number): void;
closeWallet(): void;
isOpened(): boolean;
isConnected(): boolean;
on(event: ProviderMessageEvent, fn: (...args: any[]) => void): void;
once(event: ProviderMessageEvent, fn: (...args: any[]) => void): void;
waitUntilOpened(): Promise<boolean>;
waitUntilConnected(): Promise<WalletSession>;
on<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void;
once<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void;
emit<K extends keyof ProviderEventTypes>(event: K, ...args: Parameters<ProviderEventTypes[K]>): boolean;
waitUntilOpened(): Promise<WalletSession | undefined>;
waitUntilConnected(): Promise<ConnectDetails>;
}

@@ -29,9 +22,9 @@ export interface WalletTransport extends JsonRpcHandler, ProviderMessageTransport, ProviderMessageRequestHandler {

sessionId?: string;
session?: WalletSession;
error?: string;
}): void;
notifyClose(): void;
notifyConnect(connectInfo: {
chainId?: string;
}): void;
notifyClose(error?: ProviderRpcError): void;
notifyConnect(connectDetails: ConnectDetails): void;
notifyAccountsChanged(accounts: string[]): void;
notifyChainChanged(connectInfo: any): void;
notifyChainChanged(chainIdHex: string): void;
notifyNetworks(networks: NetworkConfig[]): void;

@@ -48,5 +41,2 @@ }

export declare type ProviderMessageResponseCallback = (error: any, response?: ProviderMessageResponse) => void;
export interface ProviderConnectInfo {
chainId: string;
}
export interface ProviderRpcError extends Error {

@@ -66,5 +56,3 @@ message: string;

}
export declare type WalletMessageEvent = 'open' | 'close' | 'connect' | 'disconnect' | 'chainChanged' | 'accountsChanged' | 'networks' | 'walletContext' | 'init' | '_debug';
export declare type ProviderMessageEvent = 'message' | WalletMessageEvent;
export declare enum ProviderMessageType {
export declare enum EventType {
OPEN = "open",

@@ -75,4 +63,4 @@ CLOSE = "close",

DISCONNECT = "disconnect",
ACCOUNTS_CHANGED = "accountsChanged",
CHAIN_CHANGED = "chainChanged",
ACCOUNTS_CHANGED = "accountsChanged",
NETWORKS = "networks",

@@ -83,2 +71,20 @@ WALLET_CONTEXT = "walletContext",

}
export interface WalletEventTypes {
'open': (openInfo: {
chainId?: string;
sessionId?: string;
session?: WalletSession;
error?: string;
}) => void;
'close': (error?: ProviderRpcError) => void;
'connect': (connectDetails: ConnectDetails) => void;
'disconnect': (error?: ProviderRpcError) => void;
'accountsChanged': (accounts: string[]) => void;
'chainChanged': (chainIdHex: string) => void;
'networks': (networks: NetworkConfig[]) => void;
'walletContext': (walletContext: WalletContext) => void;
}
export interface ProviderEventTypes extends WalletEventTypes {
'message': (message: ProviderMessageResponse) => void;
}
export declare enum OpenState {

@@ -94,16 +100,21 @@ CLOSED = 0,

}
export declare type NetworkEventPayload = NetworkConfig;
export interface ConnectOptions {
networkId?: string | number;
app?: string;
origin?: string;
expiry?: number;
authorize?: boolean;
askForEmail?: boolean;
refresh?: boolean;
requestAuthorization?: boolean;
requestEmail?: boolean;
keepWalletOpened?: boolean;
}
export interface ConnectDetails {
success: boolean;
proof?: {
type?: string;
sig: string;
};
chainId?: string;
error?: string;
connected: boolean;
session?: WalletSession;
proof?: ETHAuthProof;
email?: string;
}
export declare type PromptConnectDetails = Pick<ConnectDetails, 'connected' | 'proof' | 'email'>;
export declare type OpenWalletIntent = {

@@ -121,1 +132,17 @@ type: 'connect';

}
export interface ETHAuthProof {
typedData: TypedData;
proofString: string;
}
export interface WalletSession {
walletContext?: WalletContext;
accountAddress?: string;
networks?: NetworkConfig[];
providerCache?: {
[key: string]: any;
};
}
export declare class ProviderError extends Error {
constructor(message?: string);
}
export declare const ErrSignedInRequired: ProviderError;

@@ -5,6 +5,6 @@ import { NetworkConfig, WalletContext, ChainId } from '@0xsequence/network';

import { ProxyMessageChannelPort } from './transports';
import { WalletSession, ProviderMessageEvent, ConnectOptions, OpenWalletIntent } from './types';
import { WalletSession, ProviderEventTypes, ConnectOptions, OpenWalletIntent, ConnectDetails } from './types';
import { WalletCommands } from './commands';
export interface WalletProvider {
connect(options?: ConnectOptions): Promise<boolean>;
connect(options?: ConnectOptions): Promise<ConnectDetails>;
disconnect(): void;

@@ -18,3 +18,3 @@ isOpened(): boolean;

getAuthChainId(): Promise<number>;
openWallet(path?: string, intent?: OpenWalletIntent): Promise<boolean>;
openWallet(path?: string, intent?: OpenWalletIntent, networkId?: string | number): Promise<boolean>;
closeWallet(): void;

@@ -28,4 +28,4 @@ getProvider(chainId?: ChainId): Web3Provider | undefined;

getProviderConfig(): ProviderConfig;
on(event: ProviderMessageEvent, fn: (...args: any[]) => void): void;
once(event: ProviderMessageEvent, fn: (...args: any[]) => void): void;
on<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void;
once<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void;
commands: WalletCommands;

@@ -42,3 +42,4 @@ }

private init;
connect: (options?: ConnectOptions | undefined) => Promise<boolean>;
connect: (options?: ConnectOptions | undefined) => Promise<ConnectDetails>;
authorize: (options?: ConnectOptions | undefined) => Promise<ConnectDetails>;
disconnect(): void;

@@ -53,3 +54,3 @@ getProviderConfig(): ProviderConfig;

getAuthChainId: () => Promise<number>;
openWallet: (path?: string | undefined, intent?: OpenWalletIntent | undefined) => Promise<boolean>;
openWallet: (path?: string | undefined, intent?: OpenWalletIntent | undefined, networkId?: string | number | undefined) => Promise<boolean>;
closeWallet: () => void;

@@ -68,4 +69,4 @@ getProvider(chainId?: ChainId): Web3Provider | undefined;

isDeployed(chainId?: ChainId): Promise<boolean>;
on(event: ProviderMessageEvent, fn: (...args: any[]) => void): void;
once(event: ProviderMessageEvent, fn: (...args: any[]) => void): void;
on<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void;
once<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void;
private loadSession;

@@ -72,0 +73,0 @@ private saveSession;

{
"name": "@0xsequence/provider",
"version": "0.18.0",
"version": "0.19.0",
"description": "provider sub-package for Sequence",

@@ -16,9 +16,9 @@ "repository": "https://github.com/0xsequence/sequence.js/tree/master/packages/provider",

"dependencies": {
"@0xsequence/abi": "^0.18.0",
"@0xsequence/auth": "^0.18.0",
"@0xsequence/config": "^0.18.0",
"@0xsequence/network": "^0.18.0",
"@0xsequence/transactions": "^0.18.0",
"@0xsequence/utils": "^0.18.0",
"@0xsequence/wallet": "^0.18.0",
"@0xsequence/abi": "^0.19.0",
"@0xsequence/auth": "^0.19.0",
"@0xsequence/config": "^0.19.0",
"@0xsequence/network": "^0.19.0",
"@0xsequence/transactions": "^0.19.0",
"@0xsequence/utils": "^0.19.0",
"@0xsequence/wallet": "^0.19.0",
"@ethersproject/abstract-signer": "5.0.14",

@@ -25,0 +25,0 @@ "@ethersproject/hash": "^5.0.12",

@@ -13,17 +13,13 @@ import { ethers } from 'ethers'

// naming..?
// Web3Provider, Web3Signer, Web3Relayer, Web3Indexer
//
//.. or.... SequenceProvider, SequenceSigner, SequenceRelayer, SequenceIndexer
//
export class Web3Provider extends EthersWeb3Provider implements JsonRpcHandler {
// also, make to separate util method
// static isSequenceProvider(cand: any): cand is Web3Provider {
// }
static isSequenceProvider(cand: any): cand is Web3Provider {
return isSequenceProvider(cand)
}
readonly _sender: JsonRpcSender
readonly _isSequenceProvider: boolean
// defaultChainId is the default chainId to use with requests, but may be

@@ -40,2 +36,3 @@ // overridden by passing chainId argument to a specific request

this._sender = sender
this._isSequenceProvider = true
this._defaultChainId = maybeNetworkId(defaultChainId)

@@ -68,2 +65,7 @@ }

export function isSequenceProvider(provider: any): provider is Web3Provider {
const cand = provider as Web3Provider
return cand && cand.send !== undefined && cand._isSequenceProvider === true
}
export class LocalWeb3Provider extends Web3Provider {

@@ -70,0 +72,0 @@ constructor(signer: Signer, networks?: Networks) {

import EventEmitter from 'eventemitter3'
import {
ProviderTransport, ProviderMessage, ProviderMessageRequest,
ProviderMessageType, ProviderMessageEvent, ProviderMessageResponse,
ProviderMessageResponseCallback, ProviderMessageTransport,
WalletSession, OpenState, OpenWalletIntent
ProviderTransport,
ProviderMessage,
ProviderMessageRequest,
EventType,
ProviderEventTypes,
ProviderMessageResponse,
ProviderMessageResponseCallback,
OpenState,
OpenWalletIntent,
ConnectDetails,
WalletSession,
ProviderRpcError,
InitState
} from '../types'

@@ -21,3 +30,2 @@

export abstract class BaseProviderTransport implements ProviderTransport {
protected pendingMessageRequests: ProviderMessageRequest[] = []

@@ -28,5 +36,7 @@ protected responseCallbacks = new Map<number, ProviderMessageResponseCallback>()

protected confirmationOnly: boolean = false
protected events: EventEmitter<ProviderMessageEvent, any> = new EventEmitter()
protected accountPayload: string | undefined
protected events: EventEmitter<ProviderEventTypes, any> = new EventEmitter()
protected openPayload: { sessionId?: string; session?: WalletSession } | undefined
protected connectPayload: ConnectDetails | undefined
protected accountsChangedPayload: string | undefined
protected networksPayload: NetworkConfig[] | undefined

@@ -36,2 +46,3 @@ protected walletContextPayload: WalletContext | undefined

protected _sessionId?: string
protected _init: InitState
protected _registered: boolean

@@ -42,2 +53,3 @@

this._registered = false
this._init = InitState.NIL
}

@@ -57,3 +69,3 @@

openWallet(path?: string, intent?: OpenWalletIntent, defaultNetworkId?: string | number) {
openWallet(path?: string, intent?: OpenWalletIntent, networkId?: string | number) {
throw new Error('abstract method')

@@ -72,6 +84,7 @@ }

// if we're registered, and we have the account details, then we are connected
const session = this.openPayload?.session
return (
this.registered &&
!!this.accountPayload && this.accountPayload.length === 42 &&
!!this.networksPayload && this.networksPayload.length > 0
this.registered && session !== undefined &&
!!session.accountAddress && session.accountAddress.length === 42 &&
!!session.networks && session.networks.length > 0
)

@@ -91,3 +104,7 @@ }

// automatically open the wallet when a provider request makes it here.
this.openWallet(undefined, { type: 'jsonRpcRequest', method: request.method })
//
// NOTE: if we're not signed in, then the provider will fail, users must first connect+sign in.
//
// TODO: how does this behave with a session has expired?
this.openWallet(undefined, { type: 'jsonRpcRequest', method: request.method }, chainId)

@@ -102,3 +119,3 @@ // send message request, await, and then execute callback after receiving the response

idx: nextMessageIdx(),
type: ProviderMessageType.MESSAGE,
type: EventType.MESSAGE,
data: request,

@@ -116,4 +133,28 @@ chainId: chainId

// init incoming for initial handshake with transport.
if (this._init !== InitState.OK) {
// if provider is not init'd, then we drop any received messages. the only
// message we will process is of event type 'init', as our acknowledgement
if (message.type === EventType.INIT) {
logger.debug('MessageProvider, received INIT message', message)
const { nonce } = message.data as { nonce: string }
if (!nonce || nonce.length == 0) {
logger.error('invalid init nonce')
return
}
this._init = InitState.OK
this.sendMessage({
idx: -1,
type: EventType.INIT,
data: {
sessionId: this._sessionId,
nonce: nonce
}
})
}
return
}
// message is either a notification, or its a ProviderMessageResponse
logger.debug("RECEIVED MESSAGE FROM WALLET", message.idx, message)
logger.debug('RECEIVED MESSAGE FROM WALLET', message.idx, message)

@@ -128,4 +169,4 @@ const requestIdx = message.idx

//
// Flip opened flag, and flush the pending queue
if (message.type === ProviderMessageType.OPEN && !this.isOpened()) {
// Flip opened flag, and flush the pending queue
if (message.type === EventType.OPEN && !this.isOpened()) {
if (this._sessionId && this._sessionId !== message.data?.sessionId) {

@@ -146,3 +187,4 @@ logger.debug('open event received from wallet, but does not match sessionId', this._sessionId)

this.state = OpenState.OPENED
this.events.emit('open')
this.openPayload = message.data
this.events.emit('open', this.openPayload!)

@@ -162,4 +204,3 @@ // flush pending requests when connected

// MESSAGE resposne
if (message.type === ProviderMessageType.MESSAGE) {
if (message.type === EventType.MESSAGE) {
// Require user confirmation, bring up wallet to prompt for input then close

@@ -173,3 +214,3 @@ // TODO: perhaps apply technique like in multicall to queue messages within

}
}, 1500) // TODO: be smarter about timer as we're processing the response callbacks..
}, 500) // TODO: be smarter about timer as we're processing the response callbacks..
}

@@ -186,2 +227,3 @@

if (responseCallback) {
this.events.emit('message', message)
responseCallback(undefined, message)

@@ -193,7 +235,7 @@ return

// ACCOUNTS_CHANGED -- when a user logs in or out
if (message.type === ProviderMessageType.ACCOUNTS_CHANGED) {
this.accountPayload = undefined
if (message.type === EventType.ACCOUNTS_CHANGED) {
this.accountsChangedPayload = undefined
if (message.data && message.data.length > 0) {
this.accountPayload = ethers.utils.getAddress(message.data[0])
this.events.emit('accountsChanged', [this.accountPayload])
this.accountsChangedPayload = ethers.utils.getAddress(message.data[0])
this.events.emit('accountsChanged', [this.accountsChangedPayload])
} else {

@@ -206,3 +248,3 @@ this.events.emit('accountsChanged', [])

// CHAIN_CHANGED -- when a user changes their default chain
if (message.type === ProviderMessageType.CHAIN_CHANGED) {
if (message.type === EventType.CHAIN_CHANGED) {
this.events.emit('chainChanged', message.data)

@@ -213,5 +255,5 @@ return

// NOTIFY NETWORKS -- when a user connects or logs in
if (message.type === ProviderMessageType.NETWORKS) {
if (message.type === EventType.NETWORKS) {
this.networksPayload = message.data
this.events.emit('networks', this.networksPayload)
this.events.emit('networks', this.networksPayload!)
return

@@ -221,5 +263,5 @@ }

// NOTIFY WALLET_CONTEXT -- when a user connects or logs in
if (message.type === ProviderMessageType.WALLET_CONTEXT) {
if (message.type === EventType.WALLET_CONTEXT) {
this.walletContextPayload = message.data
this.events.emit('walletContext', this.walletContextPayload)
this.events.emit('walletContext', this.walletContextPayload!)
return

@@ -229,3 +271,3 @@ }

// NOTIFY CLOSE -- when wallet instructs to close
if (message.type === ProviderMessageType.CLOSE) {
if (message.type === EventType.CLOSE) {
if (this.state !== OpenState.CLOSED) {

@@ -236,4 +278,10 @@ this.close()

// NOTIFY CONNECT -- when wallet instructs we've connected
if (message.type === EventType.CONNECT) {
this.connectPayload = message.data
this.events.emit('connect', this.connectPayload!)
}
// NOTIFY DISCONNECT -- when wallet instructs to disconnect
if (message.type === ProviderMessageType.DISCONNECT) {
if (message.type === EventType.DISCONNECT) {
if (this.isConnected()) {

@@ -249,3 +297,3 @@ this.events.emit('disconnect', message.data)

return new Promise((resolve, reject) => {
if (!message.idx || message.idx <= 0) {
if ((!message.idx || message.idx <= 0) && message.type !== 'init') {
reject(new Error('message idx not set'))

@@ -284,14 +332,18 @@ }

on(event: ProviderMessageEvent, fn: (...args: any[]) => void) {
this.events.on(event, fn)
on<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]) {
this.events.on(event, fn as any)
}
once(event: ProviderMessageEvent, fn: (...args: any[]) => void) {
this.events.once(event, fn)
once<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]) {
this.events.once(event, fn as any)
}
waitUntilOpened = async (openTimeout = PROVIDER_OPEN_TIMEOUT): Promise<boolean> => {
emit<K extends keyof ProviderEventTypes>(event: K, ...args: Parameters<ProviderEventTypes[K]>): boolean {
return this.events.emit(event, ...args as any)
}
waitUntilOpened = async (openTimeout = PROVIDER_OPEN_TIMEOUT): Promise<WalletSession | undefined> => {
let opened = false
return Promise.race([
new Promise<boolean>((_, reject) => {
new Promise<WalletSession | undefined>((_, reject) => {
const timeout = setTimeout(() => {

@@ -302,3 +354,3 @@ clearTimeout(timeout)

this.state = OpenState.CLOSED
this.events.emit('close')
this.events.emit('close', { code: 1005, message: 'opening wallet timed out' } as ProviderRpcError)
}

@@ -308,11 +360,12 @@ reject(new Error('opening wallet timed out'))

}),
new Promise<boolean>(resolve => {
new Promise<WalletSession | undefined>(resolve => {
if (this.isOpened()) {
opened = true
resolve(true)
resolve(this.openPayload?.session)
return
}
this.events.once('open', () => {
this.events.once('open', (openInfo: { session?: WalletSession }) => {
this.openPayload = openInfo
opened = true
resolve(true)
resolve(openInfo.session)
})

@@ -323,46 +376,18 @@ })

waitUntilConnected = async (): Promise<WalletSession> => {
waitUntilConnected = async (): Promise<ConnectDetails> => {
await this.waitUntilOpened()
const connect = Promise.all([
new Promise<string | undefined>(resolve => {
if (this.accountPayload) {
resolve(this.accountPayload)
return
}
this.events.once('accountsChanged', (accounts) => {
if (accounts && accounts.length > 0) {
// account logged in
resolve(accounts[0])
} else {
// account logged out
resolve(undefined)
}
})
}),
new Promise<NetworkConfig[]>(resolve => {
if (this.networksPayload) {
resolve(this.networksPayload)
return
}
this.events.once('networks', (networks) => {
resolve(networks)
})
}),
new Promise<WalletContext>(resolve => {
if (this.walletContextPayload) {
resolve(this.walletContextPayload)
return
}
this.events.once('walletContext', (walletContext) => {
resolve(walletContext)
})
const connect = new Promise<ConnectDetails>(resolve => {
if (this.connectPayload) {
resolve(this.connectPayload)
return
}
this.events.once('connect', connectDetails => {
this.connectPayload = connectDetails
resolve(connectDetails)
})
]).then(values => {
const [ accountAddress, networks, walletContext ] = values
return { accountAddress, networks, walletContext }
})
const closeWallet = new Promise<WalletSession>((_, reject) => {
const closeWallet = new Promise<ConnectDetails>((_, reject) => {
this.events.once('close', () => {

@@ -373,25 +398,8 @@ reject(new Error('user closed the wallet'))

return Promise.race<WalletSession>([
connect,
closeWallet
])
return Promise.race<ConnectDetails>([ connect, closeWallet ])
}
protected open = async (): Promise<boolean> => {
if (this.isOpened()) return true
// Set to opening state
this.state = OpenState.OPENING
// Wait for open response from wallet, or timeout
let opened: boolean | undefined = undefined
try {
opened = await this.waitUntilOpened()
} catch (err) {
opened = false
}
return opened
}
protected close() {
if (this.state === OpenState.CLOSED) return
this.state = OpenState.CLOSED

@@ -409,3 +417,5 @@ this.confirmationOnly = false

this.accountPayload = undefined
this.connectPayload = undefined
this.openPayload = undefined
this.accountsChangedPayload = undefined
this.networksPayload = undefined

@@ -412,0 +422,0 @@ this.walletContextPayload = undefined

import { ethers } from 'ethers'
import {
WalletTransport, ProviderMessage, ProviderMessageRequest,
ProviderMessageType, ProviderMessageResponse, ProviderMessageTransport,
ProviderConnectInfo, ProviderRpcError, InitState
EventType, ProviderMessageResponse, ProviderMessageTransport,
ProviderRpcError, InitState, ConnectDetails, OpenWalletIntent, WalletSession
} from '../types'

@@ -11,4 +11,7 @@

import { NetworkConfig, WalletContext, JsonRpcRequest, JsonRpcResponseCallback } from '@0xsequence/network'
import { logger } from '@0xsequence/utils'
import { logger, sanitizeAlphanumeric, sanitizeHost } from '@0xsequence/utils'
import { AuthorizationOptions } from '@0xsequence/auth'
import { PROVIDER_OPEN_TIMEOUT } from './base-provider-transport'
export abstract class BaseWalletTransport implements WalletTransport {

@@ -19,4 +22,12 @@

protected _registered: boolean
protected _init: InitState
protected _initNonce: string
protected _initCallback?: (error?: string) => void
// appOrigin identifies the dapp's origin which opened the app. A transport
// will auto-detect and set this value if it can. This is determined
// as the parent app/window which opened the wallet.
protected appOrigin?: string
constructor(walletRequestHandler: WalletRequestHandler) {

@@ -26,6 +37,6 @@ this.walletRequestHandler = walletRequestHandler

this.walletRequestHandler.on('connect', (connectInfo: any) => {
this.walletRequestHandler.on('connect', (connectDetails: ConnectDetails) => {
if (!this.registered) return
// means user has logged in and wallet is connected to the app
this.notifyConnect(connectInfo)
this.notifyConnect(connectDetails)
})

@@ -58,2 +69,7 @@

})
this.walletRequestHandler.on('close', (error?: ProviderRpcError) => {
if (!this.registered) return
this.notifyClose(error)
})
}

@@ -80,19 +96,37 @@

// ensure initial handshake is complete before accepting
// other kinds of messages.
if (this._init !== InitState.OK) {
if (request.type === EventType.INIT) {
if (this.isValidInitAck(message)) {
// successful init
if (this._initCallback) this._initCallback()
} else {
// failed init
if (this._initCallback) this._initCallback('invalid init')
return
}
} else {
// we expect init message first. do nothing here.
}
return
}
// handle request
switch (request.type) {
case ProviderMessageType.OPEN: {
case EventType.OPEN: {
if (this._init !== InitState.OK) return
const { defaultNetworkId } = request.data
this.open(defaultNetworkId)
const { intent, networkId } = request.data
await this.open(intent, networkId)
return
}
// case ProviderMessageType.CLOSE: {
// if (this._init !== InitState.OK) return
// // we echo back to close, confirming wallet close request
// this.notifyClose()
// return
// }
case EventType.CLOSE: {
if (this._init !== InitState.OK) return
// noop. just here to capture the message so event emitters may be notified
return
}
case ProviderMessageType.MESSAGE: {
case EventType.MESSAGE: {
const response = await this.walletRequestHandler.sendMessageRequest(request)

@@ -123,9 +157,9 @@ this.sendMessage(response)

notifyOpen(openInfo: { chainId?: string, sessionId?: string, error?: string }) {
const { chainId, sessionId, error } = openInfo
notifyOpen(openInfo: { chainId?: string, sessionId?: string, session?: WalletSession, error?: string }) {
const { chainId, sessionId, session, error } = openInfo
this.sendMessage({
idx: -1,
type: ProviderMessageType.OPEN,
type: EventType.OPEN,
data: {
chainId, sessionId, error
chainId, sessionId, session, error
}

@@ -135,18 +169,15 @@ })

notifyClose() {
notifyClose(error?: ProviderRpcError) {
this.sendMessage({
idx: -1,
type: ProviderMessageType.CLOSE,
data: null
type: EventType.CLOSE,
data: error
})
}
notifyConnect(connectInfo: ProviderConnectInfo & { error?: string }) {
const { chainId, error } = connectInfo
notifyConnect(connectDetails: ConnectDetails) {
this.sendMessage({
idx: -1,
type: ProviderMessageType.CONNECT,
data: {
chainId, error
}
type: EventType.CONNECT,
data: connectDetails
})

@@ -158,3 +189,3 @@ }

idx: -1,
type: ProviderMessageType.DISCONNECT,
type: EventType.DISCONNECT,
data: error

@@ -167,3 +198,3 @@ })

idx: -1,
type: ProviderMessageType.ACCOUNTS_CHANGED,
type: EventType.ACCOUNTS_CHANGED,
data: accounts

@@ -173,7 +204,7 @@ })

notifyChainChanged(hexChainId: string) {
notifyChainChanged(chainIdHex: string) {
this.sendMessage({
idx: -1,
type: ProviderMessageType.CHAIN_CHANGED,
data: hexChainId
type: EventType.CHAIN_CHANGED,
data: chainIdHex
})

@@ -185,3 +216,3 @@ }

idx: -1,
type: ProviderMessageType.NETWORKS,
type: EventType.NETWORKS,
data: networks

@@ -194,3 +225,3 @@ })

idx: -1,
type: ProviderMessageType.WALLET_CONTEXT,
type: EventType.WALLET_CONTEXT,
data: walletContext

@@ -200,61 +231,186 @@ })

protected open = async (defaultNetworkId?: string | number): Promise<boolean> => {
let loggedIn = false
const accountAddress = await this.walletRequestHandler.getAddress()
if (accountAddress && accountAddress.startsWith('0x') && accountAddress.length === 42) {
loggedIn = true
protected isValidInitAck(message: ProviderMessage<any>): boolean {
if (this._init === InitState.OK) {
// we're already in init state, we shouldn't handle this message
logger.warn('isValidInitAck, already in init\'d state, so inquiry is invalid.')
return false
}
if (message.type !== EventType.INIT) {
logger.warn('isValidInitAck, invalid message type, expecting init')
return false
}
if (!loggedIn) {
// open wallet without a specific connected chainId, as the user is not logged in
this.notifyOpen({
sessionId: this._sessionId
})
// this.notifyAccountsChanged([])
return true
const { sessionId, nonce } = (message.data as any) as { sessionId: string; nonce: string }
if (!sessionId || sessionId.length === 0 || !nonce || nonce.length === 0) {
logger.error('invalid init ack')
return false
}
if (sessionId !== this._sessionId || nonce !== this._initNonce) {
logger.error('invalid init ack match')
return false
}
// account is logged in, lets return chainId information
let chainId: number | undefined = undefined
try {
if (defaultNetworkId) {
chainId = await this.walletRequestHandler.setDefaultNetwork(defaultNetworkId, false)
} else {
chainId = await this.walletRequestHandler.getChainId()
// all checks pass, its true
return true
}
private init(): Promise<void> {
return new Promise<void>((resolve, reject) => {
// avoid re-init`ing, or if there is a transport which doesn't require
// it, then it may set this._init to OK in its constructor.
if (this._init === InitState.OK) {
resolve()
return
}
} catch (err) {
if (this._init !== InitState.NIL || this._initCallback) {
reject('transport init is in progress')
return
}
// start init timeout, if we don't receive confirmation
// from provider within this amount of time, then we timeout
const initTimeout = setTimeout(() => {
logger.warn('transport init timed out')
if (this._initCallback) {
this._initCallback('transport init timed out')
}
}, PROVIDER_OPEN_TIMEOUT / 2)
// setup callback as we receive the init message async in the handleMessage function
this._initCallback = (error?: string) => {
this._initCallback = undefined // reset
clearTimeout(initTimeout)
if (error) {
reject(error)
} else {
this._init = InitState.OK
resolve()
}
}
// send init request with random nonce to the provider, where we expect
// for the provider to echo it back to us as complete handshake
this._initNonce = `${performance.now()}`
this.sendMessage({
idx: -1,
type: EventType.INIT,
data: { nonce: this._initNonce }
})
this._init = InitState.SENT_NONCE
// NOTE: the promise will resolve in the _initCallback method
// which will be called from either handleMessage or the initTimeout
})
}
protected open = async (intent?: OpenWalletIntent, networkId?: string | number): Promise<boolean> => {
// init handshake for certain transports, before we can open the communication.
//
// for example, with the window-transport, we have to exchange messages to determine the
// origin host of the dapp.
await this.init()
// Prepare connect options from intent
if (intent && intent.type === 'connect' && intent.options) {
const connectOptions = intent.options
const authorizeOptions: AuthorizationOptions = connectOptions // overlapping types
// Sanity/integrity check the intent payload, and set authorization origin
// if its been determined as part of the init handshake from earlier.
if (this.appOrigin && authorizeOptions?.origin) {
if (authorizeOptions.origin !== this.appOrigin) {
throw new Error('origin is invalid')
} else {
// request origin and derived origins match, lets carry on
}
} else if (!this.appOrigin && authorizeOptions?.origin) {
// ie. when we can't determine the origin in our transport, but dapp provides it to us.
// we just sanitize the origin host.
connectOptions.origin = sanitizeHost(authorizeOptions.origin)
} else if (this.appOrigin) {
// ie. when we auto-determine the origin such as in window-transport
connectOptions.origin = this.appOrigin
}
if (connectOptions.app) {
connectOptions.app = sanitizeAlphanumeric(connectOptions.app)
}
// Set connect options on the walletRequestHandler as our primary
// wallet controller
this.walletRequestHandler.setConnectOptions(connectOptions)
if (connectOptions.networkId) {
networkId = connectOptions.networkId
}
} else {
this.walletRequestHandler.setConnectOptions(undefined)
}
// failed to set default network or open
if (!chainId || chainId <= 0) {
// Notify open and proceed to prompt for connection if intended
if (!this.walletRequestHandler.isSignedIn()) {
// open wallet without a specific connected chainId, as the user is not signed in
this.notifyOpen({
sessionId: this._sessionId,
error: `failed to open wallet on network ${defaultNetworkId}`
sessionId: this._sessionId
})
return false
}
return true
// successfully opened wallet to the default network
this.notifyOpen({
chainId: `${chainId}`,
sessionId: this._sessionId
})
} else {
// notify wallet context each time wallet is opened, to ensure latest
// context is always provided
await this.walletRequestHandler.notifyWalletContext()
// notify networks
await this.walletRequestHandler.notifyNetworks()
// Set default network, in case of error chainId will be undefined or 0
let chainId: number | undefined = undefined
try {
if (networkId) {
chainId = await this.walletRequestHandler.setDefaultNetwork(networkId, false)
} else {
chainId = await this.walletRequestHandler.getChainId()
}
} catch (err) {
}
// notify account address
this.notifyAccountsChanged([accountAddress])
// Failed to set default network on open -- quit + close
if (!chainId || chainId <= 0) {
this.notifyOpen({
sessionId: this._sessionId,
error: `failed to open wallet on network ${networkId}`
})
return false
}
// notify connect
// NOTE: we don't send 'connect' event to app from here, as it's handled
// by the WalletRequestHandler as it may occur outside of the open() call in
// certain cases (ie. wallet opens which isnt logged in, then signs in after)
// prompt user with a connect request. the options will be used as previously set above.
// upon success, the walletRequestHandler will notify the dapp with the ConnectDetails.
// upon cancellation by user, the walletRequestHandler will throw an error
if (intent && intent.type === 'connect') {
// notify wallet is opened, without session details
this.notifyOpen({
sessionId: this._sessionId
})
const connectDetails = await this.walletRequestHandler.promptConnect()
this.walletRequestHandler.notifyConnect(connectDetails)
// auto-close by default, unless intent is to keep open
if (!intent.options || intent.options.keepWalletOpened !== true) {
this.notifyClose()
}
} else {
// user is already connected, notify session details.
// TODO: in future, keep list if 'connected' dapps / sessions in the session
// controller, and only sync with allowed apps
this.notifyOpen({
sessionId: this._sessionId,
chainId: `${chainId}`,
session: await this.walletRequestHandler.walletSession()
})
}
}
return true
}
}
import {
ProviderMessage, ProviderMessageType, ProviderTransport,
ProviderMessageEvent, ProviderMessageRequest, ProviderMessageResponse, WalletSession, OpenWalletIntent
ProviderMessage, EventType, ProviderTransport,
ProviderEventTypes, ProviderMessageRequest, ProviderMessageResponse, WalletSession, OpenWalletIntent, ConnectDetails
} from '../../types'

@@ -53,8 +53,8 @@

openWallet = (path?: string, intent?: OpenWalletIntent, defaultNetworkId?: string | number): void => {
openWallet = (path?: string, intent?: OpenWalletIntent, networkId?: string | number): void => {
if (this.provider) {
this.provider.openWallet(path, intent, defaultNetworkId)
this.provider.openWallet(path, intent, networkId)
return
}
this.messageProviders.forEach(m => m.openWallet(path, intent, defaultNetworkId))
this.messageProviders.forEach(m => m.openWallet(path, intent, networkId))
}

@@ -82,3 +82,3 @@

on(event: ProviderMessageEvent, fn: (...args: any[]) => void) {
on<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]) {
if (this.provider) {

@@ -93,3 +93,3 @@ this.provider.on(event, fn)

once(event: ProviderMessageEvent, fn: (...args: any[]) => void) {
once<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]) {
if (this.provider) {

@@ -104,2 +104,12 @@ this.provider.once(event, fn)

emit<K extends keyof ProviderEventTypes>(event: K, ...args: Parameters<ProviderEventTypes[K]>): boolean {
if (this.provider) {
return this.provider.emit(event, ...args)
}
for (let i=0; i < this.messageProviders.length; i++) {
this.messageProviders[i].emit(event, ...args)
}
return true
}
sendAsync = async (request: JsonRpcRequest, callback: JsonRpcResponseCallback, chainId?: number) => {

@@ -140,3 +150,3 @@ if (this.provider) {

waitUntilOpened = async (): Promise<boolean> => {
waitUntilOpened = async (): Promise<WalletSession | undefined> => {
if (this.provider) {

@@ -148,3 +158,3 @@ return this.provider.waitUntilOpened()

waitUntilConnected = async (): Promise<WalletSession> => {
waitUntilConnected = async (): Promise<ConnectDetails> => {
if (this.provider) {

@@ -151,0 +161,0 @@ return this.provider.waitUntilConnected()

import EventEmitter from 'eventemitter3'
import { ProviderMessage, ProviderMessageTransport, ProviderMessageEvent } from '../../types'
import { ProviderMessage, ProviderMessageTransport, ProviderEventTypes } from '../../types'

@@ -22,3 +22,3 @@ export class ProxyMessageChannel {

conn: ProviderMessageTransport
events: EventEmitter<ProxyMessageEvent, any> = new EventEmitter()
events: EventEmitter<ProxyEventTypes, any> = new EventEmitter()

@@ -36,24 +36,24 @@ // handle messages which hit this port

if (message.type === 'open') {
this.events.emit('open', message)
this.events.emit('open', message as any)
}
if (message.type === 'close') {
this.events.emit('close', message)
this.events.emit('close', message as any)
}
if (message.type === 'connect') {
this.events.emit('connect', message)
this.events.emit('connect', message as any)
}
if (message.type === 'disconnect') {
this.events.emit('disconnect', message)
this.events.emit('disconnect', message as any)
}
}
on(event: ProxyMessageEvent, fn: (...args: any[]) => void) {
this.events.on(event, fn)
on<K extends keyof ProxyEventTypes>(event: K, fn: ProxyEventTypes[K]) {
this.events.on(event, fn as any)
}
once(event: ProxyMessageEvent, fn: (...args: any[]) => void) {
this.events.once(event, fn)
once<K extends keyof ProxyEventTypes>(event: K, fn: ProxyEventTypes[K]) {
this.events.once(event, fn as any)
}
}
type ProxyMessageEvent = 'open' | 'close' | 'connect' | 'disconnect'
export type ProxyEventTypes = Pick<ProviderEventTypes, 'open' | 'close' | 'connect' | 'disconnect'>
import { BaseProviderTransport } from '../base-provider-transport'
import {
ProviderMessage, OpenState, OpenWalletIntent, ProviderMessageType
ProviderMessage, OpenState, OpenWalletIntent, EventType, InitState
} from '../../types'
import { ProxyMessageChannelPort } from './proxy-message-channel'
import { ProxyMessageChannelPort, ProxyEventTypes } from './proxy-message-channel'

@@ -16,6 +16,10 @@ export class ProxyMessageProvider extends BaseProviderTransport {

this.state = OpenState.CLOSED
this.port = port
this.port = port
if (!port) {
throw new Error('port argument cannot be empty')
}
// disable init handshake for proxy-transport, we set it to OK, to
// consider it in completed state.
this._init = InitState.OK
}

@@ -28,13 +32,13 @@

this.on('open', (...args: any[]) => {
this.port.events.emit('open', args)
this.on('open', (...args: Parameters<ProxyEventTypes['open']>) => {
this.port.events.emit('open', ...args)
})
this.on('close', (...args: any[]) => {
this.port.events.emit('close', args)
this.on('close', (...args: Parameters<ProxyEventTypes['close']>) => {
this.port.events.emit('close', ...args)
})
this.on('connect', (...args: any[]) => {
this.port.events.emit('connect', args)
this.on('connect', (...args: Parameters<ProxyEventTypes['connect']>) => {
this.port.events.emit('connect', ...args)
})
this.on('disconnect', (...args: any[]) => {
this.port.events.emit('disconnect', args)
this.on('disconnect', (...args: Parameters<ProxyEventTypes['disconnect']>) => {
this.port.events.emit('disconnect', ...args)
})

@@ -53,8 +57,8 @@

openWallet = (path?: string, intent?: OpenWalletIntent, defaultNetworkId?: string | number): void => {
openWallet = (path?: string, intent?: OpenWalletIntent, networkId?: string | number): void => {
if (this.state === OpenState.CLOSED) {
this.state = OpenState.OPENING
this.sendMessage({
idx: -1, type: ProviderMessageType.OPEN, data: {
path, intent, defaultNetworkId
idx: -1, type: EventType.OPEN, data: {
path, intent, networkId
}

@@ -67,3 +71,3 @@ })

this.sendMessage({
idx: -1, type: ProviderMessageType.CLOSE, data: null
idx: -1, type: EventType.CLOSE, data: null
})

@@ -70,0 +74,0 @@ this.close()

@@ -5,5 +5,6 @@ import EventEmitter from 'eventemitter3'

ProviderMessage, ProviderMessageRequest, ProviderMessageResponse,
WalletMessageEvent, ProviderMessageResponseCallback,
ProviderMessageResponseCallback,
ProviderMessageRequestHandler,
MessageToSign, ProviderRpcError, ProviderConnectInfo, ConnectOptions, ConnectDetails
MessageToSign, ProviderRpcError, ConnectOptions, ConnectDetails, PromptConnectDetails, WalletSession,
ErrSignedInRequired, ProviderEventTypes
} from '../types'

@@ -19,3 +20,13 @@

import { signAuthorization, AuthorizationOptions } from '@0xsequence/auth'
import { logger, TypedData } from '@0xsequence/utils'
export interface WalletSignInOptions {
connect?: boolean
mainnetNetworks?: Networks
testnetNetworks?: Networks
defaultNetworkId?: string | number
}
export class WalletRequestHandler implements ExternalProvider, JsonRpcHandler, ProviderMessageRequestHandler {

@@ -27,6 +38,7 @@ private signer: Signer | null

private _connectOptions?: ConnectOptions
private _defaultNetworkId?: string | number
private _chainId?: number
private events: EventEmitter<WalletMessageEvent, any> = new EventEmitter()
private events: EventEmitter<ProviderEventTypes, any> = new EventEmitter()

@@ -38,11 +50,9 @@ constructor(signer: Signer | null, prompter: WalletUserPrompter | null, mainnetNetworks: Networks, testnetNetworks: Networks = []) {

this.testnetNetworks = testnetNetworks
// if (!signer.provider) {
// throw new Error('wallet.provider is undefined')
// }
}
async connect(signer: Signer | null, mainnetNetworks: Networks = [], testnetNetworks: Networks = []) {
async signIn(signer: Signer | null, options: WalletSignInOptions = {}) {
this.signer = signer
const { connect, mainnetNetworks, testnetNetworks, defaultNetworkId } = options
if (mainnetNetworks && mainnetNetworks.length > 0) {

@@ -54,12 +64,92 @@ this.mainnetNetworks = mainnetNetworks

}
if ((!this.mainnetNetworks || this.mainnetNetworks.length === 0) && (!this.testnetNetworks || this.testnetNetworks.length === 0)) {
throw new Error('signIn failed as network configuration is empty')
}
if (this._defaultNetworkId) {
if (!(await this.setDefaultNetwork(this._defaultNetworkId, false))) {
throw new Error(`WalletRequestHandler setup unable to set defaultNetworkId ${this._defaultNetworkId}`)
const networkId = defaultNetworkId || this._defaultNetworkId
if (networkId) {
if (!(await this.setDefaultNetwork(networkId, false))) {
throw new Error(`WalletRequestHandler setup unable to set defaultNetworkId ${networkId}`)
}
}
this.notifyConnect(await this.signer!.getAddress(), await this.getChainId())
// Optionally, connect the dapp and wallet. In case connectOptions are provided, we will perform
// necessary auth request, and then notify the dapp of the 'connect' details.
//
// NOTE: if a user is signing into a dapp from a fresh state, and and auth request is made
// we don't trigger the promptConnect flow, as we consider the user just authenticated
// for this dapp, so its safe to authorize in the connect() method without the prompt.
//
// NOTE: signIn can optionally connect and notify dapp at this time for new signIn flows
if (connect) {
const connectOptions = this._connectOptions
const connectDetails = await this.connect(connectOptions)
this.notifyConnect(connectDetails)
if (!connectOptions || connectOptions.keepWalletOpened !== true) {
this.notifyClose()
}
}
}
async connect(options?: ConnectOptions): Promise<ConnectDetails> {
if (!this.signer) {
return {
connected: false, chainId: '0x0', error: 'unable to connect without signed in account',
}
}
const connectDetails: ConnectDetails = {
connected: true,
chainId: ethers.utils.hexlify(await this.getChainId())
}
if (options && options.authorize) {
// Perform ethauth eip712 request and construct the ConnectDetails response
// including the auth proof
const authOptions: AuthorizationOptions = {
app: options.app, origin: options.origin, expiry: options.expiry
}
// if (typeof(options.authorize) === 'object') {
// authOptions = { ...authOptions, ...options.authorize }
// }
try {
connectDetails.proof = await signAuthorization(this.signer, authOptions)
} catch (err) {
logger.warn(`connect, signAuthorization failed for options: ${options}, due to: ${err.message}`)
return {
connected: false, chainId: '0x0', error: 'signAuthorization failed'
}
}
}
// Build session response for connect details
connectDetails.session = await this.walletSession()
return connectDetails
}
promptConnect = async (options?: ConnectOptions): Promise<ConnectDetails> => {
if (!options && !this._connectOptions) {
// this is an unexpected state and should not happen
throw new Error('prompter connect options are empty')
}
if (!this.prompter) {
// if prompter is null, we'll auto connect
return this.connect(options)
}
const promptConnectDetails = await this.prompter.promptConnect(options || this._connectOptions)
const connectDetails: ConnectDetails = promptConnectDetails
if (!connectDetails.session) {
connectDetails.session = await this.walletSession()
}
return promptConnectDetails
}
// sendMessageRequest will unwrap the ProviderMessageRequest and send it to the JsonRpcHandler

@@ -98,3 +188,4 @@ // (aka, the signer in this instance) and then responds with a wrapped response of

if ((!this.signer || this.signer === null) && !permittedJsonRpcMethods.includes(request.method)) {
throw new Error(`not logged in. ${request.method} is unavailable`)
// throw new Error(`not logged in. ${request.method} is unavailable`)
throw ErrSignedInRequired
}

@@ -143,3 +234,3 @@

// TODO:
// if (process.env.DEBUG_MODE === 'true' && this.prompter === null) {
// if (process.env.TEST_MODE === 'true' && this.prompter === null) {
if (this.prompter === null) {

@@ -428,8 +519,8 @@ // prompter is null, so we'll sign from here

on = (event: WalletMessageEvent, fn: (...args: any[]) => void) => {
this.events.on(event, fn)
on<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]) {
this.events.on(event, fn as any)
}
once = (event: WalletMessageEvent, fn: (...args: any[]) => void) => {
this.events.once(event, fn)
once<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]) {
this.events.once(event, fn as any)
}

@@ -455,2 +546,14 @@

get connectOptions(): ConnectOptions | undefined {
return this._connectOptions
}
setConnectOptions(options: ConnectOptions | undefined) {
this._connectOptions = options
}
get defaultNetworkId(): string | number | undefined {
return this._defaultNetworkId
}
async setDefaultNetwork(chainId: string | number, notifyNetworks: boolean = true): Promise<number | undefined> {

@@ -473,6 +576,2 @@ if (!chainId) return undefined

get defaultNetworkId(): string | number | undefined {
return this._defaultNetworkId
}
async getNetworks(jsonRpcResponse?: boolean): Promise<NetworkConfig[]> {

@@ -499,14 +598,15 @@ if (!this.signer) {

notifyConnect(accountAddress: string, chainId: number) {
if (!accountAddress || accountAddress.length === 0) {
this.events.emit('accountsChanged', [])
} else {
this.events.emit('accountsChanged', [accountAddress])
async walletSession(): Promise<WalletSession | undefined> {
return !this.signer ? undefined : {
walletContext: await this.signer.getWalletContext(),
accountAddress: await this.signer.getAddress(),
networks: await this.getNetworks(true)
}
this.notifyNetworks()
this.notifyWalletContext()
}
this.events.emit('connect', {
chainId: `${chainId}`
} as ProviderConnectInfo)
notifyConnect(connectDetails: ConnectDetails) {
this.events.emit('connect', connectDetails)
if (connectDetails.session?.accountAddress) {
this.events.emit('accountsChanged', [connectDetails.session?.accountAddress])
}
}

@@ -517,3 +617,3 @@

this.events.emit('networks', [])
this.events.emit('disconnect', { code: 4900 } as ProviderRpcError)
this.events.emit('disconnect')
}

@@ -540,2 +640,10 @@

notifyClose(error?: ProviderRpcError) {
this.events.emit('close', error)
}
isSignedIn(): boolean {
return !!this.signer
}
getSigner(): Signer | null {

@@ -551,3 +659,3 @@ return this.signer

export interface WalletUserPrompter {
// promptConnect(options?: ConnectOptions): Promise<ConnectDetails>
promptConnect(options?: ConnectOptions): Promise<PromptConnectDetails>
promptSignMessage(message: MessageToSign): Promise<string>

@@ -554,0 +662,0 @@ promptSignTransaction(txn: TransactionRequest, chaindId?: number): Promise<string>

@@ -1,19 +0,19 @@

import { ProviderMessageRequest, ProviderMessage, ProviderMessageType, ProviderMessageResponse, InitState } from '../../types'
import {
ProviderMessageRequest,
ProviderMessage,
EventType,
ProviderMessageResponse,
InitState,
ConnectDetails,
OpenWalletIntent
} from '../../types'
import { WalletRequestHandler } from '../wallet-request-handler'
import { BaseWalletTransport } from '../base-wallet-transport'
import { logger, sanitizeNumberString } from '@0xsequence/utils'
import { logger, sanitizeNumberString, base64DecodeObject } from '@0xsequence/utils'
export interface RegisterOptions {
loadingPath: string
}
export class WindowMessageHandler extends BaseWalletTransport {
protected parentWindow: Window
protected parentOrigin?: string
private _isPopup: boolean = false
private _initNonce: string
private _postMessageQueue: Array<any> = []
constructor(walletRequestHandler: WalletRequestHandler) {

@@ -24,3 +24,3 @@ super(walletRequestHandler)

register(options?: RegisterOptions) {
register() {
const isPopup = parent.window.opener !== null

@@ -34,5 +34,5 @@ this._isPopup = isPopup

const location = new URL(window.location.href)
this._sessionId = sanitizeNumberString(location.searchParams.get('sid')!)
location.searchParams.delete('sid')
const params = new URLSearchParams(location.search)
this._sessionId = sanitizeNumberString(params.get('sid')!)
if (this._sessionId.length === 0) {

@@ -43,12 +43,10 @@ logger.error('invalid sessionId')

const defaultNetwork = location.searchParams.get('net')!
location.searchParams.delete('net')
const intent = base64DecodeObject<OpenWalletIntent>(params.get('intent')!)
const networkId = params.get('net')!
const jsonRpcRequest = location.searchParams.get('jsonRpcRequest')
if (options?.loadingPath && !!jsonRpcRequest) {
window.history.replaceState({}, document.title, options.loadingPath)
} else {
window.history.replaceState({}, document.title, location.pathname)
}
// TODO: review how we should be intefacing with window.history, so we can route
// to the correct destination based on 'intent' ie. 'connect' or 'jsonRpcRequest'
// ie.. maybe..
// window.history.replaceState(params['jsonRpcRequest'] ? { jsonRpcRequest: true } : {}, document.title, location.pathname)

@@ -63,11 +61,19 @@ // record parent window instance for communication

// send open event to the app which opened us
this.open(defaultNetwork).then(opened => {
if (!opened) {
logger.error(`failed to open to network ${defaultNetwork}`)
this.open(intent, networkId)
.then(opened => {
if (!opened) {
const err = `failed to open to network ${networkId}`
logger.error(err)
// TODO?
// this.notifyOpen({ error: err }) // or notifyClose({ message: err })
window.close()
}
})
.catch(e => {
const err = `failed to open to network ${networkId}, due to: ${e}`
logger.error(err)
// TODO?
// this.notifyOpen({ error: err }) // or notifyClose({ message: err })
window.close()
}
}).catch(err => {
logger.error(`failed to open to network ${defaultNetwork}, due to: ${err}`)
window.close()
})
})
}

@@ -87,11 +93,6 @@

}
if (this.parentOrigin && event.origin !== this.parentOrigin) {
if (this.appOrigin && event.origin !== this.appOrigin) {
// skip message as not from expected app origin
return
}
if (this._init === InitState.OK && (!this.parentOrigin || this.parentOrigin.length < 8)) {
// impossible state
logger.error('impossible state, init.OK and parentOrigin required')
return
}

@@ -109,20 +110,9 @@ // Wallet always expects json-rpc request messages from a dapp

// Record the parent origin url on init
if (this._init !== InitState.OK) {
if (request.type === ProviderMessageType.INIT) {
const { sessionId, nonce } = request.data as any as { sessionId: string, nonce: string }
if (!sessionId || sessionId.length === 0 || !nonce || nonce.length === 0) {
logger.error('invalid init response')
return
}
if (sessionId !== this._sessionId || nonce !== this._initNonce) {
logger.error('invalid init match')
return
}
this._init = InitState.OK
this.parentOrigin = event.origin
this.flushPostMessageQueue()
} else {
// we expect init message first
}
// Record event origin for valid init ack
if (this._init !== InitState.OK && this.isValidInitAck(request)) {
this.appOrigin = event.origin
}
if (this._init === InitState.OK && (!this.appOrigin || this.appOrigin.length < 8)) {
// impossible state
logger.error('impossible state, init.OK and appOrigin required')
return

@@ -137,29 +127,12 @@ }

sendMessage(message: ProviderMessage<any>) {
if (message.type === ProviderMessageType.INIT) {
// clients should not send init requests directly
return
}
// prepare payload
const payload = JSON.stringify(message)
// queue sending messages until we're inited
if (this._init !== InitState.OK) {
this._postMessageQueue.push(payload)
// post-message to app.
// only for init requests, we send to '*' origin
if (message.type === EventType.INIT) {
this.postMessage(payload, true)
} else {
this.postMessage(payload)
}
// init stage + check
if (this._init === InitState.NIL) {
this._initNonce = `${performance.now()}`
this.parentWindow.postMessage(JSON.stringify({
idx: -1, type: ProviderMessageType.INIT, data: { nonce: this._initNonce }
} as ProviderMessage<any>), '*')
this._init = InitState.SENT_NONCE
return
} else if (this._init !== InitState.OK) {
return
}
// post-message to app
this.postMessage(payload)
}

@@ -171,23 +144,22 @@

private flushPostMessageQueue() {
if (this._postMessageQueue.length === 0) return
// logger.debug(`flushPostMessageQueue # of messages, ${this._postMessageQueue.length}`)
for (let i=0; i < this._postMessageQueue.length; i++) {
this.postMessage(this._postMessageQueue[i])
}
this._postMessageQueue.length = 0
}
private postMessage(message: any) {
if (this._init !== InitState.OK) {
private postMessage(message: any, init = false) {
if (init !== true && this._init !== InitState.OK) {
logger.error('impossible state, should not be calling postMessage until inited')
return
}
if (this.parentOrigin && this.parentOrigin.length > 8) {
this.parentWindow.postMessage(message, this.parentOrigin)
if (init) {
// init message transmission to global target -- for 'init' payloads only
this.parentWindow.postMessage(message, '*')
} else {
logger.error('unable to postMessage as parentOrigin is invalid')
// open message transmission
if (this.appOrigin && this.appOrigin.length > 4) { // just above '.com'
this.parentWindow.postMessage(message, this.appOrigin)
} else {
logger.error('unable to postMessage as parentOrigin is invalid')
}
}
}
}

@@ -1,4 +0,4 @@

import { OpenWalletIntent, ProviderMessage, InitState, ProviderMessageType } from '../../types'
import { OpenWalletIntent, ProviderMessage, InitState, EventType } from '../../types'
import { BaseProviderTransport } from '../base-provider-transport'
import { logger } from '@0xsequence/utils'
import { logger, base64EncodeObject } from '@0xsequence/utils'

@@ -11,3 +11,2 @@ // ..

private walletWindow: Window | null
private _init: InitState

@@ -37,3 +36,3 @@ constructor(walletAppURL: string) {

}
}, 1250)
}, 500)
})

@@ -66,3 +65,3 @@

openWallet = (path?: string, intent?: OpenWalletIntent, defaultNetworkId?: string | number): void => {
openWallet = (path?: string, intent?: OpenWalletIntent, networkId?: string | number): void => {
if (this.walletWindow && this.isOpened()) {

@@ -74,11 +73,2 @@ // TODO: update the location of window to path

// Set session and network id on class instance walletURL
this._init = InitState.NIL
this._sessionId = `${performance.now()}`
this.walletURL.searchParams.set('sid', this._sessionId)
if (defaultNetworkId) {
this.walletURL.searchParams.set('net', `${defaultNetworkId}`)
}
// Instantiate new walletURL for this call

@@ -90,6 +80,20 @@ const walletURL = new URL(this.walletURL.href)

// set intent of wallet opening due to jsonRpcRequest send by provider
if (intent?.type === 'jsonRpcRequest') {
walletURL.searchParams.set('jsonRpcRequest', intent.method)
// Set session, intent and network id on walletURL
this._init = InitState.NIL
this._sessionId = `${performance.now()}`
walletURL.searchParams.set('sid', this._sessionId)
if (intent) {
// for the window-transport, we eagerly/optimistically set the origin host
// when connecting to the wallet, however, this will be verified and enforced
// on the wallet-side, so if a dapp provides the wrong origin, it will be dropped.
if (intent.type === 'connect') {
if (!intent.options) intent.options = {}
intent.options.origin = window.location.origin
}
// encode intent as base6 url-encoded param
walletURL.searchParams.set('intent', base64EncodeObject(intent))
}
if (networkId) {
walletURL.searchParams.set('net', `${networkId}`)
}

@@ -161,23 +165,2 @@ // Open popup window on center of the app window

// window init
if (this._init !== InitState.OK) {
if (message.type === ProviderMessageType.INIT) {
const { nonce } = message.data as { nonce: string }
if (!nonce || nonce.length === 0) {
logger.error('invalid init nonce')
return
}
this._init = InitState.OK
this.sendMessage({
idx: -1,
type: ProviderMessageType.INIT,
data: {
sessionId: this._sessionId,
nonce: nonce
}
}, true)
}
return
}
// handle message with base message provider

@@ -187,6 +170,3 @@ this.handleMessage(message)

sendMessage(message: ProviderMessage<any>, skipIdx = false) {
if (!skipIdx && (!message.idx || message.idx <= 0)) {
throw new Error('message idx is empty')
}
sendMessage(message: ProviderMessage<any>) {
if (!this.walletWindow) {

@@ -193,0 +173,0 @@ logger.warn('WindowMessageProvider: sendMessage failed as walletWindow is unavailable')

import { NetworkConfig, WalletContext, JsonRpcRequest, JsonRpcResponse, JsonRpcHandler } from '@0xsequence/network'
import { TypedData } from '@0xsequence/utils'
// export class SequenceError extends Error {}
export interface WalletSession {
// Wallet context
walletContext?: WalletContext
// Account address of the wallet
accountAddress?: string
// Networks in use for the session. The default/dapp network will show
// up as the first one in the list as the "main chain"
networks?: NetworkConfig[]
// Caching provider responses for things such as account and chainId
providerCache?: {[key: string]: any}
}
export interface ProviderTransport extends JsonRpcHandler, ProviderMessageTransport, ProviderMessageRequestHandler {

@@ -25,3 +8,3 @@ register(): void

openWallet(path?: string, intent?: OpenWalletIntent, defaultNetworkId?: string | number): void
openWallet(path?: string, intent?: OpenWalletIntent, networkId?: string | number): void
closeWallet(): void

@@ -32,7 +15,8 @@

on(event: ProviderMessageEvent, fn: (...args: any[]) => void): void
once(event: ProviderMessageEvent, fn: (...args: any[]) => void): void
on<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void
once<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void
emit<K extends keyof ProviderEventTypes>(event: K, ...args: Parameters<ProviderEventTypes[K]>): boolean
waitUntilOpened(): Promise<boolean>
waitUntilConnected(): Promise<WalletSession>
waitUntilOpened(): Promise<WalletSession | undefined>
waitUntilConnected(): Promise<ConnectDetails>
}

@@ -44,8 +28,8 @@

notifyOpen(openInfo: { chainId?: string, sessionId?: string }): void
notifyClose(): void
notifyOpen(openInfo: { chainId?: string, sessionId?: string, session?: WalletSession, error?: string }): void
notifyClose(error?: ProviderRpcError): void
notifyConnect(connectInfo: { chainId?: string }): void
notifyConnect(connectDetails: ConnectDetails): void
notifyAccountsChanged(accounts: string[]): void
notifyChainChanged(connectInfo: any): void
notifyChainChanged(chainIdHex: string): void
notifyNetworks(networks: NetworkConfig[]): void

@@ -55,3 +39,3 @@ }

export interface ProviderMessage<T> {
idx: number // message id sequence number
idx: number // message id number
type: string // message type

@@ -71,6 +55,2 @@ data: T // the ethereum json-rpc payload

export interface ProviderConnectInfo {
chainId: string
}
export interface ProviderRpcError extends Error {

@@ -96,7 +76,3 @@ message: string

export type WalletMessageEvent = 'open' | 'close' | 'connect' | 'disconnect' | 'chainChanged' | 'accountsChanged' | 'networks' | 'walletContext' | 'init' | '_debug'
export type ProviderMessageEvent = 'message' | WalletMessageEvent
export enum ProviderMessageType {
export enum EventType {
OPEN = 'open',

@@ -108,4 +84,4 @@ CLOSE = 'close',

DISCONNECT = 'disconnect',
ACCOUNTS_CHANGED = 'accountsChanged',
CHAIN_CHANGED = 'chainChanged',
ACCOUNTS_CHANGED = 'accountsChanged',

@@ -119,2 +95,20 @@ NETWORKS = 'networks',

export interface WalletEventTypes {
'open': (openInfo: { chainId?: string, sessionId?: string, session?: WalletSession, error?: string }) => void
'close': (error?: ProviderRpcError) => void
'connect': (connectDetails: ConnectDetails) => void
'disconnect': (error?: ProviderRpcError) => void
'accountsChanged': (accounts: string[]) => void
'chainChanged': (chainIdHex: string) => void
'networks': (networks: NetworkConfig[]) => void
'walletContext': (walletContext: WalletContext) => void
}
export interface ProviderEventTypes extends WalletEventTypes {
'message': (message: ProviderMessageResponse) => void
}
export enum OpenState {

@@ -132,19 +126,54 @@ CLOSED = 0,

export type NetworkEventPayload = NetworkConfig
export interface ConnectOptions {
// networkId specifics the default network a dapp would like to connect to. This field
// is optional as it can be provided a number of different ways.
networkId?: string | number
export interface ConnectOptions {
// app name of the dapp which will be announced to user on connect screen
app?: string
// origin hint of the dapp's host opening the wallet. This value will automatically
// be determined and verified for integrity, and can be omitted.
origin?: string
// expiry number (in seconds) to expire connect session. default is 1 week of seconds.
expiry?: number
// authorize will perform an ETHAuth eip712 signing and return the proof to the dapp.
authorize?: boolean
// askForEmail will prompt to give permission to the dapp to access email address
// TODO: this feature is currently not used as the wallet does not report emails yet
askForEmail?: boolean
// refresh flag will force a full re-connect (ie. disconnect then connect again)
refresh?: boolean
requestAuthorization?: boolean
requestEmail?: boolean
// keepWalletOpened will keep the wallet window opened after connecting. The default
// is to automatically close the wallet after connecting.
keepWalletOpened?: boolean
}
export interface ConnectDetails {
success: boolean
proof?: {
type?: string
sig: string
}
// chainId (in hex) and error are defined by EIP-1193 expected fields
chainId?: string
error?: string
// connected flag denotes user-accepted the connect request
connected: boolean
// session include account and network information needed by the dapp wallet provider.
session?: WalletSession
// proof is a signed typedData (EIP-712) payload using ETHAuth domain.
// NOTE: the proof is signed to the `authChainId`, as the canonical auth chain.
proof?: ETHAuthProof
// email address provided from wallet to the dapp, as request + accepted
// by a user during a connect request
email?: string
}
export type PromptConnectDetails = Pick<ConnectDetails, 'connected' | 'proof' | 'email'>
export type OpenWalletIntent =

@@ -159,1 +188,35 @@ { type: 'connect'; options?: ConnectOptions } |

}
export interface ETHAuthProof {
// eip712 typed-data payload for ETHAuth domain as input
typedData: TypedData
// signature encoded in an ETHAuth proof string
proofString: string
}
export interface WalletSession {
// Wallet context
walletContext?: WalletContext
// Account address of the wallet
accountAddress?: string
// Networks in use for the session. The default/dapp network will show
// up as the first one in the list as the "main chain"
networks?: NetworkConfig[]
// Caching provider responses for things such as account and chainId
providerCache?: {[key: string]: any}
}
export class ProviderError extends Error {
constructor(message?: string) {
super(message)
this.name = 'ProviderError'
}
}
export const ErrSignedInRequired = new ProviderError('Wallet is not signed in. Connect a wallet and try again.')
// TODO: lets build some nice error handling tools, prob in /utils ...

@@ -11,3 +11,3 @@ import { Networks, NetworkConfig, WalletContext, sequenceContext, ChainId, getNetworkId, JsonRpcSender,

import { MuxMessageProvider, WindowMessageProvider, ProxyMessageProvider, ProxyMessageChannelPort } from './transports'
import { WalletSession, ProviderMessageEvent, ConnectOptions, OpenWalletIntent, ConnectDetails } from './types'
import { WalletSession, ProviderEventTypes, ConnectOptions, OpenWalletIntent, ConnectDetails } from './types'
import { WalletCommands } from './commands'

@@ -17,5 +17,4 @@ import { ethers } from 'ethers'

export interface WalletProvider {
// connect(options?: ConnectOptions): Promise<ConnectDetails>
connect(options?: ConnectOptions): Promise<ConnectDetails>
// authorize(options?: ConnectOptions): Promise<ConnectDetails>
connect(options?: ConnectOptions): Promise<boolean>
disconnect(): void

@@ -32,3 +31,3 @@

openWallet(path?: string, intent?: OpenWalletIntent): Promise<boolean>
openWallet(path?: string, intent?: OpenWalletIntent, networkId?: string | number): Promise<boolean>
closeWallet(): void

@@ -46,4 +45,4 @@

on(event: ProviderMessageEvent, fn: (...args: any[]) => void): void
once(event: ProviderMessageEvent, fn: (...args: any[]) => void): void
on<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void
once<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]): void

@@ -160,2 +159,20 @@ commands: WalletCommands

// NOTE: we don't listen on 'connect' even here as we handle it within connect() method
// in more synchronous flow.
// below will update the wallet session object and persist it. In case the session
// is undefined, we consider the session to have been removed by the user, so we clear it.
this.transport.messageProvider.on('open', (openInfo: { session?: WalletSession }) => {
const { session } = openInfo
if (!session) {
if (this.session && this.session.accountAddress) {
// emit disconnect even if previously we had a session, and now we don't.
this.transport.messageProvider!.emit('disconnect')
}
this.clearSession()
} else {
this.useSession(session, true)
}
})
// below will update the account upon wallet connect/disconnect (aka, login/logout)

@@ -187,32 +204,32 @@ this.transport.messageProvider.on('accountsChanged', (accounts: string[]) => {

connect = async (options?: ConnectOptions): Promise<boolean> => {
connect = async (options?: ConnectOptions): Promise<ConnectDetails> => {
if (options?.refresh === true) {
this.disconnect()
}
if (this.isConnected() && !options?.requestAuthorization && !options?.requestEmail) {
return this.isConnected()
// return {
// success: this.isConnected()
// }
if (this.isConnected() && !!this.session && !options?.authorize && !options?.askForEmail) {
return {
connected: true,
session: this.session,
chainId: ethers.utils.hexlify(await this.getChainId())
}
}
await this.openWallet(undefined, { type: 'connect', options })
const sessionPayload = await this.transport.messageProvider!.waitUntilConnected()
this.useSession(sessionPayload, true)
const connectDetails = await this.transport.messageProvider!.waitUntilConnected()
return this.isConnected()
if (connectDetails.connected) {
if (!!connectDetails.session) {
this.useSession(connectDetails.session, true)
} else {
throw new Error('impossible state, connect response is missing session')
}
}
// TODO: the wallet-webapp itself will handle the open request..
// prob with window, etc.. or other proxy-message
return connectDetails
}
// return {
// success: this.isConnected()
// // TODO: ..
// }
authorize = async (options?: ConnectOptions): Promise<ConnectDetails> => {
return this.connect({ ...options, authorize: true })
}
// authorize = async (options?: ConnectOptions): Promise<ConnectDetails> => {
// return this.connect({ ...options, requestAuthorization: true })
// }
disconnect(): void {

@@ -298,3 +315,3 @@ if (this.isOpened()) {

openWallet = async (path?: string, intent?: OpenWalletIntent): Promise<boolean> => {
openWallet = async (path?: string, intent?: OpenWalletIntent, networkId?: string | number): Promise<boolean> => {
if (intent?.type !== 'connect' && !this.isConnected()) {

@@ -304,3 +321,3 @@ throw new Error('connect first')

this.transport.messageProvider!.openWallet(path, intent, this.config.defaultNetworkId)
this.transport.messageProvider!.openWallet(path, intent, networkId || this.config.defaultNetworkId)
await this.transport.messageProvider!.waitUntilOpened()

@@ -411,7 +428,7 @@

on(event: ProviderMessageEvent, fn: (...args: any[]) => void) {
on<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]) {
this.transport.messageProvider!.on(event, fn)
}
once(event: ProviderMessageEvent, fn: (...args: any[]) => void) {
once<K extends keyof ProviderEventTypes>(event: K, fn: ProviderEventTypes[K]) {
this.transport.messageProvider!.once(event, fn)

@@ -435,2 +452,3 @@ }

private saveSession = (session: WalletSession) => {
logger.debug('wallet provider: saving session')
const data = JSON.stringify(session)

@@ -483,2 +501,3 @@ window.localStorage.setItem('@sequence.session', data)

if (!checkNetworkConfig(networks[0], this.config.defaultNetworkId)) {
// TODO: what is the correct behaviour here we want for dapps?
throw new Error(`expecting defaultNetworkId '${this.config.defaultNetworkId}' but is set to '${networks[0].name}'`)

@@ -520,2 +539,3 @@ }

private clearSession(): void {
logger.debug('wallet provider: clearing session')
window.localStorage.removeItem('@sequence.session')

@@ -533,2 +553,5 @@ this.session = undefined

// Sequence Wallet Session URL, default: https://session.sequence.app
// walletSessionURL: string
// networks is a configuration list of networks used by the wallet. This list

@@ -568,3 +591,2 @@ // is combined with the network list supplied from the wallet upon login,

walletContext?: WalletContext
}

@@ -575,2 +597,4 @@

// walletSessionURL: 'https://session.sequence.app',
transports: {

@@ -580,2 +604,2 @@ windowTransport: { enabled: true },

}
}
}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc