Skip to content

Commit

Permalink
Merge pull request #263 from koshilife/support-goerli-and-sepolia
Browse files Browse the repository at this point in the history
Support verifying VCs on the Goerli and the Sepolia
  • Loading branch information
lemoustachiste authored Jul 12, 2022
2 parents f0f110f + 1648654 commit f469fb1
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 25 deletions.
18 changes: 18 additions & 0 deletions src/constants/blockchains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,24 @@ const BLOCKCHAINS: {[chain in SupportedChains]: IBlockchainObject} = {
raw: `https://rinkeby.etherscan.io/getRawTx?tx=${TRANSACTION_ID_PLACEHOLDER}`
}
},
[SupportedChains.Ethgoerli]: {
code: SupportedChains.Ethgoerli,
name: 'Ethereum Testnet',
signatureValue: 'ethereumGoerli',
transactionTemplates: {
full: `https://goerli.etherscan.io/tx/${TRANSACTION_ID_PLACEHOLDER}`,
raw: `https://goerli.etherscan.io/getRawTx?tx=${TRANSACTION_ID_PLACEHOLDER}`
}
},
[SupportedChains.Ethsepolia]: {
code: SupportedChains.Ethsepolia,
name: 'Ethereum Testnet',
signatureValue: 'ethereumSepolia',
transactionTemplates: {
full: `https://sepolia.etherscan.io/tx/${TRANSACTION_ID_PLACEHOLDER}`,
raw: `https://sepolia.etherscan.io/getRawTx?tx=${TRANSACTION_ID_PLACEHOLDER}`
}
},
[SupportedChains.Mocknet]: {
code: SupportedChains.Mocknet,
name: 'Mocknet',
Expand Down
2 changes: 2 additions & 0 deletions src/constants/supported-chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export enum SupportedChains {
Ethmain = 'ethmain',
Ethropst = 'ethropst',
Ethrinkeby = 'ethrinkeby',
Ethgoerli = 'ethgoerli',
Ethsepolia = 'ethsepolia',
Mocknet = 'mocknet',
Regtest = 'regtest',
Testnet = 'testnet'
Expand Down
43 changes: 29 additions & 14 deletions src/explorers/ethereum/etherscan.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,47 @@
import request from '../../services/request';
import { stripHashPrefix } from '../../utils/stripHashPrefix';
import { buildTransactionServiceUrl } from '../../services/transaction-apis';
import { BLOCKCHAINS, isTestChain } from '../../constants/blockchains';
import { BLOCKCHAINS } from '../../constants/blockchains';
import { TransactionData } from '../../models/transactionData';
import { TRANSACTION_APIS, TRANSACTION_ID_PLACEHOLDER } from '../../constants/api';
import { ExplorerAPI, ExplorerURLs, IParsingFunctionAPI } from '../../models/explorers';
import { ExplorerAPI, IParsingFunctionAPI } from '../../models/explorers';
import CONFIG from '../../constants/config';
import { SupportedChains } from '../../constants/supported-chains';

const MAIN_API_BASE_URL = 'https://api.etherscan.io/api?module=proxy';
const TEST_API_BASE_URL = 'https://api-ropsten.etherscan.io/api?module=proxy';
const serviceURL: ExplorerURLs = {
main: `${MAIN_API_BASE_URL}&action=eth_getTransactionByHash&txhash=${TRANSACTION_ID_PLACEHOLDER}`,
test: `${TEST_API_BASE_URL}&action=eth_getTransactionByHash&txhash=${TRANSACTION_ID_PLACEHOLDER}`
};

function getApiBaseURL (chain: SupportedChains): string {
const testnetNameMap = {
[SupportedChains.Ethropst]: 'ropsten',
[SupportedChains.Ethrinkeby]: 'rinkeby',
[SupportedChains.Ethgoerli]: 'goerli',
[SupportedChains.Ethsepolia]: 'sepolia'
};
if (!testnetNameMap[chain]) {
return MAIN_API_BASE_URL;
}
const testnetName: string = testnetNameMap[chain];
return `https://api-${testnetName}.etherscan.io/api?module=proxy`;
}

function getTransactionServiceURL (chain: SupportedChains): string {
const baseUrl = getApiBaseURL(chain);
return `${baseUrl}&action=eth_getTransactionByHash&txhash=${TRANSACTION_ID_PLACEHOLDER}`;
}

// TODO: use tests/explorers/mocks/mockEtherscanResponse as type
async function parsingFunction ({ jsonResponse, chain, key, keyPropertyName }: IParsingFunctionAPI): Promise<TransactionData> {
const baseUrl = getApiBaseURL(chain);
const getBlockByNumberServiceUrls: Partial<ExplorerAPI> = {
serviceURL: {
main: `${MAIN_API_BASE_URL}&action=eth_getBlockByNumber&boolean=true&tag=${TRANSACTION_ID_PLACEHOLDER}`,
test: `${TEST_API_BASE_URL}&action=eth_getBlockByNumber&boolean=true&tag=${TRANSACTION_ID_PLACEHOLDER}`
main: `${baseUrl}&action=eth_getBlockByNumber&boolean=true&tag=${TRANSACTION_ID_PLACEHOLDER}`,
test: `${baseUrl}&action=eth_getBlockByNumber&boolean=true&tag=${TRANSACTION_ID_PLACEHOLDER}`
}
};
const getBlockNumberServiceUrls: Partial<ExplorerAPI> = {
serviceURL: {
main: `${MAIN_API_BASE_URL}&action=eth_blockNumber`,
test: `${TEST_API_BASE_URL}&action=eth_blockNumber`
main: `${baseUrl}&action=eth_blockNumber`,
test: `${baseUrl}&action=eth_blockNumber`
}
};

Expand Down Expand Up @@ -57,7 +72,7 @@ async function parsingFunction ({ jsonResponse, chain, key, keyPropertyName }: I
keyPropertyName
} as ExplorerAPI,
transactionId: blockNumber,
isTestApi: isTestChain(chain)
chain
});

try {
Expand All @@ -80,7 +95,7 @@ async function parsingFunction ({ jsonResponse, chain, key, keyPropertyName }: I
key,
keyPropertyName
} as ExplorerAPI,
isTestApi: isTestChain(chain)
chain
});

