Skip to content

Commit

Permalink
Merge branch 'master' into refactor-2-advanced-logic-payment-network-…
Browse files Browse the repository at this point in the history
…deeper
  • Loading branch information
vrolland committed May 6, 2021
2 parents 38178ca + 96e9486 commit 9fed086
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 28 deletions.
5 changes: 3 additions & 2 deletions packages/payment-detection/src/index.ts
Expand Up @@ -5,7 +5,7 @@ import * as BtcPaymentNetwork from './btc';
import DeclarativePaymentNetwork from './declarative';
import * as Erc20PaymentNetwork from './erc20';
import * as EthPaymentNetwork from './eth';
import { initPaymentDetectionProvider, getDefaultProvider } from './provider';
import { initPaymentDetectionApiKeys, setProviderFactory, getDefaultProvider } from './provider';

export {
PaymentNetworkFactory,
Expand All @@ -14,6 +14,7 @@ export {
DeclarativePaymentNetwork,
Erc20PaymentNetwork,
EthPaymentNetwork,
initPaymentDetectionProvider,
setProviderFactory,
initPaymentDetectionApiKeys,
getDefaultProvider,
};
125 changes: 114 additions & 11 deletions packages/payment-detection/src/provider.ts
@@ -1,21 +1,124 @@
import { ethers } from 'ethers';
import { providers } from 'ethers';

let configuration = {
type ProviderFactory = (network: string | undefined) => providers.Provider | string;

let warned = false;
/**
* @param network the network to connect to
* @param defaultFactory the defaultFactory to use as fallback if needed
*/
type CurrentProviderFactory = (
network: string | undefined,
defaultFactory: ProviderFactory,
) => providers.Provider | string;

/**
* Default API_KEYS configuration, can be overriden using initPaymentDetectionApiKeys
*/
let providersApiKeys: Record<string, string | (() => string)> = {
// fallback to Ethers v4 default projectId
infura: process.env.RN_INFURA_KEY || '7d0d81d0919f4f05b9ab6634be01ee73',
infura: () => process.env.RN_INFURA_KEY || '7d0d81d0919f4f05b9ab6634be01ee73',
};

export const initPaymentDetectionProvider = (config: Partial<typeof configuration>) => {
configuration = { ...configuration, ...config };
/**
* @param defaultProviderOptions Default Provider Options as specified in https://docs.ethers.io/v5/api/providers/#providers-getDefaultProvider */
export const initPaymentDetectionApiKeys = (
defaultProviderOptions?: typeof providersApiKeys,
): void => {
providersApiKeys = { ...providersApiKeys, ...defaultProviderOptions };
};

export const getDefaultProvider = (network?: string | ethers.providers.Network | undefined) => {
if (network === 'private') {
return new ethers.providers.JsonRpcProvider();
/**
* Define default URLs for networks supported by Request payment detection but not by ethers' Infura Provider
*/
const networkRpcs: Record<string, string> = {
private: providers.JsonRpcProvider.defaultUrl(),
matic: 'https://rpc-mainnet.matic.network/',
};

/**
* @see getDefaultProvider
* @param network
*/
const defaultProviderFactory: ProviderFactory = (network: string | undefined) => {
if (!network) {
network = 'homestead';
}
if (configuration.infura) {
return new ethers.providers.InfuraProvider(network, process.env.RN_INFURA_KEY);

// Returns environment variable override
const envVar = process?.env ? process.env[`RN_WEB3_RPC_URL_${network.toUpperCase()}`] : null;
if (envVar) {
return envVar;
}

return ethers.getDefaultProvider(network);
// check default RPCs
if (networkRpcs[network]) {
return networkRpcs[network];
}

// use infura, if supported
try {
// try getting the URL for the given network. Will throw if not supported.
providers.InfuraProvider.getUrl(providers.getNetwork(network), {});
const apiKey =
typeof providersApiKeys.infura === 'function'
? providersApiKeys.infura()
: providersApiKeys.infura;

if (!apiKey && !warned) {
console.warn(`No API Key specified for Infura, using ethers default API key.
This is not recommended for Production environments.
To override Infura's default api key, use RN_INFURA_KEY environment variable, or call
initPaymentDetectionApiKeys({ infura: () => "myApiKey" });
`);
warned = true;
}
return new providers.InfuraProvider(network, apiKey);
} catch (e) {
// suppress errors
}

if (!warned) {
console.warn(
`No provider is specified for network ${network}, using ethers default provider.
This is not recommended for Production environments.
Use setProviderFactory to override the default provider`,
);
warned = true;
}
// use getDefaultProvider to keep the original behaviour
return providers.getDefaultProvider(network, providersApiKeys);
};

/**
* Defines the behaviour to obtain a Provider for a given Network.
* May be overriden using setProviderFactory
*/
let currentProviderFactory: CurrentProviderFactory = defaultProviderFactory;

/**
* Override the default providerFactory, which relies mainly on Infura.
* @param providerFactory if not specify, will reset to the default factory
*/
export const setProviderFactory = (providerFactory?: CurrentProviderFactory): void => {
currentProviderFactory = providerFactory || defaultProviderFactory;
};

/**
* Returns a Web3 Provider for the given `network`.
*
* Configuration options:
* - Specify `RN_WEB3_RPC_URL_[NETWORK]` environment variable to override the default behaviour
* - Specify `RN_INFURA_KEY` to override the default Infura API KEY (recommended)
* - Use `initPaymentDetectionApiKeys` to override Infura API KEY when `RN_INFURA_KEY` is not usable
* - Use `setProviderFactory` for more complex configurations with multiple networks
*
* @param network the blockchain network. See https://chainid.network/chains.json `network` field for reference
*/
export const getDefaultProvider = (network?: string): providers.Provider => {
const provider = currentProviderFactory(network, defaultProviderFactory);
if (typeof provider === 'string') {
return new providers.JsonRpcProvider(provider);
}
return provider;
};
82 changes: 82 additions & 0 deletions packages/payment-detection/test/provider.test.ts
@@ -0,0 +1,82 @@
import { providers } from 'ethers';
import { getDefaultProvider, initPaymentDetectionApiKeys, setProviderFactory } from '../src';

describe('getDefaultProvider', () => {
afterEach(() => {
// reset the provider factory
setProviderFactory();
});

it('Defaults to Infura Mainnet', async () => {
const provider = getDefaultProvider();

expect(provider).toBeInstanceOf(providers.InfuraProvider);
await expect(provider.getNetwork()).resolves.toMatchObject({ chainId: 1 });
});

it('Can take a standard network', async () => {
const provider = getDefaultProvider('rinkeby');

expect(provider).toBeInstanceOf(providers.InfuraProvider);
await expect(provider.getNetwork()).resolves.toMatchObject({ chainId: 4 });
});

it('Can take a private network', async () => {
const provider = getDefaultProvider('private') as providers.JsonRpcProvider;

expect(provider).toBeInstanceOf(providers.JsonRpcProvider);
expect(provider.connection.url).toBe('http://localhost:8545');
});

it('Can take a non-standard network', async () => {
const provider = getDefaultProvider('matic');

expect(provider).toBeInstanceOf(providers.JsonRpcProvider);
await expect(provider.getNetwork()).resolves.toMatchObject({ chainId: 137 });
});

it('Throws on non-supported network', () => {
expect(() => getDefaultProvider('bitcoin')).toThrowError(
'unsupported getDefaultProvider network',
);
});

it('Can override the RPC configuration for an existing network', async () => {
expect(getDefaultProvider('matic')).toBeInstanceOf(providers.JsonRpcProvider);
expect((getDefaultProvider('matic') as providers.JsonRpcProvider).connection.url).toBe(
'https://rpc-mainnet.matic.network/',
);
setProviderFactory(() => 'http://matic.fake');
expect(getDefaultProvider('matic')).toBeInstanceOf(providers.JsonRpcProvider);
expect((getDefaultProvider('matic') as providers.JsonRpcProvider).connection.url).toBe(
'http://matic.fake',
);
});

it('Can override the RPC configuration for a new network', async () => {
expect(() => getDefaultProvider('xdai')).toThrowError('unsupported getDefaultProvider network');
setProviderFactory((network, defaultFactory) => {
if (network === 'xdai') {
return 'http://xdaichain.fake';
}
return defaultFactory(network);
});
expect(getDefaultProvider('xdai')).toBeInstanceOf(providers.JsonRpcProvider);
expect((getDefaultProvider('xdai') as providers.JsonRpcProvider).connection.url).toBe(
'http://xdaichain.fake',
);
// still works for standard providers
expect((getDefaultProvider('rinkeby') as providers.JsonRpcProvider).connection.url).toMatch(
/https:\/\/rinkeby\.infura.*/,
);
});

it('Can override the api key for a standard provider', async () => {
initPaymentDetectionApiKeys({
infura: 'foo-bar',
});

const provider = getDefaultProvider() as providers.InfuraProvider;
expect(provider.connection.url).toEqual('https://mainnet.infura.io/v3/foo-bar');
});
});
12 changes: 3 additions & 9 deletions packages/payment-processor/src/payment/utils.ts
@@ -1,6 +1,6 @@
import { ethers, getDefaultProvider, Signer, providers, BigNumber, BigNumberish } from 'ethers';
import { ethers, Signer, providers, BigNumber, BigNumberish } from 'ethers';

import { PaymentReferenceCalculator } from '@requestnetwork/payment-detection';
import { PaymentReferenceCalculator, getDefaultProvider } from '@requestnetwork/payment-detection';
import {
ClientTypes,
ExtensionTypes,
Expand Down Expand Up @@ -35,13 +35,7 @@ export function getProvider(): providers.Web3Provider {
* @param request
*/
export function getNetworkProvider(request: ClientTypes.IRequestData): providers.Provider {
if (request.currencyInfo.network === 'mainnet') {
return getDefaultProvider();
}
if (request.currencyInfo.network === 'rinkeby') {
return getDefaultProvider('rinkeby');
}
throw new UnsupportedCurrencyNetwork(request.currencyInfo.network);
return getDefaultProvider(request.currencyInfo.network);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/payment-processor/test/payment/utils.test.ts
Expand Up @@ -111,11 +111,11 @@ describe('getNetworkProvider', () => {
it('fails for other network', () => {
const request: any = {
currencyInfo: {
network: 'ropsten',
network: 'bitcoin',
},
};
expect(() => getNetworkProvider(request)).toThrowError(
'Currency network ropsten is not supported',
'unsupported getDefaultProvider network',
);
});
});
Expand Down
1 change: 1 addition & 0 deletions packages/toolbox/package.json
Expand Up @@ -41,6 +41,7 @@
"dependencies": {
"@requestnetwork/currency": "0.6.0",
"@requestnetwork/epk-signature": "0.5.32",
"@requestnetwork/payment-detection": "0.33.0",
"@requestnetwork/request-client.js": "0.37.0",
"@requestnetwork/smart-contracts": "0.26.0",
"@requestnetwork/types": "0.33.0",
Expand Down
6 changes: 2 additions & 4 deletions packages/toolbox/src/chainlinkConversionPathTools.ts
@@ -1,5 +1,6 @@
import { ethers } from 'ethers';
import { chainlinkConversionPath } from '@requestnetwork/smart-contracts';
import { getDefaultProvider } from '@requestnetwork/payment-detection';
import { ChainlinkConversionPath__factory } from '@requestnetwork/smart-contracts/types';
import { Currency } from '@requestnetwork/currency';
import { RequestLogicTypes } from '@requestnetwork/types';
Expand Down Expand Up @@ -41,10 +42,7 @@ class ChainlinkConversionPathTools {
*/
constructor(private network: string) {
// Creates a local or default provider
this.provider =
this.network === 'private'
? new ethers.providers.JsonRpcProvider()
: ethers.getDefaultProvider(this.network);
this.provider = getDefaultProvider(this.network);

// Setup the conversion proxy contract interface
this.contractChainlinkConversionPath = ChainlinkConversionPath__factory.connect(
Expand Down

0 comments on commit 9fed086

Please sign in to comment.