diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..86fa021
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,28 @@
+version: 2.1
+executors:
+ cypress-14-19-0:
+ docker:
+ - image: "cypress/base:14.19.0"
+orbs:
+ cypress: cypress-io/cypress@1
+workflows:
+ build:
+ jobs:
+ # first get the source code and install npm dependencies
+ - cypress/install:
+ executor: cypress-14-19-0
+ # run a custom app build step
+ install-command: 'yarn install --frozen-lockfile'
+ build: 'yarn build'
+ - cypress/run:
+ # make sure app has been installed and built
+ # before running tests across multiple machines
+ # this avoids installing same dependencies 10 times
+ executor: cypress-14-19-0
+ requires:
+ - cypress/install
+ record: false # record results on Cypress Dashboard
+ parallel: true # split all specs across machines
+ parallelism: 4 # use 4 CircleCI machines to finish quickly
+ group: 'all tests' # name this group "all tests" on the dashboard
+ start: 'yarn start' # start server before running tests
\ No newline at end of file
diff --git a/.eslintrc.json b/.eslintrc.json
index c38f66a..e9665de 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -5,5 +5,12 @@
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
"prettier"
- ]
+ ],
+ "rules": {
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-empty-function": "off",
+ "@typescript-eslint/no-namespace": "off",
+ "@typescript-eslint/ban-types": "off",
+ "jsx-a11y/anchor-is-valid": "off"
+ }
}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 42b1950..0b5e5a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+cypress/downloads/
+cypress/screenshots/
+
.idea
/src/types/contracts
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..b8930b5
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+@fortawesome:registry=https://npm.fontawesome.com/
+//npm.fontawesome.com/:_authToken=6326A007-3153-4B54-BFF5-8604C0BFF663
\ No newline at end of file
diff --git a/cypress.config.ts b/cypress.config.ts
new file mode 100644
index 0000000..114f99b
--- /dev/null
+++ b/cypress.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from 'cypress'
+
+export default defineConfig({
+ e2e: {
+ baseUrl: 'http://localhost:3000',
+ video: false,
+ defaultCommandTimeout: 10000,
+ viewportWidth: 1366,
+ viewportHeight: 768,
+ },
+})
diff --git a/cypress/e2e/01-wallet.cy.ts b/cypress/e2e/01-wallet.cy.ts
new file mode 100644
index 0000000..0c71a69
--- /dev/null
+++ b/cypress/e2e/01-wallet.cy.ts
@@ -0,0 +1,12 @@
+import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../utils/data';
+
+describe('Wallet', () => {
+ beforeEach(() => {
+ cy.setupWeb3Bridge();
+ });
+
+ it('eager connects wallet', () => {
+ cy.visit('/');
+ cy.get('[data-testid=wallet-connect]').contains(TEST_ADDRESS_NEVER_USE_SHORTENED);
+ });
+});
diff --git a/cypress/e2e/category.cy.ts b/cypress/e2e/category.cy.ts
new file mode 100644
index 0000000..36f632b
--- /dev/null
+++ b/cypress/e2e/category.cy.ts
@@ -0,0 +1,41 @@
+import RoutePath, { getRoute, RouteParam } from '../../src/routes';
+import { ArenaHandler } from '../utils/ethbridge/abihandlers/Arena';
+import { SupportedChainId } from '../../src/constants/chains';
+import { ARENA_ADDRESS, MULTICALL2_ADDRESS } from '../../src/constants/addresses';
+import { BaseMulticallHandler } from '../utils/ethbridge/abihandlers/Multicall';
+import { IPFS_SERVER_URL, songMeta } from '../utils/data';
+
+describe('Category', () => {
+ it('loads songs', () => {
+ cy.intercept(
+ {
+ url: `${IPFS_SERVER_URL}**`,
+ },
+ {
+ statusCode: 404,
+ },
+ );
+ cy.intercept(
+ {
+ url: `${IPFS_SERVER_URL}/choice/2.json`,
+ },
+ {
+ body: songMeta,
+ },
+ );
+ const topicId = 0;
+ cy.setupWeb3Bridge();
+ cy.setAbiHandler(ARENA_ADDRESS[SupportedChainId.GOERLI], new ArenaHandler());
+ cy.setAbiHandler(MULTICALL2_ADDRESS[SupportedChainId.GOERLI], new BaseMulticallHandler());
+
+ cy.visit(
+ getRoute(RoutePath.CATEGORY, {
+ [RouteParam.CATEGORY_ID]: String(topicId),
+ }),
+ );
+ cy.get('[data-testid=category-list-item-0]').should('exist');
+ cy.get('[data-testid=category-list-item-1]').should('exist');
+ cy.get('[data-testid=category-list-item-0-meta]').should('not.exist');
+ cy.get('[data-testid=category-list-item-1-meta]').should('exist');
+ });
+});
diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json
new file mode 100644
index 0000000..02e4254
--- /dev/null
+++ b/cypress/fixtures/example.json
@@ -0,0 +1,5 @@
+{
+ "name": "Using fixtures to represent data",
+ "email": "hello@cypress.io",
+ "body": "Fixtures are a great way to mock data for responses to routes"
+}
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
new file mode 100644
index 0000000..16b78b0
--- /dev/null
+++ b/cypress/support/commands.js
@@ -0,0 +1,56 @@
+// ***********************************************
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+
+/* eslint-disable no-undef */
+
+import { getCustomizedBridge } from '../utils/ethbridge/CustomizedBridge';
+
+Cypress.Commands.add('shouldBeCalled', (alias, timesCalled) => {
+ expect(
+ cy.state('requests').filter((call) => call.alias === alias),
+ `${alias} should have been called ${timesCalled} times`,
+ ).to.have.length(timesCalled);
+});
+
+// https://github.com/cypress-io/cypress/issues/2752#issuecomment-1039285381
+Cypress.on('window:before:load', (win) => {
+ let copyText;
+
+ if (!win.navigator.clipboard) {
+ win.navigator.clipboard = {
+ __proto__: {},
+ };
+ }
+
+ win.navigator.clipboard.__proto__.writeText = (text) => (copyText = text);
+ win.navigator.clipboard.__proto__.readText = () => copyText;
+});
+
+Cypress.Commands.add('setupWeb3Bridge', () => {
+ const web3Bridge = getCustomizedBridge()
+ cy.wrap(web3Bridge).as('web3Bridge')
+ cy.on('window:before:load', (win) => {
+ win.ethereum = web3Bridge;
+ });
+});
+
+Cypress.Commands.add('setAbiHandler', (address, abiHandler) => {
+ cy.get('@web3Bridge').then(web3Bridge => {
+ web3Bridge.setHandler(address, abiHandler)
+ });
+});
+
+beforeEach(() => {
+ cy.on('window:before:load', (win) => {
+ cy.spy(win.console, 'error').as('spyWinConsoleError');
+ cy.spy(win.console, 'warn').as('spyWinConsoleWarn');
+ });
+});
+
+afterEach(() => {
+ cy.get('@spyWinConsoleError').should('have.callCount', 0);
+ cy.get('@spyWinConsoleWarn').should('have.callCount', 0);
+});
diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts
new file mode 100644
index 0000000..698b01a
--- /dev/null
+++ b/cypress/support/commands.ts
@@ -0,0 +1,37 @@
+///
+// ***********************************************
+// This example commands.ts shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add('login', (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This will overwrite an existing command --
+// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
+//
+// declare global {
+// namespace Cypress {
+// interface Chainable {
+// login(email: string, password: string): Chainable
+// drag(subject: string, options?: Partial): Chainable
+// dismiss(subject: string, options?: Partial): Chainable
+// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable
+// }
+// }
+// }
\ No newline at end of file
diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts
new file mode 100644
index 0000000..f80f74f
--- /dev/null
+++ b/cypress/support/e2e.ts
@@ -0,0 +1,20 @@
+// ***********************************************************
+// This example support/e2e.ts is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './commands'
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')
\ No newline at end of file
diff --git a/cypress/support/index.ts b/cypress/support/index.ts
new file mode 100644
index 0000000..2feaea7
--- /dev/null
+++ b/cypress/support/index.ts
@@ -0,0 +1,24 @@
+// ***********************************************************
+// This file is processed and loaded automatically before your test files.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.ts using ES2015 syntax:
+import './commands';
+import { AbiHandler } from '../utils/ethbridge/AbiHandler';
+
+declare global {
+ namespace Cypress {
+ interface Chainable {
+ /**
+ * Custom command to select DOM element by data-cy attribute.
+ * @example cy.dataCy('greeting')
+ */
+ setupWeb3Bridge(): void;
+
+ setAbiHandler(address: string, handler: AbiHandler): void;
+ }
+ }
+}
diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json
new file mode 100644
index 0000000..a521526
--- /dev/null
+++ b/cypress/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "esModuleInterop": true,
+ "resolveJsonModule": true,
+ "strict": true,
+ "baseUrl": "../node_modules",
+ "target": "es5",
+ "lib": [
+ "es5",
+ "dom"
+ ],
+ "types": [
+ "cypress"
+ ],
+ },
+ "include": [
+ "**/*.ts"
+ ]
+}
diff --git a/cypress/utils/data.ts b/cypress/utils/data.ts
new file mode 100644
index 0000000..b62a9e8
--- /dev/null
+++ b/cypress/utils/data.ts
@@ -0,0 +1,61 @@
+import { shortenAddress } from '../../src/utils';
+import { Wallet } from '@ethersproject/wallet';
+import { ChoiceStruct, TopicStruct } from '../../src/types/contracts/Arena';
+import { SongMeta } from '../../src/types';
+
+export const TEST_PRIVATE_KEY = '0xe580410d7c37d26c6ad1a837bbae46bc27f9066a466fb3a66e770523b4666d19';
+// address of the above key
+export const TEST_ADDRESS_NEVER_USE = new Wallet(TEST_PRIVATE_KEY).address;
+export const TEST_ADDRESS_NEVER_USE_SHORTENED = shortenAddress(TEST_ADDRESS_NEVER_USE);
+
+export const SAMPLE_ERROR_MESSAGE = 'An error occurred';
+
+export const IPFS_SERVER_URL = 'https://some.ipfs.server';
+
+export const topics: {
+ [topicId: number]: TopicStruct;
+} = {
+ 0: {
+ cycleDuration: 2,
+ startBlock: 0,
+ sharePerCyclePercentage: 10000,
+ prevContributorsFeePercentage: 1200,
+ topicFeePercentage: 500,
+ maxChoiceFeePercentage: 2500,
+ relativeSupportThreshold: 0,
+ fundingPeriod: 0,
+ fundingPercentage: 0,
+ funds: '0x211eEBa0ebe516744614C35572555BdFDD13424d',
+ metaDataUrl: IPFS_SERVER_URL + '/topic/topic1.json',
+ },
+};
+
+export const choices: {
+ [topicId: number]: {
+ [choiceId: number]: ChoiceStruct;
+ };
+} = {
+ 0: {
+ ...Object.assign({}, [
+ ...[1, 2, 3, 4, 5, 6].map((i) => ({
+ description: i + ' Song',
+ funds: '0x211eEBa0ebe516744614C35572555BdFDD13424d',
+ feePercentage: 1000,
+ fundingTarget: '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
+ metaDataUrl: IPFS_SERVER_URL + `/choice/${i}.json`,
+ })),
+ ]),
+ },
+};
+
+export const songMeta: SongMeta = {
+ thumbnail: 'https://bafybeicp7kjqwzzyfuryefv2l5q23exl3dbd6rgmuqzxs3cy6vaa2iekka.ipfs.w3s.link/sample.png',
+ title: 'Dark Days and Beautiful',
+ tags: [
+ { subject: 'Mood', title: 'Confused' },
+ { subject: 'Genre', title: 'Folk' },
+ ],
+ by: 'jonathan.eth',
+ date: 'June 9, 2022',
+ opensea: 'somelink',
+};
diff --git a/cypress/utils/ethbridge/AbiHandler.ts b/cypress/utils/ethbridge/AbiHandler.ts
new file mode 100644
index 0000000..2f09f61
--- /dev/null
+++ b/cypress/utils/ethbridge/AbiHandler.ts
@@ -0,0 +1,27 @@
+import { decodeEthCall, encodeEthResult } from './abiutils';
+import { CustomizedBridgeContext } from './CustomizedBridge';
+
+export class AbiHandler {
+ abi = {};
+
+ methods: { [name: string]: (...args: any[]) => any } = {};
+
+ async handleCall(context: CustomizedBridgeContext, data: string, setResult?: (arg0: string) => void) {
+ const decoded = decodeEthCall(this.abi, data);
+ if (decoded.method === 'multicall') {
+ const [deadline, [data]] = decoded.inputs;
+ await this.handleCall(context, data, setResult);
+ return;
+ }
+ const method = this.methods[decoded.method];
+ if (method) {
+ const res = await method(context, decoded.inputs);
+ setResult?.(encodeEthResult(this.abi, decoded.method, res));
+ }
+ }
+
+ async handleTransaction(context: CustomizedBridgeContext, data: string, setResult: (arg0: string) => void) {
+ await this.handleCall(context, data);
+ setResult(context.getFakeTransactionHash());
+ }
+}
diff --git a/cypress/utils/ethbridge/CustomizedBridge.ts b/cypress/utils/ethbridge/CustomizedBridge.ts
new file mode 100644
index 0000000..dc1a280
--- /dev/null
+++ b/cypress/utils/ethbridge/CustomizedBridge.ts
@@ -0,0 +1,315 @@
+// ***********************************************
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+
+import { Wallet } from '@ethersproject/wallet';
+import { JsonRpcProvider } from '@ethersproject/providers';
+import { formatChainId } from '../../../src/utils';
+
+import { SAMPLE_ERROR_MESSAGE, TEST_ADDRESS_NEVER_USE, TEST_PRIVATE_KEY } from '../data';
+import { BigNumber } from '@ethersproject/bignumber';
+import {
+ fakeBlockByNumberResponse,
+ fakeTransactionByHashResponse,
+ fakeTransactionReceipt,
+ latestBlock,
+} from '../fake_tx_data';
+import { NETWORK_URLS, SupportedChainId } from '../../../src/constants/chains';
+import { keccak256 } from './abiutils';
+import { Eip1193Bridge } from '@ethersproject/experimental';
+import { GENERIC_ERROR_CODE, GENERIC_ERROR_CODE_2, USER_DENIED_REQUEST_ERROR_CODE } from '../../../src/utils/web3';
+import { AbiHandler } from './AbiHandler';
+
+function isTheSameAddress(address1: string, address2: string) {
+ return address1.toLowerCase() === address2.toLowerCase();
+}
+
+function sleep(ms: number) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+export class CustomizedBridgeContext {
+ chainId = formatChainId(String(SupportedChainId.GOERLI));
+ supportedChainIds: string[] = [formatChainId(String(SupportedChainId.GOERLI))];
+
+ latestBlockNumber = 1;
+ fakeTransactionIndex = 0;
+ handlers: { [key: string]: AbiHandler } = {};
+
+ getLatestBlock() {
+ this.latestBlockNumber++;
+ return Object.assign(latestBlock, {
+ number: this.latestBlockNumber,
+ });
+ }
+
+ getFakeTransactionHash() {
+ return keccak256([this.fakeTransactionIndex++]);
+ }
+
+ setHandler(address: string, handler: AbiHandler) {
+ this.handlers[address] = handler;
+ }
+}
+
+export enum EventHandlerKey {
+ CHAIN_CHANGED = 'chainChanged',
+ ACCOUNTS_CHANGED = 'accountsChanged',
+ CLOSE = 'close',
+ NETWORK_CHANGED = 'networkChanged',
+}
+
+export enum TransactionStatus {
+ SUCCESS = 'success',
+ INSUFFICIENT_FUND = 'insufficientFund',
+ USER_DENIED = 'rejected',
+ FAILED = 'failed',
+}
+
+const insufficientFundTransactionError = {
+ code: GENERIC_ERROR_CODE_2,
+ message: `err: insufficient funds for gas * price + value: address ${TEST_ADDRESS_NEVER_USE} have 2000 want 10000000000000000000000000 (supplied gas 14995852)`,
+};
+const insufficientFundGasEstimateError = {
+ code: GENERIC_ERROR_CODE,
+ message: 'Internal JSON-RPC error.',
+ data: {
+ code: GENERIC_ERROR_CODE_2,
+ message: `insufficient funds for transfer: address ${TEST_ADDRESS_NEVER_USE}`,
+ },
+};
+const userDeniedTransactionError = {
+ code: USER_DENIED_REQUEST_ERROR_CODE,
+ message: 'MetaMask Tx Signature: User denied transaction signature.',
+ stack:
+ '{\n "code": 4001,\n "message": "MetaMask Tx Signature: User denied transaction signature.",\n "stack": "Error: MetaMask Tx Signature: User denied transaction signature.\\n...',
+};
+
+function enumKeys(obj: O): K[] {
+ return Object.keys(obj).filter((k) => Number.isNaN(+k)) as K[];
+}
+
+export class CustomizedBridge extends Eip1193Bridge {
+ context = new CustomizedBridgeContext();
+
+ eventListeners = {
+ [EventHandlerKey.CHAIN_CHANGED]: function handleChainChanged(chainId: string | number) {},
+ [EventHandlerKey.ACCOUNTS_CHANGED]: function handleAccountsChanged(accounts: string[]) {},
+ [EventHandlerKey.CLOSE]: function handleClose(code: number, reason: string) {},
+ [EventHandlerKey.NETWORK_CHANGED]: function handleNetworkChanged(networkId: string | number) {},
+ };
+
+ transactionStatus = TransactionStatus.SUCCESS;
+ transactionWaitTime = 0;
+
+ setTransactionStatus(status: TransactionStatus) {
+ this.transactionStatus = status;
+ }
+
+ setTransactionWaitTime(waitTime: number) {
+ this.transactionWaitTime = waitTime;
+ }
+
+ on(eventName: string | symbol, listener: (...args: any[]) => void) {
+ let found = false;
+ for (const k of enumKeys(EventHandlerKey)) {
+ if (eventName === EventHandlerKey[k]) {
+ found = true;
+ this.eventListeners[eventName] = listener;
+ break;
+ }
+ }
+ if (!found) {
+ console.error(`Bridge: Unknown Event Key ${String(eventName)}`);
+ throw Error(`Bridge: Unknown Event Key ${String(eventName)}`);
+ }
+ return this;
+ }
+
+ switchEthereumChainSpy(chainId: string) {}
+
+ addEthereumChainSpy(chainId: string) {}
+
+ setHandler(address: string, handler: AbiHandler) {
+ this.context.setHandler(address, handler);
+ }
+
+ async sendAsync(...args: any[]) {
+ console.debug('sendAsync called', ...args);
+ return this.send(...args);
+ }
+
+ getSendArgs(args: any[]) {
+ console.debug('send called', ...args);
+ const isCallbackForm = typeof args[0] === 'object' && typeof args[1] === 'function';
+ let callback;
+ let method;
+ let params;
+ if (isCallbackForm) {
+ callback = args[1];
+ method = args[0].method;
+ params = args[0].params;
+ } else {
+ method = args[0];
+ params = args[1];
+ }
+ return {
+ isCallbackForm,
+ callback,
+ method,
+ params,
+ };
+ }
+
+ async send(...args: any[]) {
+ const { isCallbackForm, callback, method, params } = this.getSendArgs(args);
+ let result = null;
+ let resultIsSet = false;
+ let runError = null;
+ let errorIsSet = false;
+
+ function setResult(r: any) {
+ result = r;
+ resultIsSet = true;
+ }
+
+ function setError(e: any) {
+ runError = e;
+ errorIsSet = true;
+ }
+
+ if (method === 'eth_requestAccounts' || method === 'eth_accounts') {
+ setResult([TEST_ADDRESS_NEVER_USE]);
+ }
+ if (method === 'wallet_switchEthereumChain') {
+ this.switchEthereumChainSpy(params[0].chainId);
+ if (this.context.supportedChainIds.includes(params[0].chainId)) {
+ this.context.chainId = params[0].chainId;
+ this.eventListeners[EventHandlerKey.NETWORK_CHANGED](params[0].chainId);
+ this.eventListeners[EventHandlerKey.CHAIN_CHANGED](params[0].chainId);
+ setResult(null);
+ } else {
+ const chainId = params[0].chainId;
+ const error = {
+ code: 4902, // To-be-standardized "unrecognized chain ID" error
+ message: `Unrecognized chain ID "${chainId}". Try adding the chain using wallet_addEthereumChain first.`,
+ };
+ setError(error);
+ }
+ }
+ if (method === 'wallet_addEthereumChain') {
+ this.addEthereumChainSpy(params[0].chainId);
+ this.context.supportedChainIds.push(params[0].chainId);
+ setResult(null);
+ }
+ if (method === 'eth_chainId') {
+ setResult(formatChainId(String(this.context.chainId)));
+ }
+ if (method === 'eth_getBlockByNumber') {
+ if (params[0] === 'latest') {
+ setResult(this.context.getLatestBlock());
+ } else {
+ const [blockNumber, returnFullHashes] = params;
+ setResult(
+ Object.assign(fakeBlockByNumberResponse, {
+ number: BigNumber.from(blockNumber).toNumber(),
+ }),
+ );
+ }
+ }
+ if (method === 'eth_getTransactionByHash') {
+ const [transactionHash] = params;
+ setResult(
+ Object.assign(fakeTransactionByHashResponse, {
+ hash: transactionHash,
+ }),
+ );
+ }
+ if (method === 'eth_getTransactionReceipt') {
+ const [transactionHash] = params;
+ const latestBlock = this.context.getLatestBlock();
+ const resultLocal = Object.assign(fakeTransactionReceipt, {
+ transactionHash,
+ blockHash: latestBlock.hash,
+ blockNumber: latestBlock.number,
+ logs: fakeTransactionReceipt.logs.map((log) => Object.assign(log, transactionHash)),
+ });
+ setResult(resultLocal);
+ }
+ if (method === 'eth_blockNumber') {
+ setResult(this.context.getLatestBlock().number);
+ }
+ if (method === 'eth_call') {
+ for (const contractAddress in this.context.handlers) {
+ if (isTheSameAddress(contractAddress, params[0].to)) {
+ await this.context.handlers[contractAddress].handleCall(this.context, params[0].data, setResult);
+ }
+ }
+ }
+ if (method === 'eth_estimateGas') {
+ if (this.transactionStatus === TransactionStatus.INSUFFICIENT_FUND) {
+ setError(insufficientFundGasEstimateError);
+ } else {
+ setResult('0xba7f');
+ }
+ }
+ if (method === 'eth_sendTransaction') {
+ for (const contractAddress in this.context.handlers) {
+ if (isTheSameAddress(contractAddress, params[0].to)) {
+ await this.context.handlers[contractAddress].handleTransaction(this.context, params[0].data, setResult);
+ }
+ }
+ if (this.transactionStatus === TransactionStatus.SUCCESS) {
+ setResult(this.context.getFakeTransactionHash());
+ } else if (this.transactionStatus === TransactionStatus.USER_DENIED) {
+ setError(userDeniedTransactionError);
+ } else if (this.transactionStatus === TransactionStatus.INSUFFICIENT_FUND) {
+ setError(insufficientFundTransactionError);
+ } else {
+ setError({ error: { message: SAMPLE_ERROR_MESSAGE } });
+ }
+ if (this.transactionWaitTime) {
+ await sleep(this.transactionWaitTime);
+ }
+ }
+
+ if (errorIsSet) {
+ if (isCallbackForm) {
+ callback(runError, null);
+ } else {
+ throw runError;
+ }
+ } else if (resultIsSet) {
+ if (isCallbackForm) {
+ callback(null, { result });
+ } else {
+ return result;
+ }
+ } else {
+ try {
+ const result = await super.send(method, params);
+ if (isCallbackForm) {
+ callback(null, { result });
+ } else {
+ return result;
+ }
+ } catch (error) {
+ if (isCallbackForm) {
+ callback(error, null);
+ } else {
+ throw error;
+ }
+ }
+ }
+ }
+}
+
+const defaultChainId = SupportedChainId.GOERLI;
+export const provider = new JsonRpcProvider(NETWORK_URLS[defaultChainId], defaultChainId);
+export const signer = new Wallet(TEST_PRIVATE_KEY, provider);
+
+export function getCustomizedBridge() {
+ return new CustomizedBridge(signer, provider);
+}
diff --git a/cypress/utils/ethbridge/abihandlers/Arena.ts b/cypress/utils/ethbridge/abihandlers/Arena.ts
new file mode 100644
index 0000000..9548090
--- /dev/null
+++ b/cypress/utils/ethbridge/abihandlers/Arena.ts
@@ -0,0 +1,26 @@
+import { BigNumber } from '@ethersproject/bignumber/lib.esm';
+import ArenaJson from '@attentionstreams/contracts/artifacts/contracts/main/Arena.sol/Arena.json';
+
+import { AbiHandler } from '../AbiHandler';
+import { CustomizedBridgeContext } from '../CustomizedBridge';
+import { choices } from '../../data';
+
+function isTheSameAddress(address1: string, address2: string) {
+ return address1.toLowerCase() === address2.toLowerCase();
+}
+
+export class ArenaHandler extends AbiHandler {
+ abi = ArenaJson.abi;
+
+ methods = {
+ async nextChoiceId(context: CustomizedBridgeContext, decodedInput: [BigNumber]) {
+ const [topicId] = decodedInput;
+ return [Object.values(choices[topicId.toNumber()]).length];
+ },
+
+ async topicChoices(context: CustomizedBridgeContext, decodedInput: [BigNumber, BigNumber]) {
+ const [topicId, choiceId] = decodedInput;
+ return [Object.values(choices[topicId.toNumber()][choiceId.toNumber()])];
+ },
+ };
+}
diff --git a/cypress/utils/ethbridge/abihandlers/Multicall.ts b/cypress/utils/ethbridge/abihandlers/Multicall.ts
new file mode 100644
index 0000000..eb0547a
--- /dev/null
+++ b/cypress/utils/ethbridge/abihandlers/Multicall.ts
@@ -0,0 +1,29 @@
+import { FAKE_BLOCK_HASH } from '../../fake_tx_data';
+import MULTICALL2_ABI from '../../../../src/abis/MULTICALL2.json';
+import { CustomizedBridgeContext } from '../CustomizedBridge';
+import { AbiHandler } from '../AbiHandler';
+
+function isTheSameAddress(address1: string, address2: string) {
+ return address1.toLowerCase() === address2.toLowerCase();
+}
+
+export class BaseMulticallHandler extends AbiHandler {
+ abi = MULTICALL2_ABI;
+ methods = {
+ async tryBlockAndAggregate(context: CustomizedBridgeContext, decodedInput: any[]) {
+ const [_requireSuccess, calls] = decodedInput;
+ const results: any[] = [];
+ for (const call of calls) {
+ const [callAddress, callInput] = call;
+ for (const contractAddress in context.handlers) {
+ if (isTheSameAddress(contractAddress, callAddress)) {
+ await context.handlers[contractAddress].handleCall(context, callInput, (r: string) =>
+ results.push([true, r]),
+ );
+ }
+ }
+ }
+ return [0, FAKE_BLOCK_HASH, results];
+ },
+ };
+}
diff --git a/cypress/utils/ethbridge/abiutils.ts b/cypress/utils/ethbridge/abiutils.ts
new file mode 100644
index 0000000..e3dce6c
--- /dev/null
+++ b/cypress/utils/ethbridge/abiutils.ts
@@ -0,0 +1,26 @@
+import { ethers } from 'ethers';
+import { BigNumber } from '@ethersproject/bignumber/lib.esm';
+import { BytesLike } from '@ethersproject/bytes';
+
+const InputDataDecoder = require('ethereum-input-data-decoder');
+
+export function keccak256(data: BytesLike): string {
+ return ethers.utils.keccak256(data);
+}
+
+export function encodeEthResult(abi: any, funcName: string, result: (BigNumber | string | number)[]) {
+ const iface = new ethers.utils.Interface(abi);
+ return iface.encodeFunctionResult(funcName, result);
+}
+
+export type DecodedCall = {
+ inputs: any[];
+ method: string;
+ names: any[];
+ types: any[];
+};
+
+export function decodeEthCall(abi: any, input: any): DecodedCall {
+ const decoder = new InputDataDecoder(abi);
+ return decoder.decodeData(input);
+}
diff --git a/cypress/utils/fake_tx_data.ts b/cypress/utils/fake_tx_data.ts
new file mode 100644
index 0000000..75f2422
--- /dev/null
+++ b/cypress/utils/fake_tx_data.ts
@@ -0,0 +1,602 @@
+export const FAKE_BLOCK_HASH = '0xeed54f1dd0adad878c624694038ac3c70631ec800b150b9caf9eedd4aea3df95'
+
+export const fakeBlockByNumberResponse = {
+ hash: '0x0001ab190000014f9cbe48dd3164830a173dd94b042aaba7b680631f22df6598',
+ parentHash: '0x0001ab1900000144ea26c0cb4a150aec7d012c9eb3f975073e993aa18d6d20f2',
+ number: 37678828,
+ timestamp: 1651832123,
+ nonce: '0x0000000000000000',
+ difficulty: 0,
+ gasLimit: {
+ type: 'BigNumber',
+ hex: '0xffffffffffff',
+ },
+ gasUsed: {
+ type: 'BigNumber',
+ hex: '0x3da9cd',
+ },
+ miner: '0x0000000000000000000000000000000000000000',
+ extraData: '0x',
+ transactions: [
+ '0x9a5ea960b2da1b35ac3e04be63cfcc39b4380d67c117246115b17b25e1d2da0a',
+ '0xedbf93e3853419d01b177c2ad0bf9782cc9b1edd4c53fcb79da6a26075eea0fa',
+ '0xa273709cf7d68fef46d0f925e1c72287b9e8d639e6ccfb8a7b0363bfb08e9b49',
+ '0x34afd383c5bf100d754c3494eb2a4f3ebdcf1c41965e5c50b831ad2cd7886f52',
+ '0x9cd38283852fd8da61da18d8a6caa8eff07b8fe4b82afe81c3be85c6d9471eb4',
+ '0xd8901b2981acfdd3fab18e8e7c6f03b805a295ff37a1df8159bce41f7ea27a31',
+ '0x38147a5dc5bfdbe9bbc959a6a94c635b397e3a17962187ff5b5134f8fa175136',
+ '0x0f36fcd334e0a6cd5567c3cb1912dc9d03d7e28d2465c3c7b69431f07a0158d7',
+ '0x288126196a214cd226e0894a19f513dbde71a2702243aec03a9ceaf7e3e600fc',
+ ],
+ baseFeePerGas: {
+ type: 'BigNumber',
+ hex: '0x174876e800',
+ },
+ _difficulty: {
+ type: 'BigNumber',
+ hex: '0x00',
+ },
+}
+
+export const fakeTransactionByHashResponse = {
+ hash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ type: 0,
+ accessList: null,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ blockNumber: 37445406,
+ transactionIndex: 1,
+ confirmations: 233051,
+ from: '0x14C07A5F107a9d15A6C2d0C131DCB3933386503E',
+ gasPrice: {
+ type: 'BigNumber',
+ hex: '0x22ecb25c00',
+ },
+ gasLimit: {
+ type: 'BigNumber',
+ hex: '0x0fe215',
+ },
+ to: '0x2e0b129d3Dd14FcD676695c0A7dDeF9FcC13E74E',
+ value: {
+ type: 'BigNumber',
+ hex: '0x00',
+ },
+ nonce: 14545,
+ data: '0xfdb5a03e',
+ r: '0xd0f28cc47e863b3d3e0bc1a930b9619301e23232d6e4294fb7160c402373daa8',
+ s: '0x737f7b77d2ec262383cc0acf0e21d1abdd8101ed6b110631b45a084475e2b0bd',
+ v: 535,
+ creates: null,
+ chainId: 250,
+}
+
+export const fakeTransactionReceipt = {
+ to: '0x2e0b129d3Dd14FcD676695c0A7dDeF9FcC13E74E',
+ from: '0x14C07A5F107a9d15A6C2d0C131DCB3933386503E',
+ contractAddress: null,
+ transactionIndex: 1,
+ gasUsed: {
+ type: 'BigNumber',
+ hex: '0x0bec85',
+ },
+ logsBloom:
+ '0x00200800000802000000000081000000000100000400020000000000008001000000000000000200008000000000100108000000802000000000800000200000001000000009048000000008000001220000000000800000000100000000010000000000020000000040400000000800000100000008000000000010000000080000000100000002000000000000000000004880040000080000004000000810060000000400000000000100020000000000000000090010000000000000000000000042000280110100000001020000000000000080001000000400000020210010040000000000000000000000002000400000404200000000000000000002',
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ logs: [
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE',
+ topics: [
+ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
+ '0x000000000000000000000000b934be56cfdd87ccd547026b8f5a566efb5a768e',
+ ],
+ data: '0x000000000000000000000000000000000000000000000000001ad4d3925f1ddd',
+ logIndex: 0,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE',
+ topics: [
+ '0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724',
+ '0x000000000000000000000000b934be56cfdd87ccd547026b8f5a566efb5a768e',
+ ],
+ data: '0x000000000000000000000000000000000000000000000a736f763d13a7181def000000000000000000000000000000000000000000000a736f9111e739773bcc',
+ logIndex: 1,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE',
+ topics: [
+ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
+ '0x0000000000000000000000002b2929e785374c651a81a63878ab22742656dcdd',
+ ],
+ data: '0x000000000000000000000000000000000000000000000000010c5043b7b72aaa',
+ logIndex: 2,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE',
+ topics: [
+ '0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724',
+ '0x0000000000000000000000002b2929e785374c651a81a63878ab22742656dcdd',
+ ],
+ data: '0x0000000000000000000000000000000000000000000019a674d497f5a367f3500000000000000000000000000000000000000000000019a675e0e8395b1f1dfa',
+ logIndex: 3,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE',
+ topics: [
+ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
+ '0x0000000000000000000000002b2929e785374c651a81a63878ab22742656dcdd',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ ],
+ data: '0x00000000000000000000000000000000000000000000000015cc78c135d16d3a',
+ logIndex: 4,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE',
+ topics: [
+ '0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724',
+ '0x0000000000000000000000002b2929e785374c651a81a63878ab22742656dcdd',
+ ],
+ data: '0x0000000000000000000000000000000000000000000019a675e0e8395b1f1dfa0000000000000000000000000000000000000000000019a660146f78254db0c0',
+ logIndex: 5,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE',
+ topics: [
+ '0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ ],
+ data: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015cc78c135d16d3a',
+ logIndex: 6,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x648a7452DA25B4fB4BDB79bADf374a8f8a5ea2b5',
+ topics: [
+ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
+ '0x0000000000000000000000002b2929e785374c651a81a63878ab22742656dcdd',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ ],
+ data: '0x0000000000000000000000000000000000000000000000000000000000000000',
+ logIndex: 7,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x2b2929E785374c651a81A63878Ab22742656DcDd',
+ topics: [
+ '0xf279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ '0x000000000000000000000000000000000000000000000000000000000000003a',
+ ],
+ data: '0x0000000000000000000000000000000000000000000000000000000000000000',
+ logIndex: 8,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE',
+ topics: [
+ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ '0x00000000000000000000000014c07a5f107a9d15a6c2d0c131dcb3933386503e',
+ ],
+ data: '0x0000000000000000000000000000000000000000000000000037ce01ee9e4117',
+ logIndex: 9,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE',
+ topics: [
+ '0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ ],
+ data: '0x00000000000000000000000000000000000000000000000015cc78c135d16d3a0000000000000000000000000000000000000000000000001594aabf47332c23',
+ logIndex: 10,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE',
+ topics: [
+ '0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724',
+ '0x00000000000000000000000014c07a5f107a9d15a6c2d0c131dcb3933386503e',
+ ],
+ data: '0x0000000000000000000000000000000000000000000000002721788b169d10000000000000000000000000000000000000000000000000002759468d053b5117',
+ logIndex: 11,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE',
+ topics: [
+ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ '0x000000000000000000000000ec7178f4c41f346b2721907f5cf7628e388a7a58',
+ ],
+ data: '0x0000000000000000000000000000000000000000000000001594aabf47332c23',
+ logIndex: 12,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE',
+ topics: [
+ '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ '0x000000000000000000000000f491e7b69e4244ad4002bc14e878a34207e38c29',
+ ],
+ data: '0xffffffffffffffffffffffffffffffffffffffffffffff4ae0c641f53545ad0c',
+ logIndex: 13,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE',
+ topics: [
+ '0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ ],
+ data: '0x0000000000000000000000000000000000000000000000001594aabf47332c230000000000000000000000000000000000000000000000000000000000000000',
+ logIndex: 14,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE',
+ topics: [
+ '0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724',
+ '0x000000000000000000000000ec7178f4c41f346b2721907f5cf7628e388a7a58',
+ ],
+ data: '0x0000000000000000000000000000000000000000000121c62e34fd556bd356730000000000000000000000000000000000000000000121c643c9a814b3068296',
+ logIndex: 15,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83',
+ topics: [
+ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
+ '0x000000000000000000000000ec7178f4c41f346b2721907f5cf7628e388a7a58',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ ],
+ data: '0x000000000000000000000000000000000000000000000000c340df61ccc04c9f',
+ logIndex: 16,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0xEc7178F4C41f346b2721907F5cF7628E388A7a58',
+ topics: ['0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1'],
+ data: '0x0000000000000000000000000000000000000000000a430252bb8049df6714190000000000000000000000000000000000000000000121c643c9a814b3068296',
+ logIndex: 17,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0xEc7178F4C41f346b2721907F5cF7628E388A7a58',
+ topics: [
+ '0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822',
+ '0x000000000000000000000000f491e7b69e4244ad4002bc14e878a34207e38c29',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ ],
+ data: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001594aabf47332c23000000000000000000000000000000000000000000000000c340df61ccc04c9f0000000000000000000000000000000000000000000000000000000000000000',
+ logIndex: 18,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83',
+ topics: [
+ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ '0x000000000000000000000000648a7452da25b4fb4bdb79badf374a8f8a5ea2b5',
+ ],
+ data: '0x00000000000000000000000000000000000000000000000061b91a175713f5b6',
+ logIndex: 19,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83',
+ topics: [
+ '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ '0x000000000000000000000000f491e7b69e4244ad4002bc14e878a34207e38c29',
+ ],
+ data: '0xfffffffffffffffffffffffffffffffffffffffffffff8d4dcc0b26b09cc6bf1',
+ logIndex: 20,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0xF24Bcf4d1e507740041C9cFd2DddB29585aDCe1e',
+ topics: [
+ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
+ '0x000000000000000000000000648a7452da25b4fb4bdb79badf374a8f8a5ea2b5',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ ],
+ data: '0x00000000000000000000000000000000000000000000000138d2689e559928d9',
+ logIndex: 21,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x648a7452DA25B4fB4BDB79bADf374a8f8a5ea2b5',
+ topics: ['0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1'],
+ data: '0x0000000000000000000000000000000000000000000034f67922d1b9f6c14ad700000000000000000000000000000000000000000000a9dfdefbd32f0f648609',
+ logIndex: 22,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x648a7452DA25B4fB4BDB79bADf374a8f8a5ea2b5',
+ topics: [
+ '0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822',
+ '0x000000000000000000000000f491e7b69e4244ad4002bc14e878a34207e38c29',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ ],
+ data: '0x00000000000000000000000000000000000000000000000061b91a175713f5b60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000138d2689e559928d9',
+ logIndex: 23,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83',
+ topics: [
+ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ '0x000000000000000000000000648a7452da25b4fb4bdb79badf374a8f8a5ea2b5',
+ ],
+ data: '0x0000000000000000000000000000000000000000000000006187c54a75ac56e9',
+ logIndex: 24,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83',
+ topics: [
+ '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ '0x000000000000000000000000f491e7b69e4244ad4002bc14e878a34207e38c29',
+ ],
+ data: '0xfffffffffffffffffffffffffffffffffffffffffffff8d47b38ed2094201508',
+ logIndex: 25,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0xF24Bcf4d1e507740041C9cFd2DddB29585aDCe1e',
+ topics: [
+ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ '0x000000000000000000000000648a7452da25b4fb4bdb79badf374a8f8a5ea2b5',
+ ],
+ data: '0x00000000000000000000000000000000000000000000000138d2689e559928d9',
+ logIndex: 26,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0xF24Bcf4d1e507740041C9cFd2DddB29585aDCe1e',
+ topics: [
+ '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ '0x000000000000000000000000f491e7b69e4244ad4002bc14e878a34207e38c29',
+ ],
+ data: '0xfffffffffffffffffffffffffffffffffffffffffffff82d3b4d31103332b24d',
+ logIndex: 27,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x648a7452DA25B4fB4BDB79bADf374a8f8a5ea2b5',
+ topics: [
+ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
+ '0x0000000000000000000000003b3fdc40582a957206aed119842f2313de9ee21b',
+ ],
+ data: '0x0000000000000000000000000000000000000000000000000009809d62ed0753',
+ logIndex: 28,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x648a7452DA25B4fB4BDB79bADf374a8f8a5ea2b5',
+ topics: [
+ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ ],
+ data: '0x000000000000000000000000000000000000000000000000942ea785e044b922',
+ logIndex: 29,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x648a7452DA25B4fB4BDB79bADf374a8f8a5ea2b5',
+ topics: ['0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1'],
+ data: '0x0000000000000000000000000000000000000000000034f6daaa97046c6da1c000000000000000000000000000000000000000000000a9e117ce3bcd64fdaee2',
+ logIndex: 30,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x648a7452DA25B4fB4BDB79bADf374a8f8a5ea2b5',
+ topics: [
+ '0x4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f',
+ '0x000000000000000000000000f491e7b69e4244ad4002bc14e878a34207e38c29',
+ ],
+ data: '0x0000000000000000000000000000000000000000000000006187c54a75ac56e900000000000000000000000000000000000000000000000138d2689e559928d9',
+ logIndex: 31,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x648a7452DA25B4fB4BDB79bADf374a8f8a5ea2b5',
+ topics: [
+ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ '0x0000000000000000000000002b2929e785374c651a81a63878ab22742656dcdd',
+ ],
+ data: '0x000000000000000000000000000000000000000000000000942ea785e044b922',
+ logIndex: 32,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x2b2929E785374c651a81A63878Ab22742656DcDd',
+ topics: [
+ '0x90890809c654f11d6e72a28fa60149770a0d11ec6c92319d6ceb2bb0a4ea1a15',
+ '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e',
+ '0x000000000000000000000000000000000000000000000000000000000000003a',
+ ],
+ data: '0x000000000000000000000000000000000000000000000000942ea785e044b922',
+ logIndex: 33,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x2e0b129d3Dd14FcD676695c0A7dDeF9FcC13E74E',
+ topics: [
+ '0xc003f45bc224d116b6d079100d4ab57a5b9633244c47a5a92a176c5b79a85f28',
+ '0x00000000000000000000000014c07a5f107a9d15a6c2d0c131dcb3933386503e',
+ ],
+ data: '0x00000000000000000000000000000000000000000000000015cc78c135d16d3a0000000000000000000000000000000000000000000000000037ce01ee9e4117',
+ logIndex: 34,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ {
+ transactionIndex: 1,
+ blockNumber: 37445406,
+ transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43',
+ address: '0x2e0b129d3Dd14FcD676695c0A7dDeF9FcC13E74E',
+ topics: ['0x8a0df8ef054fae2c3d2d19a7b322e864870cc9fd3cb07fb9526309c596244bf4'],
+ data: '0x000000000000000000000000000000000000000000002aac1b6b749155b73638',
+ logIndex: 35,
+ blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057',
+ },
+ ],
+ blockNumber: 37445406,
+ confirmations: 233328,
+ cumulativeGasUsed: {
+ type: 'BigNumber',
+ hex: '0x0ca7e2',
+ },
+ effectiveGasPrice: {
+ type: 'BigNumber',
+ hex: '0x22ecb25c00',
+ },
+ status: 1,
+ type: 0,
+ byzantium: true,
+}
+
+export const latestBlock = {
+ hash: '0x0000000c000001832042174315212d389e57129df4d9cfef47b5de83e73b7ddb',
+ parentHash: '0x0000000c00000177f0a985c5f59a474b87ba510f05f243310444a69248652a95',
+ number: 1280,
+ timestamp: 1577607303,
+ nonce: '0x0000000000000000',
+ difficulty: 0,
+ gasLimit: {
+ type: 'BigNumber',
+ hex: '0xffffffffffff',
+ },
+ gasUsed: {
+ type: 'BigNumber',
+ hex: '0x5af7',
+ },
+ miner: '0x0000000000000000000000000000000000000000',
+ extraData: '0x',
+ transactions: ['0x18f6f7a00dbb0e3560e3f3955c4521248513ecfeb49b3a4bbee8a15135467ed7'],
+ _difficulty: {
+ type: 'BigNumber',
+ hex: '0x00',
+ },
+}
diff --git a/package.json b/package.json
index be4b0cc..ad7644e 100644
--- a/package.json
+++ b/package.json
@@ -3,16 +3,27 @@
"version": "0.1.0",
"private": true,
"dependencies": {
- "@attentionstreams/contracts": "0.0.4",
- "@emotion/react": "^11.9.0",
- "@emotion/styled": "^11.8.1",
+ "@attentionstreams/contracts": "0.0.7",
+ "@emotion/react": "^11.9.3",
+ "@emotion/styled": "^11.9.3",
"@ethersproject/address": "^5.6.0",
"@ethersproject/constants": "^5.6.0",
"@ethersproject/contracts": "^5.6.0",
"@ethersproject/providers": "^5.6.2",
+ "@fortawesome/fontawesome-svg-core": "^6.1.2",
+ "@fortawesome/free-solid-svg-icons": "^6.1.2",
+ "@fortawesome/pro-duotone-svg-icons": "^6.1.2",
+ "@fortawesome/pro-light-svg-icons": "^6.1.2",
+ "@fortawesome/pro-regular-svg-icons": "^6.1.2",
+ "@fortawesome/pro-solid-svg-icons": "^6.1.2",
+ "@fortawesome/react-fontawesome": "^0.2.0",
+ "@headlessui/react": "^1.6.6",
+ "@mui/base": "^5.0.0-alpha.88",
"@mui/icons-material": "^5.6.0",
+ "@mui/joy": "^5.0.0-alpha.35",
"@mui/material": "^5.6.0",
"@mui/styles": "^5.6.0",
+ "@reduxjs/toolkit": "^1.8.3",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@testing-library/user-event": "^13.2.1",
@@ -23,10 +34,13 @@
"@web3-react/core": "^6.1.9",
"ethers": "^5.6.2",
"jsbi": "^4.2.0",
- "react": "^18.0.0",
- "react-dom": "^18.0.0",
+ "react": "17.0.2",
+ "react-device-detect": "^2.2.2",
+ "react-dom": "17.0.2",
+ "react-redux": "^8.0.2",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.0",
+ "redux-persist": "^6.0.0",
"typescript": "^4.4.2",
"web-vitals": "^2.1.0"
},
@@ -36,7 +50,9 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
- "eject": "react-scripts eject"
+ "eject": "react-scripts eject",
+ "cypress": "start-server-and-test 'yarn start' http://localhost:3000 'cypress open'",
+ "cypress:headless": "yarn build && start-server-and-test 'yarn start' http://localhost:3000 'cypress run'"
},
"browserslist": {
"production": [
@@ -51,9 +67,12 @@
]
},
"devDependencies": {
+ "@ethersproject/experimental": "^5.6.3",
"@typechain/ethers-v5": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^5.18.0",
"@web3-react/injected-connector": "^6.0.7",
+ "autoprefixer": "^10.4.7",
+ "cypress": "^10.4.0",
"eslint": "^8.12.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-better-styled-components": "^1.1.2",
@@ -62,7 +81,13 @@
"eslint-plugin-react-hooks": "^4.4.0",
"eslint-plugin-simple-import-sort": "^7.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
+ "ethereum-input-data-decoder": "^0.4.2",
+ "node-sass": "^7.0.1",
+ "postcss": "^8.4.14",
"prettier": "^2.6.2",
+ "sass": "^1.53.0",
+ "start-server-and-test": "^1.14.0",
+ "tailwindcss": "^3.1.5",
"typechain": "^8.0.0"
}
}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..33ad091
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/public/category-header.png b/public/category-header.png
new file mode 100644
index 0000000..b00fb5c
Binary files /dev/null and b/public/category-header.png differ
diff --git a/public/index.html b/public/index.html
index aa069f2..e1f205a 100644
--- a/public/index.html
+++ b/public/index.html
@@ -15,6 +15,9 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
+
+
+