diff --git a/src.ts/contracts/contract.ts b/src.ts/contracts/contract.ts index c2b3ee72..9a08a08b 100644 --- a/src.ts/contracts/contract.ts +++ b/src.ts/contracts/contract.ts @@ -236,16 +236,17 @@ export class Contract { // @TODO: Maybe still check the addressOrName looks like a valid address or name? //address = getAddress(address); - if (contractInterface instanceof Interface) { + + if (Interface.isInterface(contractInterface)) { defineReadOnly(this, 'interface', contractInterface); } else { defineReadOnly(this, 'interface', new Interface(contractInterface)); } - if (signerOrProvider instanceof Signer) { + if (Signer.isSigner(signerOrProvider)) { defineReadOnly(this, 'provider', signerOrProvider.provider); defineReadOnly(this, 'signer', signerOrProvider); - } else if (signerOrProvider instanceof MinimalProvider) { + } else if (MinimalProvider.isProvider(signerOrProvider)) { defineReadOnly(this, 'provider', signerOrProvider); defineReadOnly(this, 'signer', null); } else { diff --git a/src.ts/contracts/interface.ts b/src.ts/contracts/interface.ts index 7e9bd521..31a9509c 100644 --- a/src.ts/contracts/interface.ts +++ b/src.ts/contracts/interface.ts @@ -8,7 +8,7 @@ import { bigNumberify } from '../utils/bignumber'; import { arrayify, concat, hexlify, hexZeroPad, isHexString } from '../utils/bytes'; import { id } from '../utils/hash'; import { keccak256 } from '../utils/keccak256'; -import { defineReadOnly, defineFrozen } from '../utils/properties'; +import { defineReadOnly, defineFrozen, isType, setType } from '../utils/properties'; import { BigNumber, BigNumberish, @@ -361,6 +361,8 @@ export class Interface { if (!this.deployFunction) { addMethod.call(this, {type: 'constructor', inputs: []}); } + + setType(this, 'Interface'); } parseTransaction(tx: { data: string, value?: BigNumberish }): _TransactionDescription { @@ -403,4 +405,8 @@ export class Interface { return null; } + + static isInterface(value: any): value is Interface { + return isType(value, 'Interface'); + } } diff --git a/src.ts/utils/bignumber.ts b/src.ts/utils/bignumber.ts index 91dd48ea..09e20ba8 100644 --- a/src.ts/utils/bignumber.ts +++ b/src.ts/utils/bignumber.ts @@ -176,7 +176,7 @@ class BigNumber extends _BigNumber { } export function bigNumberify(value: BigNumberish): _BigNumber { - if (value instanceof BigNumber) { return value; } + if (BigNumber.isBigNumber(value)) { return value; } return new BigNumber(value); } diff --git a/src.ts/utils/bytes.ts b/src.ts/utils/bytes.ts index 60b1c75d..221a00d7 100644 --- a/src.ts/utils/bytes.ts +++ b/src.ts/utils/bytes.ts @@ -12,10 +12,6 @@ export const AddressZero = '0x0000000000000000000000000000000000000000'; export const HashZero = '0x0000000000000000000000000000000000000000000000000000000000000000'; -function isBigNumber(value: any): value is BigNumber { - return (value instanceof BigNumber); -} - function addSlice(array: Uint8Array): Uint8Array { if (array.slice) { return array; } @@ -47,7 +43,7 @@ export function arrayify(value: Arrayish | BigNumber): Uint8Array { errors.throwError('cannot convert null value to array', errors.INVALID_ARGUMENT, { arg: 'value', value: value }); } - if (isBigNumber(value)) { + if (BigNumber.isBigNumber(value)) { value = value.toHexString(); } @@ -142,7 +138,7 @@ const HexCharacters: string = '0123456789abcdef'; export function hexlify(value: Arrayish | BigNumber | number): string { - if (isBigNumber(value)) { + if (BigNumber.isBigNumber(value)) { return value.toHexString(); } diff --git a/src.ts/utils/properties.ts b/src.ts/utils/properties.ts index 1bec5dca..62d67b0c 100644 --- a/src.ts/utils/properties.ts +++ b/src.ts/utils/properties.ts @@ -16,6 +16,17 @@ export function defineFrozen(object: any, name: string, value: any): void { }); } +// There are some issues with instanceof with npm link, so we use this +// to ensure types are what we expect. + +export function setType(object: any, type: string): void { + Object.defineProperty(object, '_ethersType', { configurable: false, value: type, writable: false }); +} + +export function isType(object: any, type: string): boolean { + return (object._ethersType === type); +} + export function resolveProperties(object: any): Promise { let result: any = {}; diff --git a/src.ts/utils/types.ts b/src.ts/utils/types.ts index 00930889..ad4f671e 100644 --- a/src.ts/utils/types.ts +++ b/src.ts/utils/types.ts @@ -5,6 +5,13 @@ export type Arrayish = string | ArrayLike; +function setType(object: any, type: string): void { + Object.defineProperty(object, '_ethersType', { configurable: false, value: type, writable: false }); +} + +function isType(object: any, type: string): boolean { + return (object._ethersType === type); +} /////////////////////////////// // BigNumber @@ -28,6 +35,14 @@ export abstract class BigNumber { abstract toNumber(): number; abstract toString(): string; abstract toHexString(): string; + + constructor() { + setType(this, 'BigNumber'); + } + + static isBigNumber(value: any): value is BigNumber { + return isType(value, 'BigNumber'); + } }; export type BigNumberish = BigNumber | string | number | Arrayish; @@ -254,6 +269,14 @@ export interface TransactionResponse extends Transaction { export abstract class Indexed { readonly hash: string; + + constructor() { + setType(this, 'Indexed'); + } + + static isIndexed(value: any): value is Indexed { + return isType(value, 'Indexed'); + } } export interface DeployDescription { @@ -335,12 +358,6 @@ export type EventType = string | Array | Filter; export type Listener = (...args: Array) => void; -/** - * Provider - * - * Note: We use an abstract class so we can use instanceof to determine if an - * object is a Provider. - */ export abstract class MinimalProvider implements OnceBlockable { abstract getNetwork(): Promise; @@ -374,6 +391,14 @@ export abstract class MinimalProvider implements OnceBlockable { // @TODO: This *could* be implemented here, but would pull in events... abstract waitForTransaction(transactionHash: string, timeout?: number): Promise; + + constructor() { + setType(this, 'Provider'); + } + + static isProvider(value: any): value is MinimalProvider { + return isType(value, 'Provider'); + } } export type AsyncProvider = { @@ -403,12 +428,6 @@ export type EncryptOptions = { } } -/** - * Signer - * - * Note: We use an abstract class so we can use instanceof to determine if an - * object is a Signer. - */ export abstract class Signer { provider?: MinimalProvider; @@ -416,6 +435,14 @@ export abstract class Signer { abstract signMessage(message: Arrayish | string): Promise; abstract sendTransaction(transaction: TransactionRequest): Promise; + + constructor() { + setType(this, 'Signer'); + } + + static isSigner(value: any): value is Signer { + return isType(value, 'Signer'); + } } /////////////////////////////// @@ -434,6 +461,14 @@ export abstract class HDNode { readonly depth: number; abstract derivePath(path: string): HDNode; + + constructor() { + setType(this, 'HDNode'); + } + + static isHDNode(value: any): value is HDNode { + return isType(value, 'HDNode'); + } } /////////////////////////////// diff --git a/src.ts/wallet/secret-storage.ts b/src.ts/wallet/secret-storage.ts index a7377899..99bb59a0 100644 --- a/src.ts/wallet/secret-storage.ts +++ b/src.ts/wallet/secret-storage.ts @@ -276,7 +276,7 @@ export function encrypt(privateKey: Arrayish | SigningKey, password: Arrayish | // Check the private key let privateKeyBytes: Uint8Array = null; - if (privateKey instanceof SigningKey) { + if (SigningKey.isSigningKey(privateKey)) { privateKeyBytes = arrayify(privateKey.privateKey); } else { privateKeyBytes = arrayify(privateKey); diff --git a/src.ts/wallet/signing-key.ts b/src.ts/wallet/signing-key.ts index 521e74cf..be3c9bf4 100644 --- a/src.ts/wallet/signing-key.ts +++ b/src.ts/wallet/signing-key.ts @@ -7,7 +7,7 @@ */ import { arrayify, hexlify } from '../utils/bytes'; -import { defineReadOnly } from '../utils/properties'; +import { defineReadOnly, isType, setType } from '../utils/properties'; import { computeAddress, KeyPair } from '../utils/secp256k1'; import { Arrayish, HDNode, Signature } from '../utils/types'; @@ -30,7 +30,7 @@ export class SigningKey { let privateKeyBytes = null; - if (privateKey instanceof HDNode) { + if (HDNode.isHDNode(privateKey)) { defineReadOnly(this, 'mnemonic', privateKey.mnemonic); defineReadOnly(this, 'path', privateKey.path); privateKeyBytes = arrayify(privateKey.privateKey); @@ -61,9 +61,15 @@ export class SigningKey { defineReadOnly(this, 'keyPair', new KeyPair(privateKeyBytes)); defineReadOnly(this, 'publicKey', this.keyPair.publicKey); defineReadOnly(this, 'address', computeAddress(this.keyPair.publicKey)); + + setType(this, 'SigningKey'); } signDigest(digest: Arrayish): Signature { return this.keyPair.sign(digest); } + + static isSigningKey(value: any): value is SigningKey { + return isType(value, 'SigningKey'); + } } diff --git a/src.ts/wallet/wallet.ts b/src.ts/wallet/wallet.ts index 7dd9d7c7..f9f1624d 100644 --- a/src.ts/wallet/wallet.ts +++ b/src.ts/wallet/wallet.ts @@ -28,7 +28,7 @@ export class Wallet extends Signer { errors.checkNew(this, Wallet); // Make sure we have a valid signing key - if (privateKey instanceof SigningKey) { + if (SigningKey.isSigningKey(privateKey)) { defineReadOnly(this, 'signingKey', privateKey); } else { defineReadOnly(this, 'signingKey', new SigningKey(privateKey)); @@ -49,7 +49,7 @@ export class Wallet extends Signer { * Create a new instance of this Wallet connected to provider. */ connect(provider: MinimalProvider): Wallet { - if (!(provider instanceof MinimalProvider)) { + if (!(MinimalProvider.isProvider(provider))) { errors.throwError('invalid provider', errors.INVALID_ARGUMENT, { argument: 'provider', value: provider }); } return new Wallet(this.signingKey, provider);