diff --git a/README.md b/README.md index a0225e5..d337504 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,13 @@ const customAccountLink = etherscanLink.createCustomAccountLink(account, customN const customExplorerLink = etherscanLink.createCustomExplorerLink(hash, customNetworkUrl) // Generate custom or native block explorer link based on rcpPrefs -const blockExplorerLink = etherscanLink.getBlockExplorerUrlForTx(transaction, rcpPrefs) +const blockExplorerLink = etherscanLink.getBlockExplorerLink(transaction, rcpPrefs) + +// Generate account link for custom or native network +const getAccountLink = etherscanLink.getAccountLink(address, chainId, rpcPrefs, networkId) + +// Generate token tracker link for custom or native network +const tokenTrackerLink = etherscanLink.getTokenTrackerLink(tokenAddress, chainId, networkId, holderAddress, rpcPrefs) ``` ## Running tests diff --git a/src/account-link.ts b/src/account-link.ts index 37709c7..0fc37a3 100644 --- a/src/account-link.ts +++ b/src/account-link.ts @@ -2,8 +2,12 @@ import { addPathToUrl } from './helpers'; import prefixForChain from './prefix-for-chain'; import prefixForNetwork from './prefix-for-network'; -export function createAccountLink(address: string, network: string): string { - const prefix = prefixForNetwork(network); +interface RpcPrefsInterface { + blockExplorerUrl?: string; +} + +export function createAccountLink(address: string, networkId: string): string { + const prefix = prefixForNetwork(networkId); return prefix === null ? '' : `https://${prefix}etherscan.io/address/${address}`; } @@ -16,3 +20,13 @@ export function createCustomAccountLink(address: string, customNetworkUrl: strin const parsedUrl = addPathToUrl(customNetworkUrl, 'address', address); return parsedUrl; } + +export function getAccountLink(address: string, chainId: string, rpcPrefs: RpcPrefsInterface = {}, networkId = '') { + if (rpcPrefs.blockExplorerUrl) { + return createCustomAccountLink(address, rpcPrefs.blockExplorerUrl); + } + if (networkId) { + return createAccountLink(address, networkId); + } + return createAccountLinkForChain(address, chainId); +} diff --git a/src/explorer-link.ts b/src/explorer-link.ts index 81ef7d6..88d8141 100644 --- a/src/explorer-link.ts +++ b/src/explorer-link.ts @@ -2,12 +2,12 @@ import { addPathToUrl } from './helpers'; import prefixForChain from './prefix-for-chain'; import prefixForNetwork from './prefix-for-network'; +// TODO improve type safety / discriminating unions (this may require a discriminant property) interface TransactionInterface { hash: string; chainId: string; metamaskNetworkId: string; } - interface RpcPrefsInterface { blockExplorerUrl?: string; } @@ -28,7 +28,7 @@ export function createExplorerLinkForChain(hash: string, chainId: string): strin return prefix === null ? '' : `https://${prefix}etherscan.io/tx/${hash}`; } -export function getBlockExplorerUrlForTx(transaction: TransactionInterface, rpcPrefs: RpcPrefsInterface = {}) { +export function getBlockExplorerLink(transaction: TransactionInterface, rpcPrefs: RpcPrefsInterface = {}) { if (rpcPrefs.blockExplorerUrl) { return createCustomExplorerLink(transaction.hash, rpcPrefs.blockExplorerUrl); } diff --git a/src/index.ts b/src/index.ts index d29a41f..c0b9d53 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ -import { createAccountLink, createAccountLinkForChain, createCustomAccountLink } from './account-link'; -import { createCustomExplorerLink, createExplorerLink, createExplorerLinkForChain } from './explorer-link'; -import { createTokenTrackerLink, createCustomTokenTrackerLink, createTokenTrackerLinkForChain } from './token-tracker-link'; +import { createAccountLink, createAccountLinkForChain, createCustomAccountLink, getAccountLink } from './account-link'; +import { createCustomExplorerLink, createExplorerLink, createExplorerLinkForChain, getBlockExplorerLink } from './explorer-link'; +import { createTokenTrackerLink, createCustomTokenTrackerLink, createTokenTrackerLinkForChain, getTokenTrackerLink } from './token-tracker-link'; export = { createExplorerLink, @@ -13,4 +13,7 @@ export = { createTokenTrackerLink, createCustomTokenTrackerLink, createTokenTrackerLinkForChain, + getBlockExplorerLink, + getAccountLink, + getTokenTrackerLink, }; diff --git a/src/prefix-for-network.ts b/src/prefix-for-network.ts index 7ec41ae..43028bd 100644 --- a/src/prefix-for-network.ts +++ b/src/prefix-for-network.ts @@ -1,6 +1,6 @@ -export = function getPrefixForNetwork(network: string): string | null { +export = function getPrefixForNetwork(networkId: string): string | null { // eslint-disable-next-line radix - const net = parseInt(network); + const net = parseInt(networkId); let prefix; switch (net) { diff --git a/src/token-tracker-link.ts b/src/token-tracker-link.ts index 200941c..4fe40dd 100644 --- a/src/token-tracker-link.ts +++ b/src/token-tracker-link.ts @@ -2,12 +2,16 @@ import { addPathToUrl } from './helpers'; import prefixForChain from './prefix-for-chain'; import prefixForNetwork from './prefix-for-network'; +interface RpcPrefsInterface { + blockExplorerUrl?: string; +} + export function createTokenTrackerLink( tokenAddress: string, - network: string, + networkId: string, holderAddress?: string, ): string { - const prefix = prefixForNetwork(network); + const prefix = prefixForNetwork(networkId); return prefix === null ? '' : `https://${prefix}etherscan.io/token/${tokenAddress}${ holderAddress ? `?a=${holderAddress}` : ''}`; @@ -31,3 +35,13 @@ export function createTokenTrackerLinkForChain( `https://${prefix}etherscan.io/token/${tokenAddress}${ holderAddress ? `?a=${holderAddress}` : ''}`; } + +export function getTokenTrackerLink(tokenAddress: string, chainId: string, networkId: string, holderAddress?: string, rpcPrefs: RpcPrefsInterface = {}) { + if (rpcPrefs.blockExplorerUrl) { + return createCustomTokenTrackerLink(tokenAddress, rpcPrefs.blockExplorerUrl); + } + if (networkId) { + return createTokenTrackerLink(tokenAddress, networkId, holderAddress); + } + return createTokenTrackerLinkForChain(tokenAddress, chainId, holderAddress); +} diff --git a/test/index.test.js b/test/index.test.js index 74938df..1d836ec 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -9,6 +9,9 @@ const { createExplorerLinkForChain, createAccountLinkForChain, createTokenTrackerLinkForChain, + getBlockExplorerLink, + getAccountLink, + getTokenTrackerLink, } = require('../dist'); // `https://${prefix}etherscan.io/address/${address}` @@ -51,6 +54,50 @@ describe('account-link', function () { const result = createCustomAccountLink('foo', 'https://data-seed-prebsc-1-s1.binance.org:8545'); assert.strictEqual(result, 'https://data-seed-prebsc-1-s1.binance.org:8545/address/foo', 'should return binance testnet address url'); }); + + describe('getAccountLink', function () { + it('should return the correct account-link url for an account based on chainId, networkId and rpcPref args', function () { + const getAccountLinkTests = [ + { + expected: 'https://etherscan.io/address/0xabcd', + chainId: '0x1', + address: '0xabcd', + }, + { + expected: 'https://etherscan.io/address/0xabcd', + networkId: '1', + address: '0xabcd', + }, + { + expected: 'https://ropsten.etherscan.io/address/0xdef0', + chainId: '0x3', + address: '0xdef0', + rpcPrefs: {}, + }, + { + // test handling of `blockExplorerUrl` for a custom RPC + expected: 'https://block.explorer/address/0xabcd', + chainId: '0x21', + address: '0xabcd', + rpcPrefs: { + blockExplorerUrl: 'https://block.explorer', + }, + }, + { + // test handling of trailing `/` in `blockExplorerUrl` for a custom RPC + expected: 'https://another.block.explorer/address/0xdef0', + chainId: '0x1f', + address: '0xdef0', + rpcPrefs: { + blockExplorerUrl: 'https://another.block.explorer/', + }, + }, + ]; + getAccountLinkTests.forEach(({ expected, address, chainId, rpcPrefs, networkId }) => { + assert.strictEqual(getAccountLink(address, chainId, rpcPrefs, networkId), expected); + }); + }); + }); }); // `https://${prefix}etherscan.io/tx/${hash}` @@ -158,5 +205,168 @@ describe('token-tracker-link', function () { const result = createCustomTokenTrackerLink('foo', 'https://data-seed-prebsc-1-s1.binance.org:8545/'); assert.strictEqual(result, 'https://data-seed-prebsc-1-s1.binance.org:8545/token/foo', 'should return binance testnet token url'); }); + + describe('getTokenTrackerLink', function () { + it('should return the correct token tracker url based on chainId, networkId and rpcPref args', function () { + + const getTokenTrackerTests = [ + { + expected: 'https://etherscan.io/token/0xabcd', + networkId: '1', + tokenAddress: '0xabcd', + }, + { + expected: 'https://ropsten.etherscan.io/token/0xdef0', + networkId: '3', + tokenAddress: '0xdef0', + }, + { + // test handling of `blockExplorerUrl` for a custom RPC + expected: 'https://block.explorer/token/0xar31', + tokenAddress: '0xar31', + rpcPrefs: { + blockExplorerUrl: 'https://block.explorer', + }, + }, + { + // test handling of trailing `/` in `blockExplorerUrl` for a custom RPC + expected: 'https://another.block.explorer/token/0xdef0', + tokenAddress: '0xdef0', + rpcPrefs: { + blockExplorerUrl: 'https://another.block.explorer/', + }, + }, + { + expected: 'https://etherscan.io/token/0xabcd', + chainId: '0x1', + tokenAddress: '0xabcd', + }, + { + expected: 'https://ropsten.etherscan.io/token/0xdef0', + chainId: '0x3', + tokenAddress: '0xdef0', + rpcPrefs: {}, + }, + { + // test handling of `blockExplorerUrl` for a custom RPC + expected: 'https://block.explorer/token/0xabcd', + chainId: '0x1f', + tokenAddress: '0xabcd', + rpcPrefs: { + blockExplorerUrl: 'https://block.explorer', + }, + }, + { + // test handling of trailing `/` in `blockExplorerUrl` for a custom RPC + expected: 'https://another.block.explorer/token/0xdef0', + chainId: '0x21', + tokenAddress: '0xdef0', + rpcPrefs: { + blockExplorerUrl: 'https://another.block.explorer/', + }, + }, + ]; + + getTokenTrackerTests.forEach((test) => { + assert.strictEqual( + getTokenTrackerLink(test.tokenAddress, test.chainId, test.networkId, test.holderAddress, test.rpcPrefs), + test.expected + ); + }); + }); + }); + }); + + /* + * Test getBlockExplorerLink, + * Which applies correct explorer-link generator based on args + */ + describe('getBlockExplorerLink', function () { + it('should return the correct block explorer url for an account based on chainId, networkId and rpcPref args', function () { + + const getBlockExplorerLinkTests = [ + { + expected: 'https://etherscan.io/tx/0xabcd', + transaction: { + metamaskNetworkId: '1', + hash: '0xabcd', + }, + }, + { + expected: 'https://ropsten.etherscan.io/tx/0xdef0', + transaction: { + metamaskNetworkId: '3', + hash: '0xdef0', + }, + rpcPrefs: {}, + }, + { + // test handling of `blockExplorerUrl` for a custom RPC + expected: 'https://block.explorer/tx/0xabcd', + transaction: { + metamaskNetworkId: '31', + hash: '0xabcd', + }, + rpcPrefs: { + blockExplorerUrl: 'https://block.explorer', + }, + }, + { + // test handling of trailing `/` in `blockExplorerUrl` for a custom RPC + expected: 'https://another.block.explorer/tx/0xdef0', + transaction: { + metamaskNetworkId: '33', + hash: '0xdef0', + }, + rpcPrefs: { + blockExplorerUrl: 'https://another.block.explorer/', + }, + }, + { + expected: 'https://etherscan.io/tx/0xabcd', + transaction: { + chainId: '0x1', + hash: '0xabcd', + }, + }, + { + expected: 'https://ropsten.etherscan.io/tx/0xdef0', + transaction: { + chainId: '0x3', + hash: '0xdef0', + }, + rpcPrefs: {}, + }, + { + // test handling of `blockExplorerUrl` for a custom RPC + expected: 'https://block.explorer/tx/0xabcd', + transaction: { + chainId: '0x1f', + hash: '0xabcd', + }, + rpcPrefs: { + blockExplorerUrl: 'https://block.explorer', + }, + }, + { + // test handling of trailing `/` in `blockExplorerUrl` for a custom RPC + expected: 'https://another.block.explorer/tx/0xdef0', + transaction: { + chainId: '0x21', + hash: '0xdef0', + }, + rpcPrefs: { + blockExplorerUrl: 'https://another.block.explorer/', + }, + }, + ]; + + getBlockExplorerLinkTests.forEach((test) => { + assert.strictEqual( + getBlockExplorerLink(test.transaction, test.rpcPrefs), + test.expected + ); + }); + }); }); });