Skip to content

Commit

Permalink
Merge branch 'master' into pn-any-to-erc20-swap-payment-processor
Browse files Browse the repository at this point in the history
  • Loading branch information
vrolland committed Apr 19, 2021
2 parents af61334 + 77f4fe2 commit 58f7160
Show file tree
Hide file tree
Showing 16 changed files with 709 additions and 264 deletions.
30 changes: 27 additions & 3 deletions packages/currency/README.md
Expand Up @@ -12,14 +12,38 @@ npm install @requestnetwork/currency
## Usage

```javascript
import Currency from '@requestnetwork/currency';
import { RequestLogicTypes } from '@requestnetwork/types';
import { Currency, Token } from '@requestnetwork/currency';

const decimals = Currency.getDecimalsForCurrency({
const decimals = new Currency({
type: RequestLogicTypes.CURRENCY.ETH,
value: 'ETH',
};
}).getDecimals();

console.log(decimals); // 18

const ETHHash = new Currency({
type: RequestLogicTypes.CURRENCY.ETH,
value: 'ETH',
}).getHash();

console.log(ETHHash); // 0xF5AF88e117747e87fC5929F2ff87221B1447652E

// Get currencies from their symbol
const ETHCurrency: RequestLogicTypes.ICurrency = Currency.from('ETH');
const FAUCurrency: RequestLogicTypes.ICurrency = Currency.from('DAI');
// Get currencies from their address
const DAICurrency: RequestLogicTypes.ICurrency = Currency.from(
'0x6B175474E89094C44Da98b954EedeAC495271d0F',
);

console.log(FAUCurrency.toString()); // FAU-rinkeby
console.log(DAICurrency.toString()); // DAI

// Get a token symbol from its address
const FAUToken = Token.from('0xFab46E002BbF0b4509813474841E0716E6730136');

console.log(FAUToken.symbol); // FAU
```

## Contributing
Expand Down
7 changes: 3 additions & 4 deletions packages/currency/src/chainlink-path-aggregators.ts
@@ -1,7 +1,6 @@
import { RequestLogicTypes } from '@requestnetwork/types';
import { getCurrencyHash } from './index';

import GRAPH from 'node-dijkstra';
import { Currency } from './currency';

// List of currencies supported by network (can be generated from requestNetwork/toolbox/src/chainlinkConversionPathTools.ts)
// Network => currencyFrom => currencyTo => cost
Expand Down Expand Up @@ -155,7 +154,7 @@ export function getPath(

// Get the path
return route.path(
getCurrencyHash(currencyFrom).toLowerCase(),
getCurrencyHash(currencyTo).toLowerCase(),
new Currency(currencyFrom).getHash().toLowerCase(),
new Currency(currencyTo).getHash().toLowerCase(),
);
}
185 changes: 185 additions & 0 deletions packages/currency/src/currency.ts
@@ -0,0 +1,185 @@
import { RequestLogicTypes } from '@requestnetwork/types';
import Utils from '@requestnetwork/utils';
import {
getSupportedERC20Currencies,
getErc20Currency,
getErc20Symbol,
getErc20Decimals,
} from './erc20';
import iso4217 from './iso4217';
import otherCurrencies from './others';