let response: string;
Expand All @@ -107,7 +122,7 @@ async function parsingFunction ({ jsonResponse, chain, key, keyPropertyName }: I
}

export const explorerApi: ExplorerAPI = {
serviceURL,
serviceURL: getTransactionServiceURL,
serviceName: TRANSACTION_APIS.etherscan,
parsingFunction,
priority: -1
Expand Down
3 changes: 1 addition & 2 deletions src/explorers/explorer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { buildTransactionServiceUrl } from '../services/transaction-apis';
import request from '../services/request';
import { isTestChain } from '../constants/blockchains';
import { TransactionData } from '../models/transactionData';
import { ExplorerAPI, TExplorerFunctionsArray } from '../models/explorers';
import { explorerApi as EtherscanApi } from './ethereum/etherscan';
Expand All @@ -27,7 +26,7 @@ export async function getTransactionFromApi (
const requestUrl = buildTransactionServiceUrl({
explorerAPI,
transactionId,
isTestApi: isTestChain(chain)
chain
});

try {
Expand Down
2 changes: 2 additions & 0 deletions src/lookForTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export function getExplorersByChain (chain: SupportedChains, explorerAPIs: TExpl
case BLOCKCHAINS[SupportedChains.Ethmain].code:
case BLOCKCHAINS[SupportedChains.Ethropst].code:
case BLOCKCHAINS[SupportedChains.Ethrinkeby].code:
case BLOCKCHAINS[SupportedChains.Ethgoerli].code:
case BLOCKCHAINS[SupportedChains.Ethsepolia].code:
return explorerAPIs.ethereum;
default:
if (!explorerAPIs.custom?.length) {
Expand Down
2 changes: 1 addition & 1 deletion src/models/explorers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface IParsingFunctionAPI {
export type TExplorerParsingFunction = ((data: IParsingFunctionAPI) => TransactionData) | ((data: IParsingFunctionAPI) => Promise<TransactionData>);

export interface ExplorerAPI {
serviceURL?: string | ExplorerURLs;
serviceURL?: string | ExplorerURLs | ((chain: SupportedChains) => string);
priority?: 0 | 1 | -1; // 0: custom APIs will run before the default APIs, 1: after, -1: reserved to default APIs
parsingFunction?: TExplorerParsingFunction;
serviceName?: TRANSACTION_APIS; // in case one would want to overload the default explorers
Expand Down
18 changes: 15 additions & 3 deletions src/services/transaction-apis.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ExplorerAPI } from '../models/explorers';
import { TRANSACTION_ID_PLACEHOLDER } from '../constants/api';
import { safelyAppendUrlParameter } from '../utils/url';
import { SupportedChains } from '../constants/supported-chains';
import { isTestChain } from '../constants/blockchains';

function appendApiIdentifier (url: string, explorerAPI: ExplorerAPI): string {
if (!explorerAPI.key) {
Expand All @@ -18,15 +20,25 @@ export function buildTransactionServiceUrl ({
explorerAPI,
transactionIdPlaceholder = TRANSACTION_ID_PLACEHOLDER,
transactionId = '',
isTestApi = false
chain
}: {
explorerAPI: ExplorerAPI;
transactionIdPlaceholder?: string;
transactionId?: string;
isTestApi?: boolean;
chain?: SupportedChains;
}): string {
const { serviceURL } = explorerAPI;
let apiUrl = typeof serviceURL === 'string' ? serviceURL : (isTestApi ? serviceURL.test : serviceURL.main);
let apiUrl: string;
if (typeof serviceURL === 'string') {
apiUrl = serviceURL;
} else if (typeof serviceURL === 'object') {
const isTestApi = chain ? isTestChain(chain) : false;
apiUrl = isTestApi ? serviceURL.test : serviceURL.main;
} else if (typeof serviceURL === 'function') {
apiUrl = serviceURL(chain);
} else {
throw new Error(`serviceURL is an unexpected type for explorerAPI ${explorerAPI.serviceName}`);
}
apiUrl = apiUrl.replace(transactionIdPlaceholder, transactionId);
apiUrl = appendApiIdentifier(apiUrl, explorerAPI);
return apiUrl;
Expand Down
3 changes: 2 additions & 1 deletion tests/explorers/ethereum/etherscan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as mockEtherscanResponse from '../mocks/mockEtherscanResponse.json';
import { explorerApi } from '../../../src/explorers/ethereum/etherscan';
import * as RequestServices from '../../../src/services/request';
import { TransactionData } from '../../../src/models/transactionData';
import { SupportedChains } from '../../../src/constants/supported-chains';

function getMockEtherscanResponse (): typeof mockEtherscanResponse {
return JSON.parse(JSON.stringify(mockEtherscanResponse));
Expand All @@ -24,7 +25,7 @@ describe('Etherscan Explorer test suite', function () {
time: new Date('2019-06-02T08:38:26.000Z')
};

const res = await explorerApi.parsingFunction({ jsonResponse: mockResponse });
const res = await explorerApi.parsingFunction({ jsonResponse: mockResponse, chain: SupportedChains.Ethropst });
expect(res).toEqual(assertionTransactionData);
});

Expand Down
16 changes: 16 additions & 0 deletions tests/lookForTx/lookForTx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,22 @@ describe('getExplorersByChain test suite', function () {
});
});

describe('given the chain is Ethereum goerli', function () {
it('should use the ethereum specific explorers', function () {
const selectedSelectors = getExplorersByChain(SupportedChains.Ethgoerli, explorers.getDefaultExplorers());
// because they are wrapped, we don't necessarily have the deep nature of the result, so we use a weak test to ensure
expect(selectedSelectors.length).toBe(2);
});
});

describe('given the chain is Ethereum sepolia', function () {
it('should use the ethereum specific explorers', function () {
const selectedSelectors = getExplorersByChain(SupportedChains.Ethsepolia, explorers.getDefaultExplorers());
// because they are wrapped, we don't necessarily have the deep nature of the result, so we use a weak test to ensure
expect(selectedSelectors.length).toBe(2);
});
});

describe('given the chain is Bitcoin mainnet', function () {
it('should use the bitcoin specific explorers', function () {
const selectedSelectors = getExplorersByChain(SupportedChains.Bitcoin, explorers.getDefaultExplorers());
Expand Down
85 changes: 81 additions & 4 deletions tests/services/transaction-apis.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { buildTransactionServiceUrl } from '../../src/services/transaction-apis';
import { explorerApi as Blockcypher } from '../../src/explorers/bitcoin/blockcypher';
import { explorerApi as Etherscan } from '../../src/explorers/ethereum/etherscan';
import { SupportedChains } from '../../src/constants/supported-chains';

describe('Transaction APIs test suite', function () {
let fixtureApi;
Expand All @@ -12,7 +13,7 @@ describe('Transaction APIs test suite', function () {
fixtureApi = Blockcypher;
});

describe('given isTestApi is set to false', function () {
describe('given chain is set to null', function () {
it('should return the mainnet address with the transaction ID', function () {
expect(buildTransactionServiceUrl({
explorerAPI: fixtureApi,
Expand All @@ -21,22 +22,98 @@ describe('Transaction APIs test suite', function () {
});
});

describe('given isTestApi is set to true', function () {
describe('given chain is set to the testnet', function () {
it('should return the testnet address with the transaction ID', function () {
expect(buildTransactionServiceUrl({
explorerAPI: fixtureApi,
transactionId: fixtureTransactionId,
isTestApi: true
chain: SupportedChains.Testnet
})).toEqual(`https://api.blockcypher.com/v1/btc/test3/txs/${fixtureTransactionId}?limit=500`);
});
});
});

describe('handling Etherscan APIs', function () {
beforeEach(function () {
fixtureApi = Etherscan;
});

describe('given chain is set to null', function () {
it('should return the mainnet address with the transaction ID', function () {
expect(buildTransactionServiceUrl({
explorerAPI: fixtureApi,
transactionId: fixtureTransactionId
})).toEqual(`https://api.etherscan.io/api?module=proxy&action=eth_getTransactionByHash&txhash=${fixtureTransactionId}`);
});
});

describe('given chain is set to the mainnet', function () {
it('should return the mainnet address with the transaction ID', function () {
expect(buildTransactionServiceUrl({
explorerAPI: fixtureApi,
transactionId: fixtureTransactionId,
chain: SupportedChains.Ethmain
})).toEqual(`https://api.etherscan.io/api?module=proxy&action=eth_getTransactionByHash&txhash=${fixtureTransactionId}`);
});
});

describe('given chain is set to the ropsten', function () {
it('should return the ropsten address with the transaction ID', function () {
expect(buildTransactionServiceUrl({
explorerAPI: fixtureApi,
transactionId: fixtureTransactionId,
chain: SupportedChains.Ethropst
})).toEqual(`https://api-ropsten.etherscan.io/api?module=proxy&action=eth_getTransactionByHash&txhash=${fixtureTransactionId}`);
});
});

describe('given chain is set to the rinkeby', function () {
it('should return the rinkeby address with the transaction ID', function () {
expect(buildTransactionServiceUrl({
explorerAPI: fixtureApi,
transactionId: fixtureTransactionId,
chain: SupportedChains.Ethrinkeby
})).toEqual(`https://api-rinkeby.etherscan.io/api?module=proxy&action=eth_getTransactionByHash&txhash=${fixtureTransactionId}`);
});
});

describe('given chain is set to the goerli', function () {
it('should return the goerli address with the transaction ID', function () {
expect(buildTransactionServiceUrl({
explorerAPI: fixtureApi,
transactionId: fixtureTransactionId,
chain: SupportedChains.Ethgoerli
})).toEqual(`https://api-goerli.etherscan.io/api?module=proxy&action=eth_getTransactionByHash&txhash=${fixtureTransactionId}`);
});
});

describe('given chain is set to the sepolia', function () {
it('should return the sepolia address with the transaction ID', function () {
expect(buildTransactionServiceUrl({
explorerAPI: fixtureApi,
transactionId: fixtureTransactionId,
chain: SupportedChains.Ethsepolia
})).toEqual(`https://api-sepolia.etherscan.io/api?module=proxy&action=eth_getTransactionByHash&txhash=${fixtureTransactionId}`);
});
});

describe('and the serviceURL is not set', function () {
it('should throw', function () {
expect(() => {
buildTransactionServiceUrl({
explorerAPI: JSON.parse(JSON.stringify(Etherscan))
});
}).toThrow('serviceURL is an unexpected type for explorerAPI etherscan');
});
});
});

describe('given it is called with an API token', function () {
const fixtureAPIToken = 'a-test-api-token';

beforeEach(function () {
fixtureApi = JSON.parse(JSON.stringify(Etherscan));
fixtureApi.serviceURL = Etherscan.serviceURL;
fixtureApi.key = fixtureAPIToken;
fixtureApi.keyPropertyName = 'apikey';
});
Expand All @@ -47,7 +124,7 @@ describe('Transaction APIs test suite', function () {
explorerAPI: fixtureApi,
transactionId: fixtureTransactionId
});
const expectedOutput = `https://api.etherscan.io/api?module=proxy&action=eth_getTransactionByHash&txhash=fixture-transaction-id&apikey=${fixtureAPIToken}`;
const expectedOutput = `https://api.etherscan.io/api?module=proxy&action=eth_getTransactionByHash&txhash=${fixtureTransactionId}&apikey=${fixtureAPIToken}`;
expect(output).toBe(expectedOutput);
});
});
Expand Down

0 comments on commit f469fb1

Please sign in to comment.