diff --git a/Makefile b/Makefile index 1cfa9363d..ba5950759 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ git_hook_setup = cp .githooks/pre-commit .git/hooks/pre-commit git_diff_to_master = git diff --name-only --diff-filter=ACMRTUXB origin/master > DIFF -create_test_paybutton_json = echo { \"priceAPIURL\": \"foo\", \"networkBlockchainClients\": { \"ecash\": \"chronik\", \"bitcoincash\": \"chronik\" }, \"networkBlockchainURLs\": { \"ecash\": \"https://xec.paybutton.io\", \"bitcoincash\": \"https://bch.paybutton.io\" }, \"wsBaseURL\": \"http://localhost:5000\", \"apiDomain\": \"http://localhost:3000\" } > paybutton-config.json +create_test_paybutton_json = echo { \"priceAPIURL\": \"foo\", \"networkBlockchainClients\": { \"ecash\": \"chronik\", \"bitcoincash\": \"chronik\" }, \"networkBlockchainURLs\": { \"ecash\": [\"https://xec.paybutton.io\"], \"bitcoincash\": [\"https://bch.paybutton.io\"] }, \"wsBaseURL\": \"http://localhost:5000\", \"apiDomain\": \"http://localhost:3000\" } > paybutton-config.json touch_local_env = touch .env.local prod: diff --git a/README.md b/README.md index f394d323a..93fd14a6a 100644 --- a/README.md +++ b/README.md @@ -122,8 +122,8 @@ default: false, #### networkBlockchainURLs ``` type: { - "ecash": "https://chronik.fabien.cash", - "bitcoincash": "https://chronik.pay2stay.com/bch" + "ecash": ["https://xec.paybutton.org", "https://chronik.fabien.cash"], + "bitcoincash": ["https://chronik.pay2stay.com/bch"] } ``` diff --git a/components/Admin/ChronikURLs.tsx b/components/Admin/ChronikURLs.tsx new file mode 100644 index 000000000..c474461b6 --- /dev/null +++ b/components/Admin/ChronikURLs.tsx @@ -0,0 +1,24 @@ +import { MainNetworkSlugsType } from 'constants/index' + +interface IProps { + chronikUrls: Record +} +export default function ChronikURLs ({ chronikUrls }: IProps): JSX.Element { + return <> +

Chronik URLs

+
+
+

eCash

+
    + {chronikUrls.ecash.map((url, idx) =>
  1. {url}
  2. )} +
+
+
+

Bitcoin Cash

+
    + {chronikUrls.bitcoincash.map((url, idx) =>
  1. {url}
  2. )} +
+
+
+ +} diff --git a/components/Admin/RegisteredUsers.tsx b/components/Admin/RegisteredUsers.tsx index 6e3eb7d6f..0e1c5b9fa 100644 --- a/components/Admin/RegisteredUsers.tsx +++ b/components/Admin/RegisteredUsers.tsx @@ -2,26 +2,27 @@ import { UserWithSupertokens } from 'services/userService' import style from './admin.module.css' import moment from 'moment' import TableContainer from '../../components/TableContainer/TableContainer' +import { Cell, CellProps } from 'react-table' interface IProps { users: UserWithSupertokens[] } -export default function RegisteredUsers({ users }: IProps): JSX.Element { +export default function RegisteredUsers ({ users }: IProps): JSX.Element { if (users === undefined) return <> const columns = [ { Header: 'Registered', accessor: 'registered', - Cell: ({ cell }: any) => {cell.value} + Cell: ({ cell }: CellProps) => {cell.value} }, { Header: 'Email', accessor: 'email', - Cell: ({ cell }: any) => ( + Cell: ({ cell }: Cell) => ( @@ -32,18 +33,21 @@ export default function RegisteredUsers({ users }: IProps): JSX.Element { { Header: 'Admin', accessor: 'isAdmin', - Cell: ({ cell }: any) => ( + Cell: ({ cell }: CellProps) => ( cell.value === true ? Yes : 'No' ) } ] - console.log({users}) + console.log({ users }) const data = users.map(user => ({ id: (user.stUser?.id === undefined || user.stUser?.id === '') ? user.userProfile?.id : user.stUser?.id, - registered: user.stUser ? moment(user.stUser.timeJoined).fromNow() : 'NO ST USER FOUND', + registered: (user.stUser != null) ? moment(user.stUser.timeJoined).fromNow() : 'NO ST USER FOUND', email: (user.stUser?.email === undefined || user.stUser?.email === '') ? user.userProfile?.id : user.stUser?.email, isAdmin: user.userProfile?.isAdmin })) - return + return <> +

Registered Users

+ + } diff --git a/components/Admin/SubscribedAddresses.tsx b/components/Admin/SubscribedAddresses.tsx new file mode 100644 index 000000000..803799a9c --- /dev/null +++ b/components/Admin/SubscribedAddresses.tsx @@ -0,0 +1,51 @@ +import { useEffect, useMemo, useState } from 'react' +import Image from 'next/image' +import EyeIcon from 'assets/eye-icon.png' +import TableContainer from 'components/TableContainer/TableContainer' + +export default function SubscribedAddresses (): JSX.Element { + const [ecashSubscribedAddresses, setEcashSubscribedAddresses] = useState([]) + const [bitcoincashSubscribedAddresses, setBitcoincashSubscribedAddresses] = useState([]) + + useEffect(() => { + void (async () => { + const ok = await (await fetch('chronikStatus')).json() + const subscribedEcashAddresses = ok.ecash?.map((value: string) => ({ address: value })) + const subscribedBitcoincashAddresses = ok.bitcoincash?.map((value: string) => ({ address: value })) + setEcashSubscribedAddresses(subscribedEcashAddresses) + setBitcoincashSubscribedAddresses(subscribedBitcoincashAddresses) + })() + }, []) + + const columns = useMemo( + () => [ + { + Header: 'Subscribed addresses', + accessor: 'address', + Cell: (cellProps: any) => { + return
{cellProps.cell.value}
+ } + }, + { + Header: 'View', + accessor: 'view', + Cell: (cellProps: any) => { + return
+
+ View on explorer +
+
+ } + } + ], + [] + ) + + return <> +

Subscribed Addresses

+

eCash

+ +

Bitcoin Cash

+ + +} diff --git a/components/ButtonGenerator/index.tsx b/components/ButtonGenerator/index.tsx index 45592f33a..b0db31424 100644 --- a/components/ButtonGenerator/index.tsx +++ b/components/ButtonGenerator/index.tsx @@ -4,7 +4,7 @@ import style from '../../styles/landing.module.css' import { PayButton, Widget as PayButtonWidget } from '@paybutton/react' import { ChromePicker } from 'react-color' import CodeBlock from './CodeBlock' -import { decode } from 'ecashaddrjs' +import { decodeCashAddress } from 'ecashaddrjs' import { generatorFormFields } from './data.js' import { PRIMARY_XEC_COLOR, @@ -83,7 +83,7 @@ export const initialButtonState: ButtonState = { onOpen: '', onClose: '', wsBaseURL: '', - apiBaseURL: '', + apiBaseURL: '' } export default function ButtonGenerator (): JSX.Element { @@ -93,7 +93,7 @@ export default function ButtonGenerator (): JSX.Element { const isValidAddress = (address: string): string => { try { - return decode(address).prefix + return decodeCashAddress(address).prefix } catch (err) { return 'not valid' } diff --git a/config/example-config.json b/config/example-config.json index 04ae96b36..f77182ff3 100644 --- a/config/example-config.json +++ b/config/example-config.json @@ -12,9 +12,18 @@ "bitcoincash": "chronik" }, "networkBlockchainURLs": { - "ecash": "https://xec.paybutton.io", - "bitcoincash": "https://bch.paybutton.io" - + "ecash": [ + "https://xec.paybutton.io", + "https://chronik1.alitayin.com", + "https://chronik2.alitayin.com", + "https://chronik.e.cash", + "https://chronik-native1.fabien.cash", + "https://chronik-native2.fabien.cash", + "https://chronik-native3.fabien.cash", + "https://chronik.pay2stay.com/xec", + "https://chronik.pay2stay.com/xec2" + ], + "bitcoincash": ["https://bch.paybutton.io"] }, "networksUnderMaintenance": { "bitcoincash": true diff --git a/config/index.ts b/config/index.ts index 24d80b1ea..b67055e95 100644 --- a/config/index.ts +++ b/config/index.ts @@ -15,7 +15,7 @@ interface Config { priceAPIURL: string redisURL: string networkBlockchainClients: KeyValueT - networkBlockchainURLs: KeyValueT + networkBlockchainURLs: KeyValueT networksUnderMaintenance: KeyValueT triggerPOSTTimeout: number sideshiftAffiliateId: string @@ -35,7 +35,7 @@ const readConfig = (): Config => { if ( ( config.networkBlockchainURLs.ecash === undefined || - config.networkBlockchainURLs.ecash === '' + config.networkBlockchainURLs.ecash.length === 0 ) && !config.networksUnderMaintenance.ecash ) { @@ -43,7 +43,7 @@ const readConfig = (): Config => { } else if ( ( config.networkBlockchainURLs.bitcoincash === undefined || - config.networkBlockchainURLs.bitcoincash === '' + config.networkBlockchainURLs.bitcoincash.length === 0 ) && !config.networksUnderMaintenance.bitcoincash ) { diff --git a/constants/index.ts b/constants/index.ts index c646db747..1a97bab4d 100644 --- a/constants/index.ts +++ b/constants/index.ts @@ -146,6 +146,9 @@ export const FETCH_N_TIMEOUT = 120000 // When fetching some address transactions, delay (in ms) between each fetch. export const FETCH_DELAY = 100 +// Delay to check if latency test has finished, when the app starts. +export const LATENCY_TEST_CHECK_DELAY = 200 + // Wait time (in ms) to see if there are new unsynced addresses export const SYNC_NEW_ADDRESSES_DELAY = 10000 diff --git a/package.json b/package.json index 6c13c7217..905524440 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "cors": "^2.8.5", "cross-env": "^7.0.2", "dotenv-cli": "^5.1.0", - "ecashaddrjs": "^1.5.8", + "ecashaddrjs": "^2.0.0", "express": "^4.17.1", "graphql": "^16.3.0", "helmet": "^4.4.1", @@ -97,7 +97,7 @@ "typescript": "^5.7.2" }, "resolutions": { - "chronik-client-cashtokens/ecashaddrjs": "^1.5.8" + "chronik-client-cashtokens/ecashaddrjs": "^2.0.0" }, "lint-staged": { "*.ts?(x)": [ diff --git a/pages/admin/admin.module.css b/pages/admin/admin.module.css index b97c2bc45..1c1832546 100644 --- a/pages/admin/admin.module.css +++ b/pages/admin/admin.module.css @@ -21,3 +21,7 @@ border-radius: 5px; max-width: 600px; } + +.divisor { + margin: 3rem; +} diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx index ad7890c46..384eb217b 100644 --- a/pages/admin/index.tsx +++ b/pages/admin/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useMemo } from 'react' +import React, { useEffect, useState } from 'react' import supertokensNode from 'supertokens-node' import * as SuperTokensConfig from '../../config/backendConfig' import Session from 'supertokens-node/recipe/session' @@ -8,10 +8,11 @@ import style from './admin.module.css' import { fetchUserWithSupertokens, isUserAdmin, UserWithSupertokens } from 'services/userService' import { useRouter } from 'next/router' import RegisteredUsers from 'components/Admin/RegisteredUsers' -import TableContainer from '../../components/TableContainer/TableContainer' -import EyeIcon from 'assets/eye-icon.png' -import Image from 'next/image' import { removeUnserializableFields } from 'utils' +import { multiBlockchainClient } from 'services/chronikService' +import { MainNetworkSlugsType } from 'constants/index' +import SubscribedAddresses from 'components/Admin/SubscribedAddresses' +import ChronikURLs from 'components/Admin/ChronikURLs' export const getServerSideProps: GetServerSideProps = async (context) => { // this runs on the backend, so we must call init on supertokens-node SDK @@ -32,13 +33,15 @@ export const getServerSideProps: GetServerSideProps = async (context) => { const userId = session?.getUserId() const user = await fetchUserWithSupertokens(userId) removeUnserializableFields(user.userProfile) + const chronikUrls = multiBlockchainClient.getUrls() const isAdmin = await isUserAdmin(userId) return { props: { userId, user, - isAdmin + isAdmin, + chronikUrls } } } @@ -47,12 +50,12 @@ interface IProps { userId: string isAdmin: boolean user: supertokensNode.User | undefined + chronikUrls: Record + } -export default function Admin ({ user, isAdmin }: IProps): JSX.Element { +export default function Admin ({ user, isAdmin, chronikUrls }: IProps): JSX.Element { const router = useRouter() - const [ecashSubscribedAddresses, setEcashSubscribedAddresses] = useState([]) - const [bitcoincashSubscribedAddresses, setBitcoincashSubscribedAddresses] = useState([]) const [users, setUsers] = useState([]) useEffect(() => { @@ -63,48 +66,15 @@ export default function Admin ({ user, isAdmin }: IProps): JSX.Element { useEffect(() => { void (async () => { - const ok = await (await fetch('chronikStatus')).json() - const subscribedEcashAddresses = ok.ecash?.map((value: string) => ({ address: value })) - const subscribedBitcoincashAddresses = ok.bitcoincash?.map((value: string) => ({ address: value })) - setEcashSubscribedAddresses(subscribedEcashAddresses) - setBitcoincashSubscribedAddresses(subscribedBitcoincashAddresses) - const ok2 = await (await fetch('/api/users')).json() - setUsers(ok2) + const usersJSON = await (await fetch('/api/users')).json() + setUsers(usersJSON) })() }, []) - const columns = useMemo( - () => [ - { - Header: 'Subscribed addresses', - accessor: 'address', - Cell: (cellProps: any) => { - return
{cellProps.cell.value}
- } - }, - { - Header: 'View', - accessor: 'view', - Cell: (cellProps: any) => { - return -
- View on explorer -
-
- } - } - ], - [] - ) - if (user !== null && isAdmin) { return <> -

Admin Dashboard

-

eCash

- -

Bitcoin Cash

- +

Admin Dashboard

Go to Supertokens Admin Dashboard -

Registered Users

-
+ +
+ +
+ } else { return diff --git a/services/chronikService.ts b/services/chronikService.ts index 894019ac0..a67cc96f3 100644 --- a/services/chronikService.ts +++ b/services/chronikService.ts @@ -1,8 +1,7 @@ -import { BlockInfo, ChronikClient, ScriptType, ScriptUtxo, Tx, WsConfig, WsEndpoint, WsMsgClient, WsSubScriptClient } from 'chronik-client-cashtokens' -import { encode, decode } from 'ecashaddrjs' -import bs58 from 'bs58' +import { BlockInfo, ChronikClient, ConnectionStrategy, ScriptUtxo, Tx, WsConfig, WsEndpoint, WsMsgClient, WsSubScriptClient } from 'chronik-client-cashtokens' +import { encodeCashAddress, decodeCashAddress } from 'ecashaddrjs' import { AddressWithTransaction, BlockchainInfo, TransactionDetails, ProcessedMessages, SubbedAddressesLog, SyncAndSubscriptionReturn, SubscriptionReturn, SimpleBlockInfo } from 'types/chronikTypes' -import { CHRONIK_MESSAGE_CACHE_DELAY, RESPONSE_MESSAGES, XEC_TIMESTAMP_THRESHOLD, XEC_NETWORK_ID, BCH_NETWORK_ID, BCH_TIMESTAMP_THRESHOLD, FETCH_DELAY, FETCH_N, KeyValueT, NETWORK_IDS_FROM_SLUGS, SOCKET_MESSAGES, NETWORK_IDS, NETWORK_TICKERS, MainNetworkSlugsType, MAX_MEMPOOL_TXS_TO_PROCESS_AT_A_TIME, MEMPOOL_PROCESS_DELAY, CHRONIK_INITIALIZATION_DELAY } from 'constants/index' +import { CHRONIK_MESSAGE_CACHE_DELAY, RESPONSE_MESSAGES, XEC_TIMESTAMP_THRESHOLD, XEC_NETWORK_ID, BCH_NETWORK_ID, BCH_TIMESTAMP_THRESHOLD, FETCH_DELAY, FETCH_N, KeyValueT, NETWORK_IDS_FROM_SLUGS, SOCKET_MESSAGES, NETWORK_IDS, NETWORK_TICKERS, MainNetworkSlugsType, MAX_MEMPOOL_TXS_TO_PROCESS_AT_A_TIME, MEMPOOL_PROCESS_DELAY, CHRONIK_INITIALIZATION_DELAY, LATENCY_TEST_CHECK_DELAY } from 'constants/index' import { productionAddresses } from 'prisma/seeds/addresses' import { TransactionWithAddressAndPrices, @@ -28,6 +27,7 @@ import { executeAddressTriggers } from './triggerService' import { appendTxsToFile } from 'prisma/seeds/transactions' import { PHASE_PRODUCTION_BUILD } from 'next/dist/shared/lib/constants' import { syncPastDaysNewerPrices } from './priceService' +import { AddressType } from 'ecashaddrjs/dist/types' const decoder = new TextDecoder() @@ -107,38 +107,60 @@ export function getNullDataScriptData (outputScript: string): OpReturnData | nul } export class ChronikBlockchainClient { - chronik: ChronikClient - networkId: number - networkSlug: string - chronikWSEndpoint: WsEndpoint - confirmedTxsHashesFromLastBlock: string[] - wsEndpoint: Socket - CHRONIK_MSG_PREFIX: string - lastProcessedMessages: ProcessedMessages - initializing: boolean - mempoolTxsBeingProcessed: number + chronik!: ChronikClient + networkId!: number + networkSlug!: string + chronikWSEndpoint!: WsEndpoint + confirmedTxsHashesFromLastBlock!: string[] + wsEndpoint!: Socket + CHRONIK_MSG_PREFIX!: string + lastProcessedMessages!: ProcessedMessages + initializing!: boolean + mempoolTxsBeingProcessed!: number + + private latencyTestFinished: boolean constructor (networkSlug: string) { - if (process.env.WS_AUTH_KEY === '' || process.env.WS_AUTH_KEY === undefined) { - throw new Error(RESPONSE_MESSAGES.MISSING_WS_AUTH_KEY_400.message) - } + this.latencyTestFinished = false + void (async () => { + if (process.env.WS_AUTH_KEY === '' || process.env.WS_AUTH_KEY === undefined) { + throw new Error(RESPONSE_MESSAGES.MISSING_WS_AUTH_KEY_400.message) + } - this.initializing = true - this.mempoolTxsBeingProcessed = 0 - this.networkSlug = networkSlug - this.networkId = NETWORK_IDS_FROM_SLUGS[networkSlug] - this.chronik = new ChronikClient([config.networkBlockchainURLs[networkSlug]]) - this.chronikWSEndpoint = this.chronik.ws(this.getWsConfig()) - this.confirmedTxsHashesFromLastBlock = [] - void this.chronikWSEndpoint.waitForOpen() - this.chronikWSEndpoint.subscribeToBlocks() - this.lastProcessedMessages = { confirmed: {}, unconfirmed: {} } - this.CHRONIK_MSG_PREFIX = `[CHRONIK — ${networkSlug}]` - this.wsEndpoint = io(`${config.wsBaseURL}/broadcast`, { - query: { - key: process.env.WS_AUTH_KEY + this.initializing = true + this.mempoolTxsBeingProcessed = 0 + this.networkSlug = networkSlug + this.networkId = NETWORK_IDS_FROM_SLUGS[networkSlug] + this.chronik = await ChronikClient.useStrategy( + ConnectionStrategy.ClosestFirst, + config.networkBlockchainURLs[networkSlug] + ) + this.latencyTestFinished = true + this.chronikWSEndpoint = this.chronik.ws(this.getWsConfig()) + this.confirmedTxsHashesFromLastBlock = [] + void this.chronikWSEndpoint.waitForOpen() + this.chronikWSEndpoint.subscribeToBlocks() + this.lastProcessedMessages = { confirmed: {}, unconfirmed: {} } + this.CHRONIK_MSG_PREFIX = `[CHRONIK — ${networkSlug}]` + this.wsEndpoint = io(`${config.wsBaseURL}/broadcast`, { + query: { + key: process.env.WS_AUTH_KEY + } + }) + })() + } + + public async waitForLatencyTest (): Promise { + while (true) { + if (this.latencyTestFinished) { + return } - }) + await new Promise(resolve => setTimeout(resolve, LATENCY_TEST_CHECK_DELAY)) + } + } + + public getUrls (): string[] { + return this.chronik.proxyInterface().getEndpointArray().map(e => e.url) } public setInitialized (): void { @@ -164,7 +186,7 @@ export class ChronikBlockchainClient { } public getSubscribedAddresses (): string[] { - const ret = this.chronikWSEndpoint.subs.scripts.map((script: WsSubScriptClient) => fromHash160(this.networkSlug, script.scriptType, script.payload)) + const ret = this.chronikWSEndpoint.subs.scripts.map((script: WsSubScriptClient) => fromHash160(this.networkSlug, script.scriptType as AddressType, script.payload)) return [...new Set(ret)] } @@ -611,7 +633,7 @@ export class ChronikBlockchainClient { } } -export function fromHash160 (networkSlug: string, type: string, hash160: string): string { +export function fromHash160 (networkSlug: string, type: AddressType, hash160: string): string { const buffer = Buffer.from(hash160, 'hex') // Because ecashaddrjs only accepts Uint8Array as input type, convert @@ -621,21 +643,17 @@ export function fromHash160 (networkSlug: string, type: string, hash160: string) hash160Uint8Array[i] = buffer[i] } - return encode( + return encodeCashAddress( networkSlug, - type.toUpperCase(), + type, hash160Uint8Array ) } -export function toHash160 (address: string): {type: ScriptType, hash160: string} { +export function toHash160 (address: string): {type: AddressType, hash160: string} { try { - const { type, hash } = decode(address) - const legacyAdress = bs58.encode(hash) - const addrHash160 = Buffer.from(bs58.decode(legacyAdress)).toString( - 'hex' - ) - return { type: type.toLowerCase() as ScriptType, hash160: addrHash160 } + const { type, hash } = decodeCashAddress(address) + return { type, hash160: hash } } catch (err) { console.log('[CHRONIK]: Error converting address to hash160') throw err @@ -651,14 +669,14 @@ export function outputScriptToAddress (networkSlug: string, outputScript: string let hash160 switch (typeTestSlice) { case '76a9': - addressType = 'P2PKH' + addressType = 'p2pkh' hash160 = outputScript.substring( outputScript.indexOf('76a914') + '76a914'.length, outputScript.lastIndexOf('88ac') ) break case 'a914': - addressType = 'P2SH' + addressType = 'p2sh' hash160 = outputScript.substring( outputScript.indexOf('a914') + 'a914'.length, outputScript.lastIndexOf('87') @@ -670,14 +688,16 @@ export function outputScriptToAddress (networkSlug: string, outputScript: string if (hash160.length !== 40) return undefined - return fromHash160(networkSlug, addressType, hash160) + return fromHash160(networkSlug, addressType as AddressType, hash160) } class MultiBlockchainClient { private clients!: Record + public initializing: boolean constructor () { console.log('Initializing MultiBlockchainClient...') + this.initializing = true void (async () => { if (this.isRunningApp()) { await syncPastDaysNewerPrices() @@ -699,9 +719,23 @@ class MultiBlockchainClient { } await Promise.all(asyncOperations) } + this.initializing = false })() } + public async waitForStart (): Promise { + while (this.initializing) { + await new Promise(resolve => setTimeout(resolve, LATENCY_TEST_CHECK_DELAY)) + } + } + + public getUrls (): Record { + return { + ecash: this.clients.ecash.getUrls(), + bitcoincash: this.clients.bitcoincash.getUrls() + } + } + private isRunningApp (): boolean { if ( process.env.NEXT_PHASE !== PHASE_PRODUCTION_BUILD && @@ -721,6 +755,7 @@ class MultiBlockchainClient { if (this.isRunningApp()) { asyncOperations.push( (async () => { + await newClient.waitForLatencyTest() console.log(`[CHRONIK — ${networkSlug}] Subscribing addresses in database...`) await newClient.subscribeInitialAddresses() console.log(`[CHRONIK — ${networkSlug}] Syncing missed transactions...`) @@ -728,6 +763,12 @@ class MultiBlockchainClient { console.log(`[CHRONIK — ${networkSlug}] Finished instantiating client.`) })() ) + } else if (process.env.NODE_ENV === 'test') { + asyncOperations.push( + (async () => { + await newClient.waitForLatencyTest() + })() + ) } return newClient diff --git a/styles/global.css b/styles/global.css index 4ed7ce91e..b5012b77f 100644 --- a/styles/global.css +++ b/styles/global.css @@ -470,3 +470,12 @@ body[data-theme='dark'] .button_outline:hover { .export_btn:hover { cursor: pointer; } + +.columns { + display: flex; +} + +.paybutton-table-ctn.columns > div { + flex: 1; +} + diff --git a/tests/integration-tests/api.test.ts b/tests/integration-tests/api.test.ts index d06d0b68e..bca72febd 100644 --- a/tests/integration-tests/api.test.ts +++ b/tests/integration-tests/api.test.ts @@ -30,6 +30,7 @@ import { import { RESPONSE_MESSAGES, NETWORK_SLUGS } from 'constants/index' import { Prisma, Transaction } from '@prisma/client' +import { multiBlockchainClient } from 'services/chronikService' const setUpUsers = async (): Promise => { await createUserProfile('test-u-id') await createUserProfile('test-u-id2') @@ -46,10 +47,13 @@ jest.mock('../../utils/setSession', () => { }) beforeAll(async () => { - await setUpUsers() - jest.spyOn(console, 'log').mockImplementation(() => {}) - jest.spyOn(console, 'warn').mockImplementation(() => {}) - jest.spyOn(console, 'error').mockImplementation(() => {}) + return await (async () => { + await setUpUsers() + jest.spyOn(console, 'log').mockImplementation(() => {}) + jest.spyOn(console, 'warn').mockImplementation(() => {}) + jest.spyOn(console, 'error').mockImplementation(() => {}) + await multiBlockchainClient.waitForStart() + })() }) afterAll(async () => { diff --git a/types/ecashaddrjs/index.d.ts b/types/ecashaddrjs/index.d.ts deleted file mode 100644 index b770cca6f..000000000 --- a/types/ecashaddrjs/index.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module 'ecashaddrjs' { - export function decode (address: string): { prefix: string, type: string, hash: Uint8Array } - export function encode (prefix: string, type: string, hash: Uint8Array): string -} diff --git a/yarn.lock b/yarn.lock index d188e07db..d4ddc8cbe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1528,11 +1528,6 @@ resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.2.3.tgz#cd432f280beb8d8de5b7cd2501e9f502e9f3dd72" integrity sha512-aLG2MaFs4y7IwaMTosz2r4mVbqRyCnMoFqOcmfTi7/mAS+G4IMH0vJp4oLdbshqiVoiVuKrAfqtXj55/m7Qu1Q== -"@noble/hashes@^1.2.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" - integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" @@ -2668,14 +2663,6 @@ bs58check@2.1.2, bs58check@<3.0.0, bs58check@^2.1.2: create-hash "^1.1.0" safe-buffer "^5.1.2" -bs58check@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-3.0.1.tgz#2094d13720a28593de1cba1d8c4e48602fdd841c" - integrity sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ== - dependencies: - "@noble/hashes" "^1.2.0" - bs58 "^5.0.0" - bser@2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" @@ -3281,13 +3268,10 @@ ecashaddrjs@^1.0.7: dependencies: big-integer "1.6.36" -ecashaddrjs@^1.5.8, "ecashaddrjs@file:../.cache/yarn/v6/npm-chronik-client-cashtokens-3.1.1-rc0-e5e4a3538e8010b70623974a731bf2712506c5e3-integrity/node_modules/ecashaddrjs": - version "1.5.8" - resolved "https://registry.yarnpkg.com/ecashaddrjs/-/ecashaddrjs-1.5.8.tgz#fff684d14a944cfd4bca56dd8dc3adbfce8016f9" - integrity sha512-EBL052a1Hd1+wwaBQr3Q+jXY4XfH0jXR1tK360P8GQ9YJUZ5zLG+0I7yee8/QgHY04zdoCzDOY0fRlwqtqDhRg== - dependencies: - big-integer "1.6.36" - bs58check "^3.0.1" +ecashaddrjs@^2.0.0, "ecashaddrjs@file:../.cache/yarn/v6/npm-chronik-client-cashtokens-3.1.1-rc0-e5e4a3538e8010b70623974a731bf2712506c5e3-integrity/node_modules/ecashaddrjs": + version "2.0.0" + resolved "https://registry.yarnpkg.com/ecashaddrjs/-/ecashaddrjs-2.0.0.tgz#d45ede7fb6168815dbcf664b8e0a6872e485d874" + integrity sha512-EvK1V4D3+nIEoD0ggy/b0F4lW39/72R9aOs/scm6kxMVuXu16btc+H74eQv7okNfXaQWKgolEekZkQ6wfcMMLw== ecdsa-sig-formatter@1.0.11: version "1.0.11"