/**
* @class Currency implements ICurrency with helpers
* Represents a currency supported by the Request Logic, with minimum required
* information: value, type and network (optional).
*/
export class Currency implements RequestLogicTypes.ICurrency {
public readonly value: string;
public readonly type: RequestLogicTypes.CURRENCY;
public readonly network?: string;

constructor(currency: RequestLogicTypes.ICurrency) {
({ value: this.value, type: this.type, network: this.network } = currency);
}

/**
* Gets a supported currency from a symbol, symbol-network or address.
* Iterates over all the supported networks if needed
* @param symbolOrAddress e.g. 'DAI', 'FAU', 'FAU-rinkeby' or '0xFab46E002BbF0b4509813474841E0716E6730136'
* @returns an ICurrency object
*/
static from(symbolOrAddress: string): Currency {
if (symbolOrAddress === '') {
throw new Error(`Cannot guess currency from empty string.`);
}
try {
const currencyFromSymbol = this.fromSymbol(
symbolOrAddress.split('-')[0],
symbolOrAddress.split('-')[1],
);
return currencyFromSymbol;
} catch (e) {
const erc20Currencies = getSupportedERC20Currencies();
const currencyFromAddress = erc20Currencies.find(
(c) => c.value.toLowerCase() === symbolOrAddress.toLowerCase(),
);
if (!currencyFromAddress) {
throw new Error(`The currency ${symbolOrAddress} does not exist or is not supported`);
}
return new Currency(currencyFromAddress);
}
}

/**
* Get currency from its symbol and network.
* @param symbol
* @param network
* @returns RequestLogicTypes.ICurrency
*/
static fromSymbol = (symbol: string, network?: string): Currency => {
if (symbol === '') {
throw new Error(`Cannot guess currency from empty symbol.`);
}
// Check if it's a supported cryptocurrency
if (symbol === 'BTC' && (!network || network === 'mainnet')) {
return new Currency({
type: RequestLogicTypes.CURRENCY.BTC,
value: 'BTC',
network: 'mainnet',
});
}
if (symbol === 'ETH' && (!network || network === 'mainnet' || network === 'rinkeby')) {
return new Currency({
type: RequestLogicTypes.CURRENCY.ETH,
value: 'ETH',
network: network || 'mainnet',
});
}

// Check if it's an ERC20 token and return it if found
const erc20Currency = getErc20Currency(symbol, network);
if (erc20Currency) {
return new Currency(erc20Currency);
}

// Check if it's one of ISO4217 currencies
if (iso4217.find((i) => i.code === symbol)) {
return new Currency({
type: RequestLogicTypes.CURRENCY.ISO4217,
value: symbol,
});
}
throw new Error(
`The currency symbol '${symbol}'${
network ? ` on ${network}` : ''
} is unknown or not supported`,
);
};

/**
* @returns Symbol if known (FAU, DAI, ETH etc.)
*/
public getSymbol(): string | 'unknown' {
let symbol: string;

switch (this.type) {
case RequestLogicTypes.CURRENCY.BTC:
case RequestLogicTypes.CURRENCY.ETH:
symbol = this.type;
break;
case RequestLogicTypes.CURRENCY.ISO4217:
symbol = this.value;
break;
case RequestLogicTypes.CURRENCY.ERC20:
symbol = getErc20Symbol(this) || 'unknown';
break;
default:
symbol = 'unknown';
}
return symbol;
}

/**
* Gets the hash of a currency
*
* @returns the hash of the currency
* @todo It onlys supports Ethereum-based currencies, fiat and BTC.
*/
public getHash(): string {
if (this.type === RequestLogicTypes.CURRENCY.ERC20) {
return this.value;
}
if (
this.type === RequestLogicTypes.CURRENCY.ETH ||
this.type === RequestLogicTypes.CURRENCY.BTC
) {
// ignore the network
return Utils.crypto.last20bytesOfNormalizedKeccak256Hash({
type: this.type,
value: this.value,
});
}
return Utils.crypto.last20bytesOfNormalizedKeccak256Hash(this);
}

/**
* Returns the number of decimals
*/
public getDecimals(): number {
// Return decimals if currency is an ERC20
if (this.type === RequestLogicTypes.CURRENCY.ERC20) {
return getErc20Decimals(this);
}

// Return the number of decimals for ISO-4217 currencies
if (this.type === RequestLogicTypes.CURRENCY.ISO4217) {
const iso = iso4217.find((i) => i.code === this.value);
if (!iso) {
throw new Error(`Unsupported ISO currency ${this.value}`);
}
return iso.digits;
}

// other currencies
const otherCurrency = otherCurrencies[this.type];
if (!otherCurrency) {
throw new Error(`Currency ${this.type} not implemented`);
}

return otherCurrency.decimals;
}

/**
* @returns e.g.: 'ETH', 'ETH-rinkeby', 'FAU-rinkeby' etc.
*/
public toString(): string | 'unknown' {
const symbol = this.getSymbol();

// Append currency network if relevant
const network =
this.network && this.network !== 'mainnet' && symbol !== 'unknown' ? `-${this.network}` : '';

return symbol + network;
}
}
44 changes: 37 additions & 7 deletions packages/currency/src/erc20/index.ts
Expand Up @@ -8,11 +8,19 @@ import { supportedNetworks, supportedNetworksDetails, ERC20SymbolDetails } from
*/
export function getErc20Currency(
symbol: string,
network = 'mainnet',
network?: string,
): RequestLogicTypes.ICurrency | undefined {
// Check if it's on one of the other supported networks
if (network in supportedNetworks && supportedNetworks[network].has(symbol)) {
return supportedNetworks[network].get(symbol);
if (network) {
if (network in supportedNetworks && supportedNetworks[network].has(symbol)) {
return supportedNetworks[network].get(symbol);
}
return;
}
for (network of Object.keys(supportedNetworks)) {
if (supportedNetworks[network].has(symbol)) {
return supportedNetworks[network].get(symbol);
}
}

return;
Expand Down Expand Up @@ -70,17 +78,18 @@ export function getErc20Symbol(currency: RequestLogicTypes.ICurrency): string |
interface ERC20TokenDetails extends ERC20SymbolDetails {
symbol: string;
}

/**
* Returns a list of supported ERC20 currencies
* Returns a list of supported ERC20 tokens
*
* @returns List of supported ERC20 currencies
* @returns List of supported ERC20 tokens
*/
export function getSupportedERC20Tokens(): ERC20TokenDetails[] {
return Object.entries(supportedNetworksDetails).reduce(
(acc: ERC20TokenDetails[], [networkName, network]) => {
(acc: ERC20TokenDetails[], [networkName, supportedCurrencies]) => {
return [
...acc,
...Object.entries(network).map(([symbol, token]) => ({
...Object.entries(supportedCurrencies).map(([symbol, token]) => ({
...token,
symbol: `${symbol}${networkName !== 'mainnet' ? `-${networkName}` : ''}`,
})),
Expand All @@ -89,3 +98,24 @@ export function getSupportedERC20Tokens(): ERC20TokenDetails[] {
[],
);
}

/**
* Returns a list of supported ERC20 currencies
*
* @returns List of supported ERC20 currencies
*/
export function getSupportedERC20Currencies(): RequestLogicTypes.ICurrency[] {
return Object.entries(supportedNetworksDetails).reduce(
(acc: RequestLogicTypes.ICurrency[], [networkName, supportedCurrencies]) => {
return [
...acc,
...Object.entries(supportedCurrencies).map(([, token]) => ({
network: networkName,
value: token.address,
type: RequestLogicTypes.CURRENCY.ERC20,
})),
];
},
[],
);
}
2 changes: 1 addition & 1 deletion packages/currency/src/erc20/networks/mainnet.ts
Expand Up @@ -6,7 +6,7 @@ import * as metamaskContractMap from '@metamask/contract-metadata';
// A Token description from the eth-contract-metadata list
interface ITokenDescription {
name: string;
logo: string;
logo?: string;
erc20: boolean;
symbol: string;
decimals: number;
Expand Down
2 changes: 2 additions & 0 deletions packages/currency/src/erc20/networks/rinkeby.ts
Expand Up @@ -31,12 +31,14 @@ export const supportedRinkebyERC20Details = {
address: '0x995d6A8C21F24be1Dd04E105DD0d83758343E258',
decimals: 18,
name: 'Central Bank Token',
symbol: 'CTBK',
},
// Faucet Token on rinkeby network.
FAU: {
// Faucet URL: https://erc20faucet.com/
address: '0xFab46E002BbF0b4509813474841E0716E6730136',
decimals: 18,
name: 'Faucet Token',
symbol: 'FAU',
},
};

0 comments on commit 58f7160

Please sign in to comment.